Initialize more buffers
[TortoiseGit.git] / src / TortoiseProc / RevisionGraph / RevisionGraphWnd.cpp
blob64bd795150234e5a59b5d6eaac170833d3f26524
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2012 - TortoiseSVN
4 // Copyright (C) 2012-2014 - TortoiseGit
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "stdafx.h"
21 #include "TortoiseProc.h"
22 #include "Revisiongraphwnd.h"
23 #include "MessageBox.h"
24 #include "Git.h"
25 #include "AppUtils.h"
26 #include "PathUtils.h"
27 #include "StringUtils.h"
28 #include "TempFile.h"
29 #include "UnicodeUtils.h"
30 #include "TGitPath.h"
31 #include "RevisionGraphDlg.h"
32 #include "BrowseFolder.h"
33 #include "GitProgressDlg.h"
34 #include "ChangedDlg.h"
35 //#include "RevisionGraph/StandardLayout.h"
36 //#include "RevisionGraph/UpsideDownLayout.h"
37 #include "SysInfo.h"
38 #include "FormatMessageWrapper.h"
40 #pragma warning(push)
41 #pragma warning(disable: 4100) // unreferenced formal parameter
42 #include <ogdf/planarity/PlanarizationLayout.h>
43 #include <ogdf/planarity/VariableEmbeddingInserter.h>
44 #include <ogdf/planarity/FastPlanarSubgraph.h>
45 #include <ogdf/orthogonal/OrthoLayout.h>
46 #include <ogdf/planarity/EmbedderMinDepthMaxFaceLayers.h>
47 #pragma warning(pop)
49 #ifdef _DEBUG
50 #define new DEBUG_NEW
51 #undef THIS_FILE
52 static char THIS_FILE[] = __FILE__;
53 #endif
55 using namespace Gdiplus;
56 using namespace ogdf;
58 enum RevisionGraphContextMenuCommands
60 // needs to start with 1, since 0 is the return value if *nothing* is clicked on in the context menu
61 GROUP_MASK = 0xff00,
62 ID_SHOWLOG = 1,
63 ID_CFM = 2,
64 ID_BROWSEREPO,
65 ID_COMPAREREVS = 0x100,
66 ID_COMPAREHEADS,
67 ID_UNIDIFFREVS,
68 ID_UNIDIFFHEADS,
69 ID_MERGETO = 0x300,
70 ID_UPDATE,
71 ID_SWITCHTOHEAD,
72 ID_SWITCH,
73 ID_COPYREFS = 0x400,
74 ID_EXPAND_ALL = 0x500,
75 ID_JOIN_ALL,
76 ID_GRAPH_EXPANDCOLLAPSE_ABOVE = 0x600,
77 ID_GRAPH_EXPANDCOLLAPSE_RIGHT,
78 ID_GRAPH_EXPANDCOLLAPSE_BELOW,
79 ID_GRAPH_SPLITJOIN_ABOVE,
80 ID_GRAPH_SPLITJOIN_RIGHT,
81 ID_GRAPH_SPLITJOIN_BELOW,
84 CRevisionGraphWnd::CRevisionGraphWnd()
85 : CWnd()
86 , m_SelectedEntry1(NULL)
87 , m_SelectedEntry2(NULL)
88 , m_pDlgTip(NULL)
89 , m_nFontSize(12)
90 , m_bTweakTrunkColors(true)
91 , m_bTweakTagsColors(true)
92 , m_fZoomFactor(DEFAULT_ZOOM)
93 , m_ptRubberEnd(0,0)
94 , m_ptMoveCanvas(0,0)
95 , m_bShowOverview(false)
96 , m_parent (NULL)
97 , m_hoverIndex (NULL)
98 , m_hoverGlyphs (0)
99 , m_tooltipIndex(nullptr)
100 , m_showHoverGlyphs (false)
101 , m_bIsCanvasMove(false)
102 , m_previewWidth(0)
103 , m_previewHeight(0)
104 , m_previewZoom(1)
105 , m_dwTicks(0)
106 , m_logEntries(&m_LogCache)
107 , m_bCurrentBranch(false)
109 memset(&m_lfBaseFont, 0, sizeof(LOGFONT));
110 std::fill_n(m_apFonts, MAXFONTS, (CFont*)NULL);
112 WNDCLASS wndcls;
113 HINSTANCE hInst = AfxGetInstanceHandle();
114 #define REVGRAPH_CLASSNAME _T("Revgraph_windowclass")
115 if (!(::GetClassInfo(hInst, REVGRAPH_CLASSNAME, &wndcls)))
117 // otherwise we need to register a new class
118 wndcls.style = CS_DBLCLKS | CS_OWNDC;
119 wndcls.lpfnWndProc = ::DefWindowProc;
120 wndcls.cbClsExtra = wndcls.cbWndExtra = 0;
121 wndcls.hInstance = hInst;
122 wndcls.hIcon = NULL;
123 wndcls.hCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
124 wndcls.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
125 wndcls.lpszMenuName = NULL;
126 wndcls.lpszClassName = REVGRAPH_CLASSNAME;
128 RegisterClass(&wndcls);
131 m_bTweakTrunkColors = CRegDWORD(_T("Software\\TortoiseGit\\RevisionGraph\\TweakTrunkColors"), TRUE) != FALSE;
132 m_bTweakTagsColors = CRegDWORD(_T("Software\\TortoiseGit\\RevisionGraph\\TweakTagsColors"), TRUE) != FALSE;
133 m_szTip[0] = 0;
134 m_wszTip[0] = 0;
136 m_GraphAttr.init(this->m_Graph, ogdf::GraphAttributes::nodeGraphics | ogdf::GraphAttributes::edgeGraphics |
137 ogdf:: GraphAttributes::nodeLabel | ogdf::GraphAttributes::nodeColor |
138 ogdf::GraphAttributes::edgeColor | ogdf::GraphAttributes::edgeStyle |
139 ogdf::GraphAttributes::nodeStyle | ogdf::GraphAttributes::nodeTemplate);
141 m_SugiyamLayout.setRanking(::new ogdf::OptimalRanking());
142 m_SugiyamLayout.setCrossMin(::new ogdf::MedianHeuristic());
144 double pi = 3.1415926;
145 m_ArrowCos = cos(pi/8);
146 m_ArrowSin = sin(pi/8);
147 this->m_ArrowSize = 8;
148 #if 0
149 ogdf::node one = this->m_Graph.newNode();
150 ogdf::node two = this->m_Graph.newNode();
151 ogdf::node three = this->m_Graph.newNode();
152 ogdf::node four = this->m_Graph.newNode();
155 m_GraphAttr.width(one)=100;
156 m_GraphAttr.height(one)=200;
157 m_GraphAttr.width(two)=100;
158 m_GraphAttr.height(two)=100;
159 m_GraphAttr.width(three)=100;
160 m_GraphAttr.height(three)=20;
161 m_GraphAttr.width(four)=100;
162 m_GraphAttr.height(four)=20;
164 m_GraphAttr.labelNode(one)="One";
165 m_GraphAttr.labelNode(two)="Two";
166 m_GraphAttr.labelNode(three)="three";
168 this->m_Graph.newEdge(one, two);
169 this->m_Graph.newEdge(one, three);
170 this->m_Graph.newEdge(two, four);
171 this->m_Graph.newEdge(three, four);
173 #endif
174 FastHierarchyLayout *pOHL = ::new FastHierarchyLayout;
175 //It will auto delte when m_SugiyamLayout destory
177 pOHL->layerDistance(30.0);
178 pOHL->nodeDistance(25.0);
180 m_SugiyamLayout.setLayout(pOHL);
182 #if 0
183 //this->m_OHL.layerDistance(30.0);
184 //this->m_OHL.nodeDistance(25.0);
185 //this->m_OHL.weightBalancing(0.8);
186 m_SugiyamLayout.setLayout(&m_OHL);
187 m_SugiyamLayout.call(m_GraphAttr);
188 #endif
189 #if 0
190 PlanarizationLayout pl;
192 FastPlanarSubgraph *ps = ::new FastPlanarSubgraph;
193 ps->runs(100);
194 VariableEmbeddingInserter *ves = ::new VariableEmbeddingInserter;
195 ves->removeReinsert(EdgeInsertionModule::rrAll);
196 pl.setSubgraph(ps);
197 pl.setInserter(ves);
199 EmbedderMinDepthMaxFaceLayers *emb = ::new EmbedderMinDepthMaxFaceLayers;
200 pl.setEmbedder(emb);
202 OrthoLayout *ol =::new OrthoLayout;
203 ol->separation(20.0);
204 ol->cOverhang(0.4);
205 ol->setOptions(2+4);
206 ol->preferedDir(OrthoDir::odEast);
207 pl.setPlanarLayouter(ol);
209 pl.call(m_GraphAttr);
211 node v;
212 forall_nodes(v,m_Graph) {
214 TRACE(_T("node x %f y %f %f %f\n"),/* m_GraphAttr.idNode(v), */
215 m_GraphAttr.x(v),
216 m_GraphAttr.y(v),
217 m_GraphAttr.width(v),
218 m_GraphAttr.height(v)
222 edge e;
223 forall_edges(e, m_Graph)
225 // get connection and point position
226 const DPolyline &dpl = this->m_GraphAttr.bends(e);
228 ListConstIterator<DPoint> it;
229 for(it = dpl.begin(); it.valid(); ++it)
231 TRACE(_T("edge %f %f\n"), (*it).m_x, (*it).m_y);
234 m_GraphAttr.writeGML("test.gml");
235 #endif
238 CRevisionGraphWnd::~CRevisionGraphWnd()
240 for (int i = 0; i < MAXFONTS; ++i)
242 if (m_apFonts[i] != NULL)
244 m_apFonts[i]->DeleteObject();
245 delete m_apFonts[i];
247 m_apFonts[i] = NULL;
249 delete m_pDlgTip;
250 m_Graph.clear();
253 void CRevisionGraphWnd::DoDataExchange(CDataExchange* pDX)
255 CWnd::DoDataExchange(pDX);
259 BEGIN_MESSAGE_MAP(CRevisionGraphWnd, CWnd)
260 ON_WM_PAINT()
261 ON_WM_ERASEBKGND()
262 ON_WM_HSCROLL()
263 ON_WM_VSCROLL()
264 ON_WM_SIZE()
265 ON_WM_LBUTTONDOWN()
266 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipNotify)
267 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipNotify)
268 ON_WM_MOUSEWHEEL()
269 ON_WM_MOUSEHWHEEL()
270 ON_WM_CONTEXTMENU()
271 ON_WM_MOUSEMOVE()
272 ON_WM_LBUTTONUP()
273 ON_WM_SETCURSOR()
274 ON_WM_TIMER()
275 ON_MESSAGE(WM_WORKERTHREADDONE,OnWorkerThreadDone)
276 ON_WM_CAPTURECHANGED()
277 END_MESSAGE_MAP()
279 void CRevisionGraphWnd::Init(CWnd * pParent, LPRECT rect)
281 WNDCLASS wndcls;
282 HINSTANCE hInst = AfxGetInstanceHandle();
283 #define REVGRAPH_CLASSNAME _T("Revgraph_windowclass")
284 if (!(::GetClassInfo(hInst, REVGRAPH_CLASSNAME, &wndcls)))
286 // otherwise we need to register a new class
287 wndcls.style = CS_DBLCLKS | CS_OWNDC;
288 wndcls.lpfnWndProc = ::DefWindowProc;
289 wndcls.cbClsExtra = wndcls.cbWndExtra = 0;
290 wndcls.hInstance = hInst;
291 wndcls.hIcon = NULL;
292 wndcls.hCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
293 wndcls.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
294 wndcls.lpszMenuName = NULL;
295 wndcls.lpszClassName = REVGRAPH_CLASSNAME;
297 RegisterClass(&wndcls);
300 if (!IsWindow(m_hWnd))
301 CreateEx(WS_EX_CLIENTEDGE, REVGRAPH_CLASSNAME, _T("RevGraph"), WS_CHILD|WS_VISIBLE|WS_TABSTOP, *rect, pParent, 0);
302 m_pDlgTip = new CToolTipCtrl;
303 if(!m_pDlgTip->Create(this))
305 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Unable to add tooltip!\n");
307 EnableToolTips();
309 memset(&m_lfBaseFont, 0, sizeof(m_lfBaseFont));
310 m_lfBaseFont.lfHeight = 0;
311 m_lfBaseFont.lfWeight = FW_NORMAL;
312 m_lfBaseFont.lfItalic = FALSE;
313 m_lfBaseFont.lfCharSet = DEFAULT_CHARSET;
314 m_lfBaseFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
315 m_lfBaseFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
316 m_lfBaseFont.lfQuality = DEFAULT_QUALITY;
317 m_lfBaseFont.lfPitchAndFamily = DEFAULT_PITCH;
319 m_dwTicks = GetTickCount();
321 m_parent = dynamic_cast<CRevisionGraphDlg*>(pParent);
324 CPoint CRevisionGraphWnd::GetLogCoordinates (CPoint point) const
326 // translate point into logical coordinates
328 int nVScrollPos = GetScrollPos(SB_VERT);
329 int nHScrollPos = GetScrollPos(SB_HORZ);
331 return CPoint ( (int)((point.x + nHScrollPos) / m_fZoomFactor)
332 , (int)((point.y + nVScrollPos) / m_fZoomFactor));
335 node CRevisionGraphWnd::GetHitNode (CPoint point, CSize /*border*/) const
337 #if 0
338 // any nodes at all?
340 CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());
341 if (!nodeList)
342 return index_t(NO_INDEX);
344 // search the nodes for one at that grid position
346 return nodeList->GetAt (GetLogCoordinates (point), border);
347 #endif
349 node v;
350 forall_nodes(v,m_Graph)
352 RectF noderect (GetNodeRect (v, CPoint(GetScrollPos(SB_HORZ), GetScrollPos(SB_VERT))));
353 if(point.x>noderect.X && point.x <(noderect.X+noderect.Width) &&
354 point.y>noderect.Y && point.y <(noderect.Y+noderect.Height))
356 return v;
359 return NULL;
362 DWORD CRevisionGraphWnd::GetHoverGlyphs (CPoint /*point*/) const
364 // if there is no layout, there will be no nodes,
365 // hence, no glyphs
366 DWORD result = 0;
367 #if 0
368 CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());
369 if (!nodeList)
370 return 0;
372 // get node at point or node that is close enough
373 // so that point may hit a glyph area
375 index_t nodeIndex = GetHitNode(point);
376 if (nodeIndex == NO_INDEX)
377 nodeIndex = GetHitNode(point, CSize (GLYPH_SIZE, GLYPH_SIZE / 2));
379 if (nodeIndex >= nodeList->GetCount())
380 return 0;
382 ILayoutNodeList::SNode node = nodeList->GetNode (nodeIndex);
383 const CVisibleGraphNode* base = node.node;
385 // what glyphs should be shown depending on position of point
386 // relative to the node rect?
388 CPoint logCoordinates = GetLogCoordinates (point);
389 CRect r = node.rect;
390 CPoint center = r.CenterPoint();
392 CRect rightGlyphArea ( r.right - GLYPH_SIZE, center.y - GLYPH_SIZE / 2
393 , r.right + GLYPH_SIZE, center.y + GLYPH_SIZE / 2);
394 CRect topGlyphArea ( center.x - GLYPH_SIZE, r.top - GLYPH_SIZE / 2
395 , center.x + GLYPH_SIZE, r.top + GLYPH_SIZE / 2);
396 CRect bottomGlyphArea ( center.x - GLYPH_SIZE, r.bottom - GLYPH_SIZE / 2
397 , center.x + GLYPH_SIZE, r.bottom + GLYPH_SIZE / 2);
399 bool upsideDown
400 = m_state.GetOptions()->GetOption<CUpsideDownLayout>()->IsActive();
402 if (upsideDown)
404 std::swap (topGlyphArea.top, bottomGlyphArea.top);
405 std::swap (topGlyphArea.bottom, bottomGlyphArea.bottom);
409 if (rightGlyphArea.PtInRect (logCoordinates))
410 result = base->GetFirstCopyTarget() != NULL
411 ? CGraphNodeStates::COLLAPSED_RIGHT | CGraphNodeStates::SPLIT_RIGHT
412 : 0;
414 if (topGlyphArea.PtInRect (logCoordinates))
415 result = base->GetSource() != NULL
416 ? CGraphNodeStates::COLLAPSED_ABOVE | CGraphNodeStates::SPLIT_ABOVE
417 : 0;
419 if (bottomGlyphArea.PtInRect (logCoordinates))
420 result = base->GetNext() != NULL
421 ? CGraphNodeStates::COLLAPSED_BELOW | CGraphNodeStates::SPLIT_BELOW
422 : 0;
424 // if some nodes have already been split, don't allow collapsing etc.
426 CSyncPointer<const CGraphNodeStates> nodeStates (m_state.GetNodeStates());
427 if (result & nodeStates->GetFlags (base))
428 result = 0;
429 #endif
430 return result;
432 #if 0
433 const CRevisionGraphState::SVisibleGlyph* CRevisionGraphWnd::GetHitGlyph (CPoint point) const
435 float glyphSize = GLYPH_SIZE * m_fZoomFactor;
437 CSyncPointer<const CRevisionGraphState::TVisibleGlyphs>
438 visibleGlyphs (m_state.GetVisibleGlyphs());
440 for (size_t i = 0, count = visibleGlyphs->size(); i < count; ++i)
442 const CRevisionGraphState::SVisibleGlyph* entry = &(*visibleGlyphs)[i];
444 float xRel = point.x - entry->leftTop.X;
445 float yRel = point.y - entry->leftTop.Y;
447 if ( (xRel >= 0) && (xRel < glyphSize)
448 && (yRel >= 0) && (yRel < glyphSize))
450 return entry;
454 return NULL;
456 #endif
457 void CRevisionGraphWnd::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
459 SCROLLINFO sinfo = {0};
460 sinfo.cbSize = sizeof(SCROLLINFO);
461 GetScrollInfo(SB_HORZ, &sinfo);
463 // Determine the new position of scroll box.
464 switch (nSBCode)
466 case SB_LEFT: // Scroll to far left.
467 sinfo.nPos = sinfo.nMin;
468 break;
469 case SB_RIGHT: // Scroll to far right.
470 sinfo.nPos = sinfo.nMax;
471 break;
472 case SB_ENDSCROLL: // End scroll.
473 break;
474 case SB_LINELEFT: // Scroll left.
475 if (sinfo.nPos > sinfo.nMin)
476 sinfo.nPos--;
477 break;
478 case SB_LINERIGHT: // Scroll right.
479 if (sinfo.nPos < sinfo.nMax)
480 ++sinfo.nPos;
481 break;
482 case SB_PAGELEFT: // Scroll one page left.
484 if (sinfo.nPos > sinfo.nMin)
485 sinfo.nPos = max(sinfo.nMin, sinfo.nPos - (int) sinfo.nPage);
487 break;
488 case SB_PAGERIGHT: // Scroll one page right.
490 if (sinfo.nPos < sinfo.nMax)
491 sinfo.nPos = min(sinfo.nMax, sinfo.nPos + (int) sinfo.nPage);
493 break;
494 case SB_THUMBPOSITION: // Scroll to absolute position. nPos is the position
495 sinfo.nPos = sinfo.nTrackPos; // of the scroll box at the end of the drag operation.
496 break;
497 case SB_THUMBTRACK: // Drag scroll box to specified position. nPos is the
498 sinfo.nPos = sinfo.nTrackPos; // position that the scroll box has been dragged to.
499 break;
501 SetScrollInfo(SB_HORZ, &sinfo);
502 Invalidate (FALSE);
503 __super::OnHScroll(nSBCode, nPos, pScrollBar);
506 void CRevisionGraphWnd::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
508 SCROLLINFO sinfo = {0};
509 sinfo.cbSize = sizeof(SCROLLINFO);
510 GetScrollInfo(SB_VERT, &sinfo);
512 // Determine the new position of scroll box.
513 switch (nSBCode)
515 case SB_LEFT: // Scroll to far left.
516 sinfo.nPos = sinfo.nMin;
517 break;
518 case SB_RIGHT: // Scroll to far right.
519 sinfo.nPos = sinfo.nMax;
520 break;
521 case SB_ENDSCROLL: // End scroll.
522 break;
523 case SB_LINELEFT: // Scroll left.
524 if (sinfo.nPos > sinfo.nMin)
525 sinfo.nPos--;
526 break;
527 case SB_LINERIGHT: // Scroll right.
528 if (sinfo.nPos < sinfo.nMax)
529 ++sinfo.nPos;
530 break;
531 case SB_PAGELEFT: // Scroll one page left.
533 if (sinfo.nPos > sinfo.nMin)
534 sinfo.nPos = max(sinfo.nMin, sinfo.nPos - (int) sinfo.nPage);
536 break;
537 case SB_PAGERIGHT: // Scroll one page right.
539 if (sinfo.nPos < sinfo.nMax)
540 sinfo.nPos = min(sinfo.nMax, sinfo.nPos + (int) sinfo.nPage);
542 break;
543 case SB_THUMBPOSITION: // Scroll to absolute position. nPos is the position
544 sinfo.nPos = sinfo.nTrackPos; // of the scroll box at the end of the drag operation.
545 break;
546 case SB_THUMBTRACK: // Drag scroll box to specified position. nPos is the
547 sinfo.nPos = sinfo.nTrackPos; // position that the scroll box has been dragged to.
548 break;
550 SetScrollInfo(SB_VERT, &sinfo);
551 Invalidate(FALSE);
552 __super::OnVScroll(nSBCode, nPos, pScrollBar);
555 void CRevisionGraphWnd::OnSize(UINT nType, int cx, int cy)
557 __super::OnSize(nType, cx, cy);
558 SetScrollbars(GetScrollPos(SB_VERT), GetScrollPos(SB_HORZ));
559 Invalidate(FALSE);
562 void CRevisionGraphWnd::OnLButtonDown(UINT nFlags, CPoint point)
565 if (IsUpdateJobRunning())
566 return __super::OnLButtonDown(nFlags, point);
568 // CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());
570 SetFocus();
571 bool bHit = false;
572 bool bControl = !!(GetKeyState(VK_CONTROL)&0x8000);
573 bool bOverview = m_bShowOverview && m_OverviewRect.PtInRect(point);
574 if (! bOverview)
576 #if 0
577 const CRevisionGraphState::SVisibleGlyph* hitGlyph
578 = GetHitGlyph (point);
580 if (hitGlyph != NULL)
582 ToggleNodeFlag (hitGlyph->node, hitGlyph->state);
583 return __super::OnLButtonDown(nFlags, point);
585 #endif
586 node nodeIndex = GetHitNode (point);
587 if (nodeIndex != NULL)
589 if (bControl)
591 if (m_SelectedEntry1 == nodeIndex)
593 if (m_SelectedEntry2)
595 m_SelectedEntry1 = m_SelectedEntry2;
596 m_SelectedEntry2 = NULL;
598 else
599 m_SelectedEntry1 = NULL;
601 else if (m_SelectedEntry2 == nodeIndex)
602 m_SelectedEntry2 = NULL;
603 else if (m_SelectedEntry1)
604 m_SelectedEntry2 = nodeIndex;
605 else
606 m_SelectedEntry1 = nodeIndex;
608 else
610 if (m_SelectedEntry1 == nodeIndex)
611 m_SelectedEntry1 = NULL;
612 else
613 m_SelectedEntry1 = nodeIndex;
614 m_SelectedEntry2 = NULL;
616 bHit = true;
617 Invalidate(FALSE);
621 if ((!bHit)&&(!bControl)&&(!bOverview))
623 m_SelectedEntry1 = NULL;
624 m_SelectedEntry2 = NULL;
625 m_bIsCanvasMove = true;
626 Invalidate(FALSE);
627 if (m_bShowOverview && m_OverviewRect.PtInRect(point))
628 m_bIsCanvasMove = false;
630 m_ptMoveCanvas = point;
632 UINT uEnable = MF_BYCOMMAND;
633 if ((m_SelectedEntry1 != NULL)&&(m_SelectedEntry2 != NULL))
634 uEnable |= MF_ENABLED;
635 else
636 uEnable |= MF_GRAYED;
638 EnableMenuItem(GetParent()->GetMenu()->m_hMenu, ID_VIEW_COMPAREREVISIONS, uEnable);
639 EnableMenuItem(GetParent()->GetMenu()->m_hMenu, ID_VIEW_UNIFIEDDIFF, uEnable);
641 uEnable = MF_BYCOMMAND;
642 if ((m_SelectedEntry1 != NULL)&&(m_SelectedEntry2 == NULL))
643 uEnable |= MF_ENABLED;
644 else
645 uEnable |= MF_GRAYED;
647 EnableMenuItem(GetParent()->GetMenu()->m_hMenu, ID_VIEW_UNIFIEDDIFFOFHEADREVISIONS, uEnable);
648 EnableMenuItem(GetParent()->GetMenu()->m_hMenu, ID_VIEW_COMPAREHEADREVISIONS, uEnable);
650 __super::OnLButtonDown(nFlags, point);
653 void CRevisionGraphWnd::OnCaptureChanged(CWnd *pWnd)
655 __super::OnCaptureChanged(pWnd);
658 void CRevisionGraphWnd::OnLButtonUp(UINT nFlags, CPoint point)
660 if (!m_bIsCanvasMove)
661 return; // we don't have a rubberband, so no zooming necessary
663 m_bIsCanvasMove = false;
664 ReleaseCapture();
665 if (IsUpdateJobRunning())
666 return __super::OnLButtonUp(nFlags, point);
668 // zooming is finished
669 m_ptRubberEnd = CPoint(0,0);
670 CRect rect = GetClientRect();
671 int x = abs(m_ptMoveCanvas.x - point.x);
672 int y = abs(m_ptMoveCanvas.y - point.y);
674 if ((x < 20)&&(y < 20))
676 // too small zoom rectangle
677 // assume zooming by accident
678 Invalidate(FALSE);
679 __super::OnLButtonUp(nFlags, point);
680 return;
683 float xfact = float(rect.Width())/float(x);
684 float yfact = float(rect.Height())/float(y);
685 float fact = max(yfact, xfact);
687 // find out where to scroll to
688 x = min(m_ptMoveCanvas.x, point.x) + GetScrollPos(SB_HORZ);
689 y = min(m_ptMoveCanvas.y, point.y) + GetScrollPos(SB_VERT);
691 float fZoomfactor = m_fZoomFactor*fact;
692 if (fZoomfactor > 10 * MAX_ZOOM)
694 // with such a big zoomfactor, the user
695 // most likely zoomed by accident
696 Invalidate(FALSE);
697 __super::OnLButtonUp(nFlags, point);
698 return;
700 if (fZoomfactor > MAX_ZOOM)
702 fZoomfactor = MAX_ZOOM;
703 fact = fZoomfactor/m_fZoomFactor;
706 CRevisionGraphDlg * pDlg = (CRevisionGraphDlg*)GetParent();
707 if (pDlg)
709 m_fZoomFactor = fZoomfactor;
710 pDlg->DoZoom (m_fZoomFactor);
711 SetScrollbars(int(float(y)*fact), int(float(x)*fact));
713 __super::OnLButtonUp(nFlags, point);
716 bool CRevisionGraphWnd::CancelMouseZoom()
718 bool bRet = m_bIsCanvasMove;
719 ReleaseCapture();
720 if (m_bIsCanvasMove)
721 Invalidate(FALSE);
722 m_bIsCanvasMove = false;
723 m_ptRubberEnd = CPoint(0,0);
724 return bRet;
727 INT_PTR CRevisionGraphWnd::OnToolHitTest(CPoint point, TOOLINFO* pTI) const
730 if (IsUpdateJobRunning())
731 return -1;
733 node nodeIndex = GetHitNode (point);
734 if (m_tooltipIndex != nodeIndex)
736 // force tooltip to be updated
738 m_tooltipIndex = nodeIndex;
739 return -1;
742 if (nodeIndex == NULL)
743 return -1;
745 // if ((GetHoverGlyphs (point) != 0) || (GetHitGlyph (point) != NULL))
746 // return -1;
748 pTI->hwnd = this->m_hWnd;
749 CWnd::GetClientRect(&pTI->rect);
750 pTI->uFlags |= TTF_ALWAYSTIP | TTF_IDISHWND;
751 pTI->uId = (UINT_PTR)m_hWnd;
752 pTI->lpszText = LPSTR_TEXTCALLBACK;
754 return 1;
757 BOOL CRevisionGraphWnd::OnToolTipNotify(UINT /*id*/, NMHDR *pNMHDR, LRESULT *pResult)
759 if (pNMHDR->idFrom != (UINT_PTR)m_hWnd)
760 return FALSE;
762 POINT point;
763 DWORD ptW = GetMessagePos();
764 point.x = GET_X_LPARAM(ptW);
765 point.y = GET_Y_LPARAM(ptW);
766 ScreenToClient(&point);
768 CString strTipText = TooltipText (GetHitNode (point));
770 *pResult = 0;
771 if (strTipText.IsEmpty())
772 return TRUE;
774 CSize tooltipSize = UsableTooltipRect();
775 strTipText = DisplayableText (strTipText, tooltipSize);
777 // need to handle both ANSI and UNICODE versions of the message
778 if (pNMHDR->code == TTN_NEEDTEXTA)
780 TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;
781 ::SendMessage(pNMHDR->hwndFrom, TTM_SETMAXTIPWIDTH, 0, tooltipSize.cx);
782 pTTTA->lpszText = m_szTip;
783 WideCharToMultiByte(CP_ACP, 0, strTipText, -1, m_szTip, strTipText.GetLength()+1, 0, 0);
785 else
787 TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;
788 ::SendMessage(pNMHDR->hwndFrom, TTM_SETMAXTIPWIDTH, 0, tooltipSize.cx);
789 lstrcpyn(m_wszTip, strTipText, strTipText.GetLength()+1);
790 pTTTW->lpszText = m_wszTip;
793 // show the tooltip for 32 seconds. A higher value than 32767 won't work
794 // even though it's nowhere documented!
795 ::SendMessage(pNMHDR->hwndFrom, TTM_SETDELAYTIME, TTDT_AUTOPOP, 32767);
796 return TRUE; // message was handled
799 CSize CRevisionGraphWnd::UsableTooltipRect()
801 // get screen size
803 int screenWidth = GetSystemMetrics(SM_CXSCREEN);
804 int screenHeight = GetSystemMetrics(SM_CYSCREEN);
806 // get current mouse position
808 CPoint cursorPos;
809 if (GetCursorPos (&cursorPos) == FALSE)
811 // we could not determine the mouse position
812 // use screen / 2 minus some safety margin
814 return CSize (screenWidth / 2 - 20, screenHeight / 2 - 20);
817 // tool tip will display in the biggest sector beside the cursor
818 // deduct some safety margin (for the mouse cursor itself
820 CSize biggestSector
821 ( max (screenWidth - cursorPos.x - 40, cursorPos.x - 24)
822 , max (screenHeight - cursorPos.y - 40, cursorPos.y - 24));
824 return biggestSector;
827 CString CRevisionGraphWnd::DisplayableText ( const CString& wholeText
828 , const CSize& tooltipSize)
830 CDC* dc = GetDC();
831 if (dc == NULL)
833 // no access to the device context -> truncate hard at 1000 chars
835 return wholeText.GetLength() >= MAX_TT_LENGTH_DEFAULT
836 ? wholeText.Left (MAX_TT_LENGTH_DEFAULT-4) + _T(" ...")
837 : wholeText;
840 // select the tooltip font
842 NONCLIENTMETRICS metrics;
843 metrics.cbSize = sizeof (metrics);
844 if (!SysInfo::Instance().IsVistaOrLater())
846 metrics.cbSize -= sizeof(int); // subtract the size of the iPaddedBorderWidth member which is not available on XP
848 SystemParametersInfo (SPI_GETNONCLIENTMETRICS, metrics.cbSize, &metrics, 0);
850 CFont font;
851 font.CreateFontIndirect(&metrics.lfStatusFont);
852 CFont* pOldFont = dc->SelectObject (&font);
854 // split into lines and fill the tooltip rect
856 CString result;
858 int remainingHeight = tooltipSize.cy;
859 int pos = 0;
860 while (pos < wholeText.GetLength())
862 // extract a whole line
864 int nextPos = wholeText.Find ('\n', pos);
865 if (nextPos < 0)
866 nextPos = wholeText.GetLength();
868 CString line = wholeText.Mid (pos, nextPos-pos+1);
870 // find a way to make it fit
872 CSize size = dc->GetTextExtent (line);
873 while (size.cx > tooltipSize.cx)
875 line.Delete (line.GetLength()-1);
876 int nextPos2 = line.ReverseFind (' ');
877 if (nextPos2 < 0)
878 break;
880 line.Delete (nextPos2+1, line.GetLength() - pos-1);
881 size = dc->GetTextExtent (line);
884 // enough room for the new line?
886 remainingHeight -= size.cy;
887 if (remainingHeight <= size.cy)
889 result += _T("...");
890 break;
893 // add the line
895 result += line;
896 pos += line.GetLength();
899 // relase temp. resources
901 dc->SelectObject (pOldFont);
902 ReleaseDC(dc);
904 // ready
906 return result;
909 CString CRevisionGraphWnd::TooltipText(node index)
911 if(index)
913 CString str;
914 CGitHash hash = m_logEntries[index->index()];
915 GitRev *rev = this->m_LogCache.GetCacheData(hash);
916 str += rev->m_CommitHash.ToString();
917 str += _T("\n");
918 str += rev->GetAuthorName() +_T(" ") + rev->GetAuthorEmail();
919 str += _T(" ");
920 str += rev->GetAuthorDate().Format(_T("%Y-%m-%d %H:%M"));
921 str += _T("\n\n")+rev->GetSubject();
922 str += _T("\n");
923 str += rev->GetBody();
924 return str;
926 }else
927 return CString();
930 void CRevisionGraphWnd::SaveGraphAs(CString sSavePath)
933 CString extension = CPathUtils::GetFileExtFromPath(sSavePath);
934 if (extension.CompareNoCase(_T(".wmf"))==0)
936 // save the graph as an enhanced metafile
937 CMetaFileDC wmfDC;
938 wmfDC.CreateEnhanced(NULL, sSavePath, NULL, _T("TortoiseGit\0Revision Graph\0\0"));
939 float fZoom = m_fZoomFactor;
940 m_fZoomFactor = DEFAULT_ZOOM;
941 DoZoom(m_fZoomFactor);
942 CRect rect;
943 rect = GetViewRect();
944 GraphicsDevice dev;
945 dev.pDC = &wmfDC;
946 DrawGraph(dev, rect, 0, 0, true);
947 HENHMETAFILE hemf = wmfDC.CloseEnhanced();
948 DeleteEnhMetaFile(hemf);
949 m_fZoomFactor = fZoom;
950 DoZoom(m_fZoomFactor);
952 else if (extension.CompareNoCase(_T(".svg"))==0)
954 // save the graph as a scalable vector graphic
955 SVG svg;
956 float fZoom = m_fZoomFactor;
957 m_fZoomFactor = DEFAULT_ZOOM;
958 DoZoom(m_fZoomFactor);
959 CRect rect;
960 rect = GetViewRect();
961 svg.SetViewSize(rect.Width(), rect.Height());
962 GraphicsDevice dev;
963 dev.pSVG = &svg;
964 DrawGraph(dev, rect, 0, 0, true);
965 svg.Save(sSavePath);
966 m_fZoomFactor = fZoom;
967 DoZoom(m_fZoomFactor);
969 else if (extension.CompareNoCase(_T(".gv")) == 0)
971 Graphviz graphviz;
972 float fZoom = m_fZoomFactor;
973 m_fZoomFactor = DEFAULT_ZOOM;
974 DoZoom(m_fZoomFactor);
975 CRect rect;
976 rect = GetViewRect();
977 GraphicsDevice dev;
978 dev.pGraphviz = &graphviz;
979 DrawGraph(dev, rect, 0, 0, true);
980 graphviz.Save(sSavePath);
981 m_fZoomFactor = fZoom;
982 DoZoom(m_fZoomFactor);
984 else
986 // save the graph as a pixel picture instead of a vector picture
987 // create dc to paint on
990 CString sErrormessage;
991 CWindowDC ddc(this);
992 CDC dc;
993 if (!dc.CreateCompatibleDC(&ddc))
995 CFormatMessageWrapper errorDetails;
996 if( errorDetails )
997 MessageBox( errorDetails, _T("Error"), MB_OK | MB_ICONINFORMATION );
999 return;
1001 CRect rect;
1002 rect = GetGraphRect();
1003 rect.bottom = (LONG)(float(rect.Height()) * m_fZoomFactor);
1004 rect.right = (LONG)(float(rect.Width()) * m_fZoomFactor);
1005 BITMAPINFO bmi;
1006 HBITMAP hbm;
1007 LPBYTE pBits;
1008 // Initialize header to 0s.
1009 SecureZeroMemory(&bmi, sizeof(bmi));
1010 // Fill out the fields you care about.
1011 bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
1012 bmi.bmiHeader.biWidth = rect.Width();
1013 bmi.bmiHeader.biHeight = rect.Height();
1014 bmi.bmiHeader.biPlanes = 1;
1015 bmi.bmiHeader.biBitCount = 24;
1016 bmi.bmiHeader.biCompression = BI_RGB;
1018 // Create the surface.
1019 hbm = CreateDIBSection(ddc.m_hDC, &bmi, DIB_RGB_COLORS,(void **)&pBits, NULL, 0);
1020 if (hbm==0)
1022 CMessageBox::Show(m_hWnd, IDS_REVGRAPH_ERR_NOMEMORY, IDS_APPNAME, MB_ICONERROR);
1023 return;
1025 HBITMAP oldbm = (HBITMAP)dc.SelectObject(hbm);
1026 // paint the whole graph
1027 GraphicsDevice dev;
1028 dev.pDC = &dc;
1029 DrawGraph(dev, rect, 0, 0, true);
1030 // now use GDI+ to save the picture
1031 CLSID encoderClsid;
1033 Bitmap bitmap(hbm, NULL);
1034 if (bitmap.GetLastStatus()==Ok)
1036 // Get the CLSID of the encoder.
1037 int ret = 0;
1038 if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(_T(".png"))==0)
1039 ret = GetEncoderClsid(L"image/png", &encoderClsid);
1040 else if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(_T(".jpg"))==0)
1041 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1042 else if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(_T(".jpeg"))==0)
1043 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1044 else if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(_T(".bmp"))==0)
1045 ret = GetEncoderClsid(L"image/bmp", &encoderClsid);
1046 else if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(_T(".gif"))==0)
1047 ret = GetEncoderClsid(L"image/gif", &encoderClsid);
1048 else
1050 sSavePath += _T(".jpg");
1051 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1053 if (ret >= 0)
1055 CStringW tfile = CStringW(sSavePath);
1056 bitmap.Save(tfile, &encoderClsid, NULL);
1058 else
1060 sErrormessage.Format(IDS_REVGRAPH_ERR_NOENCODER, (LPCTSTR)CPathUtils::GetFileExtFromPath(sSavePath));
1063 else
1065 sErrormessage.LoadString(IDS_REVGRAPH_ERR_NOBITMAP);
1068 dc.SelectObject(oldbm);
1069 DeleteObject(hbm);
1070 dc.DeleteDC();
1071 if (!sErrormessage.IsEmpty())
1073 ::MessageBox(m_hWnd, sErrormessage, _T("TortoiseGit"), MB_ICONERROR);
1076 catch (CException * pE)
1078 TCHAR szErrorMsg[2048] = { 0 };
1079 pE->GetErrorMessage(szErrorMsg, 2048);
1080 pE->Delete();
1081 ::MessageBox(m_hWnd, szErrorMsg, _T("TortoiseGit"), MB_ICONERROR);
1087 BOOL CRevisionGraphWnd::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
1089 if (IsUpdateJobRunning())
1090 return __super::OnMouseWheel(nFlags, zDelta, pt);
1092 if (GetKeyState(VK_CONTROL)&0x8000)
1094 float newZoom = m_fZoomFactor * (zDelta < 0 ? ZOOM_STEP : 1.0f/ZOOM_STEP);
1095 DoZoom (max (MIN_ZOOM, min (MAX_ZOOM, newZoom)));
1097 else
1099 int orientation = GetKeyState(VK_SHIFT)&0x8000 ? SB_HORZ : SB_VERT;
1100 int pos = GetScrollPos(orientation);
1101 pos -= (zDelta);
1102 SetScrollPos(orientation, pos);
1103 Invalidate(FALSE);
1105 return __super::OnMouseWheel(nFlags, zDelta, pt);
1108 void CRevisionGraphWnd::OnMouseHWheel(UINT nFlags, short zDelta, CPoint pt)
1110 if (IsUpdateJobRunning())
1111 return __super::OnMouseHWheel(nFlags, zDelta, pt);
1113 int orientation = GetKeyState(VK_SHIFT)&0x8000 ? SB_VERT : SB_HORZ;
1114 int pos = GetScrollPos(orientation);
1115 pos -= (zDelta);
1116 SetScrollPos(orientation, pos);
1117 Invalidate(FALSE);
1119 return __super::OnMouseHWheel(nFlags, zDelta, pt);
1122 bool CRevisionGraphWnd::UpdateSelectedEntry (node clickedentry)
1124 if ((m_SelectedEntry1 == NULL)&&(clickedentry == NULL))
1125 return false;
1127 if (m_SelectedEntry1 == NULL)
1129 m_SelectedEntry1 = clickedentry;
1130 Invalidate(FALSE);
1132 if ((m_SelectedEntry2 == NULL)&&(clickedentry != m_SelectedEntry1))
1134 m_SelectedEntry1 = clickedentry;
1135 Invalidate(FALSE);
1137 if (m_SelectedEntry1 && m_SelectedEntry2)
1139 if ((m_SelectedEntry2 != clickedentry)&&(m_SelectedEntry1 != clickedentry))
1140 return false;
1142 if (m_SelectedEntry1 == NULL)
1143 return false;
1145 return true;
1148 void CRevisionGraphWnd::AppendMenu
1149 ( CMenu& popup
1150 , UINT title
1151 , UINT command
1152 , UINT flags)
1154 // separate different groups / section within the context menu
1156 if (popup.GetMenuItemCount() > 0)
1158 UINT lastCommand = popup.GetMenuItemID (popup.GetMenuItemCount()-1);
1159 if ((lastCommand & GROUP_MASK) != (command & GROUP_MASK))
1160 popup.AppendMenu(MF_SEPARATOR, NULL);
1163 // actually add the new item
1165 CString titleString;
1166 titleString.LoadString (title);
1167 popup.AppendMenu (MF_STRING | flags, command, titleString);
1170 void CRevisionGraphWnd::AppendMenu(CMenu &popup, CString title, UINT command, CString *extra, CMenu *submenu)
1172 // separate different groups / section within the context menu
1173 if (popup.GetMenuItemCount() > 0)
1175 UINT lastCommand = popup.GetMenuItemID(popup.GetMenuItemCount() - 1);
1176 if ((lastCommand & GROUP_MASK) != (command & GROUP_MASK))
1177 popup.AppendMenu(MF_SEPARATOR, NULL);
1180 // actually add the new item
1181 MENUITEMINFO mii;
1182 memset(&mii, 0, sizeof(mii));
1183 mii.cbSize = sizeof(MENUITEMINFO);
1184 mii.fMask = MIIM_STRING | MIIM_ID | (extra ? MIIM_DATA : 0) | (submenu ? MIIM_SUBMENU : 0);
1185 mii.wID = command;
1186 mii.hSubMenu = submenu ? submenu->m_hMenu : NULL;
1187 mii.dwItemData = (ULONG_PTR)extra;
1188 mii.dwTypeData = title.GetBuffer();
1189 InsertMenuItem(popup, popup.GetMenuItemCount(), TRUE, &mii);
1190 title.ReleaseBuffer();
1193 void CRevisionGraphWnd::AddGraphOps (CMenu& /*popup*/, const CVisibleGraphNode * /*node*/)
1195 #if 0
1196 CSyncPointer<CGraphNodeStates> nodeStates (m_state.GetNodeStates());
1198 if (node == NULL)
1200 DWORD state = nodeStates->GetCombinedFlags();
1201 if (state != 0)
1203 if (state & CGraphNodeStates::COLLAPSED_ALL)
1204 AppendMenu (popup, IDS_REVGRAPH_POPUP_EXPAND_ALL, ID_EXPAND_ALL);
1206 if (state & CGraphNodeStates::SPLIT_ALL)
1207 AppendMenu (popup, IDS_REVGRAPH_POPUP_JOIN_ALL, ID_JOIN_ALL);
1210 else
1212 DWORD state = nodeStates->GetFlags (node);
1214 if (node->GetSource() || (state & CGraphNodeStates::COLLAPSED_ABOVE))
1215 AppendMenu ( popup
1216 , (state & CGraphNodeStates::COLLAPSED_ABOVE)
1217 ? IDS_REVGRAPH_POPUP_EXPAND_ABOVE
1218 : IDS_REVGRAPH_POPUP_COLLAPSE_ABOVE
1219 , ID_GRAPH_EXPANDCOLLAPSE_ABOVE);
1221 if (node->GetFirstCopyTarget() || (state & CGraphNodeStates::COLLAPSED_RIGHT))
1222 AppendMenu ( popup
1223 , (state & CGraphNodeStates::COLLAPSED_RIGHT)
1224 ? IDS_REVGRAPH_POPUP_EXPAND_RIGHT
1225 : IDS_REVGRAPH_POPUP_COLLAPSE_RIGHT
1226 , ID_GRAPH_EXPANDCOLLAPSE_RIGHT);
1228 if (node->GetNext() || (state & CGraphNodeStates::COLLAPSED_BELOW))
1229 AppendMenu ( popup
1230 , (state & CGraphNodeStates::COLLAPSED_BELOW)
1231 ? IDS_REVGRAPH_POPUP_EXPAND_BELOW
1232 : IDS_REVGRAPH_POPUP_COLLAPSE_BELOW
1233 , ID_GRAPH_EXPANDCOLLAPSE_BELOW);
1235 if (node->GetSource() || (state & CGraphNodeStates::SPLIT_ABOVE))
1236 AppendMenu ( popup
1237 , (state & CGraphNodeStates::SPLIT_ABOVE)
1238 ? IDS_REVGRAPH_POPUP_JOIN_ABOVE
1239 : IDS_REVGRAPH_POPUP_SPLIT_ABOVE
1240 , ID_GRAPH_SPLITJOIN_ABOVE);
1242 if (node->GetFirstCopyTarget() || (state & CGraphNodeStates::SPLIT_RIGHT))
1243 AppendMenu ( popup
1244 , (state & CGraphNodeStates::SPLIT_RIGHT)
1245 ? IDS_REVGRAPH_POPUP_JOIN_RIGHT
1246 : IDS_REVGRAPH_POPUP_SPLIT_RIGHT
1247 , ID_GRAPH_SPLITJOIN_RIGHT);
1249 if (node->GetNext() || (state & CGraphNodeStates::SPLIT_BELOW))
1250 AppendMenu ( popup
1251 , (state & CGraphNodeStates::SPLIT_BELOW)
1252 ? IDS_REVGRAPH_POPUP_JOIN_BELOW
1253 : IDS_REVGRAPH_POPUP_SPLIT_BELOW
1254 , ID_GRAPH_SPLITJOIN_BELOW);
1256 #endif
1259 CString CRevisionGraphWnd::GetSelectedURL() const
1261 #if 0
1262 if (m_SelectedEntry1 == NULL)
1263 return CString();
1265 CString URL = m_state.GetRepositoryRoot()
1266 + CUnicodeUtils::GetUnicode (m_SelectedEntry1->GetPath().GetPath().c_str());
1267 URL = CUnicodeUtils::GetUnicode(CPathUtils::PathEscape(CUnicodeUtils::GetUTF8(URL)));
1269 return URL;
1270 #endif
1271 return CString();
1274 CString CRevisionGraphWnd::GetWCURL() const
1276 #if 0
1277 CTGitPath path (m_sPath);
1278 if (path.IsUrl())
1279 return CString();
1281 SVNInfo info;
1282 const SVNInfoData * status
1283 = info.GetFirstFileInfo (path, SVNRev(), SVNRev());
1285 return status == NULL ? CString() : status->url;
1286 #endif
1287 return CString();
1290 void CRevisionGraphWnd::DoShowLog()
1293 if(m_SelectedEntry1 == NULL)
1294 return;
1296 CString sCmd;
1298 if(m_SelectedEntry2 != NULL)
1299 sCmd.Format(_T("/command:log %s /startrev:%s /endrev:%s"),
1300 this->m_sPath.IsEmpty() ? _T("") : (_T("/path:\"") + this->m_sPath + _T("\"")),
1301 this->m_logEntries[m_SelectedEntry1->index()].ToString(),
1302 this->m_logEntries[m_SelectedEntry2->index()].ToString());
1303 else
1304 sCmd.Format(_T("/command:log %s /endrev:%s"),
1305 this->m_sPath.IsEmpty() ? _T("") : (_T("/path:\"") + this->m_sPath + _T("\"")),
1306 this->m_logEntries[m_SelectedEntry1->index()].ToString());
1308 CAppUtils::RunTortoiseGitProc(sCmd);
1312 void CRevisionGraphWnd::DoCheckForModification()
1314 CChangedDlg dlg;
1315 dlg.m_pathList = CTGitPathList (CTGitPath (m_sPath));
1316 dlg.DoModal();
1319 void CRevisionGraphWnd::DoMergeTo()
1321 #if 0
1322 CString URL = GetSelectedURL();
1323 CString path = m_sPath;
1324 CBrowseFolder folderBrowser;
1325 folderBrowser.SetInfo(CString(MAKEINTRESOURCE(IDS_LOG_MERGETO)));
1326 if (folderBrowser.Show(GetSafeHwnd(), path, path) == CBrowseFolder::OK)
1328 CSVNProgressDlg dlg;
1329 dlg.SetCommand(CSVNProgressDlg::SVNProgress_Merge);
1330 dlg.SetPathList(CTGitPathList(CTGitPath(path)));
1331 dlg.SetUrl(URL);
1332 dlg.SetSecondUrl(URL);
1333 SVNRevRangeArray revarray;
1334 revarray.AddRevRange (m_SelectedEntry1->GetRevision()-1, svn_revnum_t(m_SelectedEntry1->GetRevision()));
1335 dlg.SetRevisionRanges(revarray);
1336 dlg.DoModal();
1338 #endif
1341 void CRevisionGraphWnd::DoUpdate()
1343 #if 0
1344 CSVNProgressDlg progDlg;
1345 progDlg.SetCommand (CSVNProgressDlg::SVNProgress_Update);
1346 progDlg.SetOptions (0); // don't ignore externals
1347 progDlg.SetPathList (CTGitPathList (CTGitPath (m_sPath)));
1348 progDlg.SetRevision (m_SelectedEntry1->GetRevision());
1349 progDlg.SetDepth();
1350 progDlg.DoModal();
1352 if (m_state.GetFetchedWCState())
1353 m_parent->UpdateFullHistory();
1354 #endif
1357 void CRevisionGraphWnd::DoSwitch(CString rev)
1359 CAppUtils::PerformSwitch(rev);
1362 void CRevisionGraphWnd::DoSwitchToHead()
1364 #if 0
1365 CSVNProgressDlg progDlg;
1366 progDlg.SetCommand (CSVNProgressDlg::SVNProgress_Switch);
1367 progDlg.SetPathList (CTGitPathList (CTGitPath (m_sPath)));
1368 progDlg.SetUrl (GetSelectedURL());
1369 progDlg.SetRevision (SVNRev::REV_HEAD);
1370 progDlg.SetPegRevision (m_SelectedEntry1->GetRevision());
1371 progDlg.DoModal();
1373 if (m_state.GetFetchedWCState())
1374 m_parent->UpdateFullHistory();
1375 #endif
1378 void CRevisionGraphWnd::DoBrowseRepo()
1380 if (m_SelectedEntry1 == NULL)
1381 return;
1383 CString sCmd;
1384 sCmd.Format(_T("/command:repobrowser %s /rev:%s"),
1385 this->m_sPath.IsEmpty() ? _T("") : (_T("/path:\"") + this->m_sPath + _T("\"")),
1386 GetFriendRefName(m_SelectedEntry1));
1388 CAppUtils::RunTortoiseGitProc(sCmd);
1391 void CRevisionGraphWnd::ResetNodeFlags (DWORD /*flags*/)
1393 // m_state.GetNodeStates()->ResetFlags (flags);
1394 // m_parent->StartWorkerThread();
1397 void CRevisionGraphWnd::ToggleNodeFlag (const CVisibleGraphNode * /*node*/, DWORD /*flag*/)
1399 #if 0
1400 CSyncPointer<CGraphNodeStates> nodeStates (m_state.GetNodeStates());
1402 if (nodeStates->GetFlags (node) & flag)
1403 nodeStates->ResetFlags (node, flag);
1404 else
1405 nodeStates->SetFlags (node, flag);
1407 m_parent->StartWorkerThread();
1408 #endif
1411 void CRevisionGraphWnd::DoCopyRefs()
1413 if (m_SelectedEntry1 == NULL)
1414 return;
1416 STRING_VECTOR list = GetFriendRefNames(m_SelectedEntry1);
1417 CString text;
1418 if (list.empty())
1419 text = m_logEntries[m_SelectedEntry1->index()].ToString();
1420 for (size_t i = 0; i < list.size(); ++i)
1422 if (i > 0)
1423 text.Append(_T("\r\n"));
1424 text.Append(list[i]);
1426 CStringUtils::WriteAsciiStringToClipboard(text, m_hWnd);
1429 void CRevisionGraphWnd::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
1431 if (IsUpdateJobRunning())
1432 return;
1434 CPoint clientpoint = point;
1435 this->ScreenToClient(&clientpoint);
1437 node nodeIndex = GetHitNode (clientpoint);
1439 if ( !UpdateSelectedEntry (nodeIndex))
1441 return;
1444 CMenu popup;
1445 if (!popup.CreatePopupMenu())
1446 return;
1448 bool bothPresent = (m_SelectedEntry2 && m_SelectedEntry1);
1450 AppendMenu (popup, IDS_REPOBROWSE_SHOWLOG, ID_SHOWLOG);
1452 STRING_VECTOR branchNames;
1453 if (m_SelectedEntry1 && (m_SelectedEntry2 == nullptr))
1455 AppendMenu(popup, IDS_LOG_BROWSEREPO, ID_BROWSEREPO);
1457 CString currentBranch = g_Git.GetCurrentBranch();
1458 CGit::REF_TYPE refType = CGit::LOCAL_BRANCH;
1459 STRING_VECTOR allBranchNames = GetFriendRefNames(m_SelectedEntry1, &refType, 1);
1460 for (size_t i = 0; i < allBranchNames.size(); ++i)
1461 if (allBranchNames[i] != currentBranch)
1462 branchNames.push_back(allBranchNames[i]);
1463 if (branchNames.size() == 1)
1465 CString text;
1466 text.Format(_T("%s \"%s\""), CString(MAKEINTRESOURCE(IDS_SWITCH_BRANCH)), branchNames[0]);
1467 AppendMenu(popup, text, ID_SWITCH, &branchNames[0]);
1469 else if (branchNames.size() > 1)
1471 CMenu switchMenu;
1472 switchMenu.CreatePopupMenu();
1473 for (size_t i = 0; i < branchNames.size(); ++i)
1474 AppendMenu(switchMenu, branchNames[i], ID_SWITCH + ((int)(i + 1) << 16), &branchNames[i]);
1475 AppendMenu(popup, CString(MAKEINTRESOURCE(IDS_SWITCH_BRANCH)), ID_SWITCH, NULL, &switchMenu);
1478 AppendMenu(popup, IDS_COPY_REF_NAMES, ID_COPYREFS);
1480 AppendMenu(popup, IDS_REVGRAPH_POPUP_COMPAREHEADS, ID_COMPAREHEADS);
1481 AppendMenu(popup, IDS_REVGRAPH_POPUP_UNIDIFFHEADS, ID_UNIDIFFHEADS);
1484 if (bothPresent)
1486 AppendMenu (popup, IDS_REVGRAPH_POPUP_COMPAREREVS, ID_COMPAREREVS);
1487 AppendMenu (popup, IDS_REVGRAPH_POPUP_UNIDIFFREVS, ID_UNIDIFFREVS);
1490 // AddGraphOps (popup, clickedentry);
1492 // if the context menu is invoked through the keyboard, we have to use
1493 // a calculated position on where to anchor the menu on
1494 if ((point.x == -1) && (point.y == -1))
1496 CRect rect = GetWindowRect();
1497 point = rect.CenterPoint();
1500 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RIGHTBUTTON, point.x, point.y, this, 0);
1501 switch (cmd & 0xFFFF)
1503 case ID_COMPAREREVS:
1504 if (m_SelectedEntry1 != NULL)
1505 CompareRevs(false);
1506 break;
1507 case ID_UNIDIFFREVS:
1508 if (m_SelectedEntry1 != NULL)
1509 UnifiedDiffRevs(false);
1510 break;
1511 case ID_UNIDIFFHEADS:
1512 if (m_SelectedEntry1 != NULL)
1513 UnifiedDiffRevs(true);
1514 break;
1515 case ID_SHOWLOG:
1516 DoShowLog();
1517 break;
1518 case ID_SWITCH:
1520 MENUITEMINFO mii;
1521 memset(&mii, 0, sizeof(mii));
1522 mii.cbSize = sizeof(mii);
1523 mii.fMask |= MIIM_DATA;
1524 GetMenuItemInfo(popup, cmd, FALSE, &mii);
1525 CString *rev = (CString *)mii.dwItemData;
1526 if (rev != NULL)
1528 DoSwitch(*rev);
1529 m_parent->UpdateFullHistory();
1531 break;
1533 case ID_COPYREFS:
1534 DoCopyRefs();
1535 break;
1536 case ID_BROWSEREPO:
1537 DoBrowseRepo();
1538 break;
1539 case ID_COMPAREHEADS:
1540 if (m_SelectedEntry1 != NULL)
1541 CompareRevs(true);
1542 break;
1545 #if 0
1546 case ID_COMPAREREVS:
1547 if (m_SelectedEntry1 != NULL)
1548 CompareRevs(false);
1549 break;
1550 case ID_UNIDIFFREVS:
1551 if (m_SelectedEntry1 != NULL)
1552 UnifiedDiffRevs(false);
1553 break;
1554 case ID_UNIDIFFHEADS:
1555 if (m_SelectedEntry1 != NULL)
1556 UnifiedDiffRevs(true);
1557 break;
1558 case ID_SHOWLOG:
1559 DoShowLog();
1560 break;
1561 case ID_CFM:
1562 DoCheckForModification();
1563 break;
1564 case ID_MERGETO:
1565 DoMergeTo();
1566 break;
1567 case ID_UPDATE:
1568 DoUpdate();
1569 break;
1570 case ID_SWITCHTOHEAD:
1571 DoSwitchToHead();
1572 break;
1573 case ID_EXPAND_ALL:
1574 ResetNodeFlags (CGraphNodeStates::COLLAPSED_ALL);
1575 break;
1576 case ID_JOIN_ALL:
1577 ResetNodeFlags (CGraphNodeStates::SPLIT_ALL);
1578 break;
1579 case ID_GRAPH_EXPANDCOLLAPSE_ABOVE:
1580 ToggleNodeFlag (clickedentry, CGraphNodeStates::COLLAPSED_ABOVE);
1581 break;
1582 case ID_GRAPH_EXPANDCOLLAPSE_RIGHT:
1583 ToggleNodeFlag (clickedentry, CGraphNodeStates::COLLAPSED_RIGHT);
1584 break;
1585 case ID_GRAPH_EXPANDCOLLAPSE_BELOW:
1586 ToggleNodeFlag (clickedentry, CGraphNodeStates::COLLAPSED_BELOW);
1587 break;
1588 case ID_GRAPH_SPLITJOIN_ABOVE:
1589 ToggleNodeFlag (clickedentry, CGraphNodeStates::SPLIT_ABOVE);
1590 break;
1591 case ID_GRAPH_SPLITJOIN_RIGHT:
1592 ToggleNodeFlag (clickedentry, CGraphNodeStates::SPLIT_RIGHT);
1593 break;
1594 case ID_GRAPH_SPLITJOIN_BELOW:
1595 ToggleNodeFlag (clickedentry, CGraphNodeStates::SPLIT_BELOW);
1596 break;
1597 #endif
1602 void CRevisionGraphWnd::OnMouseMove(UINT nFlags, CPoint point)
1605 if (IsUpdateJobRunning())
1607 return __super::OnMouseMove(nFlags, point);
1609 if (!m_bIsCanvasMove)
1611 if (m_bShowOverview && (m_OverviewRect.PtInRect(point))&&(nFlags & MK_LBUTTON))
1613 // scrolling
1614 CRect viewRect = GetViewRect();
1615 int x = (int)((point.x-m_OverviewRect.left - (m_OverviewPosRect.Width()/2)) / m_previewZoom * m_fZoomFactor);
1616 int y = (int)((point.y - m_OverviewRect.top - (m_OverviewPosRect.Height()/2)) / m_previewZoom * m_fZoomFactor);
1617 x = max(0, x);
1618 y = max(0, y);
1619 SetScrollbars(y, x);
1620 Invalidate(FALSE);
1621 return __super::OnMouseMove(nFlags, point);
1623 else
1625 // update screen if we hover over a different
1626 // node than during the last redraw
1628 CPoint clientPoint = point;
1629 GetCursorPos (&clientPoint);
1630 ScreenToClient (&clientPoint);
1632 #if 0
1633 const CRevisionGraphState::SVisibleGlyph* hitGlyph
1634 = GetHitGlyph (clientPoint);
1635 const CFullGraphNode* glyphNode
1636 = hitGlyph ? hitGlyph->node->GetBase() : NULL;
1638 const CFullGraphNode* hoverNode = NULL;
1639 if (m_hoverIndex != NO_INDEX)
1641 CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());
1642 if (m_hoverIndex < nodeList->GetCount())
1643 hoverNode = nodeList->GetNode (m_hoverIndex).node->GetBase();
1646 //bool onHoverNodeGlyph = (hoverNode != NULL) && (glyphNode == hoverNode);
1647 if ( !m_hoverIndex
1648 && ( (m_hoverIndex != GetHitNode (clientPoint))))
1650 m_showHoverGlyphs = false;
1652 KillTimer (GLYPH_HOVER_EVENT);
1653 SetTimer (GLYPH_HOVER_EVENT, GLYPH_HOVER_DELAY, NULL);
1655 Invalidate(FALSE);
1657 #endif
1658 return __super::OnMouseMove(nFlags, point);
1661 SetCapture();
1663 int pos_h = GetScrollPos(SB_HORZ);
1664 pos_h -= point.x - m_ptMoveCanvas.x;
1665 SetScrollPos(SB_HORZ, pos_h);
1667 int pos_v = GetScrollPos(SB_VERT);
1668 pos_v -= point.y - m_ptMoveCanvas.y;
1669 SetScrollPos(SB_VERT, pos_v);
1671 m_ptMoveCanvas = point;
1673 this->Invalidate();
1675 __super::OnMouseMove(nFlags, point);
1678 BOOL CRevisionGraphWnd::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
1680 CRect viewRect = GetViewRect();
1682 LPTSTR cursorID = IDC_ARROW;
1683 HINSTANCE resourceHandle = NULL;
1685 if ((nHitTest == HTCLIENT)&&(pWnd == this)&&(viewRect.Width())&&(viewRect.Height())&&(message))
1687 POINT pt;
1688 if (GetCursorPos(&pt))
1690 ScreenToClient(&pt);
1691 if (m_OverviewPosRect.PtInRect(pt))
1693 resourceHandle = AfxGetResourceHandle();
1694 cursorID = GetKeyState(VK_LBUTTON) & 0x8000
1695 ? MAKEINTRESOURCE(IDC_PANCURDOWN)
1696 : MAKEINTRESOURCE(IDC_PANCUR);
1698 if (m_bIsCanvasMove)
1699 cursorID = IDC_HAND;
1703 HCURSOR hCur = LoadCursor(resourceHandle, MAKEINTRESOURCE(cursorID));
1704 if (GetCursor() != hCur)
1705 SetCursor (hCur);
1707 return TRUE;
1710 void CRevisionGraphWnd::OnTimer (UINT_PTR nIDEvent)
1712 if (nIDEvent == GLYPH_HOVER_EVENT)
1714 KillTimer (GLYPH_HOVER_EVENT);
1716 m_showHoverGlyphs = true;
1717 Invalidate (FALSE);
1719 else
1721 __super::OnTimer (nIDEvent);
1725 LRESULT CRevisionGraphWnd::OnWorkerThreadDone(WPARAM, LPARAM)
1727 // handle potential race condition between PostMessage and leaving job:
1728 // the background job may not have exited, yet
1730 if (updateJob.get())
1731 updateJob->GetResult();
1733 InitView();
1734 BuildPreview();
1736 SCROLLINFO sinfo = {0};
1737 sinfo.cbSize = sizeof(SCROLLINFO);
1738 GetScrollInfo(SB_HORZ, &sinfo);
1739 sinfo.nPos = sinfo.nMax;
1740 SetScrollInfo(SB_HORZ, &sinfo);
1742 Invalidate(FALSE);
1744 if (m_parent && !m_parent->GetOutputFile().IsEmpty())
1746 // save the graph to the output file and exit
1747 SaveGraphAs(m_parent->GetOutputFile());
1748 PostQuitMessage(0);
1750 return 0;
1753 void CRevisionGraphWnd::SetDlgTitle (bool /*offline*/)
1755 #if 0
1756 if (m_sTitle.IsEmpty())
1757 GetParent()->GetWindowText(m_sTitle);
1759 CString newTitle;
1760 if (offline)
1761 newTitle.Format (IDS_REVGRAPH_DLGTITLEOFFLINE, (LPCTSTR)m_sTitle);
1762 else
1763 newTitle = m_sTitle;
1765 CAppUtils::SetWindowTitle(GetParent()->GetSafeHwnd(), m_sPath, newTitle);
1766 #endif