1 // TortoiseSVN - a Windows shell extension for easy version control
\r
3 // Copyright (C) 2003-2009 - TortoiseSVN
\r
5 // This program is free software; you can redistribute it and/or
\r
6 // modify it under the terms of the GNU General Public License
\r
7 // as published by the Free Software Foundation; either version 2
\r
8 // of the License, or (at your option) any later version.
\r
10 // This program is distributed in the hope that it will be useful,
\r
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
13 // GNU General Public License for more details.
\r
15 // You should have received a copy of the GNU General Public License
\r
16 // along with this program; if not, write to the Free Software Foundation,
\r
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\r
20 #include "TortoiseProc.h"
\r
21 #include "MyMemDC.h"
\r
22 #include "Revisiongraphdlg.h"
\r
23 #include "MessageBox.h"
\r
25 #include "TempFile.h"
\r
26 #include "UnicodeUtils.h"
\r
27 #include "TSVNPath.h"
\r
28 #include "SVNInfo.h"
\r
29 #include "SVNDiff.h"
\r
30 #include ".\revisiongraphwnd.h"
\r
31 #include "IRevisionGraphLayout.h"
\r
32 #include "UpsideDownLayout.h"
\r
33 #include "ShowTreeStripes.h"
\r
36 #define new DEBUG_NEW
\r
38 static char THIS_FILE[] = __FILE__;
\r
41 using namespace Gdiplus;
\r
43 /************************************************************************/
\r
44 /* Graphing functions */
\r
45 /************************************************************************/
\r
46 CFont* CRevisionGraphWnd::GetFont(BOOL bItalic /*= FALSE*/, BOOL bBold /*= FALSE*/)
\r
53 if (m_apFonts[nIndex] == NULL)
\r
55 m_apFonts[nIndex] = new CFont;
\r
56 m_lfBaseFont.lfWeight = bBold ? FW_BOLD : FW_NORMAL;
\r
57 m_lfBaseFont.lfItalic = (BYTE) bItalic;
\r
58 m_lfBaseFont.lfStrikeOut = (BYTE) FALSE;
\r
59 CDC * pDC = GetDC();
\r
60 m_lfBaseFont.lfHeight = -MulDiv(m_nFontSize, GetDeviceCaps(pDC->m_hDC, LOGPIXELSY), 72);
\r
62 // use the empty font name, so GDI takes the first font which matches
\r
63 // the specs. Maybe this will help render chinese/japanese chars correctly.
\r
64 _tcsncpy_s(m_lfBaseFont.lfFaceName, 32, _T("MS Shell Dlg 2"), 32);
\r
65 if (!m_apFonts[nIndex]->CreateFontIndirect(&m_lfBaseFont))
\r
67 delete m_apFonts[nIndex];
\r
68 m_apFonts[nIndex] = NULL;
\r
69 return CWnd::GetFont();
\r
72 return m_apFonts[nIndex];
\r
75 BOOL CRevisionGraphWnd::OnEraseBkgnd(CDC* /*pDC*/)
\r
80 void CRevisionGraphWnd::OnPaint()
\r
82 CPaintDC dc(this); // device context for painting
\r
83 CRect rect = GetClientRect();
\r
84 if (m_bThreadRunning)
\r
86 dc.FillSolidRect(rect, ::GetSysColor(COLOR_APPWORKSPACE));
\r
90 else if (!m_state.GetNodes())
\r
92 CString sNoGraphText;
\r
93 sNoGraphText.LoadString(IDS_REVGRAPH_ERR_NOGRAPH);
\r
94 dc.FillSolidRect(rect, RGB(255,255,255));
\r
95 dc.ExtTextOut(20,20,ETO_CLIPPED,NULL,sNoGraphText,NULL);
\r
99 DrawGraph(&dc, rect, GetScrollPos(SB_VERT), GetScrollPos(SB_HORZ), false);
\r
102 void CRevisionGraphWnd::ClearVisibleGlyphs (const CRect& rect)
\r
104 float glyphSize = GLYPH_SIZE * m_fZoomFactor;
\r
106 CSyncPointer<CRevisionGraphState::TVisibleGlyphs>
\r
107 visibleGlyphs (m_state.GetVisibleGlyphs());
\r
109 for (size_t i = visibleGlyphs->size(), count = i; i > 0; --i)
\r
111 const PointF& leftTop = (*visibleGlyphs)[i-1].leftTop;
\r
112 CRect glyphRect ( static_cast<int>(leftTop.X)
\r
113 , static_cast<int>(leftTop.Y)
\r
114 , static_cast<int>(leftTop.X + glyphSize)
\r
115 , static_cast<int>(leftTop.Y + glyphSize));
\r
117 if (CRect().IntersectRect (glyphRect, rect))
\r
119 (*visibleGlyphs)[i-1] = (*visibleGlyphs)[--count];
\r
120 visibleGlyphs->pop_back();
\r
125 void CRevisionGraphWnd::CutawayPoints (const RectF& rect, float cutLen, TCutRectangle& result)
\r
127 result[0] = PointF (rect.X, rect.Y + cutLen);
\r
128 result[1] = PointF (rect.X + cutLen, rect.Y);
\r
129 result[2] = PointF (rect.GetRight() - cutLen, rect.Y);
\r
130 result[3] = PointF (rect.GetRight(), rect.Y + cutLen);
\r
131 result[4] = PointF (rect.GetRight(), rect.GetBottom() - cutLen);
\r
132 result[5] = PointF (rect.GetRight() - cutLen, rect.GetBottom());
\r
133 result[6] = PointF (rect.X + cutLen, rect.GetBottom());
\r
134 result[7] = PointF (rect.X, rect.GetBottom() - cutLen);
\r
137 void CRevisionGraphWnd::DrawRoundedRect (Graphics& graphics, const Pen* pen, const Brush* brush, const RectF& rect)
\r
139 enum {POINT_COUNT = 8};
\r
141 float radius = 16 * m_fZoomFactor;
\r
142 PointF points[POINT_COUNT];
\r
143 CutawayPoints (rect, radius, points);
\r
146 path.AddArc (points[0].X, points[1].Y, radius, radius, 180, 90);
\r
147 path.AddArc (points[2].X, points[2].Y, radius, radius, 270, 90);
\r
148 path.AddArc (points[5].X, points[4].Y, radius, radius, 0, 90);
\r
149 path.AddArc (points[7].X, points[7].Y, radius, radius, 90, 90);
\r
151 points[0].Y -= radius / 2;
\r
152 path.AddLine (points[7], points[0]);
\r
155 graphics.FillPath (brush, &path);
\r
157 graphics.DrawPath (pen, &path);
\r
160 void CRevisionGraphWnd::DrawOctangle (Graphics& graphics, const Pen* pen, const Brush* brush, const RectF& rect)
\r
162 enum {POINT_COUNT = 8};
\r
164 // show left & right edges of low boxes as "<===>"
\r
166 float minCutAway = min (16 * m_fZoomFactor, rect.Height / 2);
\r
168 // larger boxes: remove 25% of the shorter side
\r
170 float suggestedCutAway = min (rect.Height, rect.Width) / 4;
\r
172 // use the more visible one of the former two
\r
174 PointF points[POINT_COUNT];
\r
175 CutawayPoints (rect, max (minCutAway, suggestedCutAway), points);
\r
180 graphics.FillPolygon (brush, points, POINT_COUNT);
\r
182 graphics.DrawPolygon (pen, points, POINT_COUNT);
\r
185 void CRevisionGraphWnd::DrawShape (Graphics& graphics, const Pen* pen, const Brush* brush, const RectF& rect, NodeShape shape)
\r
189 case TSVNRectangle:
\r
191 graphics.FillRectangle (brush, rect);
\r
193 graphics.DrawRectangle (pen, rect);
\r
195 case TSVNRoundRect:
\r
196 DrawRoundedRect (graphics, pen, brush, rect);
\r
199 DrawOctangle (graphics, pen, brush, rect);
\r
203 graphics.FillEllipse (brush, rect);
\r
205 graphics.DrawEllipse(pen, rect);
\r
208 ASSERT(FALSE); //unknown type
\r
213 inline BYTE LimitedScaleColor (BYTE c1, BYTE c2, float factor)
\r
215 BYTE scaled = c2 + (BYTE)((c1-c2)*factor);
\r
218 : min (c1, scaled);
\r
221 Color LimitedScaleColor (const Color& c1, const Color& c2, float factor)
\r
223 return Color ( LimitedScaleColor (c1.GetA(), c2.GetA(), factor)
\r
224 , LimitedScaleColor (c1.GetR(), c2.GetR(), factor)
\r
225 , LimitedScaleColor (c1.GetG(), c2.GetG(), factor)
\r
226 , LimitedScaleColor (c1.GetB(), c2.GetB(), factor));
\r
229 inline BYTE Darken (BYTE c)
\r
233 : BYTE(int(2*c) - 0x100);
\r
236 Color Darken (const Color& c)
\r
238 return Color ( 0xff
\r
239 , Darken (c.GetR())
\r
240 , Darken (c.GetG())
\r
241 , Darken (c.GetB()));
\r
244 BYTE MaxComponentDiff (const Color& c1, const Color& c2)
\r
246 int rDiff = abs ((int)c1.GetR() - (int)c2.GetR());
\r
247 int gDiff = abs ((int)c1.GetG() - (int)c2.GetG());
\r
248 int bDiff = abs ((int)c1.GetB() - (int)c2.GetB());
\r
250 return (BYTE) max (max (rDiff, gDiff), bDiff);
\r
253 void CRevisionGraphWnd::DrawShadow (Graphics& graphics, const RectF& rect,
\r
254 Color shadowColor, NodeShape shape)
\r
258 RectF shadow = rect;
\r
259 shadow.Offset (2, 2);
\r
261 Pen pen (shadowColor);
\r
262 SolidBrush brush (shadowColor);
\r
264 DrawShape (graphics, &pen, &brush, shadow, shape);
\r
267 void CRevisionGraphWnd::DrawNode(Graphics& graphics, const RectF& rect,
\r
268 Color contour, Color overlayColor,
\r
269 const CVisibleGraphNode *node, NodeShape shape)
\r
271 // special case: line deleted but deletion node removed
\r
272 // (don't show as "deleted" if the following node has been folded / split)
\r
276 MASK = CGraphNodeStates::COLLAPSED_BELOW | CGraphNodeStates::SPLIT_BELOW
\r
279 CNodeClassification nodeClassification = node->GetClassification();
\r
280 if ( (node->GetNext() == NULL)
\r
281 && (nodeClassification.Is (CNodeClassification::PATH_ONLY_DELETED))
\r
282 && ((m_state.GetNodeStates()->GetFlags (node) & MASK) == 0))
\r
284 contour = m_Colors.GetColor (CColors::gdpDeletedNode);
\r
287 // calculate the GDI+ color values we need to draw the node
\r
290 background.SetFromCOLORREF (GetSysColor(COLOR_WINDOW));
\r
292 if (nodeClassification.Is (CNodeClassification::IS_MODIFIED_WC))
\r
293 textColor = m_Colors.GetColor (CColors::gdpWCNodeBorder);
\r
295 textColor.SetFromCOLORREF (GetSysColor(COLOR_WINDOWTEXT));
\r
297 Color brightColor = LimitedScaleColor (background, contour, 0.9f);
\r
299 // Draw the main shape
\r
301 bool isWorkingCopy
\r
302 = nodeClassification.Is (CNodeClassification::IS_WORKINGCOPY);
\r
303 bool textAsBorderColor
\r
304 = nodeClassification.IsAnyOf ( CNodeClassification::IS_LAST
\r
305 | CNodeClassification::IS_MODIFIED_WC)
\r
306 | nodeClassification.Matches ( CNodeClassification::IS_COPY_SOURCE
\r
307 , CNodeClassification::IS_OPERATION_MASK);
\r
309 Color penColor = textAsBorderColor
\r
313 Pen pen (penColor, isWorkingCopy ? 3.0f : 1.0f);
\r
314 SolidBrush brush (brightColor);
\r
315 DrawShape (graphics, &pen, &brush, rect, shape);
\r
317 // overlay with some other color
\r
319 if (overlayColor.GetValue() != 0)
\r
321 SolidBrush brush2 (overlayColor);
\r
322 DrawShape (graphics, &pen, &brush2, rect, shape);
\r
326 RectF CRevisionGraphWnd::TransformRectToScreen (const CRect& rect, const CSize& offset) const
\r
328 PointF leftTop ( rect.left * m_fZoomFactor
\r
329 , rect.top * m_fZoomFactor);
\r
330 return RectF ( leftTop.X - offset.cx
\r
331 , leftTop.Y - offset.cy
\r
332 , rect.right * m_fZoomFactor - leftTop.X - 1
\r
333 , rect.bottom * m_fZoomFactor - leftTop.Y);
\r
336 RectF CRevisionGraphWnd::GetNodeRect (const ILayoutNodeList::SNode& node, const CSize& offset) const
\r
338 // get node and position
\r
340 RectF noderect (TransformRectToScreen (node.rect, offset));
\r
342 // show two separate lines for touching nodes,
\r
343 // unless the scale is too small
\r
345 if (noderect.Height > 4.0f)
\r
346 noderect.Height -= 1.0f;
\r
353 RectF CRevisionGraphWnd::GetBranchCover
\r
354 ( const ILayoutNodeList* nodeList
\r
355 , index_t nodeIndex
\r
357 , const CSize& offset)
\r
359 // construct a rect that covers the respective branch
\r
361 CRect cover (0, 0, 0, 0);
\r
362 while (nodeIndex != NO_INDEX)
\r
364 ILayoutNodeList::SNode node = nodeList->GetNode (nodeIndex);
\r
365 cover |= node.rect;
\r
367 const CVisibleGraphNode* nextNode = upward
\r
368 ? node.node->GetPrevious()
\r
369 : node.node->GetNext();
\r
371 nodeIndex = nextNode == NULL ? NO_INDEX : nextNode->GetIndex();
\r
374 // expand it just a little to make it look nicer
\r
376 cover.InflateRect (10, 2, 10, 2);
\r
378 // and now, transfrom it
\r
380 return TransformRectToScreen (cover, offset);
\r
383 void CRevisionGraphWnd::DrawShadows (Graphics& graphics, const CRect& logRect, const CSize& offset)
\r
385 // shadow color to use
\r
388 background.SetFromCOLORREF (GetSysColor(COLOR_WINDOW));
\r
390 textColor.SetFromCOLORREF (GetSysColor(COLOR_WINDOWTEXT));
\r
392 Color shadowColor = LimitedScaleColor (background, ARGB (Color::Black), 0.5f);
\r
394 // iterate over all visible nodes
\r
396 CSyncPointer<const ILayoutNodeList> nodes (m_state.GetNodes());
\r
397 for ( index_t index = nodes->GetFirstVisible (logRect)
\r
398 ; index != NO_INDEX
\r
399 ; index = nodes->GetNextVisible (index, logRect))
\r
401 // get node and position
\r
403 ILayoutNodeList::SNode node = nodes->GetNode (index);
\r
404 RectF noderect (GetNodeRect (node, offset));
\r
408 switch (node.style)
\r
410 case ILayoutNodeList::SNode::STYLE_DELETED:
\r
411 case ILayoutNodeList::SNode::STYLE_RENAMED:
\r
412 DrawShadow (graphics, noderect, shadowColor, TSVNOctangle);
\r
414 case ILayoutNodeList::SNode::STYLE_ADDED:
\r
415 DrawShadow(graphics, noderect, shadowColor, TSVNRoundRect);
\r
417 case ILayoutNodeList::SNode::STYLE_LAST:
\r
418 DrawShadow(graphics, noderect, shadowColor, TSVNEllipse);
\r
421 DrawShadow(graphics, noderect, shadowColor, TSVNRectangle);
\r
427 void CRevisionGraphWnd::DrawSquare
\r
428 ( Graphics& graphics
\r
429 , const PointF& leftTop
\r
430 , const Color& lightColor
\r
431 , const Color& darkColor
\r
432 , const Color& penColor)
\r
434 float squareSize = MARKER_SIZE * m_fZoomFactor;
\r
436 PointF leftBottom (leftTop.X, leftTop.Y + squareSize);
\r
437 RectF square (leftTop, SizeF (squareSize, squareSize));
\r
439 Pen pen (penColor, max (1, 1.5f * m_fZoomFactor));
\r
440 LinearGradientBrush lgBrush (leftTop, leftBottom, lightColor, darkColor);
\r
441 graphics.FillRectangle (&lgBrush, square);
\r
442 graphics.DrawRectangle (&pen, square);
\r
445 void CRevisionGraphWnd::DrawGlyph
\r
446 ( Graphics& graphics
\r
448 , const PointF& leftTop
\r
450 , GlyphPosition position)
\r
454 if (glyph == NoGlyph)
\r
457 // bitmap source area
\r
459 REAL x = ((REAL)position + (REAL)glyph) * GLYPH_SIZE;
\r
461 // screen target area
\r
463 float glyphSize = GLYPH_SIZE * m_fZoomFactor;
\r
464 RectF target (leftTop, SizeF (glyphSize, glyphSize));
\r
468 graphics.DrawImage ( glyphs
\r
470 , x, 0.0f, GLYPH_SIZE, GLYPH_SIZE
\r
471 , UnitPixel, NULL, NULL, NULL);
\r
474 void CRevisionGraphWnd::DrawGlyphs
\r
475 ( Graphics& graphics
\r
477 , const CVisibleGraphNode* node
\r
478 , const PointF& center
\r
481 , GlyphPosition position
\r
486 // don't show collapse and cut glyths by default
\r
488 if (!showAll && ((glyph1 == CollapseGlyph) || (glyph1 == SplitGlyph)))
\r
490 if (!showAll && ((glyph2 == CollapseGlyph) || (glyph2 == SplitGlyph)))
\r
493 // glyth2 shall be set only if 2 glyphs are in use
\r
495 if (glyph1 == NoGlyph)
\r
497 std::swap (glyph1, glyph2);
\r
498 std::swap (state1, state2);
\r
503 if (glyph1 == NoGlyph)
\r
508 CSyncPointer<CRevisionGraphState::TVisibleGlyphs>
\r
509 visibleGlyphs (m_state.GetVisibleGlyphs());
\r
511 float squareSize = GLYPH_SIZE * m_fZoomFactor;
\r
512 if (glyph2 == NoGlyph)
\r
514 PointF leftTop (center.X - 0.5f * squareSize, center.Y - 0.5f * squareSize);
\r
515 DrawGlyph (graphics, glyphs, leftTop, glyph1, position);
\r
516 visibleGlyphs->push_back
\r
517 (CRevisionGraphState::SVisibleGlyph (state1, leftTop, node));
\r
521 PointF leftTop1 (center.X - squareSize, center.Y - 0.5f * squareSize);
\r
522 DrawGlyph (graphics, glyphs, leftTop1, glyph1, position);
\r
523 visibleGlyphs->push_back
\r
524 (CRevisionGraphState::SVisibleGlyph (state1, leftTop1, node));
\r
526 PointF leftTop2 (center.X, center.Y - 0.5f * squareSize);
\r
527 DrawGlyph (graphics, glyphs, leftTop2, glyph2, position);
\r
528 visibleGlyphs->push_back
\r
529 (CRevisionGraphState::SVisibleGlyph (state2, leftTop2, node));
\r
533 void CRevisionGraphWnd::DrawGlyphs
\r
534 ( Graphics& graphics
\r
536 , const CVisibleGraphNode* node
\r
537 , const RectF& nodeRect
\r
544 if ((state == 0) && (allowed == 0))
\r
549 PointF topCenter (0.5f * nodeRect.GetLeft() + 0.5f * nodeRect.GetRight(), nodeRect.GetTop());
\r
550 PointF rightCenter (nodeRect.GetRight(), 0.5f * nodeRect.GetTop() + 0.5f * nodeRect.GetBottom());
\r
551 PointF bottomCenter (0.5f * nodeRect.GetLeft() + 0.5f * nodeRect.GetRight(), nodeRect.GetBottom());
\r
553 DrawGlyphs ( graphics
\r
556 , upsideDown ? bottomCenter : topCenter
\r
557 , (state & CGraphNodeStates::COLLAPSED_ABOVE) ? ExpandGlyph : CollapseGlyph
\r
558 , (state & CGraphNodeStates::SPLIT_ABOVE) ? JoinGlyph : SplitGlyph
\r
559 , upsideDown ? Below : Above
\r
560 , CGraphNodeStates::COLLAPSED_ABOVE
\r
561 , CGraphNodeStates::SPLIT_ABOVE
\r
562 , (allowed & CGraphNodeStates::COLLAPSED_ABOVE) != 0);
\r
564 DrawGlyphs ( graphics
\r
568 , (state & CGraphNodeStates::COLLAPSED_RIGHT) ? ExpandGlyph : CollapseGlyph
\r
569 , (state & CGraphNodeStates::SPLIT_RIGHT) ? JoinGlyph : SplitGlyph
\r
571 , CGraphNodeStates::COLLAPSED_RIGHT
\r
572 , CGraphNodeStates::SPLIT_RIGHT
\r
573 , (allowed & CGraphNodeStates::COLLAPSED_RIGHT) != 0);
\r
575 DrawGlyphs ( graphics
\r
578 , upsideDown ? topCenter : bottomCenter
\r
579 , (state & CGraphNodeStates::COLLAPSED_BELOW) ? ExpandGlyph : CollapseGlyph
\r
580 , (state & CGraphNodeStates::SPLIT_BELOW) ? JoinGlyph : SplitGlyph
\r
581 , upsideDown ? Above : Below
\r
582 , CGraphNodeStates::COLLAPSED_BELOW
\r
583 , CGraphNodeStates::SPLIT_BELOW
\r
584 , (allowed & CGraphNodeStates::COLLAPSED_BELOW) != 0);
\r
587 void CRevisionGraphWnd::IndicateGlyphDirection
\r
588 ( Graphics& graphics
\r
589 , const ILayoutNodeList* nodeList
\r
590 , const ILayoutNodeList::SNode& node
\r
591 , const RectF& nodeRect
\r
594 , const CSize& offset)
\r
601 // where to place the indication?
\r
603 bool indicateAbove = (glyphs & CGraphNodeStates::COLLAPSED_ABOVE) != 0;
\r
604 bool indicateRight = (glyphs & CGraphNodeStates::COLLAPSED_RIGHT) != 0;
\r
605 bool indicateBelow = (glyphs & CGraphNodeStates::COLLAPSED_BELOW) != 0;
\r
607 // fill indication area a semi-transparent blend of red
\r
608 // and the background color
\r
611 color.SetFromCOLORREF (GetSysColor(COLOR_WINDOW));
\r
612 color.SetValue ((color.GetValue() & 0x807f7f7f) + 0x800000);
\r
614 SolidBrush brush (color);
\r
616 // draw the indication (only one condition should match)
\r
618 RectF glyphCenter = indicateAbove ^ upsideDown
\r
619 ? RectF (nodeRect.X, nodeRect.Y - 1.0f, 0.0f, 0.0f)
\r
620 : RectF (nodeRect.X, nodeRect.GetBottom() - 1.0f, 0.0f, 0.0f);
\r
624 const CVisibleGraphNode* firstAffected = node.node->GetSource();
\r
626 assert (firstAffected);
\r
628 = GetBranchCover (nodeList, firstAffected->GetIndex(), true, offset);
\r
629 RectF::Union (branchCover, branchCover, glyphCenter);
\r
631 graphics.FillRectangle (&brush, branchCover);
\r
636 for ( const CVisibleGraphNode::CCopyTarget* branch
\r
637 = node.node->GetFirstCopyTarget()
\r
639 ; branch = branch->next())
\r
642 = GetBranchCover (nodeList, branch->value()->GetIndex(), false, offset);
\r
643 graphics.FillRectangle (&brush, branchCover);
\r
649 const CVisibleGraphNode* firstAffected
\r
650 = node.node->GetNext();
\r
653 = GetBranchCover (nodeList, firstAffected->GetIndex(), false, offset);
\r
654 RectF::Union (branchCover, branchCover, glyphCenter);
\r
656 graphics.FillRectangle (&brush, branchCover);
\r
660 void CRevisionGraphWnd::DrawMarker
\r
661 ( Graphics& graphics
\r
662 , const RectF& noderect
\r
663 , MarkerPosition position
\r
668 float squareSize = MARKER_SIZE * m_fZoomFactor;
\r
669 float squareDist = min ( (noderect.Height - squareSize) / 2
\r
674 REAL offset = squareSize * (0.75f + relPosition);
\r
675 REAL left = position == mpRight
\r
676 ? noderect.GetRight() - offset - squareSize
\r
677 : noderect.GetLeft() + offset;
\r
678 PointF leftTop (left, noderect.Y + squareDist);
\r
682 Color lightColor (m_Colors.GetColor (CColors::ctMarkers, colorIndex));
\r
683 Color darkColor (Darken (lightColor));
\r
684 Color borderColor (0x80000000);
\r
688 DrawSquare (graphics, leftTop, lightColor, darkColor, borderColor);
\r
691 void CRevisionGraphWnd::DrawStripes (Graphics& graphics, const CSize& offset)
\r
693 // we need to fill this visible area of the the screen
\r
694 // (even if there is graph in that part)
\r
697 graphics.GetVisibleClipBounds (&clipRect);
\r
699 // don't show stripes if we don't have mutiple roots
\r
701 CSyncPointer<const ILayoutRectList> trees (m_state.GetTrees());
\r
702 if (trees->GetCount() < 2)
\r
705 // iterate over all trees
\r
707 for ( index_t i = 0, count = trees->GetCount(); i < count; ++i)
\r
709 // screen coordinates coverd by the tree
\r
711 CRect tree = trees->GetRect(i);
\r
712 REAL left = tree.left * m_fZoomFactor;
\r
713 REAL right = tree.right * m_fZoomFactor;
\r
714 RectF rect ( left - offset.cx
\r
716 , i+1 == count ? clipRect.Width : right - left
\r
717 , clipRect.Height);
\r
721 if (rect.IntersectsWith (clipRect))
\r
723 // draw the background stripe
\r
725 Color color ( (i & 1) == 0
\r
726 ? m_Colors.GetColor (CColors::gdpStripeColor1)
\r
727 : m_Colors.GetColor (CColors::gdpStripeColor2));
\r
728 SolidBrush brush (color);
\r
729 graphics.FillRectangle (&brush, rect);
\r
734 void CRevisionGraphWnd::DrawNodes (Graphics& graphics, Image* glyphs, const CRect& logRect, const CSize& offset)
\r
736 CSyncPointer<CGraphNodeStates> nodeStates (m_state.GetNodeStates());
\r
737 CSyncPointer<const ILayoutNodeList> nodes (m_state.GetNodes());
\r
740 = m_state.GetOptions()->GetOption<CUpsideDownLayout>()->IsActive();
\r
742 // iterate over all visible nodes
\r
744 for ( index_t index = nodes->GetFirstVisible (logRect)
\r
745 ; index != NO_INDEX
\r
746 ; index = nodes->GetNextVisible (index, logRect))
\r
748 // get node and position
\r
750 ILayoutNodeList::SNode node = nodes->GetNode (index);
\r
751 RectF noderect (GetNodeRect (node, offset));
\r
755 Color transparent (0);
\r
756 Color overlayColor = transparent;
\r
758 switch (node.style)
\r
760 case ILayoutNodeList::SNode::STYLE_DELETED:
\r
761 DrawNode(graphics, noderect, m_Colors.GetColor(CColors::gdpDeletedNode), transparent, node.node, TSVNOctangle);
\r
764 case ILayoutNodeList::SNode::STYLE_ADDED:
\r
765 if (m_bTweakTagsColors && node.node->GetClassification().Is (CNodeClassification::IS_TAG))
\r
766 overlayColor = m_Colors.GetColor(CColors::gdpTagOverlay);
\r
767 else if (m_bTweakTrunkColors && node.node->GetClassification().Is (CNodeClassification::IS_TRUNK))
\r
768 overlayColor = m_Colors.GetColor(CColors::gdpTrunkOverlay);
\r
769 DrawNode(graphics, noderect, m_Colors.GetColor(CColors::gdpAddedNode), overlayColor, node.node, TSVNRoundRect);
\r
772 case ILayoutNodeList::SNode::STYLE_RENAMED:
\r
773 DrawNode(graphics, noderect, m_Colors.GetColor(CColors::gdpRenamedNode), overlayColor, node.node, TSVNOctangle);
\r
776 case ILayoutNodeList::SNode::STYLE_LAST:
\r
777 DrawNode(graphics, noderect, m_Colors.GetColor(CColors::gdpLastCommitNode), transparent, node.node, TSVNEllipse);
\r
780 case ILayoutNodeList::SNode::STYLE_MODIFIED:
\r
781 DrawNode(graphics, noderect, m_Colors.GetColor(CColors::gdpModifiedNode), transparent, node.node, TSVNRectangle);
\r
784 case ILayoutNodeList::SNode::STYLE_MODIFIED_WC:
\r
785 DrawNode(graphics, noderect, m_Colors.GetColor(CColors::gdpWCNode), transparent, node.node, TSVNEllipse);
\r
789 DrawNode(graphics, noderect, m_Colors.GetColor(CColors::gdpUnchangedNode), transparent, node.node, TSVNRectangle);
\r
793 // Draw the "tagged" icon
\r
795 if (node.node->GetFirstTag() != NULL)
\r
796 DrawMarker (graphics, noderect, mpRight, 0, 0);
\r
798 if ((m_SelectedEntry1 == node.node) || (m_SelectedEntry2 == node.node))
\r
799 DrawMarker (graphics, noderect, mpLeft, 0, 1);
\r
801 // expansion glypths etc.
\r
803 DrawGlyphs (graphics, glyphs, node.node, noderect, nodeStates->GetFlags (node.node), 0, upsideDown);
\r
807 void CRevisionGraphWnd::DrawConnections (CDC* pDC, const CRect& logRect, const CSize& offset)
\r
809 enum {MAX_POINTS = 100};
\r
810 CPoint points[MAX_POINTS];
\r
812 CPen newpen(PS_SOLID, 0, GetSysColor(COLOR_WINDOWTEXT));
\r
813 CPen * pOldPen = pDC->SelectObject(&newpen);
\r
815 // iterate over all visible lines
\r
817 CSyncPointer<const ILayoutConnectionList> connections (m_state.GetConnections());
\r
818 for ( index_t index = connections->GetFirstVisible (logRect)
\r
819 ; index != NO_INDEX
\r
820 ; index = connections->GetNextVisible (index, logRect))
\r
822 // get connection and point position
\r
824 ILayoutConnectionList::SConnection connection
\r
825 = connections->GetConnection (index);
\r
827 if (connection.numberOfPoints > MAX_POINTS)
\r
830 for (index_t i = 0; i < connection.numberOfPoints; ++i)
\r
832 points[i].x = (int)(connection.points[i].x * m_fZoomFactor) - offset.cx;
\r
833 points[i].y = (int)(connection.points[i].y * m_fZoomFactor) - offset.cy;
\r
836 // draw the connection
\r
838 pDC->PolyBezier (points, connection.numberOfPoints);
\r
841 pDC->SelectObject(pOldPen);
\r
844 void CRevisionGraphWnd::DrawTexts (CDC* pDC, const CRect& logRect, const CSize& offset)
\r
846 COLORREF standardTextColor = GetSysColor(COLOR_WINDOWTEXT);
\r
847 if (m_nFontSize <= 0)
\r
850 // iterate over all visible nodes
\r
852 pDC->SetTextAlign (TA_CENTER | TA_TOP);
\r
853 CSyncPointer<const ILayoutTextList> texts (m_state.GetTexts());
\r
854 for ( index_t index = texts->GetFirstVisible (logRect)
\r
855 ; index != NO_INDEX
\r
856 ; index = texts->GetNextVisible (index, logRect))
\r
858 // get node and position
\r
860 ILayoutTextList::SText text = texts->GetText (index);
\r
861 CRect textRect ( (int)(text.rect.left * m_fZoomFactor) - offset.cx
\r
862 , (int)(text.rect.top * m_fZoomFactor) - offset.cy
\r
863 , (int)(text.rect.right * m_fZoomFactor) - offset.cx
\r
864 , (int)(text.rect.bottom * m_fZoomFactor) - offset.cy);
\r
866 // draw the revision text
\r
868 pDC->SetTextColor (text.style == ILayoutTextList::SText::STYLE_WARNING
\r
869 ? m_Colors.GetColor (CColors::gdpWCNodeBorder).ToCOLORREF()
\r
870 : standardTextColor );
\r
871 pDC->SelectObject (GetFont (FALSE, text.style != ILayoutTextList::SText::STYLE_DEFAULT));
\r
872 pDC->ExtTextOut ((textRect.left + textRect.right)/2, textRect.top, 0, &textRect, text.text, NULL);
\r
876 void CRevisionGraphWnd::DrawCurrentNodeGlyphs (Graphics& graphics, Image* glyphs, const CSize& offset)
\r
878 CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());
\r
880 = m_state.GetOptions()->GetOption<CUpsideDownLayout>()->IsActive();
\r
882 // don't draw glyphs if we are outside the client area
\r
883 // (e.g. within a scrollbar)
\r
886 GetCursorPos (&point);
\r
887 ScreenToClient (&point);
\r
888 if (!GetClientRect().PtInRect (point))
\r
891 // expansion glypths etc.
\r
893 m_hoverIndex = GetHitNode (point);
\r
894 m_hoverGlyphs = GetHoverGlyphs (point);
\r
896 if ((m_hoverIndex != NO_INDEX) || (m_hoverGlyphs != 0))
\r
898 index_t nodeIndex = m_hoverIndex == NO_INDEX
\r
899 ? GetHitNode (point, CSize (GLYPH_SIZE, GLYPH_SIZE / 2))
\r
902 if (nodeIndex >= nodeList->GetCount())
\r
905 ILayoutNodeList::SNode node = nodeList->GetNode (nodeIndex);
\r
906 RectF noderect (GetNodeRect (node, offset));
\r
908 DWORD flags = m_state.GetNodeStates()->GetFlags (node.node);
\r
910 IndicateGlyphDirection (graphics, nodeList.get(), node, noderect, m_hoverGlyphs, upsideDown, offset);
\r
911 DrawGlyphs (graphics, glyphs, node.node, noderect, flags, m_hoverGlyphs, upsideDown);
\r
915 void CRevisionGraphWnd::DrawGraph(CDC* pDC, const CRect& rect, int nVScrollPos, int nHScrollPos, bool bDirectDraw)
\r
917 CMemDC* memDC = NULL;
\r
920 memDC = new CMemDC (*pDC, rect);
\r
921 pDC = &memDC->GetDC();
\r
924 pDC->FillSolidRect(rect, GetSysColor(COLOR_WINDOW));
\r
925 pDC->SetBkMode(TRANSPARENT);
\r
927 // preparation & sync
\r
929 CSyncPointer<CAllRevisionGraphOptions> options (m_state.GetOptions());
\r
930 ClearVisibleGlyphs (rect);
\r
932 // transform visible
\r
934 CSize offset (nHScrollPos, nVScrollPos);
\r
935 CRect logRect ( (int)(offset.cx / m_fZoomFactor)-1
\r
936 , (int)(offset.cy / m_fZoomFactor)-1
\r
937 , (int)((rect.Width() + offset.cx) / m_fZoomFactor) + 1
\r
938 , (int)((rect.Height() + offset.cy) / m_fZoomFactor) + 1);
\r
940 // draw the different components
\r
942 Graphics* graphics = Graphics::FromHDC(*pDC);
\r
943 graphics->SetPageUnit (UnitPixel);
\r
944 graphics->SetInterpolationMode (InterpolationModeHighQualityBicubic);
\r
946 if (options->GetOption<CShowTreeStripes>()->IsActive())
\r
947 DrawStripes (*graphics, offset);
\r
949 if (m_fZoomFactor > 0.2f)
\r
950 DrawShadows (*graphics, logRect, offset);
\r
952 Bitmap glyphs (AfxGetInstanceHandle(), MAKEINTRESOURCE(IDR_REVGRAPHGLYPHS));
\r
954 DrawNodes (*graphics, &glyphs, logRect, offset);
\r
955 DrawConnections (pDC, logRect, offset);
\r
956 DrawTexts (pDC, logRect, offset);
\r
958 if (m_showHoverGlyphs)
\r
959 DrawCurrentNodeGlyphs (*graphics, &glyphs, offset);
\r
963 if ((!bDirectDraw)&&(m_Preview.GetSafeHandle())&&(m_bShowOverview))
\r
965 // draw the overview image rectangle in the top right corner
\r
966 CMyMemDC memDC2(pDC, true);
\r
967 memDC2.SetWindowOrg(0, 0);
\r
968 HBITMAP oldhbm = (HBITMAP)memDC2.SelectObject(&m_Preview);
\r
969 pDC->BitBlt(rect.Width()-m_previewWidth, 0, m_previewWidth, m_previewHeight,
\r
970 &memDC2, 0, 0, SRCCOPY);
\r
971 memDC2.SelectObject(oldhbm);
\r
972 // draw the border for the overview rectangle
\r
973 m_OverviewRect.left = rect.Width()-m_previewWidth;
\r
974 m_OverviewRect.top = 0;
\r
975 m_OverviewRect.right = rect.Width();
\r
976 m_OverviewRect.bottom = m_previewHeight;
\r
977 pDC->DrawEdge(&m_OverviewRect, EDGE_BUMP, BF_RECT);
\r
978 // now draw a rectangle where the current view is located in the overview
\r
980 CRect viewRect = GetViewRect();
\r
981 LONG width = (long)(rect.Width() * m_previewZoom / m_fZoomFactor);
\r
982 LONG height = (long)(rect.Height() * m_previewZoom / m_fZoomFactor);
\r
983 LONG xpos = (long)(nHScrollPos * m_previewZoom / m_fZoomFactor);
\r
984 LONG ypos = (long)(nVScrollPos * m_previewZoom / m_fZoomFactor);
\r
986 tempRect.left = rect.Width()-m_previewWidth+xpos;
\r
987 tempRect.top = ypos;
\r
988 tempRect.right = tempRect.left + width;
\r
989 tempRect.bottom = tempRect.top + height;
\r
990 // make sure the position rect is not bigger than the preview window itself
\r
991 ::IntersectRect(&m_OverviewPosRect, &m_OverviewRect, &tempRect);
\r
993 RectF rect2 ( (float)m_OverviewPosRect.left, (float)m_OverviewPosRect.top
\r
994 , (float)m_OverviewPosRect.Width(), (float)m_OverviewPosRect.Height());
\r
995 SolidBrush brush (Color (64, 0, 0, 0));
\r
996 graphics->FillRectangle (&brush, rect2);
\r
997 pDC->DrawEdge(&m_OverviewPosRect, EDGE_BUMP, BF_RECT);
\r
1000 // flush changes to screen
\r
1009 void CRevisionGraphWnd::DrawRubberBand()
\r
1011 CDC * pDC = GetDC();
\r
1012 pDC->SetROP2(R2_NOT);
\r
1013 pDC->SelectObject(GetStockObject(NULL_BRUSH));
\r
1014 pDC->Rectangle(min(m_ptRubberStart.x, m_ptRubberEnd.x), min(m_ptRubberStart.y, m_ptRubberEnd.y),
\r
1015 max(m_ptRubberStart.x, m_ptRubberEnd.x), max(m_ptRubberStart.y, m_ptRubberEnd.y));
\r