1 // TortoiseSVN - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2011 - TortoiseSVN
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "TortoiseProc.h"
22 #include "Revisiongraphdlg.h"
23 #include "MessageBox.h"
26 #include "UnicodeUtils.h"
28 //#include "SVNInfo.h"
29 //#include "SVNDiff.h"
30 #include ".\revisiongraphwnd.h"
31 //#include "IRevisionGraphLayout.h"
32 //#include "UpsideDownLayout.h"
33 //#include "ShowTreeStripes.h"
38 static char THIS_FILE
[] = __FILE__
;
41 using namespace Gdiplus
;
44 /************************************************************************/
45 /* Graphing functions */
46 /************************************************************************/
47 CFont
* CRevisionGraphWnd::GetFont(BOOL bItalic
/*= FALSE*/, BOOL bBold
/*= FALSE*/)
54 if (m_apFonts
[nIndex
] == NULL
)
56 m_apFonts
[nIndex
] = new CFont
;
57 m_lfBaseFont
.lfWeight
= bBold
? FW_BOLD
: FW_NORMAL
;
58 m_lfBaseFont
.lfItalic
= (BYTE
) bItalic
;
59 m_lfBaseFont
.lfStrikeOut
= (BYTE
) FALSE
;
61 m_lfBaseFont
.lfHeight
= -MulDiv(m_nFontSize
, GetDeviceCaps(pDC
->m_hDC
, LOGPIXELSY
), 72);
63 // use the empty font name, so GDI takes the first font which matches
64 // the specs. Maybe this will help render chinese/japanese chars correctly.
65 _tcsncpy_s(m_lfBaseFont
.lfFaceName
, _T("MS Shell Dlg 2"), 32);
66 if (!m_apFonts
[nIndex
]->CreateFontIndirect(&m_lfBaseFont
))
68 delete m_apFonts
[nIndex
];
69 m_apFonts
[nIndex
] = NULL
;
70 return CWnd::GetFont();
73 return m_apFonts
[nIndex
];
76 BOOL
CRevisionGraphWnd::OnEraseBkgnd(CDC
* /*pDC*/)
81 void CRevisionGraphWnd::OnPaint()
84 CPaintDC
dc(this); // device context for painting
85 CRect rect
= GetClientRect();
87 if (IsUpdateJobRunning())
89 dc.FillSolidRect(rect, ::GetSysColor(COLOR_APPWORKSPACE));
94 else if (this->m_Graph.empty())
97 sNoGraphText.LoadString(IDS_REVGRAPH_ERR_NOGRAPH);
98 dc.FillSolidRect(rect, RGB(255,255,255));
99 dc.ExtTextOut(20,20,ETO_CLIPPED,NULL,sNoGraphText,NULL);
105 DrawGraph(dev
, rect
, GetScrollPos(SB_VERT
), GetScrollPos(SB_HORZ
), false);
109 void CRevisionGraphWnd::ClearVisibleGlyphs (const CRect
& rect
)
112 float glyphSize
= GLYPH_SIZE
* m_fZoomFactor
;
114 CSyncPointer
<CRevisionGraphState::TVisibleGlyphs
>
115 visibleGlyphs (m_state
.GetVisibleGlyphs());
117 for (size_t i
= visibleGlyphs
->size(), count
= i
; i
> 0; --i
)
119 const PointF
& leftTop
= (*visibleGlyphs
)[i
-1].leftTop
;
120 CRect
glyphRect ( static_cast<int>(leftTop
.X
)
121 , static_cast<int>(leftTop
.Y
)
122 , static_cast<int>(leftTop
.X
+ glyphSize
)
123 , static_cast<int>(leftTop
.Y
+ glyphSize
));
125 if (CRect().IntersectRect (glyphRect
, rect
))
127 (*visibleGlyphs
)[i
-1] = (*visibleGlyphs
)[--count
];
128 visibleGlyphs
->pop_back();
134 void CRevisionGraphWnd::CutawayPoints (const RectF
& rect
, float cutLen
, TCutRectangle
& result
)
136 result
[0] = PointF (rect
.X
, rect
.Y
+ cutLen
);
137 result
[1] = PointF (rect
.X
+ cutLen
, rect
.Y
);
138 result
[2] = PointF (rect
.GetRight() - cutLen
, rect
.Y
);
139 result
[3] = PointF (rect
.GetRight(), rect
.Y
+ cutLen
);
140 result
[4] = PointF (rect
.GetRight(), rect
.GetBottom() - cutLen
);
141 result
[5] = PointF (rect
.GetRight() - cutLen
, rect
.GetBottom());
142 result
[6] = PointF (rect
.X
+ cutLen
, rect
.GetBottom());
143 result
[7] = PointF (rect
.X
, rect
.GetBottom() - cutLen
);
146 void CRevisionGraphWnd::DrawRoundedRect (GraphicsDevice
& graphics
, const Color
& penColor
, int penWidth
, const Pen
* pen
, const Color
& fillColor
, const Brush
* brush
, const RectF
& rect
)
149 enum {POINT_COUNT
= 8};
151 float radius
= CORNER_SIZE
* m_fZoomFactor
;
152 PointF points
[POINT_COUNT
];
153 CutawayPoints (rect
, radius
, points
);
155 if (graphics
.graphics
)
158 path
.AddArc (points
[0].X
, points
[1].Y
, radius
, radius
, 180, 90);
159 path
.AddArc (points
[2].X
, points
[2].Y
, radius
, radius
, 270, 90);
160 path
.AddArc (points
[5].X
, points
[4].Y
, radius
, radius
, 0, 90);
161 path
.AddArc (points
[7].X
, points
[7].Y
, radius
, radius
, 90, 90);
163 points
[0].Y
-= radius
/ 2;
164 path
.AddLine (points
[7], points
[0]);
168 graphics
.graphics
->FillPath (brush
, &path
);
171 graphics
.graphics
->DrawPath (pen
, &path
);
173 else if (graphics
.pSVG
)
175 graphics
.pSVG
->RoundedRectangle((int)rect
.X
, (int)rect
.Y
, (int)rect
.Width
, (int)rect
.Height
, penColor
, penWidth
, fillColor
, (int)radius
);
182 void CRevisionGraphWnd::DrawOctangle (GraphicsDevice
& graphics
, const Color
& penColor
, int penWidth
, const Pen
* pen
, const Color
& fillColor
, const Brush
* brush
, const RectF
& rect
)
184 enum {POINT_COUNT
= 8};
186 // show left & right edges of low boxes as "<===>"
188 float minCutAway
= min (CORNER_SIZE
* m_fZoomFactor
, rect
.Height
/ 2);
190 // larger boxes: remove 25% of the shorter side
192 float suggestedCutAway
= min (rect
.Height
, rect
.Width
) / 4;
194 // use the more visible one of the former two
196 PointF points
[POINT_COUNT
];
197 CutawayPoints (rect
, max (minCutAway
, suggestedCutAway
), points
);
201 if (graphics
.graphics
)
204 graphics
.graphics
->FillPolygon (brush
, points
, POINT_COUNT
);
206 graphics
.graphics
->DrawPolygon (pen
, points
, POINT_COUNT
);
208 else if (graphics
.pSVG
)
210 graphics
.pSVG
->Polygon(points
, POINT_COUNT
, penColor
, penWidth
, fillColor
);
216 void CRevisionGraphWnd::DrawShape (GraphicsDevice
& graphics
, const Color
& penColor
, int penWidth
, const Pen
* pen
, const Color
& fillColor
, const Brush
* brush
, const RectF
& rect
, NodeShape shape
)
221 if (graphics
.graphics
)
224 graphics
.graphics
->FillRectangle (brush
, rect
);
226 graphics
.graphics
->DrawRectangle (pen
, rect
);
228 else if (graphics
.pSVG
)
230 graphics
.pSVG
->RoundedRectangle((int)rect
.X
, (int)rect
.Y
, (int)rect
.Width
, (int)rect
.Height
, penColor
, penWidth
, fillColor
);
234 DrawRoundedRect (graphics
, penColor
, penWidth
, pen
, fillColor
, brush
, rect
);
237 DrawOctangle (graphics
, penColor
, penWidth
, pen
, fillColor
, brush
, rect
);
240 if (graphics
.graphics
)
243 graphics
.graphics
->FillEllipse (brush
, rect
);
245 graphics
.graphics
->DrawEllipse(pen
, rect
);
247 else if (graphics
.pSVG
)
248 graphics
.pSVG
->Ellipse((int)rect
.X
, (int)rect
.Y
, (int)rect
.Width
, (int)rect
.Height
, penColor
, penWidth
, fillColor
);
251 ASSERT(FALSE
); //unknown type
257 inline BYTE
LimitedScaleColor (BYTE c1
, BYTE c2
, float factor
)
259 BYTE scaled
= c2
+ (BYTE
)((c1
-c2
)*factor
);
265 Color
LimitedScaleColor (const Color
& c1
, const Color
& c2
, float factor
)
267 return Color ( LimitedScaleColor (c1
.GetA(), c2
.GetA(), factor
)
268 , LimitedScaleColor (c1
.GetR(), c2
.GetR(), factor
)
269 , LimitedScaleColor (c1
.GetG(), c2
.GetG(), factor
)
270 , LimitedScaleColor (c1
.GetB(), c2
.GetB(), factor
));
273 inline BYTE
Darken (BYTE c
)
277 : BYTE(int(2*c
) - 0x100);
280 Color
Darken (const Color
& c
)
285 , Darken (c
.GetB()));
288 BYTE
MaxComponentDiff (const Color
& c1
, const Color
& c2
)
290 int rDiff
= abs ((int)c1
.GetR() - (int)c2
.GetR());
291 int gDiff
= abs ((int)c1
.GetG() - (int)c2
.GetG());
292 int bDiff
= abs ((int)c1
.GetB() - (int)c2
.GetB());
294 return (BYTE
) max (max (rDiff
, gDiff
), bDiff
);
298 void CRevisionGraphWnd::DrawShadow (GraphicsDevice
& graphics
, const RectF
& rect
,
299 Color shadowColor
, NodeShape shape
)
304 shadow
.Offset (2, 2);
306 Pen
pen (shadowColor
);
307 SolidBrush
brush (shadowColor
);
309 DrawShape (graphics
, shadowColor
, 1, &pen
, shadowColor
, &brush
, shadow
, shape
);
314 void CRevisionGraphWnd::DrawNode(GraphicsDevice
& graphics
, const RectF
& rect
,
315 Color contour
, Color overlayColor
,
316 const CVisibleGraphNode
*node
, NodeShape shape
)
318 // special case: line deleted but deletion node removed
319 // (don't show as "deleted" if the following node has been folded / split)
323 // MASK = CGraphNodeStates::COLLAPSED_BELOW | CGraphNodeStates::SPLIT_BELOW
326 //CNodeClassification nodeClassification = node->GetClassification();
327 //if ( (node->GetNext() == NULL)
328 // && (nodeClassification.Is (CNodeClassification::PATH_ONLY_DELETED))
329 // && ((m_state.GetNodeStates()->GetFlags (node) & MASK) == 0))
331 // contour = m_Colors.GetColor (CColors::gdpDeletedNode);
334 // calculate the GDI+ color values we need to draw the node
337 background
.SetFromCOLORREF (GetSysColor(COLOR_WINDOW
));
339 // if (nodeClassification.Is (CNodeClassification::IS_MODIFIED_WC))
340 // textColor = m_Colors.GetColor (CColors::gdpWCNodeBorder);
342 textColor
.SetFromCOLORREF (GetSysColor(COLOR_WINDOWTEXT
));
344 Color brightColor
= LimitedScaleColor (background
, contour
, 0.9f
);
346 // Draw the main shape
348 bool isWorkingCopy
=0;
349 // = nodeClassification.Is (CNodeClassification::IS_WORKINGCOPY);
351 // = nodeClassification.Is (CNodeClassification::IS_MODIFIED_WC);
352 bool textAsBorderColor
=0;
353 // = nodeClassification.IsAnyOf ( CNodeClassification::IS_LAST
354 // | CNodeClassification::IS_MODIFIED_WC)
355 // | nodeClassification.Matches ( CNodeClassification::IS_COPY_SOURCE
356 // , CNodeClassification::IS_OPERATION_MASK)
357 // | (contour.GetValue() == brightColor.GetValue());
359 Color penColor
= textAsBorderColor
363 Pen
pen (penColor
, isWorkingCopy
? 3.0f
: 1.0f
);
365 if (isWorkingCopy
&& !isModifiedWC
)
367 CSyncPointer
<const CFullHistory
> history (m_state
.GetFullHistory());
368 const CFullHistory::SWCInfo
& wcInfo
= history
->GetWCInfo();
369 revision_t revision
= node
->GetRevision();
371 bool isCommitRev
= (wcInfo
.minCommit
== revision
)
372 || (wcInfo
.maxCommit
== revision
);
373 bool isMinAtRev
= (wcInfo
.minAtRev
== revision
)
374 && (wcInfo
.minAtRev
!= wcInfo
.maxAtRev
);
376 DashStyle style
= wcInfo
.maxAtRev
== revision
378 : isCommitRev
? isMinAtRev
? DashStyleDashDot
382 pen
.SetDashStyle (style
);
385 SolidBrush
brush (brightColor
);
386 DrawShape (graphics
, penColor
, isWorkingCopy
? 3 : 1, &pen
, brightColor
, &brush
, rect
, shape
);
388 // overlay with some other color
390 if (overlayColor
.GetValue() != 0)
392 SolidBrush
brush2 (overlayColor
);
393 DrawShape (graphics
, penColor
, isWorkingCopy
? 3 : 1, &pen
, overlayColor
, &brush2
, rect
, shape
);
398 RectF
CRevisionGraphWnd::TransformRectToScreen (const CRect
& rect
, const CSize
& offset
) const
400 PointF
leftTop ( rect
.left
* m_fZoomFactor
401 , rect
.top
* m_fZoomFactor
);
402 return RectF ( leftTop
.X
- offset
.cx
403 , leftTop
.Y
- offset
.cy
404 , rect
.right
* m_fZoomFactor
- leftTop
.X
- 1
405 , rect
.bottom
* m_fZoomFactor
- leftTop
.Y
);
409 RectF
CRevisionGraphWnd::GetNodeRect (const node
& node
, const CSize
& offset
) const
411 // get node and position
414 rect
.left
= this->m_GraphAttr
.x(node
) - m_GraphAttr
.width(node
)/2;
415 rect
.top
= this->m_GraphAttr
.y(node
) - m_GraphAttr
.height(node
)/2;
416 rect
.bottom
= rect
.top
+ m_GraphAttr
.height(node
);
417 rect
.right
= rect
.left
+ m_GraphAttr
.width(node
);
419 RectF
noderect (TransformRectToScreen (rect
, offset
));
421 // show two separate lines for touching nodes,
422 // unless the scale is too small
424 if (noderect
.Height
> 15.0f
)
425 noderect
.Height
-= 1.0f
;
435 isionGraphWnd::GetBranchCover
436 ( const ILayoutNodeList
* nodeList
439 , const CSize
& offset
)
441 // construct a rect that covers the respective branch
443 CRect
cover (0, 0, 0, 0);
444 while (nodeIndex
!= NO_INDEX
)
446 ILayoutNodeList::SNode node
= nodeList
->GetNode (nodeIndex
);
449 const CVisibleGraphNode
* nextNode
= upward
450 ? node
.node
->GetPrevious()
451 : node
.node
->GetNext();
453 nodeIndex
= nextNode
== NULL
? NO_INDEX
: nextNode
->GetIndex();
456 // expand it just a little to make it look nicer
458 cover
.InflateRect (10, 2, 10, 2);
460 // and now, transfrom it
462 return TransformRectToScreen (cover
, offset
);
467 void CRevisionGraphWnd::DrawShadows (GraphicsDevice
& graphics
, const CRect
& logRect
, const CSize
& offset
)
469 #if 0 // shadow color to use
472 background
.SetFromCOLORREF (GetSysColor(COLOR_WINDOW
));
474 textColor
.SetFromCOLORREF (GetSysColor(COLOR_WINDOWTEXT
));
476 Color shadowColor
= LimitedScaleColor (background
, ARGB (Color::Black
), 0.5f
);
478 // iterate over all visible nodes
480 CSyncPointer
<const ILayoutNodeList
> nodes (m_state
.GetNodes());
481 for ( index_t index
= nodes
->GetFirstVisible (logRect
)
483 ; index
= nodes
->GetNextVisible (index
, logRect
))
485 // get node and position
487 ILayoutNodeList::SNode node
= nodes
->GetNode (index
);
488 RectF
noderect (GetNodeRect (node
, offset
));
494 case ILayoutNodeList::SNode::STYLE_DELETED
:
495 case ILayoutNodeList::SNode::STYLE_RENAMED
:
496 DrawShadow (graphics
, noderect
, shadowColor
, TSVNOctangle
);
498 case ILayoutNodeList::SNode::STYLE_ADDED
:
499 DrawShadow(graphics
, noderect
, shadowColor
, TSVNRoundRect
);
501 case ILayoutNodeList::SNode::STYLE_LAST
:
502 DrawShadow(graphics
, noderect
, shadowColor
, TSVNEllipse
);
505 DrawShadow(graphics
, noderect
, shadowColor
, TSVNRectangle
);
514 void CRevisionGraphWnd::DrawSquare
515 ( GraphicsDevice
& graphics
516 , const PointF
& leftTop
517 , const Color
& lightColor
518 , const Color
& darkColor
519 , const Color
& penColor
)
521 float squareSize
= MARKER_SIZE
* m_fZoomFactor
;
523 PointF
leftBottom (leftTop
.X
, leftTop
.Y
+ squareSize
);
524 RectF
square (leftTop
, SizeF (squareSize
, squareSize
));
526 if (graphics
.graphics
)
528 LinearGradientBrush
lgBrush (leftTop
, leftBottom
, lightColor
, darkColor
);
529 graphics
.graphics
->FillRectangle (&lgBrush
, square
);
530 if (squareSize
> 4.0f
)
533 graphics
.graphics
->DrawRectangle (&pen
, square
);
536 else if (graphics
.pSVG
)
538 graphics
.pSVG
->GradientRectangle((int)square
.X
, (int)square
.Y
, (int)square
.Width
, (int)square
.Height
,
539 lightColor
, darkColor
, penColor
);
543 void CRevisionGraphWnd::DrawGlyph
544 ( GraphicsDevice
& graphics
546 , const PointF
& leftTop
548 , GlyphPosition position
)
552 if (glyph
== NoGlyph
)
555 // bitmap source area
557 REAL x
= ((REAL
)position
+ (REAL
)glyph
) * GLYPH_BITMAP_SIZE
;
559 // screen target area
561 float glyphSize
= GLYPH_SIZE
* m_fZoomFactor
;
562 RectF
target (leftTop
, SizeF (glyphSize
, glyphSize
));
566 if (graphics
.graphics
)
568 graphics
.graphics
->DrawImage ( glyphs
570 , x
, 0.0f
, GLYPH_BITMAP_SIZE
, GLYPH_BITMAP_SIZE
571 , UnitPixel
, NULL
, NULL
, NULL
);
573 else if (graphics
.pSVG
)
575 // instead of inserting a bitmap, draw a
576 // round rectangle instead.
577 // Embedding images would blow up the resulting
578 // svg file a lot, and the round rectangle
581 // images could be embedded like this:
582 // <image y="100" x="100" id="imgId1234" xlink:href="data:image/png;base64,...base64endodeddata..." height="16" width="16" />
584 graphics
.pSVG
->RoundedRectangle((int)target
.X
, (int)target
.Y
, (int)target
.Width
, (int)target
.Height
,
585 Color(0,0,0), 2, Color(255,255,255), (int)(target
.Width
/3.0));
589 void CRevisionGraphWnd::DrawGlyphs
590 ( GraphicsDevice
& graphics
592 , const CVisibleGraphNode
* node
593 , const PointF
& center
596 , GlyphPosition position
601 // don't show collapse and cut glyths by default
603 if (!showAll
&& ((glyph1
== CollapseGlyph
) || (glyph1
== SplitGlyph
)))
605 if (!showAll
&& ((glyph2
== CollapseGlyph
) || (glyph2
== SplitGlyph
)))
608 // glyth2 shall be set only if 2 glyphs are in use
610 if (glyph1
== NoGlyph
)
612 std::swap (glyph1
, glyph2
);
613 std::swap (state1
, state2
);
618 if (glyph1
== NoGlyph
)
623 CSyncPointer
<CRevisionGraphState::TVisibleGlyphs
>
624 visibleGlyphs (m_state
.GetVisibleGlyphs());
626 float squareSize
= GLYPH_SIZE
* m_fZoomFactor
;
627 if (glyph2
== NoGlyph
)
629 PointF
leftTop (center
.X
- 0.5f
* squareSize
, center
.Y
- 0.5f
* squareSize
);
630 DrawGlyph (graphics
, glyphs
, leftTop
, glyph1
, position
);
631 visibleGlyphs
->push_back
632 (CRevisionGraphState::SVisibleGlyph (state1
, leftTop
, node
));
636 PointF
leftTop1 (center
.X
- squareSize
, center
.Y
- 0.5f
* squareSize
);
637 DrawGlyph (graphics
, glyphs
, leftTop1
, glyph1
, position
);
638 visibleGlyphs
->push_back
639 (CRevisionGraphState::SVisibleGlyph (state1
, leftTop1
, node
));
641 PointF
leftTop2 (center
.X
, center
.Y
- 0.5f
* squareSize
);
642 DrawGlyph (graphics
, glyphs
, leftTop2
, glyph2
, position
);
643 visibleGlyphs
->push_back
644 (CRevisionGraphState::SVisibleGlyph (state2
, leftTop2
, node
));
649 void CRevisionGraphWnd::DrawGlyphs
650 ( GraphicsDevice
& graphics
652 , const CVisibleGraphNode
* node
653 , const RectF
& nodeRect
660 if ((state
== 0) && (allowed
== 0))
665 PointF
topCenter (0.5f
* nodeRect
.GetLeft() + 0.5f
* nodeRect
.GetRight(), nodeRect
.GetTop());
666 PointF
rightCenter (nodeRect
.GetRight(), 0.5f
* nodeRect
.GetTop() + 0.5f
* nodeRect
.GetBottom());
667 PointF
bottomCenter (0.5f
* nodeRect
.GetLeft() + 0.5f
* nodeRect
.GetRight(), nodeRect
.GetBottom());
669 DrawGlyphs ( graphics
672 , upsideDown
? bottomCenter
: topCenter
673 , (state
& CGraphNodeStates::COLLAPSED_ABOVE
) ? ExpandGlyph
: CollapseGlyph
674 , (state
& CGraphNodeStates::SPLIT_ABOVE
) ? JoinGlyph
: SplitGlyph
675 , upsideDown
? Below
: Above
676 , CGraphNodeStates::COLLAPSED_ABOVE
677 , CGraphNodeStates::SPLIT_ABOVE
678 , (allowed
& CGraphNodeStates::COLLAPSED_ABOVE
) != 0);
680 DrawGlyphs ( graphics
684 , (state
& CGraphNodeStates::COLLAPSED_RIGHT
) ? ExpandGlyph
: CollapseGlyph
685 , (state
& CGraphNodeStates::SPLIT_RIGHT
) ? JoinGlyph
: SplitGlyph
687 , CGraphNodeStates::COLLAPSED_RIGHT
688 , CGraphNodeStates::SPLIT_RIGHT
689 , (allowed
& CGraphNodeStates::COLLAPSED_RIGHT
) != 0);
691 DrawGlyphs ( graphics
694 , upsideDown
? topCenter
: bottomCenter
695 , (state
& CGraphNodeStates::COLLAPSED_BELOW
) ? ExpandGlyph
: CollapseGlyph
696 , (state
& CGraphNodeStates::SPLIT_BELOW
) ? JoinGlyph
: SplitGlyph
697 , upsideDown
? Above
: Below
698 , CGraphNodeStates::COLLAPSED_BELOW
699 , CGraphNodeStates::SPLIT_BELOW
700 , (allowed
& CGraphNodeStates::COLLAPSED_BELOW
) != 0);
706 void CRevisionGraphWnd::IndicateGlyphDirection
707 ( GraphicsDevice
& graphics
708 , const ILayoutNodeList
* nodeList
709 , const ILayoutNodeList::SNode
& node
710 , const RectF
& nodeRect
713 , const CSize
& offset
)
720 // where to place the indication?
722 bool indicateAbove
= (glyphs
& CGraphNodeStates::COLLAPSED_ABOVE
) != 0;
723 bool indicateRight
= (glyphs
& CGraphNodeStates::COLLAPSED_RIGHT
) != 0;
724 bool indicateBelow
= (glyphs
& CGraphNodeStates::COLLAPSED_BELOW
) != 0;
726 // fill indication area a semi-transparent blend of red
727 // and the background color
730 color
.SetFromCOLORREF (GetSysColor(COLOR_WINDOW
));
731 color
.SetValue ((color
.GetValue() & 0x807f7f7f) + 0x800000);
733 SolidBrush
brush (color
);
735 // draw the indication (only one condition should match)
737 RectF glyphCenter
= indicateAbove
^ upsideDown
738 ? RectF (nodeRect
.X
, nodeRect
.Y
- 1.0f
, 0.0f
, 0.0f
)
739 : RectF (nodeRect
.X
, nodeRect
.GetBottom() - 1.0f
, 0.0f
, 0.0f
);
743 const CVisibleGraphNode
* firstAffected
= node
.node
->GetSource();
745 assert (firstAffected
);
747 = GetBranchCover (nodeList
, firstAffected
->GetIndex(), true, offset
);
748 RectF::Union (branchCover
, branchCover
, glyphCenter
);
750 if (graphics
.graphics
)
751 graphics
.graphics
->FillRectangle (&brush
, branchCover
);
752 else if (graphics
.pSVG
)
753 graphics
.pSVG
->RoundedRectangle((int)branchCover
.X
, (int)branchCover
.Y
, (int)branchCover
.Width
, (int)branchCover
.Height
,
759 for ( const CVisibleGraphNode::CCopyTarget
* branch
760 = node
.node
->GetFirstCopyTarget()
762 ; branch
= branch
->next())
765 = GetBranchCover (nodeList
, branch
->value()->GetIndex(), false, offset
);
766 if (graphics
.graphics
)
767 graphics
.graphics
->FillRectangle (&brush
, branchCover
);
768 else if (graphics
.pSVG
)
769 graphics
.pSVG
->RoundedRectangle((int)branchCover
.X
, (int)branchCover
.Y
, (int)branchCover
.Width
, (int)branchCover
.Height
,
776 const CVisibleGraphNode
* firstAffected
777 = node
.node
->GetNext();
780 = GetBranchCover (nodeList
, firstAffected
->GetIndex(), false, offset
);
781 RectF::Union (branchCover
, branchCover
, glyphCenter
);
783 if (graphics
.graphics
)
784 graphics
.graphics
->FillRectangle (&brush
, branchCover
);
785 else if (graphics
.pSVG
)
786 graphics
.pSVG
->RoundedRectangle((int)branchCover
.X
, (int)branchCover
.Y
, (int)branchCover
.Width
, (int)branchCover
.Height
,
791 void CRevisionGraphWnd::DrawMarker
792 ( GraphicsDevice
& graphics
793 , const RectF
& noderect
794 , MarkerPosition position
799 float squareSize
= MARKER_SIZE
* m_fZoomFactor
;
800 float squareDist
= min ( (noderect
.Height
- squareSize
) / 2
805 REAL offset
= squareSize
* (0.75f
+ relPosition
);
806 REAL left
= position
== mpRight
807 ? noderect
.GetRight() - offset
- squareSize
808 : noderect
.GetLeft() + offset
;
809 PointF
leftTop (left
, noderect
.Y
+ squareDist
);
813 Color
lightColor (m_Colors
.GetColor (CColors::ctMarkers
, colorIndex
));
814 Color
darkColor (Darken (lightColor
));
815 Color
borderColor (0x80000000);
819 DrawSquare (graphics
, leftTop
, lightColor
, darkColor
, borderColor
);
822 void CRevisionGraphWnd::DrawStripes (GraphicsDevice
& graphics
, const CSize
& offset
)
824 // we need to fill this visible area of the the screen
825 // (even if there is graph in that part)
828 if (graphics
.graphics
)
829 graphics
.graphics
->GetVisibleClipBounds (&clipRect
);
831 // don't show stripes if we don't have multiple roots
833 CSyncPointer
<const ILayoutRectList
> trees (m_state
.GetTrees());
834 if (trees
->GetCount() < 2)
837 // iterate over all trees
839 for ( index_t i
= 0, count
= trees
->GetCount(); i
< count
; ++i
)
841 // screen coordinates covered by the tree
843 CRect tree
= trees
->GetRect(i
);
844 REAL left
= tree
.left
* m_fZoomFactor
;
845 REAL right
= tree
.right
* m_fZoomFactor
;
846 RectF
rect ( left
- offset
.cx
848 , i
+1 == count
? clipRect
.Width
: right
- left
853 if (rect
.IntersectsWith (clipRect
))
855 // draw the background stripe
857 Color
color ( (i
& 1) == 0
858 ? m_Colors
.GetColor (CColors::gdpStripeColor1
)
859 : m_Colors
.GetColor (CColors::gdpStripeColor2
));
860 if (graphics
.graphics
)
862 SolidBrush
brush (color
);
863 graphics
.graphics
->FillRectangle (&brush
, rect
);
865 else if (graphics
.pSVG
)
866 graphics
.pSVG
->RoundedRectangle((int)rect
.X
, (int)rect
.Y
, (int)rect
.Width
, (int)rect
.Height
,
872 void CRevisionGraphWnd::DrawNodes (GraphicsDevice
& graphics
, Image
* glyphs
, const CRect
& logRect
, const CSize
& offset
)
875 // CSyncPointer<CGraphNodeStates> nodeStates (m_state.GetNodeStates());
876 // CSyncPointer<const ILayoutNodeList> nodes (m_state.GetNodes());
879 // = m_state.GetOptions()->GetOption<CUpsideDownLayout>()->IsActive();
881 // iterate over all visible nodes
883 /* for ( index_t index = nodes->GetFirstVisible (logRect)
885 ; index = nodes->GetNextVisible (index, logRect))
888 forall_nodes(v
,m_Graph
)
890 // get node and position
892 // ILayoutNodeList::SNode node = nodes->GetNode (index);
893 RectF
noderect (GetNodeRect (v
, offset
));
897 Color
transparent (0);
898 Color overlayColor
= transparent
;
900 SVGGrouper
grouper(graphics
.pSVG
);
902 DrawNode(graphics
, noderect
, RGB(255,0,0), overlayColor
, v
, TSVNRoundRect
);
906 case ILayoutNodeList::SNode::STYLE_DELETED
:
907 DrawNode(graphics
, noderect
, m_Colors
.GetColor(CColors::gdpDeletedNode
), transparent
, node
.node
, TSVNOctangle
);
910 case ILayoutNodeList::SNode::STYLE_ADDED
:
911 if (m_bTweakTagsColors
&& node
.node
->GetClassification().Is (CNodeClassification::IS_TAG
))
912 overlayColor
= m_Colors
.GetColor(CColors::gdpTagOverlay
);
913 else if (m_bTweakTrunkColors
&& node
.node
->GetClassification().Is (CNodeClassification::IS_TRUNK
))
914 overlayColor
= m_Colors
.GetColor(CColors::gdpTrunkOverlay
);
915 DrawNode(graphics
, noderect
, m_Colors
.GetColor(CColors::gdpAddedNode
), overlayColor
, node
.node
, TSVNRoundRect
);
918 case ILayoutNodeList::SNode::STYLE_RENAMED
:
919 DrawNode(graphics
, noderect
, m_Colors
.GetColor(CColors::gdpRenamedNode
), overlayColor
, node
.node
, TSVNOctangle
);
922 case ILayoutNodeList::SNode::STYLE_LAST
:
923 DrawNode(graphics
, noderect
, m_Colors
.GetColor(CColors::gdpLastCommitNode
), transparent
, node
.node
, TSVNEllipse
);
926 case ILayoutNodeList::SNode::STYLE_MODIFIED
:
927 DrawNode(graphics
, noderect
, m_Colors
.GetColor(CColors::gdpModifiedNode
), transparent
, node
.node
, TSVNRectangle
);
930 case ILayoutNodeList::SNode::STYLE_MODIFIED_WC
:
931 DrawNode(graphics
, noderect
, m_Colors
.GetColor(CColors::gdpWCNode
), transparent
, node
.node
, TSVNEllipse
);
935 DrawNode(graphics
, noderect
, m_Colors
.GetColor(CColors::gdpUnchangedNode
), transparent
, node
.node
, TSVNRectangle
);
939 // Draw the "tagged" icon
941 //if (node.node->GetFirstTag() != NULL)
942 // DrawMarker (graphics, noderect, mpRight, 0, 0);
944 //if ((m_SelectedEntry1 == node.node) || (m_SelectedEntry2 == node.node))
945 // DrawMarker (graphics, noderect, mpLeft, 0, 1);
947 // expansion glypths etc.
949 DrawGlyphs (graphics
, glyphs
, v
, noderect
, 0, 0, 0);
954 PointF
CRevisionGraphWnd::cutPoint(node v
,double lw
,PointF ps
, PointF pt
)
956 double x
= m_GraphAttr
.x(v
);
957 double y
= m_GraphAttr
.y(v
);
958 double xmin
= x
- this->m_GraphAttr
.width(v
)/2 - lw
/2;
959 double xmax
= x
+ this->m_GraphAttr
.width(v
)/2 + lw
/2;
960 double ymin
= y
- this->m_GraphAttr
.height(v
)/2 - lw
/2;
961 double ymax
= y
+ this->m_GraphAttr
.height(v
)/2 + lw
/2;;
963 double dx
= pt
.X
- ps
.X
;
964 double dy
= pt
.Y
- ps
.Y
;
969 double t
= (ymax
-ps
.Y
) / dy
;
970 double x
= ps
.X
+ t
*dx
;
972 if(xmin
<= x
&& x
<= xmax
)
973 return PointF(x
,ymax
);
976 } else if(pt
.Y
< ymin
) {
977 double t
= (ymin
-ps
.Y
) / dy
;
978 double x
= ps
.X
+ t
*dx
;
980 if(xmin
<= x
&& x
<= xmax
)
981 return PointF(x
,ymin
);
989 double t
= (xmax
-ps
.X
) / dx
;
990 double y
= ps
.Y
+ t
*dy
;
992 if(ymin
<= y
&& y
<= ymax
)
993 return PointF(xmax
,y
);
996 } else if(pt
.X
< xmin
) {
997 double t
= (xmin
-ps
.X
) / dx
;
998 double y
= ps
.Y
+ t
*dy
;
1000 if(ymin
<= y
&& y
<= ymax
)
1001 return PointF(xmin
,y
);
1010 void CRevisionGraphWnd::DrawConnections (GraphicsDevice
& graphics
, const CRect
& logRect
, const CSize
& offset
)
1013 CArray
<PointF
> points
;
1016 if(graphics
.graphics
)
1017 graphics
.graphics
->SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias
);
1019 Gdiplus::Pen
pen(Color(0,0,0),2);
1021 // iterate over all visible lines
1023 forall_edges(e
, m_Graph
)
1025 // get connection and point position
1026 const DPolyline
&dpl
= this->m_GraphAttr
.bends(e
);
1032 pt
.X
= m_GraphAttr
.x(e
->source());
1033 pt
.Y
= m_GraphAttr
.y(e
->source());
1037 ListConstIterator
<DPoint
> it
;
1038 for(it
= dpl
.begin(); it
.valid(); ++it
)
1045 pt
.X
= m_GraphAttr
.x(e
->target());
1046 pt
.Y
= m_GraphAttr
.y(e
->target());
1050 points
[0] = this->cutPoint(e
->source(), 1, points
[0], points
[1]);
1051 points
[points
.GetCount()-1] = this->cutPoint(e
->target(), 1, points
[points
.GetCount()-1], points
[points
.GetCount()-2]);
1052 // draw the connection
1054 for(int i
=0; i
< points
.GetCount(); i
++)
1057 points
[i
].X
= points
[i
].X
* this->m_fZoomFactor
- offset
.cx
;
1058 points
[i
].Y
= points
[i
].Y
* this->m_fZoomFactor
- offset
.cy
;
1062 if (graphics
.graphics
)
1064 graphics
.graphics
->DrawLines(&pen
, points
.GetData(), points
.GetCount());
1067 else if (graphics
.pSVG
)
1070 color
.SetFromCOLORREF(GetSysColor(COLOR_WINDOWTEXT
));
1071 graphics
.pSVG
->PolyBezier(pts
.GetData(), pts
.GetCount(), color
);
1078 void CRevisionGraphWnd::DrawTexts (GraphicsDevice
& graphics
, const CRect
& logRect
, const CSize
& offset
)
1081 COLORREF standardTextColor
= GetSysColor(COLOR_WINDOWTEXT
);
1082 if (m_nFontSize
<= 0)
1085 // iterate over all visible nodes
1088 graphics
.pDC
->SetTextAlign (TA_CENTER
| TA_TOP
);
1092 forall_nodes(v
,m_Graph
)
1094 // get node and position
1096 String label
=this->m_GraphAttr
.labelNode(v
);
1098 CRect
textRect ( (int)(text
.rect
.left
* m_fZoomFactor
) - offset
.cx
1099 , (int)(text
.rect
.top
* m_fZoomFactor
) - offset
.cy
1100 , (int)(text
.rect
.right
* m_fZoomFactor
) - offset
.cx
1101 , (int)(text
.rect
.bottom
* m_fZoomFactor
) - offset
.cy
);
1103 // draw the revision text
1107 graphics
.pDC
->SetTextColor (text
.style
== ILayoutTextList::SText::STYLE_WARNING
1108 ? m_Colors
.GetColor (CColors::gdpWCNodeBorder
).ToCOLORREF()
1109 : standardTextColor
);
1110 graphics
.pDC
->SelectObject (GetFont (FALSE
, text
.style
!= ILayoutTextList::SText::STYLE_DEFAULT
));
1111 graphics
.pDC
->ExtTextOut ((textRect
.left
+ textRect
.right
)/2, textRect
.top
, 0, &textRect
, text
.text
, NULL
);
1113 else if (graphics
.pSVG
)
1115 graphics
.pSVG
->CenteredText((textRect
.left
+ textRect
.right
)/2, textRect
.top
+m_nFontSize
+3, "Arial", m_nFontSize
,
1116 false, text
.style
!= ILayoutTextList::SText::STYLE_DEFAULT
,
1117 text
.style
== ILayoutTextList::SText::STYLE_WARNING
1118 ? m_Colors
.GetColor (CColors::gdpWCNodeBorder
)
1119 : standardTextColor
, CUnicodeUtils::GetUTF8(text
.text
));
1127 void CRevisionGraphWnd::DrawCurrentNodeGlyphs (GraphicsDevice
& graphics
, Image
* glyphs
, const CSize
& offset
)
1130 CSyncPointer
<const ILayoutNodeList
> nodeList (m_state
.GetNodes());
1132 = m_state
.GetOptions()->GetOption
<CUpsideDownLayout
>()->IsActive();
1134 // don't draw glyphs if we are outside the client area
1135 // (e.g. within a scrollbar)
1138 GetCursorPos (&point
);
1139 ScreenToClient (&point
);
1140 if (!GetClientRect().PtInRect (point
))
1143 // expansion glypths etc.
1145 m_hoverIndex
= GetHitNode (point
);
1146 m_hoverGlyphs
= GetHoverGlyphs (point
);
1148 if ((m_hoverIndex
!= NO_INDEX
) || (m_hoverGlyphs
!= 0))
1150 index_t nodeIndex
= m_hoverIndex
== NO_INDEX
1151 ? GetHitNode (point
, CSize (GLYPH_SIZE
, GLYPH_SIZE
/ 2))
1154 if (nodeIndex
>= nodeList
->GetCount())
1157 ILayoutNodeList::SNode node
= nodeList
->GetNode (nodeIndex
);
1158 RectF
noderect (GetNodeRect (node
, offset
));
1160 DWORD flags
= m_state
.GetNodeStates()->GetFlags (node
.node
);
1162 IndicateGlyphDirection (graphics
, nodeList
.get(), node
, noderect
, m_hoverGlyphs
, upsideDown
, offset
);
1163 DrawGlyphs (graphics
, glyphs
, node
.node
, noderect
, flags
, m_hoverGlyphs
, upsideDown
);
1169 void CRevisionGraphWnd::DrawGraph(GraphicsDevice
& graphics
, const CRect
& rect
, int nVScrollPos
, int nHScrollPos
, bool bDirectDraw
)
1171 CMemDC
* memDC
= NULL
;
1176 memDC
= new CMemDC (*graphics
.pDC
, rect
);
1177 graphics
.pDC
= &memDC
->GetDC();
1180 graphics
.pDC
->FillSolidRect(rect
, GetSysColor(COLOR_WINDOW
));
1181 graphics
.pDC
->SetBkMode(TRANSPARENT
);
1184 // preparation & sync
1186 //CSyncPointer<CAllRevisionGraphOptions> options (m_state.GetOptions());
1187 ClearVisibleGlyphs (rect
);
1189 // transform visible
1191 CSize
offset (nHScrollPos
, nVScrollPos
);
1192 CRect
logRect ( (int)(offset
.cx
/ m_fZoomFactor
)-1
1193 , (int)(offset
.cy
/ m_fZoomFactor
)-1
1194 , (int)((rect
.Width() + offset
.cx
) / m_fZoomFactor
) + 1
1195 , (int)((rect
.Height() + offset
.cy
) / m_fZoomFactor
) + 1);
1197 // draw the different components
1201 Graphics
* gcs
= Graphics::FromHDC(*graphics
.pDC
);
1202 graphics
.graphics
= gcs
;
1203 gcs
->SetPageUnit (UnitPixel
);
1204 gcs
->SetInterpolationMode (InterpolationModeHighQualityBicubic
);
1205 gcs
->SetSmoothingMode(SmoothingModeAntiAlias
);
1206 gcs
->SetClip(RectF(Gdiplus::REAL(rect
.left
), Gdiplus::REAL(rect
.top
), Gdiplus::REAL(rect
.Width()), Gdiplus::REAL(rect
.Height())));
1209 // if (options->GetOption<CShowTreeStripes>()->IsActive())
1210 // DrawStripes (graphics, offset);
1212 if (m_fZoomFactor
> SHADOW_ZOOM_THRESHOLD
)
1213 DrawShadows (graphics
, logRect
, offset
);
1215 Bitmap
glyphs (AfxGetInstanceHandle(), MAKEINTRESOURCE(IDR_REVGRAPHGLYPHS
));
1217 DrawNodes (graphics
, &glyphs
, logRect
, offset
);
1218 DrawConnections (graphics
, logRect
, offset
);
1219 DrawTexts (graphics
, logRect
, offset
);
1221 if (m_showHoverGlyphs
)
1222 DrawCurrentNodeGlyphs (graphics
, &glyphs
, offset
);
1226 if ((!bDirectDraw
)&&(m_Preview
.GetSafeHandle())&&(m_bShowOverview
)&&(graphics
.pDC
))
1228 // draw the overview image rectangle in the top right corner
1229 CMyMemDC
memDC2(graphics
.pDC
, true);
1230 memDC2
.SetWindowOrg(0, 0);
1231 HBITMAP oldhbm
= (HBITMAP
)memDC2
.SelectObject(&m_Preview
);
1232 graphics
.pDC
->BitBlt(rect
.Width()-m_previewWidth
, 0, m_previewWidth
, m_previewHeight
,
1233 &memDC2
, 0, 0, SRCCOPY
);
1234 memDC2
.SelectObject(oldhbm
);
1235 // draw the border for the overview rectangle
1236 m_OverviewRect
.left
= rect
.Width()-m_previewWidth
;
1237 m_OverviewRect
.top
= 0;
1238 m_OverviewRect
.right
= rect
.Width();
1239 m_OverviewRect
.bottom
= m_previewHeight
;
1240 graphics
.pDC
->DrawEdge(&m_OverviewRect
, EDGE_BUMP
, BF_RECT
);
1241 // now draw a rectangle where the current view is located in the overview
1243 CRect viewRect
= GetViewRect();
1244 LONG width
= (long)(rect
.Width() * m_previewZoom
/ m_fZoomFactor
);
1245 LONG height
= (long)(rect
.Height() * m_previewZoom
/ m_fZoomFactor
);
1246 LONG xpos
= (long)(nHScrollPos
* m_previewZoom
/ m_fZoomFactor
);
1247 LONG ypos
= (long)(nVScrollPos
* m_previewZoom
/ m_fZoomFactor
);
1249 tempRect
.left
= rect
.Width()-m_previewWidth
+xpos
;
1250 tempRect
.top
= ypos
;
1251 tempRect
.right
= tempRect
.left
+ width
;
1252 tempRect
.bottom
= tempRect
.top
+ height
;
1253 // make sure the position rect is not bigger than the preview window itself
1254 ::IntersectRect(&m_OverviewPosRect
, &m_OverviewRect
, &tempRect
);
1256 RectF
rect2 ( (float)m_OverviewPosRect
.left
, (float)m_OverviewPosRect
.top
1257 , (float)m_OverviewPosRect
.Width(), (float)m_OverviewPosRect
.Height());
1258 if (graphics
.graphics
)
1260 SolidBrush
brush (Color (64, 0, 0, 0));
1261 graphics
.graphics
->FillRectangle (&brush
, rect2
);
1262 graphics
.pDC
->DrawEdge(&m_OverviewPosRect
, EDGE_BUMP
, BF_RECT
);
1266 // flush changes to screen
1268 delete graphics
.graphics
;
1273 void CRevisionGraphWnd::DrawRubberBand()
1275 CDC
* pDC
= GetDC();
1276 pDC
->SetROP2(R2_NOT
);
1277 pDC
->SelectObject(GetStockObject(NULL_BRUSH
));
1278 pDC
->Rectangle(min(m_ptRubberStart
.x
, m_ptRubberEnd
.x
), min(m_ptRubberStart
.y
, m_ptRubberEnd
.y
),
1279 max(m_ptRubberStart
.x
, m_ptRubberEnd
.x
), max(m_ptRubberStart
.y
, m_ptRubberEnd
.y
));