Fixed issue #3116: Revision graph: add ability to delete branches
[TortoiseGit.git] / src / TortoiseProc / RevisionGraph / RevisionGraphWnd.cpp
blob0a3265ed547788ac530713b35ae2922221a1671b
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2012, 2015 - TortoiseSVN
4 // Copyright (C) 2012-2017 - 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 "FormatMessageWrapper.h"
38 #include "GitRevLoglist.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_COMPAREWT,
68 ID_UNIDIFFREVS,
69 ID_UNIDIFFHEADS,
70 ID_MERGETO = 0x300,
71 ID_UPDATE,
72 ID_SWITCHTOHEAD,
73 ID_SWITCH,
74 ID_DELETE,
75 ID_COPYREFS = 0x400,
76 ID_EXPAND_ALL = 0x500,
77 ID_JOIN_ALL,
78 ID_GRAPH_EXPANDCOLLAPSE_ABOVE = 0x600,
79 ID_GRAPH_EXPANDCOLLAPSE_RIGHT,
80 ID_GRAPH_EXPANDCOLLAPSE_BELOW,
81 ID_GRAPH_SPLITJOIN_ABOVE,
82 ID_GRAPH_SPLITJOIN_RIGHT,
83 ID_GRAPH_SPLITJOIN_BELOW,
86 CRevisionGraphWnd::CRevisionGraphWnd()
87 : CWnd()
88 , m_SelectedEntry1(nullptr)
89 , m_SelectedEntry2(nullptr)
90 , m_HeadNode(nullptr)
91 , m_pDlgTip(nullptr)
92 , m_nFontSize(12)
93 , m_bTweakTrunkColors(true)
94 , m_bTweakTagsColors(true)
95 , m_fZoomFactor(DEFAULT_ZOOM)
96 , m_ptRubberEnd(0,0)
97 , m_ptMoveCanvas(0,0)
98 , m_bShowOverview(false)
99 , m_parent(nullptr)
100 , m_hoverIndex(nullptr)
101 , m_hoverGlyphs (0)
102 , m_tooltipIndex(nullptr)
103 , m_showHoverGlyphs (false)
104 , m_bIsCanvasMove(false)
105 , m_previewWidth(0)
106 , m_previewHeight(0)
107 , m_previewZoom(1)
108 , m_ullTicks(0)
109 , m_logEntries(&m_LogCache)
110 , m_bCurrentBranch(false)
111 , m_bLocalBranches(FALSE)
113 memset(&m_lfBaseFont, 0, sizeof(LOGFONT));
114 std::fill_n(m_apFonts, MAXFONTS, nullptr);
116 WNDCLASS wndcls;
117 HINSTANCE hInst = AfxGetInstanceHandle();
118 #define REVGRAPH_CLASSNAME L"Revgraph_windowclass"
119 if (!(::GetClassInfo(hInst, REVGRAPH_CLASSNAME, &wndcls)))
121 // otherwise we need to register a new class
122 wndcls.style = CS_DBLCLKS | CS_OWNDC;
123 wndcls.lpfnWndProc = ::DefWindowProc;
124 wndcls.cbClsExtra = wndcls.cbWndExtra = 0;
125 wndcls.hInstance = hInst;
126 wndcls.hIcon = nullptr;
127 wndcls.hCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
128 wndcls.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
129 wndcls.lpszMenuName = nullptr;
130 wndcls.lpszClassName = REVGRAPH_CLASSNAME;
132 RegisterClass(&wndcls);
135 m_bTweakTrunkColors = CRegDWORD(L"Software\\TortoiseGit\\RevisionGraph\\TweakTrunkColors", TRUE) != FALSE;
136 m_bTweakTagsColors = CRegDWORD(L"Software\\TortoiseGit\\RevisionGraph\\TweakTagsColors", TRUE) != FALSE;
137 m_szTip[0] = '\0';
138 m_wszTip[0] = L'\0';
140 m_GraphAttr.init(this->m_Graph, ogdf::GraphAttributes::nodeGraphics | ogdf::GraphAttributes::edgeGraphics |
141 ogdf:: GraphAttributes::nodeLabel | ogdf::GraphAttributes::nodeColor |
142 ogdf::GraphAttributes::edgeColor | ogdf::GraphAttributes::edgeStyle |
143 ogdf::GraphAttributes::nodeStyle | ogdf::GraphAttributes::nodeTemplate);
145 m_SugiyamLayout.setRanking(::new ogdf::OptimalRanking());
146 m_SugiyamLayout.setCrossMin(::new ogdf::MedianHeuristic());
148 double pi = 3.1415926;
149 m_ArrowCos = cos(pi/8);
150 m_ArrowSin = sin(pi/8);
151 this->m_ArrowSize = 8;
152 #if 0
153 ogdf::node one = this->m_Graph.newNode();
154 ogdf::node two = this->m_Graph.newNode();
155 ogdf::node three = this->m_Graph.newNode();
156 ogdf::node four = this->m_Graph.newNode();
159 m_GraphAttr.width(one)=100;
160 m_GraphAttr.height(one)=200;
161 m_GraphAttr.width(two)=100;
162 m_GraphAttr.height(two)=100;
163 m_GraphAttr.width(three)=100;
164 m_GraphAttr.height(three)=20;
165 m_GraphAttr.width(four)=100;
166 m_GraphAttr.height(four)=20;
168 m_GraphAttr.labelNode(one)="One";
169 m_GraphAttr.labelNode(two)="Two";
170 m_GraphAttr.labelNode(three)="three";
172 this->m_Graph.newEdge(one, two);
173 this->m_Graph.newEdge(one, three);
174 this->m_Graph.newEdge(two, four);
175 this->m_Graph.newEdge(three, four);
177 #endif
178 FastHierarchyLayout *pOHL = ::new FastHierarchyLayout;
179 //It will auto delte when m_SugiyamLayout destory
181 pOHL->layerDistance(30.0);
182 pOHL->nodeDistance(25.0);
184 m_SugiyamLayout.setLayout(pOHL);
186 #if 0
187 //this->m_OHL.layerDistance(30.0);
188 //this->m_OHL.nodeDistance(25.0);
189 //this->m_OHL.weightBalancing(0.8);
190 m_SugiyamLayout.setLayout(&m_OHL);
191 m_SugiyamLayout.call(m_GraphAttr);
192 #endif
193 #if 0
194 PlanarizationLayout pl;
196 FastPlanarSubgraph *ps = ::new FastPlanarSubgraph;
197 ps->runs(100);
198 VariableEmbeddingInserter *ves = ::new VariableEmbeddingInserter;
199 ves->removeReinsert(EdgeInsertionModule::rrAll);
200 pl.setSubgraph(ps);
201 pl.setInserter(ves);
203 EmbedderMinDepthMaxFaceLayers *emb = ::new EmbedderMinDepthMaxFaceLayers;
204 pl.setEmbedder(emb);
206 OrthoLayout *ol =::new OrthoLayout;
207 ol->separation(20.0);
208 ol->cOverhang(0.4);
209 ol->setOptions(2+4);
210 ol->preferedDir(OrthoDir::odEast);
211 pl.setPlanarLayouter(ol);
213 pl.call(m_GraphAttr);
215 node v;
216 forall_nodes(v,m_Graph) {
217 TRACE(L"node x %f y %f %f %f\n",/* m_GraphAttr.idNode(v), */
218 m_GraphAttr.x(v),
219 m_GraphAttr.y(v),
220 m_GraphAttr.width(v),
221 m_GraphAttr.height(v)
225 edge e;
226 forall_edges(e, m_Graph)
228 // get connection and point position
229 const DPolyline &dpl = this->m_GraphAttr.bends(e);
231 ListConstIterator<DPoint> it;
232 for(it = dpl.begin(); it.valid(); ++it)
234 TRACE(L"edge %f %f\n", (*it).m_x, (*it).m_y);
237 m_GraphAttr.writeGML("test.gml");
238 #endif
241 CRevisionGraphWnd::~CRevisionGraphWnd()
243 for (int i = 0; i < MAXFONTS; ++i)
245 if (m_apFonts[i])
247 m_apFonts[i]->DeleteObject();
248 delete m_apFonts[i];
250 m_apFonts[i] = nullptr;
252 delete m_pDlgTip;
253 m_Graph.clear();
256 void CRevisionGraphWnd::DoDataExchange(CDataExchange* pDX)
258 CWnd::DoDataExchange(pDX);
262 BEGIN_MESSAGE_MAP(CRevisionGraphWnd, CWnd)
263 ON_WM_PAINT()
264 ON_WM_ERASEBKGND()
265 ON_WM_HSCROLL()
266 ON_WM_VSCROLL()
267 ON_WM_SIZE()
268 ON_WM_LBUTTONDOWN()
269 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipNotify)
270 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipNotify)
271 ON_WM_MOUSEWHEEL()
272 ON_WM_MOUSEHWHEEL()
273 ON_WM_CONTEXTMENU()
274 ON_WM_MOUSEMOVE()
275 ON_WM_LBUTTONUP()
276 ON_WM_SETCURSOR()
277 ON_WM_TIMER()
278 ON_MESSAGE(WM_WORKERTHREADDONE,OnWorkerThreadDone)
279 ON_WM_CAPTURECHANGED()
280 END_MESSAGE_MAP()
282 void CRevisionGraphWnd::Init(CWnd * pParent, LPRECT rect)
284 WNDCLASS wndcls;
285 HINSTANCE hInst = AfxGetInstanceHandle();
286 #define REVGRAPH_CLASSNAME L"Revgraph_windowclass"
287 if (!(::GetClassInfo(hInst, REVGRAPH_CLASSNAME, &wndcls)))
289 // otherwise we need to register a new class
290 wndcls.style = CS_DBLCLKS | CS_OWNDC;
291 wndcls.lpfnWndProc = ::DefWindowProc;
292 wndcls.cbClsExtra = wndcls.cbWndExtra = 0;
293 wndcls.hInstance = hInst;
294 wndcls.hIcon = nullptr;
295 wndcls.hCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
296 wndcls.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
297 wndcls.lpszMenuName = nullptr;
298 wndcls.lpszClassName = REVGRAPH_CLASSNAME;
300 RegisterClass(&wndcls);
303 if (!IsWindow(m_hWnd))
304 CreateEx(WS_EX_CLIENTEDGE, REVGRAPH_CLASSNAME, L"RevGraph", WS_CHILD|WS_VISIBLE|WS_TABSTOP, *rect, pParent, 0);
305 m_pDlgTip = new CToolTipCtrl;
306 if(!m_pDlgTip->Create(this))
308 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Unable to add tooltip!\n");
310 EnableToolTips();
312 memset(&m_lfBaseFont, 0, sizeof(m_lfBaseFont));
313 m_lfBaseFont.lfHeight = 0;
314 m_lfBaseFont.lfWeight = FW_NORMAL;
315 m_lfBaseFont.lfItalic = FALSE;
316 m_lfBaseFont.lfCharSet = DEFAULT_CHARSET;
317 m_lfBaseFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
318 m_lfBaseFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
319 m_lfBaseFont.lfQuality = DEFAULT_QUALITY;
320 m_lfBaseFont.lfPitchAndFamily = DEFAULT_PITCH;
322 m_ullTicks = GetTickCount64();
324 m_parent = dynamic_cast<CRevisionGraphDlg*>(pParent);
327 CPoint CRevisionGraphWnd::GetLogCoordinates (CPoint point) const
329 // translate point into logical coordinates
331 int nVScrollPos = GetScrollPos(SB_VERT);
332 int nHScrollPos = GetScrollPos(SB_HORZ);
334 return CPoint ( (int)((point.x + nHScrollPos) / m_fZoomFactor)
335 , (int)((point.y + nVScrollPos) / m_fZoomFactor));
338 node CRevisionGraphWnd::GetHitNode (CPoint point, CSize /*border*/) const
340 #if 0
341 // any nodes at all?
343 CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());
344 if (!nodeList)
345 return index_t(NO_INDEX);
347 // search the nodes for one at that grid position
349 return nodeList->GetAt (GetLogCoordinates (point), border);
350 #endif
352 node v;
353 forall_nodes(v,m_Graph)
355 RectF noderect (GetNodeRect (v, CPoint(GetScrollPos(SB_HORZ), GetScrollPos(SB_VERT))));
356 if(point.x>noderect.X && point.x <(noderect.X+noderect.Width) &&
357 point.y>noderect.Y && point.y <(noderect.Y+noderect.Height))
359 return v;
362 return nullptr;
365 DWORD CRevisionGraphWnd::GetHoverGlyphs (CPoint /*point*/) const
367 // if there is no layout, there will be no nodes,
368 // hence, no glyphs
369 DWORD result = 0;
370 #if 0
371 CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());
372 if (!nodeList)
373 return 0;
375 // get node at point or node that is close enough
376 // so that point may hit a glyph area
378 index_t nodeIndex = GetHitNode(point);
379 if (nodeIndex == NO_INDEX)
380 nodeIndex = GetHitNode(point, CSize (GLYPH_SIZE, GLYPH_SIZE / 2));
382 if (nodeIndex >= nodeList->GetCount())
383 return 0;
385 ILayoutNodeList::SNode node = nodeList->GetNode (nodeIndex);
386 const CVisibleGraphNode* base = node.node;
388 // what glyphs should be shown depending on position of point
389 // relative to the node rect?
391 CPoint logCoordinates = GetLogCoordinates (point);
392 CRect r = node.rect;
393 CPoint center = r.CenterPoint();
395 CRect rightGlyphArea ( r.right - GLYPH_SIZE, center.y - GLYPH_SIZE / 2
396 , r.right + GLYPH_SIZE, center.y + GLYPH_SIZE / 2);
397 CRect topGlyphArea ( center.x - GLYPH_SIZE, r.top - GLYPH_SIZE / 2
398 , center.x + GLYPH_SIZE, r.top + GLYPH_SIZE / 2);
399 CRect bottomGlyphArea ( center.x - GLYPH_SIZE, r.bottom - GLYPH_SIZE / 2
400 , center.x + GLYPH_SIZE, r.bottom + GLYPH_SIZE / 2);
402 bool upsideDown
403 = m_state.GetOptions()->GetOption<CUpsideDownLayout>()->IsActive();
405 if (upsideDown)
407 std::swap (topGlyphArea.top, bottomGlyphArea.top);
408 std::swap (topGlyphArea.bottom, bottomGlyphArea.bottom);
412 if (rightGlyphArea.PtInRect (logCoordinates))
413 result = base->GetFirstCopyTarget()
414 ? CGraphNodeStates::COLLAPSED_RIGHT | CGraphNodeStates::SPLIT_RIGHT
415 : 0;
417 if (topGlyphArea.PtInRect (logCoordinates))
418 result = base->GetSource()
419 ? CGraphNodeStates::COLLAPSED_ABOVE | CGraphNodeStates::SPLIT_ABOVE
420 : 0;
422 if (bottomGlyphArea.PtInRect (logCoordinates))
423 result = base->GetNext()
424 ? CGraphNodeStates::COLLAPSED_BELOW | CGraphNodeStates::SPLIT_BELOW
425 : 0;
427 // if some nodes have already been split, don't allow collapsing etc.
429 CSyncPointer<const CGraphNodeStates> nodeStates (m_state.GetNodeStates());
430 if (result & nodeStates->GetFlags (base))
431 result = 0;
432 #endif
433 return result;
435 #if 0
436 const CRevisionGraphState::SVisibleGlyph* CRevisionGraphWnd::GetHitGlyph (CPoint point) const
438 float glyphSize = GLYPH_SIZE * m_fZoomFactor;
440 CSyncPointer<const CRevisionGraphState::TVisibleGlyphs>
441 visibleGlyphs (m_state.GetVisibleGlyphs());
443 for (size_t i = 0, count = visibleGlyphs->size(); i < count; ++i)
445 const CRevisionGraphState::SVisibleGlyph* entry = &(*visibleGlyphs)[i];
447 float xRel = point.x - entry->leftTop.X;
448 float yRel = point.y - entry->leftTop.Y;
450 if ( (xRel >= 0) && (xRel < glyphSize)
451 && (yRel >= 0) && (yRel < glyphSize))
453 return entry;
457 return nullptr;
459 #endif
460 void CRevisionGraphWnd::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
462 SCROLLINFO sinfo = {0};
463 sinfo.cbSize = sizeof(SCROLLINFO);
464 GetScrollInfo(SB_HORZ, &sinfo);
466 // Determine the new position of scroll box.
467 switch (nSBCode)
469 case SB_LEFT: // Scroll to far left.
470 sinfo.nPos = sinfo.nMin;
471 break;
472 case SB_RIGHT: // Scroll to far right.
473 sinfo.nPos = sinfo.nMax;
474 break;
475 case SB_ENDSCROLL: // End scroll.
476 break;
477 case SB_LINELEFT: // Scroll left.
478 if (sinfo.nPos > sinfo.nMin)
479 sinfo.nPos--;
480 break;
481 case SB_LINERIGHT: // Scroll right.
482 if (sinfo.nPos < sinfo.nMax)
483 ++sinfo.nPos;
484 break;
485 case SB_PAGELEFT: // Scroll one page left.
487 if (sinfo.nPos > sinfo.nMin)
488 sinfo.nPos = max(sinfo.nMin, sinfo.nPos - (int) sinfo.nPage);
490 break;
491 case SB_PAGERIGHT: // Scroll one page right.
493 if (sinfo.nPos < sinfo.nMax)
494 sinfo.nPos = min(sinfo.nMax, sinfo.nPos + (int) sinfo.nPage);
496 break;
497 case SB_THUMBPOSITION: // Scroll to absolute position. nPos is the position
498 sinfo.nPos = sinfo.nTrackPos; // of the scroll box at the end of the drag operation.
499 break;
500 case SB_THUMBTRACK: // Drag scroll box to specified position. nPos is the
501 sinfo.nPos = sinfo.nTrackPos; // position that the scroll box has been dragged to.
502 break;
504 SetScrollInfo(SB_HORZ, &sinfo);
505 Invalidate (FALSE);
506 __super::OnHScroll(nSBCode, nPos, pScrollBar);
509 void CRevisionGraphWnd::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
511 SCROLLINFO sinfo = {0};
512 sinfo.cbSize = sizeof(SCROLLINFO);
513 GetScrollInfo(SB_VERT, &sinfo);
515 // Determine the new position of scroll box.
516 switch (nSBCode)
518 case SB_LEFT: // Scroll to far left.
519 sinfo.nPos = sinfo.nMin;
520 break;
521 case SB_RIGHT: // Scroll to far right.
522 sinfo.nPos = sinfo.nMax;
523 break;
524 case SB_ENDSCROLL: // End scroll.
525 break;
526 case SB_LINELEFT: // Scroll left.
527 if (sinfo.nPos > sinfo.nMin)
528 sinfo.nPos--;
529 break;
530 case SB_LINERIGHT: // Scroll right.
531 if (sinfo.nPos < sinfo.nMax)
532 ++sinfo.nPos;
533 break;
534 case SB_PAGELEFT: // Scroll one page left.
536 if (sinfo.nPos > sinfo.nMin)
537 sinfo.nPos = max(sinfo.nMin, sinfo.nPos - (int) sinfo.nPage);
539 break;
540 case SB_PAGERIGHT: // Scroll one page right.
542 if (sinfo.nPos < sinfo.nMax)
543 sinfo.nPos = min(sinfo.nMax, sinfo.nPos + (int) sinfo.nPage);
545 break;
546 case SB_THUMBPOSITION: // Scroll to absolute position. nPos is the position
547 sinfo.nPos = sinfo.nTrackPos; // of the scroll box at the end of the drag operation.
548 break;
549 case SB_THUMBTRACK: // Drag scroll box to specified position. nPos is the
550 sinfo.nPos = sinfo.nTrackPos; // position that the scroll box has been dragged to.
551 break;
553 SetScrollInfo(SB_VERT, &sinfo);
554 Invalidate(FALSE);
555 __super::OnVScroll(nSBCode, nPos, pScrollBar);
558 void CRevisionGraphWnd::OnSize(UINT nType, int cx, int cy)
560 __super::OnSize(nType, cx, cy);
561 SetScrollbars(GetScrollPos(SB_VERT), GetScrollPos(SB_HORZ));
562 Invalidate(FALSE);
565 void CRevisionGraphWnd::OnLButtonDown(UINT nFlags, CPoint point)
567 if (IsUpdateJobRunning())
568 return __super::OnLButtonDown(nFlags, point);
570 // CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());
572 SetFocus();
573 bool bHit = false;
574 bool bControl = !!(GetKeyState(VK_CONTROL)&0x8000);
575 bool bOverview = m_bShowOverview && m_OverviewRect.PtInRect(point);
576 if (! bOverview)
578 #if 0
579 const CRevisionGraphState::SVisibleGlyph* hitGlyph
580 = GetHitGlyph (point);
582 if (hitGlyph)
584 ToggleNodeFlag (hitGlyph->node, hitGlyph->state);
585 return __super::OnLButtonDown(nFlags, point);
587 #endif
588 node nodeIndex = GetHitNode (point);
589 if (nodeIndex)
591 if (bControl)
593 if (m_SelectedEntry1 == nodeIndex)
595 if (m_SelectedEntry2)
597 m_SelectedEntry1 = m_SelectedEntry2;
598 m_SelectedEntry2 = nullptr;
600 else
601 m_SelectedEntry1 = nullptr;
603 else if (m_SelectedEntry2 == nodeIndex)
604 m_SelectedEntry2 = nullptr;
605 else if (m_SelectedEntry1)
606 m_SelectedEntry2 = nodeIndex;
607 else
608 m_SelectedEntry1 = nodeIndex;
610 else
612 if (m_SelectedEntry1 == nodeIndex)
613 m_SelectedEntry1 = nullptr;
614 else
615 m_SelectedEntry1 = nodeIndex;
616 m_SelectedEntry2 = nullptr;
618 bHit = true;
619 Invalidate(FALSE);
623 if ((!bHit)&&(!bControl)&&(!bOverview))
625 m_SelectedEntry1 = nullptr;
626 m_SelectedEntry2 = nullptr;
627 m_bIsCanvasMove = true;
628 Invalidate(FALSE);
629 if (m_bShowOverview && m_OverviewRect.PtInRect(point))
630 m_bIsCanvasMove = false;
632 m_ptMoveCanvas = point;
634 UINT uEnable = MF_BYCOMMAND;
635 if (m_SelectedEntry1 && m_SelectedEntry2)
636 uEnable |= MF_ENABLED;
637 else
638 uEnable |= MF_GRAYED;
640 auto hMenu = GetParent()->GetMenu()->m_hMenu;
641 EnableMenuItem(hMenu, ID_VIEW_COMPAREREVISIONS, uEnable);
642 EnableMenuItem(hMenu, ID_VIEW_UNIFIEDDIFF, uEnable);
644 uEnable = MF_BYCOMMAND;
645 if (m_SelectedEntry1 && !m_SelectedEntry2)
646 uEnable |= MF_ENABLED;
647 else
648 uEnable |= MF_GRAYED;
650 EnableMenuItem(hMenu, ID_VIEW_UNIFIEDDIFFOFHEADREVISIONS, uEnable);
651 EnableMenuItem(hMenu, ID_VIEW_COMPAREHEADREVISIONS, uEnable);
653 __super::OnLButtonDown(nFlags, point);
656 void CRevisionGraphWnd::OnCaptureChanged(CWnd *pWnd)
658 __super::OnCaptureChanged(pWnd);
661 void CRevisionGraphWnd::OnLButtonUp(UINT nFlags, CPoint point)
663 if (!m_bIsCanvasMove)
664 return; // we don't have a rubberband, so no zooming necessary
666 m_bIsCanvasMove = false;
667 ReleaseCapture();
668 if (IsUpdateJobRunning())
669 return __super::OnLButtonUp(nFlags, point);
671 // zooming is finished
672 m_ptRubberEnd = CPoint(0,0);
673 CRect rect = GetClientRect();
674 int x = abs(m_ptMoveCanvas.x - point.x);
675 int y = abs(m_ptMoveCanvas.y - point.y);
677 if ((x < 20)&&(y < 20))
679 // too small zoom rectangle
680 // assume zooming by accident
681 Invalidate(FALSE);
682 __super::OnLButtonUp(nFlags, point);
683 return;
686 float xfact = float(rect.Width())/float(x);
687 float yfact = float(rect.Height())/float(y);
688 float fact = max(yfact, xfact);
690 // find out where to scroll to
691 x = min(m_ptMoveCanvas.x, point.x) + GetScrollPos(SB_HORZ);
692 y = min(m_ptMoveCanvas.y, point.y) + GetScrollPos(SB_VERT);
694 float fZoomfactor = m_fZoomFactor*fact;
695 if (fZoomfactor > 10 * MAX_ZOOM)
697 // with such a big zoomfactor, the user
698 // most likely zoomed by accident
699 Invalidate(FALSE);
700 __super::OnLButtonUp(nFlags, point);
701 return;
703 if (fZoomfactor > MAX_ZOOM)
705 fZoomfactor = MAX_ZOOM;
706 fact = fZoomfactor/m_fZoomFactor;
709 auto pDlg = static_cast<CRevisionGraphDlg*>(GetParent());
710 if (pDlg)
712 m_fZoomFactor = fZoomfactor;
713 pDlg->DoZoom (m_fZoomFactor);
714 SetScrollbars(int(float(y)*fact), int(float(x)*fact));
716 __super::OnLButtonUp(nFlags, point);
719 bool CRevisionGraphWnd::CancelMouseZoom()
721 bool bRet = m_bIsCanvasMove;
722 ReleaseCapture();
723 if (m_bIsCanvasMove)
724 Invalidate(FALSE);
725 m_bIsCanvasMove = false;
726 m_ptRubberEnd = CPoint(0,0);
727 return bRet;
730 INT_PTR CRevisionGraphWnd::OnToolHitTest(CPoint point, TOOLINFO* pTI) const
732 if (IsUpdateJobRunning())
733 return -1;
735 node nodeIndex = GetHitNode (point);
736 if (m_tooltipIndex != nodeIndex)
738 // force tooltip to be updated
740 m_tooltipIndex = nodeIndex;
741 return -1;
744 if (!nodeIndex)
745 return -1;
747 // if ((GetHoverGlyphs (point) != 0) || (GetHitGlyph (point) != nullptr))
748 // return -1;
750 pTI->hwnd = this->m_hWnd;
751 CWnd::GetClientRect(&pTI->rect);
752 pTI->uFlags |= TTF_ALWAYSTIP | TTF_IDISHWND;
753 pTI->uId = (UINT_PTR)m_hWnd;
754 pTI->lpszText = LPSTR_TEXTCALLBACK;
756 return 1;
759 BOOL CRevisionGraphWnd::OnToolTipNotify(UINT /*id*/, NMHDR *pNMHDR, LRESULT *pResult)
761 if (pNMHDR->idFrom != (UINT_PTR)m_hWnd)
762 return FALSE;
764 POINT point;
765 DWORD ptW = GetMessagePos();
766 point.x = GET_X_LPARAM(ptW);
767 point.y = GET_Y_LPARAM(ptW);
768 ScreenToClient(&point);
770 CString strTipText = TooltipText (GetHitNode (point));
772 *pResult = 0;
773 if (strTipText.IsEmpty())
774 return TRUE;
776 CSize tooltipSize = UsableTooltipRect();
777 strTipText = DisplayableText (strTipText, tooltipSize);
779 // need to handle both ANSI and UNICODE versions of the message
780 if (pNMHDR->code == TTN_NEEDTEXTA)
782 TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;
783 ::SendMessage(pNMHDR->hwndFrom, TTM_SETMAXTIPWIDTH, 0, tooltipSize.cx);
784 pTTTA->lpszText = m_szTip;
785 WideCharToMultiByte(CP_ACP, 0, strTipText, -1, m_szTip, strTipText.GetLength()+1, 0, 0);
787 else
789 TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;
790 ::SendMessage(pNMHDR->hwndFrom, TTM_SETMAXTIPWIDTH, 0, tooltipSize.cx);
791 lstrcpyn(m_wszTip, strTipText, strTipText.GetLength()+1);
792 pTTTW->lpszText = m_wszTip;
795 // show the tooltip for 32 seconds. A higher value than 32767 won't work
796 // even though it's nowhere documented!
797 ::SendMessage(pNMHDR->hwndFrom, TTM_SETDELAYTIME, TTDT_AUTOPOP, 32767);
798 return TRUE; // message was handled
801 CSize CRevisionGraphWnd::UsableTooltipRect()
803 // get screen size
805 int screenWidth = GetSystemMetrics(SM_CXSCREEN);
806 int screenHeight = GetSystemMetrics(SM_CYSCREEN);
808 // get current mouse position
810 CPoint cursorPos;
811 if (GetCursorPos (&cursorPos) == FALSE)
813 // we could not determine the mouse position
814 // use screen / 2 minus some safety margin
816 return CSize (screenWidth / 2 - 20, screenHeight / 2 - 20);
819 // tool tip will display in the biggest sector beside the cursor
820 // deduct some safety margin (for the mouse cursor itself
822 CSize biggestSector
823 ( max (screenWidth - cursorPos.x - 40, cursorPos.x - 24)
824 , max (screenHeight - cursorPos.y - 40, cursorPos.y - 24));
826 return biggestSector;
829 CString CRevisionGraphWnd::DisplayableText ( const CString& wholeText
830 , const CSize& tooltipSize)
832 CDC* dc = GetDC();
833 if (!dc)
835 // no access to the device context -> truncate hard at 1000 chars
837 return wholeText.GetLength() >= MAX_TT_LENGTH_DEFAULT
838 ? wholeText.Left(MAX_TT_LENGTH_DEFAULT - 4) + L" ..."
839 : wholeText;
842 // select the tooltip font
844 NONCLIENTMETRICS metrics;
845 metrics.cbSize = sizeof (metrics);
846 SystemParametersInfo (SPI_GETNONCLIENTMETRICS, metrics.cbSize, &metrics, 0);
848 CFont font;
849 font.CreateFontIndirect(&metrics.lfStatusFont);
850 CFont* pOldFont = dc->SelectObject (&font);
852 // split into lines and fill the tooltip rect
854 CString result;
856 int remainingHeight = tooltipSize.cy;
857 int pos = 0;
858 while (pos < wholeText.GetLength())
860 // extract a whole line
862 int nextPos = wholeText.Find ('\n', pos);
863 if (nextPos < 0)
864 nextPos = wholeText.GetLength();
866 CString line = wholeText.Mid (pos, nextPos-pos+1);
868 // find a way to make it fit
870 CSize size = dc->GetTextExtent (line);
871 while (size.cx > tooltipSize.cx)
873 line.Delete (line.GetLength()-1);
874 int nextPos2 = line.ReverseFind (' ');
875 if (nextPos2 < 0)
876 break;
878 line.Delete (nextPos2+1, line.GetLength() - pos-1);
879 size = dc->GetTextExtent (line);
882 // enough room for the new line?
884 remainingHeight -= size.cy;
885 if (remainingHeight <= size.cy)
887 result += L"...";
888 break;
891 // add the line
893 result += line;
894 pos += line.GetLength();
897 // relase temp. resources
899 dc->SelectObject (pOldFont);
900 ReleaseDC(dc);
902 // ready
904 return result;
907 CString CRevisionGraphWnd::TooltipText(node index)
909 if(index)
911 CString str;
912 CGitHash hash = m_logEntries[index->index()];
913 GitRevLoglist* rev = this->m_LogCache.GetCacheData(hash);
914 str += rev->m_CommitHash.ToString();
915 str += L'\n';
916 str += rev->GetAuthorName() + L' ' + rev->GetAuthorEmail();
917 str += L' ';
918 str += rev->GetAuthorDate().Format(L"%Y-%m-%d %H:%M");
919 str += L"\n\n" + rev->GetSubject();
920 str += L'\n';
921 str += rev->GetBody();
922 return str;
923 }else
924 return CString();
927 void CRevisionGraphWnd::SaveGraphAs(CString sSavePath)
929 CString extension = CPathUtils::GetFileExtFromPath(sSavePath);
930 if (extension.CompareNoCase(L".wmf") == 0)
932 // save the graph as an enhanced metafile
933 CMetaFileDC wmfDC;
934 wmfDC.CreateEnhanced(nullptr, sSavePath, nullptr, L"TortoiseGit\0Revision Graph\0\0");
935 float fZoom = m_fZoomFactor;
936 m_fZoomFactor = DEFAULT_ZOOM;
937 DoZoom(m_fZoomFactor);
938 CRect rect;
939 rect = GetViewRect();
940 GraphicsDevice dev;
941 dev.pDC = &wmfDC;
942 DrawGraph(dev, rect, 0, 0, true);
943 HENHMETAFILE hemf = wmfDC.CloseEnhanced();
944 DeleteEnhMetaFile(hemf);
945 m_fZoomFactor = fZoom;
946 DoZoom(m_fZoomFactor);
948 else if (extension.CompareNoCase(L".svg") == 0)
950 // save the graph as a scalable vector graphic
951 SVG svg;
952 float fZoom = m_fZoomFactor;
953 m_fZoomFactor = DEFAULT_ZOOM;
954 DoZoom(m_fZoomFactor);
955 CRect rect;
956 rect = GetViewRect();
957 svg.SetViewSize(rect.Width(), rect.Height());
958 GraphicsDevice dev;
959 dev.pSVG = &svg;
960 DrawGraph(dev, rect, 0, 0, true);
961 svg.Save(sSavePath);
962 m_fZoomFactor = fZoom;
963 DoZoom(m_fZoomFactor);
965 else if (extension.CompareNoCase(L".gv") == 0)
967 Graphviz graphviz;
968 float fZoom = m_fZoomFactor;
969 m_fZoomFactor = DEFAULT_ZOOM;
970 DoZoom(m_fZoomFactor);
971 CRect rect;
972 rect = GetViewRect();
973 GraphicsDevice dev;
974 dev.pGraphviz = &graphviz;
975 DrawGraph(dev, rect, 0, 0, true);
976 graphviz.Save(sSavePath);
977 m_fZoomFactor = fZoom;
978 DoZoom(m_fZoomFactor);
980 else
982 // save the graph as a pixel picture instead of a vector picture
983 // create dc to paint on
986 CString sErrormessage;
987 CWindowDC ddc(this);
988 CDC dc;
989 if (!dc.CreateCompatibleDC(&ddc))
991 CFormatMessageWrapper errorDetails;
992 if( errorDetails )
993 MessageBox(errorDetails, L"Error", MB_OK | MB_ICONINFORMATION);
995 return;
997 CRect rect;
998 rect = GetGraphRect();
999 rect.bottom = (LONG)(float(rect.Height()) * m_fZoomFactor);
1000 rect.right = (LONG)(float(rect.Width()) * m_fZoomFactor);
1001 BITMAPINFO bmi = { 0 };
1002 HBITMAP hbm;
1003 LPBYTE pBits;
1004 // Fill out the fields you care about.
1005 bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
1006 bmi.bmiHeader.biWidth = rect.Width();
1007 bmi.bmiHeader.biHeight = rect.Height();
1008 bmi.bmiHeader.biPlanes = 1;
1009 bmi.bmiHeader.biBitCount = 24;
1010 bmi.bmiHeader.biCompression = BI_RGB;
1012 // Create the surface.
1013 hbm = CreateDIBSection(ddc.m_hDC, &bmi, DIB_RGB_COLORS,(void**)&pBits, nullptr, 0);
1014 if (!hbm)
1016 CMessageBox::Show(m_hWnd, IDS_REVGRAPH_ERR_NOMEMORY, IDS_APPNAME, MB_ICONERROR);
1017 return;
1019 HBITMAP oldbm = (HBITMAP)dc.SelectObject(hbm);
1020 // paint the whole graph
1021 GraphicsDevice dev;
1022 dev.pDC = &dc;
1023 DrawGraph(dev, rect, 0, 0, true);
1024 // now use GDI+ to save the picture
1025 CLSID encoderClsid;
1027 Bitmap bitmap(hbm, nullptr);
1028 if (bitmap.GetLastStatus()==Ok)
1030 // Get the CLSID of the encoder.
1031 int ret = 0;
1032 if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(L".png") == 0)
1033 ret = GetEncoderClsid(L"image/png", &encoderClsid);
1034 else if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(L".jpg") == 0)
1035 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1036 else if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(L".jpeg") == 0)
1037 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1038 else if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(L".bmp") == 0)
1039 ret = GetEncoderClsid(L"image/bmp", &encoderClsid);
1040 else if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(L".gif") == 0)
1041 ret = GetEncoderClsid(L"image/gif", &encoderClsid);
1042 else
1044 sSavePath += L".jpg";
1045 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1047 if (ret >= 0)
1049 CStringW tfile = CStringW(sSavePath);
1050 bitmap.Save(tfile, &encoderClsid, nullptr);
1052 else
1053 sErrormessage.Format(IDS_REVGRAPH_ERR_NOENCODER, (LPCTSTR)CPathUtils::GetFileExtFromPath(sSavePath));
1055 else
1056 sErrormessage.LoadString(IDS_REVGRAPH_ERR_NOBITMAP);
1058 dc.SelectObject(oldbm);
1059 DeleteObject(hbm);
1060 dc.DeleteDC();
1061 if (!sErrormessage.IsEmpty())
1062 ::MessageBox(m_hWnd, sErrormessage, L"TortoiseGit", MB_ICONERROR);
1064 catch (CException * pE)
1066 TCHAR szErrorMsg[2048] = { 0 };
1067 pE->GetErrorMessage(szErrorMsg, 2048);
1068 pE->Delete();
1069 ::MessageBox(m_hWnd, szErrorMsg, L"TortoiseGit", MB_ICONERROR);
1074 BOOL CRevisionGraphWnd::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
1076 if (IsUpdateJobRunning())
1077 return __super::OnMouseWheel(nFlags, zDelta, pt);
1079 if (GetKeyState(VK_CONTROL)&0x8000)
1081 float newZoom = m_fZoomFactor * (zDelta < 0 ? ZOOM_STEP : 1.0f/ZOOM_STEP);
1082 DoZoom (max (MIN_ZOOM, min (MAX_ZOOM, newZoom)));
1084 else
1086 int orientation = (GetKeyState(VK_SHIFT) & 0x8000) ? SB_HORZ : SB_VERT;
1087 int pos = GetScrollPos(orientation);
1088 pos -= (zDelta);
1089 SetScrollPos(orientation, pos);
1090 Invalidate(FALSE);
1092 return __super::OnMouseWheel(nFlags, zDelta, pt);
1095 void CRevisionGraphWnd::OnMouseHWheel(UINT nFlags, short zDelta, CPoint pt)
1097 if (IsUpdateJobRunning())
1098 return __super::OnMouseHWheel(nFlags, zDelta, pt);
1100 int orientation = (GetKeyState(VK_SHIFT) & 0x8000) ? SB_VERT : SB_HORZ;
1101 int pos = GetScrollPos(orientation);
1102 pos -= (zDelta);
1103 SetScrollPos(orientation, pos);
1104 Invalidate(FALSE);
1106 return __super::OnMouseHWheel(nFlags, zDelta, pt);
1109 bool CRevisionGraphWnd::UpdateSelectedEntry (node clickedentry)
1111 if (!m_SelectedEntry1 && !clickedentry)
1112 return false;
1114 if (!m_SelectedEntry1)
1116 m_SelectedEntry1 = clickedentry;
1117 Invalidate(FALSE);
1119 if (!m_SelectedEntry2 && clickedentry != m_SelectedEntry1)
1121 m_SelectedEntry1 = clickedentry;
1122 Invalidate(FALSE);
1124 if (m_SelectedEntry1 && m_SelectedEntry2)
1126 if ((m_SelectedEntry2 != clickedentry)&&(m_SelectedEntry1 != clickedentry))
1127 return false;
1129 if (!m_SelectedEntry1)
1130 return false;
1132 return true;
1135 void CRevisionGraphWnd::AppendMenu
1136 ( CMenu& popup
1137 , UINT title
1138 , UINT command
1139 , UINT flags)
1141 // separate different groups / section within the context menu
1143 if (popup.GetMenuItemCount() > 0)
1145 UINT lastCommand = popup.GetMenuItemID (popup.GetMenuItemCount()-1);
1146 if ((lastCommand & GROUP_MASK) != (command & GROUP_MASK))
1147 popup.AppendMenu(MF_SEPARATOR, NULL);
1150 // actually add the new item
1152 CString titleString;
1153 titleString.LoadString (title);
1154 popup.AppendMenu (MF_STRING | flags, command, titleString);
1157 void CRevisionGraphWnd::AppendMenu(CMenu &popup, CString title, UINT command, CString *extra, CMenu *submenu)
1159 // separate different groups / section within the context menu
1160 if (popup.GetMenuItemCount() > 0)
1162 UINT lastCommand = popup.GetMenuItemID(popup.GetMenuItemCount() - 1);
1163 if ((lastCommand & GROUP_MASK) != (command & GROUP_MASK))
1164 popup.AppendMenu(MF_SEPARATOR, NULL);
1167 // actually add the new item
1168 MENUITEMINFO mii = { 0 };
1169 mii.cbSize = sizeof(MENUITEMINFO);
1170 mii.fMask = MIIM_STRING | MIIM_ID | (extra ? MIIM_DATA : 0) | (submenu ? MIIM_SUBMENU : 0);
1171 mii.wID = command;
1172 mii.hSubMenu = submenu ? submenu->m_hMenu : nullptr;
1173 mii.dwItemData = (ULONG_PTR)extra;
1174 mii.dwTypeData = title.GetBuffer();
1175 InsertMenuItem(popup, popup.GetMenuItemCount(), TRUE, &mii);
1176 title.ReleaseBuffer();
1179 void CRevisionGraphWnd::AddGraphOps (CMenu& /*popup*/, const CVisibleGraphNode * /*node*/)
1181 #if 0
1182 CSyncPointer<CGraphNodeStates> nodeStates (m_state.GetNodeStates());
1184 if (!node)
1186 DWORD state = nodeStates->GetCombinedFlags();
1187 if (state != 0)
1189 if (state & CGraphNodeStates::COLLAPSED_ALL)
1190 AppendMenu (popup, IDS_REVGRAPH_POPUP_EXPAND_ALL, ID_EXPAND_ALL);
1192 if (state & CGraphNodeStates::SPLIT_ALL)
1193 AppendMenu (popup, IDS_REVGRAPH_POPUP_JOIN_ALL, ID_JOIN_ALL);
1196 else
1198 DWORD state = nodeStates->GetFlags (node);
1200 if (node->GetSource() || (state & CGraphNodeStates::COLLAPSED_ABOVE))
1201 AppendMenu ( popup
1202 , (state & CGraphNodeStates::COLLAPSED_ABOVE)
1203 ? IDS_REVGRAPH_POPUP_EXPAND_ABOVE
1204 : IDS_REVGRAPH_POPUP_COLLAPSE_ABOVE
1205 , ID_GRAPH_EXPANDCOLLAPSE_ABOVE);
1207 if (node->GetFirstCopyTarget() || (state & CGraphNodeStates::COLLAPSED_RIGHT))
1208 AppendMenu ( popup
1209 , (state & CGraphNodeStates::COLLAPSED_RIGHT)
1210 ? IDS_REVGRAPH_POPUP_EXPAND_RIGHT
1211 : IDS_REVGRAPH_POPUP_COLLAPSE_RIGHT
1212 , ID_GRAPH_EXPANDCOLLAPSE_RIGHT);
1214 if (node->GetNext() || (state & CGraphNodeStates::COLLAPSED_BELOW))
1215 AppendMenu ( popup
1216 , (state & CGraphNodeStates::COLLAPSED_BELOW)
1217 ? IDS_REVGRAPH_POPUP_EXPAND_BELOW
1218 : IDS_REVGRAPH_POPUP_COLLAPSE_BELOW
1219 , ID_GRAPH_EXPANDCOLLAPSE_BELOW);
1221 if (node->GetSource() || (state & CGraphNodeStates::SPLIT_ABOVE))
1222 AppendMenu ( popup
1223 , (state & CGraphNodeStates::SPLIT_ABOVE)
1224 ? IDS_REVGRAPH_POPUP_JOIN_ABOVE
1225 : IDS_REVGRAPH_POPUP_SPLIT_ABOVE
1226 , ID_GRAPH_SPLITJOIN_ABOVE);
1228 if (node->GetFirstCopyTarget() || (state & CGraphNodeStates::SPLIT_RIGHT))
1229 AppendMenu ( popup
1230 , (state & CGraphNodeStates::SPLIT_RIGHT)
1231 ? IDS_REVGRAPH_POPUP_JOIN_RIGHT
1232 : IDS_REVGRAPH_POPUP_SPLIT_RIGHT
1233 , ID_GRAPH_SPLITJOIN_RIGHT);
1235 if (node->GetNext() || (state & CGraphNodeStates::SPLIT_BELOW))
1236 AppendMenu ( popup
1237 , (state & CGraphNodeStates::SPLIT_BELOW)
1238 ? IDS_REVGRAPH_POPUP_JOIN_BELOW
1239 : IDS_REVGRAPH_POPUP_SPLIT_BELOW
1240 , ID_GRAPH_SPLITJOIN_BELOW);
1242 #endif
1245 CString CRevisionGraphWnd::GetSelectedURL() const
1247 #if 0
1248 if (!m_SelectedEntry1)
1249 return CString();
1251 CString URL = m_state.GetRepositoryRoot()
1252 + CUnicodeUtils::GetUnicode (m_SelectedEntry1->GetPath().GetPath().c_str());
1253 URL = CUnicodeUtils::GetUnicode(CPathUtils::PathEscape(CUnicodeUtils::GetUTF8(URL)));
1255 return URL;
1256 #endif
1257 return CString();
1260 CString CRevisionGraphWnd::GetWCURL() const
1262 #if 0
1263 CTGitPath path (m_sPath);
1264 if (path.IsUrl())
1265 return CString();
1267 SVNInfo info;
1268 const SVNInfoData * status
1269 = info.GetFirstFileInfo (path, SVNRev(), SVNRev());
1271 return !status ? CString() : status->url;
1272 #endif
1273 return CString();
1276 void CRevisionGraphWnd::DoShowLog()
1278 if (!m_SelectedEntry1)
1279 return;
1281 CString sCmd;
1283 if(m_SelectedEntry2)
1284 sCmd.Format(L"/command:log %s /startrev:%s /endrev:%s",
1285 this->m_sPath.IsEmpty() ? L"" : (LPCTSTR)(L"/path:\"" + this->m_sPath + L'"'),
1286 (LPCTSTR)this->m_logEntries[m_SelectedEntry1->index()].ToString(),
1287 (LPCTSTR)this->m_logEntries[m_SelectedEntry2->index()].ToString());
1288 else
1289 sCmd.Format(L"/command:log %s /endrev:%s",
1290 (LPCTSTR)this->m_sPath.IsEmpty() ? L"" : (L"/path:\"" + this->m_sPath + L'"'),
1291 (LPCTSTR)this->m_logEntries[m_SelectedEntry1->index()].ToString());
1293 CAppUtils::RunTortoiseGitProc(sCmd);
1296 void CRevisionGraphWnd::DoCheckForModification()
1298 CChangedDlg dlg;
1299 dlg.m_pathList = CTGitPathList (CTGitPath (m_sPath));
1300 dlg.DoModal();
1303 void CRevisionGraphWnd::DoMergeTo()
1305 #if 0
1306 CString URL = GetSelectedURL();
1307 CString path = m_sPath;
1308 CBrowseFolder folderBrowser;
1309 folderBrowser.SetInfo(CString(MAKEINTRESOURCE(IDS_LOG_MERGETO)));
1310 if (folderBrowser.Show(GetSafeHwnd(), path, path) == CBrowseFolder::OK)
1312 CSVNProgressDlg dlg;
1313 dlg.SetCommand(CSVNProgressDlg::SVNProgress_Merge);
1314 dlg.SetPathList(CTGitPathList(CTGitPath(path)));
1315 dlg.SetUrl(URL);
1316 dlg.SetSecondUrl(URL);
1317 SVNRevRangeArray revarray;
1318 revarray.AddRevRange (m_SelectedEntry1->GetRevision()-1, svn_revnum_t(m_SelectedEntry1->GetRevision()));
1319 dlg.SetRevisionRanges(revarray);
1320 dlg.DoModal();
1322 #endif
1325 void CRevisionGraphWnd::DoUpdate()
1327 #if 0
1328 CSVNProgressDlg progDlg;
1329 progDlg.SetCommand (CSVNProgressDlg::SVNProgress_Update);
1330 progDlg.SetOptions (0); // don't ignore externals
1331 progDlg.SetPathList (CTGitPathList (CTGitPath (m_sPath)));
1332 progDlg.SetRevision (m_SelectedEntry1->GetRevision());
1333 progDlg.SetDepth();
1334 progDlg.DoModal();
1336 if (m_state.GetFetchedWCState())
1337 m_parent->UpdateFullHistory();
1338 #endif
1341 void CRevisionGraphWnd::DoSwitch(CString rev)
1343 CAppUtils::PerformSwitch(rev);
1346 void CRevisionGraphWnd::DoSwitchToHead()
1348 #if 0
1349 CSVNProgressDlg progDlg;
1350 progDlg.SetCommand (CSVNProgressDlg::SVNProgress_Switch);
1351 progDlg.SetPathList (CTGitPathList (CTGitPath (m_sPath)));
1352 progDlg.SetUrl (GetSelectedURL());
1353 progDlg.SetRevision (SVNRev::REV_HEAD);
1354 progDlg.SetPegRevision (m_SelectedEntry1->GetRevision());
1355 progDlg.DoModal();
1357 if (m_state.GetFetchedWCState())
1358 m_parent->UpdateFullHistory();
1359 #endif
1362 void CRevisionGraphWnd::DoBrowseRepo()
1364 if (!m_SelectedEntry1)
1365 return;
1367 CString sCmd;
1368 sCmd.Format(L"/command:repobrowser %s /rev:%s",
1369 this->m_sPath.IsEmpty() ? L"" : (LPCTSTR)(L"/path:\"" + this->m_sPath + L'"'),
1370 (LPCTSTR)GetFriendRefName(m_SelectedEntry1));
1372 CAppUtils::RunTortoiseGitProc(sCmd);
1375 void CRevisionGraphWnd::ResetNodeFlags (DWORD /*flags*/)
1377 // m_state.GetNodeStates()->ResetFlags (flags);
1378 // m_parent->StartWorkerThread();
1381 void CRevisionGraphWnd::ToggleNodeFlag (const CVisibleGraphNode * /*node*/, DWORD /*flag*/)
1383 #if 0
1384 CSyncPointer<CGraphNodeStates> nodeStates (m_state.GetNodeStates());
1386 if (nodeStates->GetFlags (node) & flag)
1387 nodeStates->ResetFlags (node, flag);
1388 else
1389 nodeStates->SetFlags (node, flag);
1391 m_parent->StartWorkerThread();
1392 #endif
1395 void CRevisionGraphWnd::DoCopyRefs()
1397 if (!m_SelectedEntry1)
1398 return;
1400 STRING_VECTOR list = GetFriendRefNames(m_SelectedEntry1);
1401 CString text;
1402 if (list.empty())
1403 text = m_logEntries[m_SelectedEntry1->index()].ToString();
1404 for (size_t i = 0; i < list.size(); ++i)
1406 if (i > 0)
1407 text.Append(L"\r\n");
1408 text.Append(list[i]);
1410 CStringUtils::WriteAsciiStringToClipboard(text, m_hWnd);
1413 void CRevisionGraphWnd::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
1415 if (IsUpdateJobRunning())
1416 return;
1418 CPoint clientpoint = point;
1419 this->ScreenToClient(&clientpoint);
1421 node nodeIndex = GetHitNode (clientpoint);
1423 if ( !UpdateSelectedEntry (nodeIndex))
1424 return;
1426 CMenu popup;
1427 if (!popup.CreatePopupMenu())
1428 return;
1430 bool bothPresent = (m_SelectedEntry2 && m_SelectedEntry1);
1432 AppendMenu (popup, IDS_REPOBROWSE_SHOWLOG, ID_SHOWLOG);
1434 STRING_VECTOR branchNames;
1435 STRING_VECTOR allRefNames;
1436 if (m_SelectedEntry1 && (m_SelectedEntry2 == nullptr))
1438 AppendMenu(popup, IDS_LOG_BROWSEREPO, ID_BROWSEREPO);
1440 CString currentBranch = g_Git.GetCurrentBranch();
1441 CGit::REF_TYPE refType = CGit::LOCAL_BRANCH;
1442 branchNames = GetFriendRefNames(m_SelectedEntry1, &currentBranch, &refType);
1443 if (branchNames.size() == 1)
1445 CString text;
1446 text.Format(L"%s \"%s\"", (LPCTSTR)CString(MAKEINTRESOURCE(IDS_SWITCH_BRANCH)), (LPCTSTR)branchNames[0]);
1447 AppendMenu(popup, text, ID_SWITCH, &branchNames[0]);
1449 else if (branchNames.size() > 1)
1451 CMenu switchMenu;
1452 switchMenu.CreatePopupMenu();
1453 for (size_t i = 0; i < branchNames.size(); ++i)
1454 AppendMenu(switchMenu, branchNames[i], ID_SWITCH + ((int)(i + 1) << 16), &branchNames[i]);
1455 AppendMenu(popup, CString(MAKEINTRESOURCE(IDS_SWITCH_BRANCH)), ID_SWITCH, nullptr, &switchMenu);
1458 AppendMenu(popup, IDS_COPY_REF_NAMES, ID_COPYREFS);
1460 allRefNames = GetFriendRefNames(m_SelectedEntry1, &currentBranch);
1461 if (allRefNames.size() == 1)
1463 CString str;
1464 str.LoadString(IDS_DELETE_BRANCHTAG_SHORT);
1465 str += L' ';
1466 str += allRefNames[0];
1467 AppendMenu(popup, str, ID_DELETE, &allRefNames[0]);
1469 else if (allRefNames.size() > 1)
1471 CString str;
1472 str.LoadString(IDS_DELETE_BRANCHTAG);
1473 CIconMenu submenu;
1474 submenu.CreatePopupMenu();
1475 for (size_t i = 0; i < allRefNames.size(); ++i)
1477 submenu.AppendMenuIcon(ID_DELETE + (i << 16), allRefNames[i]);
1478 submenu.SetMenuItemData(ID_DELETE + (i << 16), (ULONG_PTR)&allRefNames[i]);
1480 submenu.AppendMenuIcon(ID_DELETE + (allRefNames.size() << 16), IDS_ALL);
1481 submenu.SetMenuItemData(ID_DELETE + (allRefNames.size() << 16), (ULONG_PTR)MAKEINTRESOURCE(IDS_ALL));
1483 AppendMenu(popup, str, ID_DELETE, nullptr, &submenu);
1486 AppendMenu(popup, IDS_REVGRAPH_POPUP_COMPAREHEADS, ID_COMPAREHEADS);
1487 AppendMenu(popup, IDS_REVGRAPH_POPUP_UNIDIFFHEADS, ID_UNIDIFFHEADS);
1489 AppendMenu(popup, IDS_LOG_POPUP_COMPARE, ID_COMPAREWT);
1492 if (bothPresent)
1494 AppendMenu (popup, IDS_REVGRAPH_POPUP_COMPAREREVS, ID_COMPAREREVS);
1495 AppendMenu (popup, IDS_REVGRAPH_POPUP_UNIDIFFREVS, ID_UNIDIFFREVS);
1498 // AddGraphOps (popup, clickedentry);
1500 // if the context menu is invoked through the keyboard, we have to use
1501 // a calculated position on where to anchor the menu on
1502 if ((point.x == -1) && (point.y == -1))
1504 CRect rect = GetWindowRect();
1505 point = rect.CenterPoint();
1508 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RIGHTBUTTON, point.x, point.y, this);
1509 switch (cmd & 0xFFFF)
1511 case ID_COMPAREREVS:
1512 if (m_SelectedEntry1)
1513 CompareRevs(false);
1514 break;
1515 case ID_UNIDIFFREVS:
1516 if (m_SelectedEntry1)
1517 UnifiedDiffRevs(false);
1518 break;
1519 case ID_UNIDIFFHEADS:
1520 if (m_SelectedEntry1)
1521 UnifiedDiffRevs(true);
1522 break;
1523 case ID_SHOWLOG:
1524 DoShowLog();
1525 break;
1526 case ID_SWITCH:
1528 MENUITEMINFO mii = { 0 };
1529 mii.cbSize = sizeof(mii);
1530 mii.fMask |= MIIM_DATA;
1531 GetMenuItemInfo(popup, cmd, FALSE, &mii);
1532 CString *rev = (CString *)mii.dwItemData;
1533 if (rev)
1535 DoSwitch(*rev);
1536 m_parent->UpdateFullHistory();
1538 break;
1540 case ID_COPYREFS:
1541 DoCopyRefs();
1542 break;
1543 case ID_DELETE:
1545 MENUITEMINFO mii = { 0 };
1546 mii.cbSize = sizeof(mii);
1547 mii.fMask |= MIIM_DATA;
1548 GetMenuItemInfo(popup, cmd, FALSE, &mii);
1549 CString *rev = (CString*)mii.dwItemData;
1550 if (!rev)
1551 break;
1553 CString shortname;
1554 if (rev == (CString*)MAKEINTRESOURCE(IDS_ALL))
1556 bool nothingDeleted = true;
1557 for (const auto& ref : allRefNames)
1559 if (!CAppUtils::DeleteRef(this, ref))
1560 break;
1561 nothingDeleted = false;
1563 if (nothingDeleted)
1564 return;
1566 else if (!CAppUtils::DeleteRef(this, *rev))
1567 return;
1569 m_parent->UpdateFullHistory();
1570 break;
1572 case ID_BROWSEREPO:
1573 DoBrowseRepo();
1574 break;
1575 case ID_COMPAREHEADS:
1576 if (m_SelectedEntry1)
1577 CompareRevs(L"HEAD");
1578 break;
1579 case ID_COMPAREWT:
1580 if (m_SelectedEntry1)
1581 CompareRevs(CGitHash().ToString());
1582 break;
1584 #if 0
1585 case ID_COMPAREREVS:
1586 if (m_SelectedEntry1)
1587 CompareRevs(false);
1588 break;
1589 case ID_UNIDIFFREVS:
1590 if (m_SelectedEntry1)
1591 UnifiedDiffRevs(false);
1592 break;
1593 case ID_UNIDIFFHEADS:
1594 if (m_SelectedEntry1)
1595 UnifiedDiffRevs(true);
1596 break;
1597 case ID_SHOWLOG:
1598 DoShowLog();
1599 break;
1600 case ID_CFM:
1601 DoCheckForModification();
1602 break;
1603 case ID_MERGETO:
1604 DoMergeTo();
1605 break;
1606 case ID_UPDATE:
1607 DoUpdate();
1608 break;
1609 case ID_SWITCHTOHEAD:
1610 DoSwitchToHead();
1611 break;
1612 case ID_EXPAND_ALL:
1613 ResetNodeFlags (CGraphNodeStates::COLLAPSED_ALL);
1614 break;
1615 case ID_JOIN_ALL:
1616 ResetNodeFlags (CGraphNodeStates::SPLIT_ALL);
1617 break;
1618 case ID_GRAPH_EXPANDCOLLAPSE_ABOVE:
1619 ToggleNodeFlag (clickedentry, CGraphNodeStates::COLLAPSED_ABOVE);
1620 break;
1621 case ID_GRAPH_EXPANDCOLLAPSE_RIGHT:
1622 ToggleNodeFlag (clickedentry, CGraphNodeStates::COLLAPSED_RIGHT);
1623 break;
1624 case ID_GRAPH_EXPANDCOLLAPSE_BELOW:
1625 ToggleNodeFlag (clickedentry, CGraphNodeStates::COLLAPSED_BELOW);
1626 break;
1627 case ID_GRAPH_SPLITJOIN_ABOVE:
1628 ToggleNodeFlag (clickedentry, CGraphNodeStates::SPLIT_ABOVE);
1629 break;
1630 case ID_GRAPH_SPLITJOIN_RIGHT:
1631 ToggleNodeFlag (clickedentry, CGraphNodeStates::SPLIT_RIGHT);
1632 break;
1633 case ID_GRAPH_SPLITJOIN_BELOW:
1634 ToggleNodeFlag (clickedentry, CGraphNodeStates::SPLIT_BELOW);
1635 break;
1636 #endif
1640 void CRevisionGraphWnd::OnMouseMove(UINT nFlags, CPoint point)
1642 if (IsUpdateJobRunning())
1643 return __super::OnMouseMove(nFlags, point);
1644 if (!m_bIsCanvasMove)
1646 if (m_bShowOverview && (m_OverviewRect.PtInRect(point))&&(nFlags & MK_LBUTTON))
1648 // scrolling
1649 int x = (int)((point.x-m_OverviewRect.left - (m_OverviewPosRect.Width()/2)) / m_previewZoom * m_fZoomFactor);
1650 int y = (int)((point.y - m_OverviewRect.top - (m_OverviewPosRect.Height()/2)) / m_previewZoom * m_fZoomFactor);
1651 x = max(0, x);
1652 y = max(0, y);
1653 SetScrollbars(y, x);
1654 Invalidate(FALSE);
1655 return __super::OnMouseMove(nFlags, point);
1657 else
1659 // update screen if we hover over a different
1660 // node than during the last redraw
1662 CPoint clientPoint = point;
1663 GetCursorPos (&clientPoint);
1664 ScreenToClient (&clientPoint);
1666 #if 0
1667 const CRevisionGraphState::SVisibleGlyph* hitGlyph
1668 = GetHitGlyph (clientPoint);
1669 const CFullGraphNode* glyphNode
1670 = hitGlyph ? hitGlyph->node->GetBase() : nullptr;
1672 const CFullGraphNode* hoverNode = nullptr;
1673 if (m_hoverIndex != NO_INDEX)
1675 CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());
1676 if (m_hoverIndex < nodeList->GetCount())
1677 hoverNode = nodeList->GetNode (m_hoverIndex).node->GetBase();
1680 //bool onHoverNodeGlyph = hoverNode && (glyphNode == hoverNode);
1681 if ( !m_hoverIndex
1682 && ( (m_hoverIndex != GetHitNode (clientPoint))))
1684 m_showHoverGlyphs = false;
1686 KillTimer (GLYPH_HOVER_EVENT);
1687 SetTimer(GLYPH_HOVER_EVENT, GLYPH_HOVER_DELAY, nullptr);
1689 Invalidate(FALSE);
1691 #endif
1692 return __super::OnMouseMove(nFlags, point);
1695 SetCapture();
1697 int pos_h = GetScrollPos(SB_HORZ);
1698 pos_h -= point.x - m_ptMoveCanvas.x;
1699 SetScrollPos(SB_HORZ, pos_h);
1701 int pos_v = GetScrollPos(SB_VERT);
1702 pos_v -= point.y - m_ptMoveCanvas.y;
1703 SetScrollPos(SB_VERT, pos_v);
1705 m_ptMoveCanvas = point;
1707 this->Invalidate();
1709 __super::OnMouseMove(nFlags, point);
1712 BOOL CRevisionGraphWnd::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
1714 CRect viewRect = GetViewRect();
1716 LPTSTR cursorID = IDC_ARROW;
1717 HINSTANCE resourceHandle = nullptr;
1719 if ((nHitTest == HTCLIENT)&&(pWnd == this)&&(viewRect.Width())&&(viewRect.Height())&&(message))
1721 POINT pt;
1722 if (GetCursorPos(&pt))
1724 ScreenToClient(&pt);
1725 if (m_OverviewPosRect.PtInRect(pt))
1727 resourceHandle = AfxGetResourceHandle();
1728 cursorID = (GetKeyState(VK_LBUTTON) & 0x8000)
1729 ? MAKEINTRESOURCE(IDC_PANCURDOWN)
1730 : MAKEINTRESOURCE(IDC_PANCUR);
1732 if (m_bIsCanvasMove)
1733 cursorID = IDC_HAND;
1737 HCURSOR hCur = LoadCursor(resourceHandle, cursorID);
1738 if (GetCursor() != hCur)
1739 SetCursor (hCur);
1741 return TRUE;
1744 void CRevisionGraphWnd::OnTimer (UINT_PTR nIDEvent)
1746 if (nIDEvent == GLYPH_HOVER_EVENT)
1748 KillTimer (GLYPH_HOVER_EVENT);
1750 m_showHoverGlyphs = true;
1751 Invalidate (FALSE);
1753 else
1754 __super::OnTimer (nIDEvent);
1757 LRESULT CRevisionGraphWnd::OnWorkerThreadDone(WPARAM, LPARAM)
1759 // handle potential race condition between PostMessage and leaving job:
1760 // the background job may not have exited, yet
1762 if (updateJob.get())
1763 updateJob->GetResult();
1765 InitView();
1766 BuildPreview();
1768 if (m_HeadNode)
1770 SCROLLINFO sinfo = { 0 };
1771 sinfo.cbSize = sizeof(SCROLLINFO);
1772 if (GetScrollInfo(SB_HORZ, &sinfo))
1774 sinfo.nPos = (int)min(max(sinfo.nMin, m_GraphAttr.x(m_HeadNode) - m_GraphAttr.width(m_HeadNode) / 2), sinfo.nMax);
1775 SetScrollInfo(SB_HORZ, &sinfo);
1777 if (GetScrollInfo(SB_VERT, &sinfo))
1779 sinfo.nPos = (int)min(max(sinfo.nMin, m_GraphAttr.y(m_HeadNode) - m_GraphAttr.height(m_HeadNode) / 2), sinfo.nMax);
1780 SetScrollInfo(SB_VERT, &sinfo);
1784 Invalidate(FALSE);
1786 if (m_parent && !m_parent->GetOutputFile().IsEmpty())
1788 // save the graph to the output file and exit
1789 SaveGraphAs(m_parent->GetOutputFile());
1790 PostQuitMessage(0);
1792 return 0;
1795 void CRevisionGraphWnd::SetDlgTitle (bool /*offline*/)
1797 #if 0
1798 if (m_sTitle.IsEmpty())
1799 GetParent()->GetWindowText(m_sTitle);
1801 CString newTitle;
1802 if (offline)
1803 newTitle.Format (IDS_REVGRAPH_DLGTITLEOFFLINE, (LPCTSTR)m_sTitle);
1804 else
1805 newTitle = m_sTitle;
1807 CAppUtils::SetWindowTitle(GetParent()->GetSafeHwnd(), m_sPath, newTitle);
1808 #endif
1811 ULONG CRevisionGraphWnd::GetGestureStatus(CPoint /*ptTouch*/)
1813 return 0;