RevisionGraph: using antiAlias to smooth lines
[TortoiseGit.git] / src / TortoiseProc / RevisionGraph / RevisionGraphDlgDraw.cpp
bloba3b5d621c3a19b043b2b80d0dc299188c8743a31
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.
19 #include "stdafx.h"
20 #include "TortoiseProc.h"
21 #include "MyMemDC.h"
22 #include "Revisiongraphdlg.h"
23 #include "MessageBox.h"
24 #include "Git.h"
25 #include "TempFile.h"
26 #include "UnicodeUtils.h"
27 #include "TGitPath.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"
35 #ifdef _DEBUG
36 #define new DEBUG_NEW
37 #undef THIS_FILE
38 static char THIS_FILE[] = __FILE__;
39 #endif
41 using namespace Gdiplus;
42 using namespace ogdf;
44 /************************************************************************/
45 /* Graphing functions */
46 /************************************************************************/
47 CFont* CRevisionGraphWnd::GetFont(BOOL bItalic /*= FALSE*/, BOOL bBold /*= FALSE*/)
49 int nIndex = 0;
50 if (bBold)
51 nIndex |= 1;
52 if (bItalic)
53 nIndex |= 2;
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;
60 CDC * pDC = GetDC();
61 m_lfBaseFont.lfHeight = -MulDiv(m_nFontSize, GetDeviceCaps(pDC->m_hDC, LOGPIXELSY), 72);
62 ReleaseDC(pDC);
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*/)
78 return TRUE;
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));
90 CWnd::OnPaint();
91 return;
94 else if (this->m_Graph.empty())
96 CString sNoGraphText;
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);
100 return;
103 GraphicsDevice dev;
104 dev.pDC = &dc;
105 DrawGraph(dev, rect, GetScrollPos(SB_VERT), GetScrollPos(SB_HORZ), false);
109 void CRevisionGraphWnd::ClearVisibleGlyphs (const CRect& rect)
111 #if 0
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();
131 #endif
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)
157 GraphicsPath path;
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]);
166 if (brush != NULL)
168 graphics.graphics->FillPath (brush, &path);
170 if (pen != NULL)
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);
199 // now, draw it
201 if (graphics.graphics)
203 if (brush != NULL)
204 graphics.graphics->FillPolygon (brush, points, POINT_COUNT);
205 if (pen != NULL)
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)
218 switch( shape )
220 case TSVNRectangle:
221 if (graphics.graphics)
223 if (brush != NULL)
224 graphics.graphics->FillRectangle (brush, rect);
225 if (pen != NULL)
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);
232 break;
233 case TSVNRoundRect:
234 DrawRoundedRect (graphics, penColor, penWidth, pen, fillColor, brush, rect);
235 break;
236 case TSVNOctangle:
237 DrawOctangle (graphics, penColor, penWidth, pen, fillColor, brush, rect);
238 break;
239 case TSVNEllipse:
240 if (graphics.graphics)
242 if (brush != NULL)
243 graphics.graphics->FillEllipse (brush, rect);
244 if (pen != NULL)
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);
249 break;
250 default:
251 ASSERT(FALSE); //unknown type
252 return;
257 inline BYTE LimitedScaleColor (BYTE c1, BYTE c2, float factor)
259 BYTE scaled = c2 + (BYTE)((c1-c2)*factor);
260 return c1 < c2
261 ? max (c1, scaled)
262 : min (c1, scaled);
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)
275 return c < 0xc0
276 ? (c / 3) * 2
277 : BYTE(int(2*c) - 0x100);
280 Color Darken (const Color& c)
282 return Color ( 0xff
283 , Darken (c.GetR())
284 , Darken (c.GetG())
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);
297 #if 0
298 void CRevisionGraphWnd::DrawShadow (GraphicsDevice& graphics, const RectF& rect,
299 Color shadowColor, NodeShape shape)
301 // draw the shadow
303 RectF shadow = rect;
304 shadow.Offset (2, 2);
306 Pen pen (shadowColor);
307 SolidBrush brush (shadowColor);
309 DrawShape (graphics, shadowColor, 1, &pen, shadowColor, &brush, shadow, shape);
311 #endif
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)
321 //enum
323 // MASK = CGraphNodeStates::COLLAPSED_BELOW | CGraphNodeStates::SPLIT_BELOW
324 //};
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))
330 // {
331 // contour = m_Colors.GetColor (CColors::gdpDeletedNode);
332 // }
334 // calculate the GDI+ color values we need to draw the node
336 Color background;
337 background.SetFromCOLORREF (GetSysColor(COLOR_WINDOW));
338 Color textColor;
339 // if (nodeClassification.Is (CNodeClassification::IS_MODIFIED_WC))
340 // textColor = m_Colors.GetColor (CColors::gdpWCNodeBorder);
341 // else
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);
350 bool isModifiedWC=0;
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
360 ? textColor
361 : contour;
363 Pen pen (penColor, isWorkingCopy ? 3.0f : 1.0f);
364 #if 0
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
377 ? DashStyleSolid
378 : isCommitRev ? isMinAtRev ? DashStyleDashDot
379 : DashStyleDot
380 : DashStyleDash;
382 pen.SetDashStyle (style);
384 #endif
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
413 CRect rect;
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;
427 // done
429 return noderect;
433 #if 0
434 RectF CRev
435 isionGraphWnd::GetBranchCover
436 ( const ILayoutNodeList* nodeList
437 , index_t nodeIndex
438 , bool upward
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);
447 cover |= node.rect;
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);
464 #endif
467 void CRevisionGraphWnd::DrawShadows (GraphicsDevice& graphics, const CRect& logRect, const CSize& offset)
469 #if 0 // shadow color to use
471 Color background;
472 background.SetFromCOLORREF (GetSysColor(COLOR_WINDOW));
473 Color textColor;
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)
482 ; index != NO_INDEX
483 ; index = nodes->GetNextVisible (index, logRect))
485 // get node and position
487 ILayoutNodeList::SNode node = nodes->GetNode (index);
488 RectF noderect (GetNodeRect (node, offset));
490 // actual drawing
492 switch (node.style)
494 case ILayoutNodeList::SNode::STYLE_DELETED:
495 case ILayoutNodeList::SNode::STYLE_RENAMED:
496 DrawShadow (graphics, noderect, shadowColor, TSVNOctangle);
497 break;
498 case ILayoutNodeList::SNode::STYLE_ADDED:
499 DrawShadow(graphics, noderect, shadowColor, TSVNRoundRect);
500 break;
501 case ILayoutNodeList::SNode::STYLE_LAST:
502 DrawShadow(graphics, noderect, shadowColor, TSVNEllipse);
503 break;
504 default:
505 DrawShadow(graphics, noderect, shadowColor, TSVNRectangle);
506 break;
509 #endif
513 #if 0
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)
532 Pen pen (penColor);
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
545 , Image* glyphs
546 , const PointF& leftTop
547 , GlyphType glyph
548 , GlyphPosition position)
550 // special case
552 if (glyph == NoGlyph)
553 return;
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));
564 // scaled copy
566 if (graphics.graphics)
568 graphics.graphics->DrawImage ( glyphs
569 , target
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
579 // is enough IMHO.
580 // Note:
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));
588 #endif
589 void CRevisionGraphWnd::DrawGlyphs
590 ( GraphicsDevice& graphics
591 , Image* glyphs
592 , const CVisibleGraphNode* node
593 , const PointF& center
594 , GlyphType glyph1
595 , GlyphType glyph2
596 , GlyphPosition position
597 , DWORD state1
598 , DWORD state2
599 , bool showAll)
601 // don't show collapse and cut glyths by default
602 #if 0
603 if (!showAll && ((glyph1 == CollapseGlyph) || (glyph1 == SplitGlyph)))
604 glyph1 = NoGlyph;
605 if (!showAll && ((glyph2 == CollapseGlyph) || (glyph2 == SplitGlyph)))
606 glyph2 = NoGlyph;
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);
616 // anything to do?
618 if (glyph1 == NoGlyph)
619 return;
621 // 1 or 2 glyphs?
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));
634 else
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));
646 #endif
649 void CRevisionGraphWnd::DrawGlyphs
650 ( GraphicsDevice& graphics
651 , Image* glyphs
652 , const CVisibleGraphNode* node
653 , const RectF& nodeRect
654 , DWORD state
655 , DWORD allowed
656 , bool upsideDown)
658 // shortcut
659 #if 0
660 if ((state == 0) && (allowed == 0))
661 return;
663 // draw all glyphs
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
670 , glyphs
671 , node
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
681 , glyphs
682 , node
683 , rightCenter
684 , (state & CGraphNodeStates::COLLAPSED_RIGHT) ? ExpandGlyph : CollapseGlyph
685 , (state & CGraphNodeStates::SPLIT_RIGHT) ? JoinGlyph : SplitGlyph
686 , Right
687 , CGraphNodeStates::COLLAPSED_RIGHT
688 , CGraphNodeStates::SPLIT_RIGHT
689 , (allowed & CGraphNodeStates::COLLAPSED_RIGHT) != 0);
691 DrawGlyphs ( graphics
692 , glyphs
693 , node
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);
701 #endif
705 #if 0
706 void CRevisionGraphWnd::IndicateGlyphDirection
707 ( GraphicsDevice& graphics
708 , const ILayoutNodeList* nodeList
709 , const ILayoutNodeList::SNode& node
710 , const RectF& nodeRect
711 , DWORD glyphs
712 , bool upsideDown
713 , const CSize& offset)
715 // shortcut
717 if (glyphs == 0)
718 return;
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
729 Color 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);
741 if (indicateAbove)
743 const CVisibleGraphNode* firstAffected = node.node->GetSource();
745 assert (firstAffected);
746 RectF branchCover
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,
754 color, 1, color);
757 if (indicateRight)
759 for ( const CVisibleGraphNode::CCopyTarget* branch
760 = node.node->GetFirstCopyTarget()
761 ; branch != NULL
762 ; branch = branch->next())
764 RectF branchCover
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,
770 color, 1, color);
774 if (indicateBelow)
776 const CVisibleGraphNode* firstAffected
777 = node.node->GetNext();
779 RectF branchCover
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,
787 color, 1, color);
791 void CRevisionGraphWnd::DrawMarker
792 ( GraphicsDevice& graphics
793 , const RectF& noderect
794 , MarkerPosition position
795 , int relPosition
796 , int colorIndex )
798 // marker size
799 float squareSize = MARKER_SIZE * m_fZoomFactor;
800 float squareDist = min ( (noderect.Height - squareSize) / 2
801 , squareSize / 2);
803 // position
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);
811 // color
813 Color lightColor (m_Colors.GetColor (CColors::ctMarkers, colorIndex));
814 Color darkColor (Darken (lightColor));
815 Color borderColor (0x80000000);
817 // draw it
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)
827 RectF clipRect;
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)
835 return;
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
847 , clipRect.Y
848 , i+1 == count ? clipRect.Width : right - left
849 , clipRect.Height);
851 // relevant?
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,
867 color, 1, color);
871 #endif
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());
878 // bool upsideDown
879 // = m_state.GetOptions()->GetOption<CUpsideDownLayout>()->IsActive();
881 // iterate over all visible nodes
883 /* for ( index_t index = nodes->GetFirstVisible (logRect)
884 ; index != NO_INDEX
885 ; index = nodes->GetNextVisible (index, logRect))
887 node v;
888 forall_nodes(v,m_Graph)
890 // get node and position
892 // ILayoutNodeList::SNode node = nodes->GetNode (index);
893 RectF noderect (GetNodeRect (v, offset));
895 // actual drawing
897 Color transparent (0);
898 Color overlayColor = transparent;
900 SVGGrouper grouper(graphics.pSVG);
902 DrawNode(graphics, noderect, RGB(255,0,0), overlayColor, v, TSVNRoundRect);
903 #if 0
904 switch (node.style)
906 case ILayoutNodeList::SNode::STYLE_DELETED:
907 DrawNode(graphics, noderect, m_Colors.GetColor(CColors::gdpDeletedNode), transparent, node.node, TSVNOctangle);
908 break;
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);
916 break;
918 case ILayoutNodeList::SNode::STYLE_RENAMED:
919 DrawNode(graphics, noderect, m_Colors.GetColor(CColors::gdpRenamedNode), overlayColor, node.node, TSVNOctangle);
920 break;
922 case ILayoutNodeList::SNode::STYLE_LAST:
923 DrawNode(graphics, noderect, m_Colors.GetColor(CColors::gdpLastCommitNode), transparent, node.node, TSVNEllipse);
924 break;
926 case ILayoutNodeList::SNode::STYLE_MODIFIED:
927 DrawNode(graphics, noderect, m_Colors.GetColor(CColors::gdpModifiedNode), transparent, node.node, TSVNRectangle);
928 break;
930 case ILayoutNodeList::SNode::STYLE_MODIFIED_WC:
931 DrawNode(graphics, noderect, m_Colors.GetColor(CColors::gdpWCNode), transparent, node.node, TSVNEllipse);
932 break;
934 default:
935 DrawNode(graphics, noderect, m_Colors.GetColor(CColors::gdpUnchangedNode), transparent, node.node, TSVNRectangle);
936 break;
938 #endif
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;
966 if(dy != 0) {
967 // below
968 if(pt.Y > ymax) {
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);
975 // above
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);
986 if(dx != 0) {
987 // right
988 if(pt.X > xmax) {
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);
995 // left
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);
1006 return pt;
1010 void CRevisionGraphWnd::DrawConnections (GraphicsDevice& graphics, const CRect& logRect, const CSize& offset)
1013 CArray<PointF> points;
1014 CArray<CPoint> pts;
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
1022 edge e;
1023 forall_edges(e, m_Graph)
1025 // get connection and point position
1026 const DPolyline &dpl = this->m_GraphAttr.bends(e);
1028 points.RemoveAll();
1029 pts.RemoveAll();
1031 PointF pt;
1032 pt.X = m_GraphAttr.x(e->source());
1033 pt.Y = m_GraphAttr.y(e->source());
1035 points.Add(pt);
1037 ListConstIterator<DPoint> it;
1038 for(it = dpl.begin(); it.valid(); ++it)
1040 pt.X = (*it).m_x;
1041 pt.Y = (*it).m_y;
1042 points.Add(pt);
1045 pt.X = m_GraphAttr.x(e->target());
1046 pt.Y = m_GraphAttr.y(e->target());
1048 points.Add(pt);
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++)
1056 //CPoint pt;
1057 points[i].X = points[i].X * this->m_fZoomFactor - offset.cx;
1058 points[i].Y = points[i].Y * this->m_fZoomFactor - offset.cy;
1059 //pts.Add(pt);
1062 if (graphics.graphics)
1064 graphics.graphics->DrawLines(&pen, points.GetData(), points.GetCount());
1067 else if (graphics.pSVG)
1069 Color color;
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)
1080 #if 0
1081 COLORREF standardTextColor = GetSysColor(COLOR_WINDOWTEXT);
1082 if (m_nFontSize <= 0)
1083 return;
1085 // iterate over all visible nodes
1087 if (graphics.pDC)
1088 graphics.pDC->SetTextAlign (TA_CENTER | TA_TOP);
1091 node v;
1092 forall_nodes(v,m_Graph)
1094 // get node and position
1096 String label=this->m_GraphAttr.labelNode(v);
1097 this->m_GraphAttr.l
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
1105 if (graphics.pDC)
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));
1122 #endif
1127 void CRevisionGraphWnd::DrawCurrentNodeGlyphs (GraphicsDevice& graphics, Image* glyphs, const CSize& offset)
1129 #if 0
1130 CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());
1131 bool upsideDown
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)
1137 CPoint point;
1138 GetCursorPos (&point);
1139 ScreenToClient (&point);
1140 if (!GetClientRect().PtInRect (point))
1141 return;
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))
1152 : m_hoverIndex;
1154 if (nodeIndex >= nodeList->GetCount())
1155 return;
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);
1165 #endif
1169 void CRevisionGraphWnd::DrawGraph(GraphicsDevice& graphics, const CRect& rect, int nVScrollPos, int nHScrollPos, bool bDirectDraw)
1171 CMemDC* memDC = NULL;
1172 if (graphics.pDC)
1174 if (!bDirectDraw)
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
1199 if (graphics.pDC)
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);
1224 // draw preview
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);
1248 RECT tempRect;
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;
1269 delete memDC;
1272 #if 0
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));
1280 ReleaseDC(pDC);
1283 #endif