1 // TortoiseSVN - a Windows shell extension for easy version control
\r
3 // Copyright (C) 2003-2008 - 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 "RevisionGraph/IRevisionGraphLayout.h"
\r
34 #define new DEBUG_NEW
\r
36 static char THIS_FILE[] = __FILE__;
\r
39 using namespace Gdiplus;
\r
41 /************************************************************************/
\r
42 /* Graphing functions */
\r
43 /************************************************************************/
\r
44 CFont* CRevisionGraphWnd::GetFont(BOOL bItalic /*= FALSE*/, BOOL bBold /*= FALSE*/)
\r
51 if (m_apFonts[nIndex] == NULL)
\r
53 m_apFonts[nIndex] = new CFont;
\r
54 m_lfBaseFont.lfWeight = bBold ? FW_BOLD : FW_NORMAL;
\r
55 m_lfBaseFont.lfItalic = (BYTE) bItalic;
\r
56 m_lfBaseFont.lfStrikeOut = (BYTE) FALSE;
\r
57 CDC * pDC = GetDC();
\r
58 m_lfBaseFont.lfHeight = -MulDiv(m_nFontSize, GetDeviceCaps(pDC->m_hDC, LOGPIXELSY), 72);
\r
60 // use the empty font name, so GDI takes the first font which matches
\r
61 // the specs. Maybe this will help render chinese/japanese chars correctly.
\r
62 _tcsncpy_s(m_lfBaseFont.lfFaceName, 32, _T("MS Shell Dlg 2"), 32);
\r
63 if (!m_apFonts[nIndex]->CreateFontIndirect(&m_lfBaseFont))
\r
65 delete m_apFonts[nIndex];
\r
66 m_apFonts[nIndex] = NULL;
\r
67 return CWnd::GetFont();
\r
70 return m_apFonts[nIndex];
\r
73 BOOL CRevisionGraphWnd::OnEraseBkgnd(CDC* /*pDC*/)
\r
78 void CRevisionGraphWnd::OnPaint()
\r
80 CPaintDC dc(this); // device context for painting
\r
82 GetClientRect(&rect);
\r
83 if (m_bThreadRunning)
\r
85 dc.FillSolidRect(rect, ::GetSysColor(COLOR_APPWORKSPACE));
\r
89 else if (m_layout.get() == NULL)
\r
91 CString sNoGraphText;
\r
92 sNoGraphText.LoadString(IDS_REVGRAPH_ERR_NOGRAPH);
\r
93 dc.FillSolidRect(rect, RGB(255,255,255));
\r
94 dc.ExtTextOut(20,20,ETO_CLIPPED,NULL,sNoGraphText,NULL);
\r
98 DrawGraph(&dc, rect, GetScrollPos(SB_VERT), GetScrollPos(SB_HORZ), false);
\r
101 void CRevisionGraphWnd::CutawayPoints (const RectF& rect, float cutLen, TCutRectangle& result)
\r
103 result[0] = PointF (rect.X, rect.Y + cutLen);
\r
104 result[1] = PointF (rect.X + cutLen, rect.Y);
\r
105 result[2] = PointF (rect.GetRight() - cutLen, rect.Y);
\r
106 result[3] = PointF (rect.GetRight(), rect.Y + cutLen);
\r
107 result[4] = PointF (rect.GetRight(), rect.GetBottom() - cutLen);
\r
108 result[5] = PointF (rect.GetRight() - cutLen, rect.GetBottom());
\r
109 result[6] = PointF (rect.X + cutLen, rect.GetBottom());
\r
110 result[7] = PointF (rect.X, rect.GetBottom() - cutLen);
\r
113 void CRevisionGraphWnd::DrawRoundedRect (Graphics& graphics, const Pen* pen, const Brush* brush, const RectF& rect)
\r
115 enum {POINT_COUNT = 8};
\r
117 float radius = 16 * m_fZoomFactor;
\r
118 PointF points[POINT_COUNT];
\r
119 CutawayPoints (rect, radius, points);
\r
122 path.AddArc (points[0].X, points[1].Y, radius, radius, 180, 90);
\r
123 path.AddArc (points[2].X, points[2].Y, radius, radius, 270, 90);
\r
124 path.AddArc (points[5].X, points[4].Y, radius, radius, 0, 90);
\r
125 path.AddArc (points[7].X, points[7].Y, radius, radius, 90, 90);
\r
127 points[0].Y -= radius / 2;
\r
128 path.AddLine (points[7], points[0]);
\r
131 graphics.FillPath (brush, &path);
\r
133 graphics.DrawPath (pen, &path);
\r
136 void CRevisionGraphWnd::DrawOctangle (Graphics& graphics, const Pen* pen, const Brush* brush, const RectF& rect)
\r
138 enum {POINT_COUNT = 8};
\r
140 PointF points[POINT_COUNT];
\r
141 CutawayPoints (rect, min (rect.Height, rect.Width) / 4, points);
\r
144 graphics.FillPolygon (brush, points, POINT_COUNT);
\r
146 graphics.DrawPolygon (pen, points, POINT_COUNT);
\r
149 void CRevisionGraphWnd::DrawShape (Graphics& graphics, const Pen* pen, const Brush* brush, const RectF& rect, NodeShape shape)
\r
153 case TSVNRectangle:
\r
155 graphics.FillRectangle (brush, rect);
\r
157 graphics.DrawRectangle (pen, rect);
\r
159 case TSVNRoundRect:
\r
160 DrawRoundedRect (graphics, pen, brush, rect);
\r
163 DrawOctangle (graphics, pen, brush, rect);
\r
167 graphics.FillEllipse (brush, rect);
\r
169 graphics.DrawEllipse(pen, rect);
\r
172 ASSERT(FALSE); //unknown type
\r
177 inline BYTE LimitedScaleColor (BYTE c1, BYTE c2, float factor)
\r
179 BYTE scaled = c2 + (BYTE)((c1-c2)*factor);
\r
182 : min (c1, scaled);
\r
185 Color LimitedScaleColor (const Color& c1, const Color& c2, float factor)
\r
187 return Color ( LimitedScaleColor (c1.GetA(), c2.GetA(), factor)
\r
188 , LimitedScaleColor (c1.GetR(), c2.GetR(), factor)
\r
189 , LimitedScaleColor (c1.GetG(), c2.GetG(), factor)
\r
190 , LimitedScaleColor (c1.GetB(), c2.GetB(), factor));
\r
193 void CRevisionGraphWnd::DrawShadow (Graphics& graphics, const RectF& rect,
\r
194 Color shadowColor, NodeShape shape)
\r
198 RectF shadow = rect;
\r
199 shadow.Offset (2, 2);
\r
201 Pen pen (shadowColor);
\r
202 SolidBrush brush (shadowColor);
\r
204 DrawShape (graphics, &pen, &brush, shadow, shape);
\r
207 void CRevisionGraphWnd::DrawNode(Graphics& graphics, const RectF& rect,
\r
208 COLORREF contourRef, Color overlayColor,
\r
209 const CVisibleGraphNode *node, NodeShape shape)
\r
211 // special case: line deleted but deletion node removed
\r
213 if ( (node->GetNext() == NULL)
\r
214 && (node->GetClassification().Is (CNodeClassification::PATH_ONLY_DELETED)))
\r
216 contourRef = m_Colors.GetColor(CColors::DeletedNode);
\r
219 bool nodeSelected = (m_SelectedEntry1 == node) || (m_SelectedEntry2 == node);
\r
221 // calculate the RGB color values we need to draw the node
\r
224 contour.SetFromCOLORREF (contourRef);
\r
227 background.SetFromCOLORREF (GetSysColor(COLOR_WINDOW));
\r
229 textColor.SetFromCOLORREF (GetSysColor(COLOR_WINDOWTEXT));
\r
231 Color selColor = LimitedScaleColor (background, contour, 0.5f);
\r
232 Color brightColor = LimitedScaleColor (background, contour, 0.9f);
\r
234 // Draw the main shape
\r
236 bool isWorkingCopy = node->GetClassification().Is (CNodeClassification::IS_WORKINGCOPY);
\r
237 Color penColor = (contour.GetValue() == Color::White) || isWorkingCopy
\r
240 Color brushColor = nodeSelected ? selColor : brightColor;
\r
242 Pen pen (penColor, isWorkingCopy ? 3.0f : 1.0f);
\r
243 SolidBrush brush (brushColor);
\r
245 Pen* penRef = overlayColor.GetValue() == 0 ? &pen : NULL;
\r
246 DrawShape (graphics, penRef, &brush, rect, shape);
\r
248 // overlay with some other color
\r
250 if (overlayColor.GetValue() != 0)
\r
252 SolidBrush brush (overlayColor);
\r
253 DrawShape (graphics, &pen, &brush, rect, shape);
\r
257 RectF CRevisionGraphWnd::GetNodeRect (const ILayoutNodeList::SNode& node, const CSize& offset) const
\r
259 // get node and position
\r
261 PointF leftTop ( node.rect.left * m_fZoomFactor
\r
262 , node.rect.top * m_fZoomFactor);
\r
263 RectF noderect ( leftTop.X - offset.cx
\r
264 , leftTop.Y - offset.cy
\r
265 , node.rect.right * m_fZoomFactor - leftTop.X - 1
\r
266 , node.rect.bottom * m_fZoomFactor - leftTop.Y);
\r
268 // show two separate lines for touching nodes,
\r
269 // unless the scale is too small
\r
271 if (noderect.Height > 4.0f)
\r
272 noderect.Height -= 1.0f;
\r
279 void CRevisionGraphWnd::DrawShadows (Graphics& graphics, const CRect& logRect, const CSize& offset)
\r
281 // shadow color to use
\r
284 background.SetFromCOLORREF (GetSysColor(COLOR_WINDOW));
\r
286 textColor.SetFromCOLORREF (GetSysColor(COLOR_WINDOWTEXT));
\r
288 Color shadowColor = LimitedScaleColor (background, ARGB (Color::Black), 0.5f);
\r
290 // iterate over all visible nodes
\r
292 std::auto_ptr<const ILayoutNodeList> nodes (m_layout->GetNodes());
\r
293 for ( index_t index = nodes->GetFirstVisible (logRect)
\r
294 ; index != NO_INDEX
\r
295 ; index = nodes->GetNextVisible (index, logRect))
\r
297 // get node and position
\r
299 ILayoutNodeList::SNode node = nodes->GetNode (index);
\r
300 RectF noderect (GetNodeRect (node, offset));
\r
304 switch (node.style)
\r
306 case ILayoutNodeList::SNode::STYLE_DELETED:
\r
307 case ILayoutNodeList::SNode::STYLE_RENAMED:
\r
308 DrawShadow (graphics, noderect, shadowColor, TSVNOctangle);
\r
310 case ILayoutNodeList::SNode::STYLE_ADDED:
\r
311 DrawShadow(graphics, noderect, shadowColor, TSVNRoundRect);
\r
313 case ILayoutNodeList::SNode::STYLE_LAST:
\r
314 DrawShadow(graphics, noderect, shadowColor, TSVNEllipse);
\r
317 DrawShadow(graphics, noderect, shadowColor, TSVNRectangle);
\r
323 void CRevisionGraphWnd::DrawNodes (Graphics& graphics, const CRect& logRect, const CSize& offset)
\r
325 // iterate over all visible nodes
\r
327 std::auto_ptr<const ILayoutNodeList> nodes (m_layout->GetNodes());
\r
328 for ( index_t index = nodes->GetFirstVisible (logRect)
\r
329 ; index != NO_INDEX
\r
330 ; index = nodes->GetNextVisible (index, logRect))
\r
332 // get node and position
\r
334 ILayoutNodeList::SNode node = nodes->GetNode (index);
\r
335 RectF noderect (GetNodeRect (node, offset));
\r
339 Color transparent (0);
\r
340 Color overlayColor = transparent;
\r
342 switch (node.style)
\r
344 case ILayoutNodeList::SNode::STYLE_DELETED:
\r
345 DrawNode(graphics, noderect, m_Colors.GetColor(CColors::DeletedNode), transparent, node.node, TSVNOctangle);
\r
348 case ILayoutNodeList::SNode::STYLE_ADDED:
\r
349 if (m_bTweakTagsColors && node.node->GetClassification().Is (CNodeClassification::IS_TAG))
\r
350 overlayColor = Color (128, 250, 250, 92);
\r
351 else if (m_bTweakTrunkColors && node.node->GetClassification().Is (CNodeClassification::IS_TRUNK))
\r
352 overlayColor = Color (64, 64, 255, 64);
\r
353 DrawNode(graphics, noderect, m_Colors.GetColor(CColors::AddedNode), overlayColor, node.node, TSVNRoundRect);
\r
356 case ILayoutNodeList::SNode::STYLE_RENAMED:
\r
357 if (m_bTweakTagsColors && node.node->GetClassification().Is (CNodeClassification::IS_TAG))
\r
358 overlayColor = Color (128, 92, 160, 160);
\r
359 else if (m_bTweakTrunkColors && node.node->GetClassification().Is (CNodeClassification::IS_TRUNK))
\r
360 overlayColor = Color (64, 0, 255, 160);
\r
361 DrawNode(graphics, noderect, m_Colors.GetColor(CColors::RenamedNode), overlayColor, node.node, TSVNOctangle);
\r
364 case ILayoutNodeList::SNode::STYLE_LAST:
\r
365 DrawNode(graphics, noderect, m_Colors.GetColor(CColors::LastCommitNode), transparent, node.node, TSVNEllipse);
\r
368 case ILayoutNodeList::SNode::STYLE_MODIFIED:
\r
369 DrawNode(graphics, noderect, GetSysColor(COLOR_WINDOWTEXT), transparent, node.node, TSVNRectangle);
\r
373 DrawNode(graphics, noderect, GetSysColor(COLOR_WINDOW), transparent, node.node, TSVNRectangle);
\r
377 // Draw the "tagged" icon
\r
379 if (node.node->GetFirstTag() != NULL)
\r
382 float squareSize = 16 * m_fZoomFactor;
\r
383 float squareDist = min ( (noderect.Height - squareSize) / 2
\r
386 PointF leftTop (noderect.GetRight() - squareSize * 3 / 2, noderect.Y + squareDist);
\r
387 PointF leftBottom (leftTop.X, leftTop.Y + squareSize);
\r
388 RectF square (leftTop, SizeF (squareSize, squareSize));
\r
390 Pen pen (Color(128, 0, 0, 0));
\r
391 LinearGradientBrush lgBrush (leftTop, leftBottom, Color (250, 250, 92), Color (230, 230, 64));
\r
392 graphics.FillRectangle (&lgBrush, square);
\r
393 graphics.DrawRectangle (&pen, square);
\r
398 void CRevisionGraphWnd::DrawConnections (CDC* pDC, const CRect& logRect, const CSize& offset)
\r
400 enum {MAX_POINTS = 100};
\r
401 CPoint points[MAX_POINTS];
\r
403 CPen newpen(PS_SOLID, 0, GetSysColor(COLOR_WINDOWTEXT));
\r
404 CPen * pOldPen = pDC->SelectObject(&newpen);
\r
406 // iterate over all visible lines
\r
408 std::auto_ptr<const ILayoutConnectionList> connections (m_layout->GetConnections());
\r
409 for ( index_t index = connections->GetFirstVisible (logRect)
\r
410 ; index != NO_INDEX
\r
411 ; index = connections->GetNextVisible (index, logRect))
\r
413 // get connection and point position
\r
415 ILayoutConnectionList::SConnection connection
\r
416 = connections->GetConnection (index);
\r
418 if (connection.numberOfPoints > MAX_POINTS)
\r
421 for (index_t i = 0; i < connection.numberOfPoints; ++i)
\r
423 points[i].x = (int)(connection.points[i].x * m_fZoomFactor) - offset.cx;
\r
424 points[i].y = (int)(connection.points[i].y * m_fZoomFactor) - offset.cy;
\r
427 // draw the connection
\r
429 pDC->PolyBezier (points, connection.numberOfPoints);
\r
432 pDC->SelectObject(pOldPen);
\r
435 void CRevisionGraphWnd::DrawTexts (CDC* pDC, const CRect& logRect, const CSize& offset)
\r
437 COLORREF textcolor = GetSysColor(COLOR_WINDOWTEXT);
\r
438 if (m_nFontSize <= 0)
\r
441 // iterate over all visible nodes
\r
443 pDC->SetTextAlign (TA_CENTER | TA_TOP);
\r
444 std::auto_ptr<const ILayoutTextList> texts (m_layout->GetTexts());
\r
445 for ( index_t index = texts->GetFirstVisible (logRect)
\r
446 ; index != NO_INDEX
\r
447 ; index = texts->GetNextVisible (index, logRect))
\r
449 // get node and position
\r
451 ILayoutTextList::SText text = texts->GetText (index);
\r
452 CRect textRect ( (int)(text.rect.left * m_fZoomFactor) - offset.cx
\r
453 , (int)(text.rect.top * m_fZoomFactor) - offset.cy
\r
454 , (int)(text.rect.right * m_fZoomFactor) - offset.cx
\r
455 , (int)(text.rect.bottom * m_fZoomFactor) - offset.cy);
\r
457 // draw the revision text
\r
459 pDC->SetTextColor (textcolor);
\r
460 pDC->SelectObject (GetFont (FALSE, text.style));
\r
461 pDC->ExtTextOut ((textRect.left + textRect.right)/2, textRect.top, 0, &textRect, text.text, NULL);
\r
465 void CRevisionGraphWnd::DrawGraph(CDC* pDC, const CRect& rect, int nVScrollPos, int nHScrollPos, bool bDirectDraw)
\r
467 CDC * memDC = bDirectDraw
\r
469 : new CMyMemDC(pDC);
\r
471 memDC->FillSolidRect(rect, GetSysColor(COLOR_WINDOW));
\r
472 memDC->SetBkMode(TRANSPARENT);
\r
474 // transform visible
\r
476 CSize offset (nHScrollPos, nVScrollPos);
\r
477 CRect logRect ( (int)(offset.cx / m_fZoomFactor)-1
\r
478 , (int)(offset.cy / m_fZoomFactor)-1
\r
479 , (int)((rect.Width() + offset.cx) / m_fZoomFactor) + 1
\r
480 , (int)((rect.Height() + offset.cy) / m_fZoomFactor) + 1);
\r
482 // draw the different components
\r
484 Graphics graphics (*memDC);
\r
485 graphics.SetPageUnit (UnitPixel);
\r
487 if (m_fZoomFactor > 0.2f)
\r
488 DrawShadows (graphics, logRect, offset);
\r
490 DrawNodes (graphics, logRect, offset);
\r
491 DrawConnections (memDC, logRect, offset);
\r
492 DrawTexts (memDC, logRect, offset);
\r
494 // find out which nodes are in the visible area of the client rect
\r
496 if ((!bDirectDraw)&&(m_Preview.GetSafeHandle())&&(m_bShowOverview))
\r
498 // draw the overview image rectangle in the top right corner
\r
499 CMyMemDC memDC2(memDC, true);
\r
500 memDC2.SetWindowOrg(0, 0);
\r
501 HBITMAP oldhbm = (HBITMAP)memDC2.SelectObject(&m_Preview);
\r
502 memDC->BitBlt(rect.Width()-m_previewWidth, 0, m_previewWidth, m_previewHeight,
\r
503 &memDC2, 0, 0, SRCCOPY);
\r
504 memDC2.SelectObject(oldhbm);
\r
505 // draw the border for the overview rectangle
\r
506 m_OverviewRect.left = rect.Width()-m_previewWidth;
\r
507 m_OverviewRect.top = 0;
\r
508 m_OverviewRect.right = rect.Width();
\r
509 m_OverviewRect.bottom = m_previewHeight;
\r
510 memDC->DrawEdge(&m_OverviewRect, EDGE_BUMP, BF_RECT);
\r
511 // now draw a rectangle where the current view is located in the overview
\r
513 CRect viewRect = GetViewRect();
\r
514 LONG width = (long)(rect.Width() * m_previewZoom / m_fZoomFactor);
\r
515 LONG height = (long)(rect.Height() * m_previewZoom / m_fZoomFactor);
\r
516 LONG xpos = (long)(nHScrollPos * m_previewZoom / m_fZoomFactor);
\r
517 LONG ypos = (long)(nVScrollPos * m_previewZoom / m_fZoomFactor);
\r
519 tempRect.left = rect.Width()-m_previewWidth+xpos;
\r
520 tempRect.top = ypos;
\r
521 tempRect.right = tempRect.left + width;
\r
522 tempRect.bottom = tempRect.top + height;
\r
523 // make sure the position rect is not bigger than the preview window itself
\r
524 ::IntersectRect(&m_OverviewPosRect, &m_OverviewRect, &tempRect);
\r
526 RectF rect ( (float)m_OverviewPosRect.left, (float)m_OverviewPosRect.top
\r
527 , (float)m_OverviewPosRect.Width(), (float)m_OverviewPosRect.Height());
\r
528 SolidBrush brush (Color (64, 0, 0, 0));
\r
529 graphics.FillRectangle (&brush, rect);
\r
530 memDC->DrawEdge(&m_OverviewPosRect, EDGE_BUMP, BF_RECT);
\r
537 void CRevisionGraphWnd::DrawRubberBand()
\r
539 CDC * pDC = GetDC();
\r
540 pDC->SetROP2(R2_NOT);
\r
541 pDC->SelectObject(GetStockObject(NULL_BRUSH));
\r
542 pDC->Rectangle(min(m_ptRubberStart.x, m_ptRubberEnd.x), min(m_ptRubberStart.y, m_ptRubberEnd.y),
\r
543 max(m_ptRubberStart.x, m_ptRubberEnd.x), max(m_ptRubberStart.y, m_ptRubberEnd.y));
\r