OSDN Git Service

Update RevisionGraph to 15570
[tortoisegit/TortoiseGitJp.git] / src / TortoiseProc / RevisionGraph / RevisionGraphDlgDraw.cpp
1 // TortoiseSVN - a Windows shell extension for easy version control\r
2 \r
3 // Copyright (C) 2003-2009 - TortoiseSVN\r
4 \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
9 \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
14 \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
18 //\r
19 #include "stdafx.h"\r
20 #include "TortoiseProc.h"\r
21 #include "MyMemDC.h"\r
22 #include "Revisiongraphdlg.h"\r
23 #include "MessageBox.h"\r
24 #include "SVN.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
34 \r
35 #ifdef _DEBUG\r
36 #define new DEBUG_NEW\r
37 #undef THIS_FILE\r
38 static char THIS_FILE[] = __FILE__;\r
39 #endif\r
40 \r
41 using namespace Gdiplus;\r
42 \r
43 /************************************************************************/\r
44 /* Graphing functions                                                   */\r
45 /************************************************************************/\r
46 CFont* CRevisionGraphWnd::GetFont(BOOL bItalic /*= FALSE*/, BOOL bBold /*= FALSE*/)\r
47 {\r
48         int nIndex = 0;\r
49         if (bBold)\r
50                 nIndex |= 1;\r
51         if (bItalic)\r
52                 nIndex |= 2;\r
53         if (m_apFonts[nIndex] == NULL)\r
54         {\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
61                 ReleaseDC(pDC);\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
66                 {\r
67                         delete m_apFonts[nIndex];\r
68                         m_apFonts[nIndex] = NULL;\r
69                         return CWnd::GetFont();\r
70                 }\r
71         }\r
72         return m_apFonts[nIndex];\r
73 }\r
74 \r
75 BOOL CRevisionGraphWnd::OnEraseBkgnd(CDC* /*pDC*/)\r
76 {\r
77         return TRUE;\r
78 }\r
79 \r
80 void CRevisionGraphWnd::OnPaint() \r
81 {\r
82         CPaintDC dc(this); // device context for painting\r
83         CRect rect = GetClientRect();\r
84         if (m_bThreadRunning)\r
85         {\r
86                 dc.FillSolidRect(rect, ::GetSysColor(COLOR_APPWORKSPACE));\r
87                 CWnd::OnPaint();\r
88                 return;\r
89         }\r
90     else if (!m_state.GetNodes())\r
91         {\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
96                 return;\r
97         }\r
98         \r
99     DrawGraph(&dc, rect, GetScrollPos(SB_VERT), GetScrollPos(SB_HORZ), false);\r
100 }\r
101 \r
102 void CRevisionGraphWnd::ClearVisibleGlyphs (const CRect& rect)\r
103 {\r
104     float glyphSize = GLYPH_SIZE * m_fZoomFactor;\r
105 \r
106     CSyncPointer<CRevisionGraphState::TVisibleGlyphs> \r
107         visibleGlyphs (m_state.GetVisibleGlyphs());\r
108 \r
109     for (size_t i = visibleGlyphs->size(), count = i; i > 0; --i)\r
110     {\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
116 \r
117         if (CRect().IntersectRect (glyphRect, rect))\r
118         {\r
119             (*visibleGlyphs)[i-1] = (*visibleGlyphs)[--count];\r
120             visibleGlyphs->pop_back();\r
121         }\r
122     }\r
123 }\r
124 \r
125 void CRevisionGraphWnd::CutawayPoints (const RectF& rect, float cutLen, TCutRectangle& result)\r
126 {\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
135 }\r
136 \r
137 void CRevisionGraphWnd::DrawRoundedRect (Graphics& graphics, const Pen* pen, const Brush* brush, const RectF& rect)\r
138 {\r
139     enum {POINT_COUNT = 8};\r
140 \r
141     float radius = 16 * m_fZoomFactor;\r
142         PointF points[POINT_COUNT];\r
143     CutawayPoints (rect, radius, points);\r
144 \r
145     GraphicsPath path;\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
150 \r
151     points[0].Y -= radius / 2;\r
152     path.AddLine (points[7], points[0]);\r
153 \r
154     if (brush != NULL)\r
155         graphics.FillPath (brush, &path);\r
156     if (pen != NULL)\r
157         graphics.DrawPath (pen, &path);\r
158 }\r
159 \r
160 void CRevisionGraphWnd::DrawOctangle (Graphics& graphics, const Pen* pen, const Brush* brush, const RectF& rect)\r
161 {\r
162     enum {POINT_COUNT = 8};\r
163 \r
164     // show left & right edges of low boxes as "<===>"\r
165 \r
166     float minCutAway = min (16 * m_fZoomFactor, rect.Height / 2);\r
167 \r
168     // larger boxes: remove 25% of the shorter side\r
169 \r
170     float suggestedCutAway = min (rect.Height, rect.Width) / 4;\r
171 \r
172     // use the more visible one of the former two\r
173 \r
174         PointF points[POINT_COUNT];\r
175     CutawayPoints (rect, max (minCutAway, suggestedCutAway), points);\r
176 \r
177     // now, draw it\r
178 \r
179     if (brush != NULL)\r
180         graphics.FillPolygon (brush, points, POINT_COUNT);\r
181     if (pen != NULL)\r
182         graphics.DrawPolygon (pen, points, POINT_COUNT);\r
183 }\r
184 \r
185 void CRevisionGraphWnd::DrawShape (Graphics& graphics, const Pen* pen, const Brush* brush, const RectF& rect, NodeShape shape)\r
186 {\r
187         switch( shape )\r
188         {\r
189         case TSVNRectangle:\r
190         if (brush != NULL)\r
191             graphics.FillRectangle (brush, rect);\r
192         if (pen != NULL)\r
193             graphics.DrawRectangle (pen, rect);\r
194                 break;\r
195         case TSVNRoundRect:\r
196                 DrawRoundedRect (graphics, pen, brush, rect);\r
197                 break;\r
198         case TSVNOctangle:\r
199                 DrawOctangle (graphics, pen, brush, rect);\r
200                 break;\r
201         case TSVNEllipse:\r
202         if (brush != NULL)\r
203             graphics.FillEllipse (brush, rect);\r
204         if (pen != NULL)\r
205             graphics.DrawEllipse(pen, rect);\r
206                 break;\r
207         default:\r
208                 ASSERT(FALSE);  //unknown type\r
209                 return;\r
210         }\r
211 }\r
212 \r
213 inline BYTE LimitedScaleColor (BYTE c1, BYTE c2, float factor)\r
214 {\r
215     BYTE scaled = c2 + (BYTE)((c1-c2)*factor);\r
216     return c1 < c2\r
217         ? max (c1, scaled)\r
218         : min (c1, scaled);\r
219 }\r
220 \r
221 Color LimitedScaleColor (const Color& c1, const Color& c2, float factor)\r
222 {\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
227 }\r
228 \r
229 inline BYTE Darken (BYTE c)\r
230 {\r
231     return c < 0xc0\r
232         ? (c / 3) * 2\r
233         : BYTE(int(2*c) - 0x100);\r
234 }\r
235 \r
236 Color Darken (const Color& c)\r
237 {\r
238     return Color ( 0xff\r
239                  , Darken (c.GetR())\r
240                  , Darken (c.GetG())\r
241                  , Darken (c.GetB()));\r
242 }\r
243 \r
244 BYTE MaxComponentDiff (const Color& c1, const Color& c2)\r
245 {\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
249 \r
250     return (BYTE) max (max (rDiff, gDiff), bDiff);\r
251 }\r
252 \r
253 void CRevisionGraphWnd::DrawShadow (Graphics& graphics, const RectF& rect,\r
254                                     Color shadowColor, NodeShape shape)\r
255 {\r
256         // draw the shadow\r
257 \r
258         RectF shadow = rect;\r
259     shadow.Offset (2, 2);\r
260 \r
261     Pen pen (shadowColor);\r
262         SolidBrush brush (shadowColor);\r
263 \r
264     DrawShape (graphics, &pen, &brush, shadow, shape);\r
265 }\r
266 \r
267 void CRevisionGraphWnd::DrawNode(Graphics& graphics, const RectF& rect,\r
268                                  Color contour, Color overlayColor, \r
269                                  const CVisibleGraphNode *node, NodeShape shape)\r
270 {\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
273 \r
274     enum \r
275     {\r
276         MASK = CGraphNodeStates::COLLAPSED_BELOW | CGraphNodeStates::SPLIT_BELOW\r
277     };\r
278 \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
283     {\r
284         contour = m_Colors.GetColor (CColors::gdpDeletedNode);\r
285     }\r
286 \r
287     // calculate the GDI+ color values we need to draw the node\r
288 \r
289     Color background;\r
290     background.SetFromCOLORREF (GetSysColor(COLOR_WINDOW));\r
291     Color textColor;\r
292     if (nodeClassification.Is (CNodeClassification::IS_MODIFIED_WC))\r
293         textColor = m_Colors.GetColor (CColors::gdpWCNodeBorder);\r
294     else\r
295         textColor.SetFromCOLORREF (GetSysColor(COLOR_WINDOWTEXT));\r
296 \r
297         Color brightColor = LimitedScaleColor (background, contour, 0.9f);\r
298 \r
299         // Draw the main shape\r
300 \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
308 \r
309     Color penColor = textAsBorderColor\r
310                    ? textColor\r
311                    : contour;\r
312 \r
313     Pen pen (penColor, isWorkingCopy ? 3.0f : 1.0f);\r
314     SolidBrush brush (brightColor);\r
315     DrawShape (graphics, &pen, &brush, rect, shape);\r
316 \r
317     // overlay with some other color\r
318 \r
319     if (overlayColor.GetValue() != 0)\r
320     {\r
321         SolidBrush brush2 (overlayColor);\r
322         DrawShape (graphics, &pen, &brush2, rect, shape);\r
323     }\r
324 }\r
325 \r
326 RectF CRevisionGraphWnd::TransformRectToScreen (const CRect& rect, const CSize& offset) const\r
327 {\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
334 }\r
335 \r
336 RectF CRevisionGraphWnd::GetNodeRect (const ILayoutNodeList::SNode& node, const CSize& offset) const\r
337 {\r
338     // get node and position\r
339 \r
340     RectF noderect (TransformRectToScreen (node.rect, offset));\r
341 \r
342     // show two separate lines for touching nodes, \r
343     // unless the scale is too small\r
344 \r
345     if (noderect.Height > 4.0f)\r
346         noderect.Height -= 1.0f;\r
347 \r
348     // done\r
349 \r
350     return noderect;\r
351 }\r
352 \r
353 RectF CRevisionGraphWnd::GetBranchCover \r
354     ( const ILayoutNodeList* nodeList\r
355     , index_t nodeIndex\r
356     , bool upward\r
357     , const CSize& offset)\r
358 {\r
359     // construct a rect that covers the respective branch\r
360 \r
361     CRect cover (0, 0, 0, 0);\r
362     while (nodeIndex != NO_INDEX)\r
363     {\r
364         ILayoutNodeList::SNode node = nodeList->GetNode (nodeIndex);\r
365         cover |= node.rect;\r
366 \r
367         const CVisibleGraphNode* nextNode = upward\r
368             ? node.node->GetPrevious()\r
369             : node.node->GetNext();\r
370 \r
371         nodeIndex = nextNode == NULL ? NO_INDEX : nextNode->GetIndex();\r
372     }\r
373 \r
374     // expand it just a little to make it look nicer\r
375 \r
376     cover.InflateRect (10, 2, 10, 2);\r
377 \r
378     // and now, transfrom it\r
379 \r
380     return TransformRectToScreen (cover, offset);\r
381 }\r
382 \r
383 void CRevisionGraphWnd::DrawShadows (Graphics& graphics, const CRect& logRect, const CSize& offset)\r
384 {\r
385     // shadow color to use\r
386 \r
387     Color background;\r
388     background.SetFromCOLORREF (GetSysColor(COLOR_WINDOW));\r
389     Color textColor;\r
390     textColor.SetFromCOLORREF (GetSysColor(COLOR_WINDOWTEXT));\r
391 \r
392     Color shadowColor = LimitedScaleColor (background, ARGB (Color::Black), 0.5f);\r
393 \r
394     // iterate over all visible nodes\r
395 \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
400         {\r
401         // get node and position\r
402 \r
403         ILayoutNodeList::SNode node = nodes->GetNode (index);\r
404                 RectF noderect (GetNodeRect (node, offset));\r
405 \r
406         // actual drawing\r
407 \r
408                 switch (node.style)\r
409                 {\r
410                 case ILayoutNodeList::SNode::STYLE_DELETED:\r
411                 case ILayoutNodeList::SNode::STYLE_RENAMED:\r
412                         DrawShadow (graphics, noderect, shadowColor, TSVNOctangle);\r
413                         break;\r
414                 case ILayoutNodeList::SNode::STYLE_ADDED:\r
415             DrawShadow(graphics, noderect, shadowColor, TSVNRoundRect);\r
416             break;\r
417                 case ILayoutNodeList::SNode::STYLE_LAST:\r
418                         DrawShadow(graphics, noderect, shadowColor, TSVNEllipse);\r
419                         break;\r
420                 default:\r
421             DrawShadow(graphics, noderect, shadowColor, TSVNRectangle);\r
422                         break;\r
423                 }\r
424     }\r
425 }\r
426 \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
433 {\r
434     float squareSize = MARKER_SIZE * m_fZoomFactor;\r
435 \r
436     PointF leftBottom (leftTop.X, leftTop.Y + squareSize);\r
437     RectF square (leftTop, SizeF (squareSize, squareSize));\r
438 \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
443 }\r
444 \r
445 void CRevisionGraphWnd::DrawGlyph \r
446     ( Graphics& graphics\r
447     , Image* glyphs\r
448     , const PointF& leftTop\r
449     , GlyphType glyph\r
450     , GlyphPosition position)\r
451 {\r
452     // special case\r
453 \r
454     if (glyph == NoGlyph)\r
455         return;\r
456 \r
457     // bitmap source area\r
458 \r
459     REAL x = ((REAL)position + (REAL)glyph) * GLYPH_SIZE;\r
460 \r
461     // screen target area\r
462 \r
463     float glyphSize = GLYPH_SIZE * m_fZoomFactor;\r
464     RectF target (leftTop, SizeF (glyphSize, glyphSize));\r
465 \r
466     // scaled copy\r
467 \r
468     graphics.DrawImage ( glyphs\r
469                        , target\r
470                        , x, 0.0f, GLYPH_SIZE, GLYPH_SIZE\r
471                        , UnitPixel, NULL, NULL, NULL);\r
472 }\r
473 \r
474 void CRevisionGraphWnd::DrawGlyphs\r
475     ( Graphics& graphics\r
476     , Image* glyphs\r
477     , const CVisibleGraphNode* node\r
478     , const PointF& center\r
479     , GlyphType glyph1\r
480     , GlyphType glyph2\r
481     , GlyphPosition position\r
482     , DWORD state1\r
483     , DWORD state2\r
484     , bool showAll)\r
485 {\r
486     // don't show collapse and cut glyths by default\r
487 \r
488     if (!showAll && ((glyph1 == CollapseGlyph) || (glyph1 == SplitGlyph)))\r
489         glyph1 = NoGlyph;\r
490     if (!showAll && ((glyph2 == CollapseGlyph) || (glyph2 == SplitGlyph)))\r
491         glyph2 = NoGlyph;\r
492 \r
493     // glyth2 shall be set only if 2 glyphs are in use\r
494 \r
495     if (glyph1 == NoGlyph)\r
496     {\r
497         std::swap (glyph1, glyph2);\r
498         std::swap (state1, state2);\r
499     }\r
500 \r
501     // anything to do?\r
502 \r
503     if (glyph1 == NoGlyph)\r
504         return;\r
505 \r
506     // 1 or 2 glyphs?\r
507 \r
508     CSyncPointer<CRevisionGraphState::TVisibleGlyphs> \r
509         visibleGlyphs (m_state.GetVisibleGlyphs());\r
510 \r
511     float squareSize = GLYPH_SIZE * m_fZoomFactor;\r
512     if (glyph2 == NoGlyph)\r
513     {\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
518     }\r
519     else\r
520     {\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
525 \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
530     }\r
531 }\r
532 \r
533 void CRevisionGraphWnd::DrawGlyphs\r
534     ( Graphics& graphics\r
535     , Image* glyphs\r
536     , const CVisibleGraphNode* node\r
537     , const RectF& nodeRect\r
538     , DWORD state\r
539     , DWORD allowed\r
540     , bool upsideDown)\r
541 {\r
542     // shortcut\r
543 \r
544     if ((state == 0) && (allowed == 0))\r
545         return;\r
546 \r
547     // draw all glyphs\r
548 \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
552 \r
553     DrawGlyphs ( graphics\r
554                , glyphs\r
555                , node\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
563 \r
564     DrawGlyphs ( graphics\r
565                , glyphs\r
566                , node\r
567                , rightCenter\r
568                , (state & CGraphNodeStates::COLLAPSED_RIGHT) ? ExpandGlyph : CollapseGlyph\r
569                , (state & CGraphNodeStates::SPLIT_RIGHT) ? JoinGlyph : SplitGlyph\r
570                , Right\r
571                , CGraphNodeStates::COLLAPSED_RIGHT\r
572                , CGraphNodeStates::SPLIT_RIGHT\r
573                , (allowed & CGraphNodeStates::COLLAPSED_RIGHT) != 0);\r
574 \r
575     DrawGlyphs ( graphics\r
576                , glyphs\r
577                , node\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
585 }\r
586 \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
592     , DWORD glyphs\r
593     , bool upsideDown\r
594     , const CSize& offset)\r
595 {\r
596     // shortcut\r
597 \r
598     if (glyphs == 0)\r
599         return;\r
600 \r
601     // where to place the indication?\r
602 \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
606 \r
607     // fill indication area a semi-transparent blend of red \r
608     // and the background color\r
609 \r
610     Color color;\r
611     color.SetFromCOLORREF (GetSysColor(COLOR_WINDOW));\r
612     color.SetValue ((color.GetValue() & 0x807f7f7f) + 0x800000);\r
613 \r
614     SolidBrush brush (color);\r
615 \r
616     // draw the indication (only one condition should match)\r
617 \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
621 \r
622     if (indicateAbove)\r
623     {\r
624         const CVisibleGraphNode* firstAffected = node.node->GetSource();\r
625 \r
626         assert (firstAffected);\r
627         RectF branchCover \r
628             = GetBranchCover (nodeList, firstAffected->GetIndex(), true, offset);\r
629         RectF::Union (branchCover, branchCover, glyphCenter);\r
630 \r
631         graphics.FillRectangle (&brush, branchCover);\r
632     }\r
633 \r
634     if (indicateRight)\r
635     {\r
636         for ( const CVisibleGraphNode::CCopyTarget* branch \r
637                 = node.node->GetFirstCopyTarget()\r
638             ; branch != NULL\r
639             ; branch = branch->next())\r
640         {\r
641             RectF branchCover \r
642                 = GetBranchCover (nodeList, branch->value()->GetIndex(), false, offset);\r
643             graphics.FillRectangle (&brush, branchCover);\r
644         }\r
645     }\r
646 \r
647     if (indicateBelow)\r
648     {\r
649         const CVisibleGraphNode* firstAffected \r
650             = node.node->GetNext();\r
651 \r
652         RectF branchCover \r
653             = GetBranchCover (nodeList, firstAffected->GetIndex(), false, offset);\r
654         RectF::Union (branchCover, branchCover, glyphCenter);\r
655 \r
656         graphics.FillRectangle (&brush, branchCover);\r
657     }\r
658 }\r
659 \r
660 void CRevisionGraphWnd::DrawMarker \r
661     ( Graphics& graphics\r
662     , const RectF& noderect\r
663     , MarkerPosition position\r
664     , int relPosition\r
665     , int colorIndex )\r
666 {\r
667         // marker size\r
668     float squareSize = MARKER_SIZE * m_fZoomFactor;\r
669     float squareDist = min ( (noderect.Height - squareSize) / 2\r
670                            , squareSize / 2);\r
671 \r
672     // position\r
673 \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
679 \r
680     // color\r
681 \r
682     Color lightColor (m_Colors.GetColor (CColors::ctMarkers, colorIndex));\r
683     Color darkColor (Darken (lightColor));\r
684     Color borderColor (0x80000000);\r
685 \r
686     // draw it\r
687 \r
688     DrawSquare (graphics, leftTop, lightColor, darkColor, borderColor);\r
689 }\r
690 \r
691 void CRevisionGraphWnd::DrawStripes (Graphics& graphics, const CSize& offset)\r
692 {\r
693     // we need to fill this visible area of the the screen \r
694     // (even if there is graph in that part)\r
695 \r
696     RectF clipRect;\r
697     graphics.GetVisibleClipBounds (&clipRect);\r
698 \r
699     // don't show stripes if we don't have mutiple roots\r
700 \r
701     CSyncPointer<const ILayoutRectList> trees (m_state.GetTrees());\r
702     if (trees->GetCount() < 2)\r
703         return;\r
704 \r
705     // iterate over all trees\r
706 \r
707     for ( index_t i = 0, count = trees->GetCount(); i < count; ++i)\r
708         {\r
709         // screen coordinates coverd by the tree\r
710 \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
715                    , clipRect.Y\r
716                    , i+1 == count ? clipRect.Width : right - left\r
717                    , clipRect.Height);\r
718 \r
719         // relevant?\r
720 \r
721         if (rect.IntersectsWith (clipRect))\r
722         {\r
723             // draw the background stripe\r
724 \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
730         }\r
731     }\r
732 }\r
733 \r
734 void CRevisionGraphWnd::DrawNodes (Graphics& graphics, Image* glyphs, const CRect& logRect, const CSize& offset)\r
735 {\r
736     CSyncPointer<CGraphNodeStates> nodeStates (m_state.GetNodeStates());\r
737     CSyncPointer<const ILayoutNodeList> nodes (m_state.GetNodes());\r
738 \r
739     bool upsideDown \r
740         = m_state.GetOptions()->GetOption<CUpsideDownLayout>()->IsActive();\r
741 \r
742     // iterate over all visible nodes\r
743 \r
744     for ( index_t index = nodes->GetFirstVisible (logRect)\r
745         ; index != NO_INDEX\r
746         ; index = nodes->GetNextVisible (index, logRect))\r
747         {\r
748         // get node and position\r
749 \r
750         ILayoutNodeList::SNode node = nodes->GetNode (index);\r
751                 RectF noderect (GetNodeRect (node, offset));\r
752 \r
753         // actual drawing\r
754 \r
755         Color transparent (0);\r
756         Color overlayColor = transparent;\r
757 \r
758                 switch (node.style)\r
759                 {\r
760                 case ILayoutNodeList::SNode::STYLE_DELETED:\r
761                         DrawNode(graphics, noderect, m_Colors.GetColor(CColors::gdpDeletedNode), transparent, node.node, TSVNOctangle);\r
762                         break;\r
763 \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
770             break;\r
771 \r
772         case ILayoutNodeList::SNode::STYLE_RENAMED:\r
773             DrawNode(graphics, noderect, m_Colors.GetColor(CColors::gdpRenamedNode), overlayColor, node.node, TSVNOctangle);\r
774                         break;\r
775 \r
776         case ILayoutNodeList::SNode::STYLE_LAST:\r
777                         DrawNode(graphics, noderect, m_Colors.GetColor(CColors::gdpLastCommitNode), transparent, node.node, TSVNEllipse);\r
778                         break;\r
779 \r
780         case ILayoutNodeList::SNode::STYLE_MODIFIED:\r
781                         DrawNode(graphics, noderect, m_Colors.GetColor(CColors::gdpModifiedNode), transparent, node.node, TSVNRectangle);\r
782                         break;\r
783 \r
784         case ILayoutNodeList::SNode::STYLE_MODIFIED_WC:\r
785                         DrawNode(graphics, noderect, m_Colors.GetColor(CColors::gdpWCNode), transparent, node.node, TSVNEllipse);\r
786                         break;\r
787 \r
788         default:\r
789             DrawNode(graphics, noderect, m_Colors.GetColor(CColors::gdpUnchangedNode), transparent, node.node, TSVNRectangle);\r
790                         break;\r
791                 }\r
792 \r
793         // Draw the "tagged" icon\r
794 \r
795         if (node.node->GetFirstTag() != NULL)\r
796             DrawMarker (graphics, noderect, mpRight, 0, 0);\r
797 \r
798         if ((m_SelectedEntry1 == node.node) || (m_SelectedEntry2 == node.node))\r
799             DrawMarker (graphics, noderect, mpLeft, 0, 1);\r
800 \r
801         // expansion glypths etc.\r
802 \r
803         DrawGlyphs (graphics, glyphs, node.node, noderect, nodeStates->GetFlags (node.node), 0, upsideDown);\r
804     }\r
805 }\r
806 \r
807 void CRevisionGraphWnd::DrawConnections (CDC* pDC, const CRect& logRect, const CSize& offset)\r
808 {\r
809     enum {MAX_POINTS = 100};\r
810     CPoint points[MAX_POINTS];\r
811 \r
812         CPen newpen(PS_SOLID, 0, GetSysColor(COLOR_WINDOWTEXT));\r
813         CPen * pOldPen = pDC->SelectObject(&newpen);\r
814 \r
815     // iterate over all visible lines\r
816 \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
821         {\r
822         // get connection and point position\r
823 \r
824         ILayoutConnectionList::SConnection connection \r
825             = connections->GetConnection (index);\r
826 \r
827         if (connection.numberOfPoints > MAX_POINTS)\r
828             continue;\r
829 \r
830         for (index_t i = 0; i < connection.numberOfPoints; ++i)\r
831         {\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
834         }\r
835 \r
836                 // draw the connection\r
837 \r
838                 pDC->PolyBezier (points, connection.numberOfPoints);\r
839         }\r
840 \r
841         pDC->SelectObject(pOldPen);\r
842 }\r
843 \r
844 void CRevisionGraphWnd::DrawTexts (CDC* pDC, const CRect& logRect, const CSize& offset)\r
845 {\r
846         COLORREF standardTextColor = GetSysColor(COLOR_WINDOWTEXT);\r
847     if (m_nFontSize <= 0)\r
848         return;\r
849 \r
850     // iterate over all visible nodes\r
851 \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
857         {\r
858         // get node and position\r
859 \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
865 \r
866                 // draw the revision text\r
867 \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
873     }\r
874 }\r
875 \r
876 void CRevisionGraphWnd::DrawCurrentNodeGlyphs (Graphics& graphics, Image* glyphs, const CSize& offset)\r
877 {\r
878     CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());\r
879     bool upsideDown \r
880         = m_state.GetOptions()->GetOption<CUpsideDownLayout>()->IsActive();\r
881 \r
882     // don't draw glyphs if we are outside the client area \r
883     // (e.g. within a scrollbar)\r
884 \r
885     CPoint point;\r
886     GetCursorPos (&point);\r
887     ScreenToClient (&point);\r
888     if (!GetClientRect().PtInRect (point))\r
889         return;\r
890 \r
891     // expansion glypths etc.\r
892 \r
893     m_hoverIndex = GetHitNode (point);\r
894     m_hoverGlyphs = GetHoverGlyphs (point);\r
895 \r
896     if ((m_hoverIndex != NO_INDEX) || (m_hoverGlyphs != 0))\r
897     {\r
898         index_t nodeIndex = m_hoverIndex == NO_INDEX\r
899                           ? GetHitNode (point, CSize (GLYPH_SIZE, GLYPH_SIZE / 2))\r
900                           : m_hoverIndex;\r
901 \r
902         if (nodeIndex >= nodeList->GetCount())\r
903             return;\r
904 \r
905         ILayoutNodeList::SNode node = nodeList->GetNode (nodeIndex);\r
906         RectF noderect (GetNodeRect (node, offset));\r
907 \r
908         DWORD flags = m_state.GetNodeStates()->GetFlags (node.node);\r
909 \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
912     }\r
913 }\r
914 \r
915 void CRevisionGraphWnd::DrawGraph(CDC* pDC, const CRect& rect, int nVScrollPos, int nHScrollPos, bool bDirectDraw)\r
916 {\r
917     CMemDC* memDC = NULL;\r
918     if (!bDirectDraw)\r
919     {\r
920         memDC = new CMemDC (*pDC, rect);\r
921         pDC = &memDC->GetDC();\r
922     }\r
923         \r
924         pDC->FillSolidRect(rect, GetSysColor(COLOR_WINDOW));\r
925         pDC->SetBkMode(TRANSPARENT);\r
926 \r
927     // preparation & sync\r
928 \r
929     CSyncPointer<CAllRevisionGraphOptions> options (m_state.GetOptions());\r
930     ClearVisibleGlyphs (rect);\r
931 \r
932     // transform visible\r
933 \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
939 \r
940     // draw the different components\r
941 \r
942     Graphics* graphics = Graphics::FromHDC(*pDC);\r
943     graphics->SetPageUnit (UnitPixel);\r
944     graphics->SetInterpolationMode (InterpolationModeHighQualityBicubic);\r
945 \r
946     if (options->GetOption<CShowTreeStripes>()->IsActive())\r
947         DrawStripes (*graphics, offset);\r
948 \r
949     if (m_fZoomFactor > 0.2f)\r
950         DrawShadows (*graphics, logRect, offset);\r
951 \r
952     Bitmap glyphs (AfxGetInstanceHandle(), MAKEINTRESOURCE(IDR_REVGRAPHGLYPHS));\r
953 \r
954     DrawNodes (*graphics, &glyphs, logRect, offset);\r
955     DrawConnections (pDC, logRect, offset);\r
956     DrawTexts (pDC, logRect, offset);\r
957 \r
958     if (m_showHoverGlyphs)\r
959         DrawCurrentNodeGlyphs (*graphics, &glyphs, offset);\r
960 \r
961     // draw preview\r
962 \r
963         if ((!bDirectDraw)&&(m_Preview.GetSafeHandle())&&(m_bShowOverview))\r
964         {\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
979 \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
985                 RECT tempRect;\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
992 \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
998         }\r
999 \r
1000     // flush changes to screen\r
1001 \r
1002     if (graphics)\r
1003         delete graphics;\r
1004 \r
1005         if (memDC)\r
1006                 delete memDC;\r
1007 }\r
1008 \r
1009 void CRevisionGraphWnd::DrawRubberBand()\r
1010 {\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
1016         ReleaseDC(pDC);\r
1017 }\r
1018 \r