Don't import ogdf namespace
[TortoiseGit.git] / src / TortoiseProc / RevisionGraph / RevisionGraphWnd.cpp
bloba56645a822f9827f22188d0d35c1817549d9df3e
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2012, 2015 - TortoiseSVN
4 // Copyright (C) 2012-2018 - 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;
57 enum RevisionGraphContextMenuCommands
59 // needs to start with 1, since 0 is the return value if *nothing* is clicked on in the context menu
60 GROUP_MASK = 0xff00,
61 ID_SHOWLOG = 1,
62 ID_CFM = 2,
63 ID_BROWSEREPO,
64 ID_COMPAREREVS = 0x100,
65 ID_COMPAREHEADS,
66 ID_COMPAREWT,
67 ID_UNIDIFFREVS,
68 ID_UNIDIFFHEADS,
69 ID_MERGETO = 0x300,
70 ID_UPDATE,
71 ID_SWITCHTOHEAD,
72 ID_SWITCH,
73 ID_DELETE,
74 ID_COPYREFS = 0x400,
75 ID_EXPAND_ALL = 0x500,
76 ID_JOIN_ALL,
77 ID_GRAPH_EXPANDCOLLAPSE_ABOVE = 0x600,
78 ID_GRAPH_EXPANDCOLLAPSE_RIGHT,
79 ID_GRAPH_EXPANDCOLLAPSE_BELOW,
80 ID_GRAPH_SPLITJOIN_ABOVE,
81 ID_GRAPH_SPLITJOIN_RIGHT,
82 ID_GRAPH_SPLITJOIN_BELOW,
85 CRevisionGraphWnd::CRevisionGraphWnd()
86 : CWnd()
87 , m_SelectedEntry1(nullptr)
88 , m_SelectedEntry2(nullptr)
89 , m_HeadNode(nullptr)
90 , m_pDlgTip(nullptr)
91 , m_nFontSize(12)
92 , m_bTweakTrunkColors(true)
93 , m_bTweakTagsColors(true)
94 , m_fZoomFactor(DEFAULT_ZOOM)
95 , m_ptRubberEnd(0,0)
96 , m_ptMoveCanvas(0,0)
97 , m_bShowOverview(false)
98 , m_parent(nullptr)
99 , m_hoverIndex(nullptr)
100 , m_hoverGlyphs (0)
101 , m_tooltipIndex(nullptr)
102 , m_showHoverGlyphs (false)
103 , m_bIsCanvasMove(false)
104 , m_previewWidth(0)
105 , m_previewHeight(0)
106 , m_previewZoom(1)
107 , m_ullTicks(0)
108 , m_logEntries(&m_LogCache)
109 , m_bCurrentBranch(false)
110 , m_bLocalBranches(FALSE)
112 memset(&m_lfBaseFont, 0, sizeof(LOGFONT));
113 std::fill_n(m_apFonts, MAXFONTS, nullptr);
115 WNDCLASS wndcls;
116 HINSTANCE hInst = AfxGetInstanceHandle();
117 #define REVGRAPH_CLASSNAME L"Revgraph_windowclass"
118 if (!(::GetClassInfo(hInst, REVGRAPH_CLASSNAME, &wndcls)))
120 // otherwise we need to register a new class
121 wndcls.style = CS_DBLCLKS | CS_OWNDC;
122 wndcls.lpfnWndProc = ::DefWindowProc;
123 wndcls.cbClsExtra = wndcls.cbWndExtra = 0;
124 wndcls.hInstance = hInst;
125 wndcls.hIcon = nullptr;
126 wndcls.hCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
127 wndcls.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
128 wndcls.lpszMenuName = nullptr;
129 wndcls.lpszClassName = REVGRAPH_CLASSNAME;
131 RegisterClass(&wndcls);
134 m_bTweakTrunkColors = CRegDWORD(L"Software\\TortoiseGit\\RevisionGraph\\TweakTrunkColors", TRUE) != FALSE;
135 m_bTweakTagsColors = CRegDWORD(L"Software\\TortoiseGit\\RevisionGraph\\TweakTagsColors", TRUE) != FALSE;
136 m_szTip[0] = '\0';
137 m_wszTip[0] = L'\0';
139 m_GraphAttr.init(this->m_Graph, ogdf::GraphAttributes::nodeGraphics | ogdf::GraphAttributes::edgeGraphics |
140 ogdf:: GraphAttributes::nodeLabel | ogdf::GraphAttributes::nodeColor |
141 ogdf::GraphAttributes::edgeColor | ogdf::GraphAttributes::edgeStyle |
142 ogdf::GraphAttributes::nodeStyle | ogdf::GraphAttributes::nodeTemplate);
144 m_SugiyamLayout.setRanking(::new ogdf::OptimalRanking());
145 m_SugiyamLayout.setCrossMin(::new ogdf::MedianHeuristic());
147 double pi = 3.1415926;
148 m_ArrowCos = cos(pi/8);
149 m_ArrowSin = sin(pi/8);
150 this->m_ArrowSize = 8;
151 #if 0
152 ogdf::node one = this->m_Graph.newNode();
153 ogdf::node two = this->m_Graph.newNode();
154 ogdf::node three = this->m_Graph.newNode();
155 ogdf::node four = this->m_Graph.newNode();
158 m_GraphAttr.width(one)=100;
159 m_GraphAttr.height(one)=200;
160 m_GraphAttr.width(two)=100;
161 m_GraphAttr.height(two)=100;
162 m_GraphAttr.width(three)=100;
163 m_GraphAttr.height(three)=20;
164 m_GraphAttr.width(four)=100;
165 m_GraphAttr.height(four)=20;
167 m_GraphAttr.labelNode(one)="One";
168 m_GraphAttr.labelNode(two)="Two";
169 m_GraphAttr.labelNode(three)="three";
171 this->m_Graph.newEdge(one, two);
172 this->m_Graph.newEdge(one, three);
173 this->m_Graph.newEdge(two, four);
174 this->m_Graph.newEdge(three, four);
176 #endif
177 auto pOHL = ::new ogdf::FastHierarchyLayout;
178 //It will auto delte when m_SugiyamLayout destroy
180 pOHL->layerDistance(30.0);
181 pOHL->nodeDistance(25.0);
183 m_SugiyamLayout.setLayout(pOHL);
185 #if 0
186 //this->m_OHL.layerDistance(30.0);
187 //this->m_OHL.nodeDistance(25.0);
188 //this->m_OHL.weightBalancing(0.8);
189 m_SugiyamLayout.setLayout(&m_OHL);
190 m_SugiyamLayout.call(m_GraphAttr);
191 #endif
192 #if 0
193 PlanarizationLayout pl;
195 FastPlanarSubgraph *ps = ::new FastPlanarSubgraph;
196 ps->runs(100);
197 VariableEmbeddingInserter *ves = ::new VariableEmbeddingInserter;
198 ves->removeReinsert(EdgeInsertionModule::rrAll);
199 pl.setSubgraph(ps);
200 pl.setInserter(ves);
202 EmbedderMinDepthMaxFaceLayers *emb = ::new EmbedderMinDepthMaxFaceLayers;
203 pl.setEmbedder(emb);
205 OrthoLayout *ol =::new OrthoLayout;
206 ol->separation(20.0);
207 ol->cOverhang(0.4);
208 ol->setOptions(2+4);
209 ol->preferedDir(OrthoDir::odEast);
210 pl.setPlanarLayouter(ol);
212 pl.call(m_GraphAttr);
214 node v;
215 forall_nodes(v,m_Graph) {
216 TRACE(L"node x %f y %f %f %f\n",/* m_GraphAttr.idNode(v), */
217 m_GraphAttr.x(v),
218 m_GraphAttr.y(v),
219 m_GraphAttr.width(v),
220 m_GraphAttr.height(v)
224 edge e;
225 forall_edges(e, m_Graph)
227 // get connection and point position
228 const DPolyline &dpl = this->m_GraphAttr.bends(e);
230 ListConstIterator<DPoint> it;
231 for(it = dpl.begin(); it.valid(); ++it)
233 TRACE(L"edge %f %f\n", (*it).m_x, (*it).m_y);
236 m_GraphAttr.writeGML("test.gml");
237 #endif
240 CRevisionGraphWnd::~CRevisionGraphWnd()
242 for (int i = 0; i < MAXFONTS; ++i)
244 if (m_apFonts[i])
246 m_apFonts[i]->DeleteObject();
247 delete m_apFonts[i];
249 m_apFonts[i] = nullptr;
251 delete m_pDlgTip;
252 m_Graph.clear();
255 void CRevisionGraphWnd::DoDataExchange(CDataExchange* pDX)
257 CWnd::DoDataExchange(pDX);
261 BEGIN_MESSAGE_MAP(CRevisionGraphWnd, CWnd)
262 ON_WM_PAINT()
263 ON_WM_ERASEBKGND()
264 ON_WM_HSCROLL()
265 ON_WM_VSCROLL()
266 ON_WM_SIZE()
267 ON_WM_LBUTTONDOWN()
268 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipNotify)
269 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipNotify)
270 ON_WM_MOUSEWHEEL()
271 ON_WM_MOUSEHWHEEL()
272 ON_WM_CONTEXTMENU()
273 ON_WM_MOUSEMOVE()
274 ON_WM_LBUTTONUP()
275 ON_WM_SETCURSOR()
276 ON_WM_TIMER()
277 ON_MESSAGE(WM_WORKERTHREADDONE,OnWorkerThreadDone)
278 ON_WM_CAPTURECHANGED()
279 END_MESSAGE_MAP()
281 void CRevisionGraphWnd::Init(CWnd * pParent, LPRECT rect)
283 WNDCLASS wndcls;
284 HINSTANCE hInst = AfxGetInstanceHandle();
285 #define REVGRAPH_CLASSNAME L"Revgraph_windowclass"
286 if (!(::GetClassInfo(hInst, REVGRAPH_CLASSNAME, &wndcls)))
288 // otherwise we need to register a new class
289 wndcls.style = CS_DBLCLKS | CS_OWNDC;
290 wndcls.lpfnWndProc = ::DefWindowProc;
291 wndcls.cbClsExtra = wndcls.cbWndExtra = 0;
292 wndcls.hInstance = hInst;
293 wndcls.hIcon = nullptr;
294 wndcls.hCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
295 wndcls.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
296 wndcls.lpszMenuName = nullptr;
297 wndcls.lpszClassName = REVGRAPH_CLASSNAME;
299 RegisterClass(&wndcls);
302 if (!IsWindow(m_hWnd))
303 CreateEx(WS_EX_CLIENTEDGE, REVGRAPH_CLASSNAME, L"RevGraph", WS_CHILD|WS_VISIBLE|WS_TABSTOP, *rect, pParent, 0);
304 m_pDlgTip = new CToolTipCtrl;
305 if(!m_pDlgTip->Create(this))
307 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Unable to add tooltip!\n");
309 EnableToolTips();
311 memset(&m_lfBaseFont, 0, sizeof(m_lfBaseFont));
312 m_lfBaseFont.lfHeight = 0;
313 m_lfBaseFont.lfWeight = FW_NORMAL;
314 m_lfBaseFont.lfItalic = FALSE;
315 m_lfBaseFont.lfCharSet = DEFAULT_CHARSET;
316 m_lfBaseFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
317 m_lfBaseFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
318 m_lfBaseFont.lfQuality = DEFAULT_QUALITY;
319 m_lfBaseFont.lfPitchAndFamily = DEFAULT_PITCH;
321 m_ullTicks = GetTickCount64();
323 m_parent = dynamic_cast<CRevisionGraphDlg*>(pParent);
326 CPoint CRevisionGraphWnd::GetLogCoordinates (CPoint point) const
328 // translate point into logical coordinates
330 int nVScrollPos = GetScrollPos(SB_VERT);
331 int nHScrollPos = GetScrollPos(SB_HORZ);
333 return CPoint ( (int)((point.x + nHScrollPos) / m_fZoomFactor)
334 , (int)((point.y + nVScrollPos) / m_fZoomFactor));
337 ogdf::node CRevisionGraphWnd::GetHitNode(CPoint point, CSize /*border*/) const
339 #if 0
340 // any nodes at all?
342 CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());
343 if (!nodeList)
344 return index_t(NO_INDEX);
346 // search the nodes for one at that grid position
348 return nodeList->GetAt (GetLogCoordinates (point), border);
349 #endif
351 ogdf::node v;
352 forall_nodes(v,m_Graph)
354 RectF noderect (GetNodeRect (v, CPoint(GetScrollPos(SB_HORZ), GetScrollPos(SB_VERT))));
355 if(point.x>noderect.X && point.x <(noderect.X+noderect.Width) &&
356 point.y>noderect.Y && point.y <(noderect.Y+noderect.Height))
358 return v;
361 return nullptr;
364 DWORD CRevisionGraphWnd::GetHoverGlyphs (CPoint /*point*/) const
366 // if there is no layout, there will be no nodes,
367 // hence, no glyphs
368 DWORD result = 0;
369 #if 0
370 CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());
371 if (!nodeList)
372 return 0;
374 // get node at point or node that is close enough
375 // so that point may hit a glyph area
377 index_t nodeIndex = GetHitNode(point);
378 if (nodeIndex == NO_INDEX)
379 nodeIndex = GetHitNode(point, CSize (GLYPH_SIZE, GLYPH_SIZE / 2));
381 if (nodeIndex >= nodeList->GetCount())
382 return 0;
384 ILayoutNodeList::SNode node = nodeList->GetNode (nodeIndex);
385 const CVisibleGraphNode* base = node.node;
387 // what glyphs should be shown depending on position of point
388 // relative to the node rect?
390 CPoint logCoordinates = GetLogCoordinates (point);
391 CRect r = node.rect;
392 CPoint center = r.CenterPoint();
394 CRect rightGlyphArea ( r.right - GLYPH_SIZE, center.y - GLYPH_SIZE / 2
395 , r.right + GLYPH_SIZE, center.y + GLYPH_SIZE / 2);
396 CRect topGlyphArea ( center.x - GLYPH_SIZE, r.top - GLYPH_SIZE / 2
397 , center.x + GLYPH_SIZE, r.top + GLYPH_SIZE / 2);
398 CRect bottomGlyphArea ( center.x - GLYPH_SIZE, r.bottom - GLYPH_SIZE / 2
399 , center.x + GLYPH_SIZE, r.bottom + GLYPH_SIZE / 2);
401 bool upsideDown
402 = m_state.GetOptions()->GetOption<CUpsideDownLayout>()->IsActive();
404 if (upsideDown)
406 std::swap (topGlyphArea.top, bottomGlyphArea.top);
407 std::swap (topGlyphArea.bottom, bottomGlyphArea.bottom);
411 if (rightGlyphArea.PtInRect (logCoordinates))
412 result = base->GetFirstCopyTarget()
413 ? CGraphNodeStates::COLLAPSED_RIGHT | CGraphNodeStates::SPLIT_RIGHT
414 : 0;
416 if (topGlyphArea.PtInRect (logCoordinates))
417 result = base->GetSource()
418 ? CGraphNodeStates::COLLAPSED_ABOVE | CGraphNodeStates::SPLIT_ABOVE
419 : 0;
421 if (bottomGlyphArea.PtInRect (logCoordinates))
422 result = base->GetNext()
423 ? CGraphNodeStates::COLLAPSED_BELOW | CGraphNodeStates::SPLIT_BELOW
424 : 0;
426 // if some nodes have already been split, don't allow collapsing etc.
428 CSyncPointer<const CGraphNodeStates> nodeStates (m_state.GetNodeStates());
429 if (result & nodeStates->GetFlags (base))
430 result = 0;
431 #endif
432 return result;
434 #if 0
435 const CRevisionGraphState::SVisibleGlyph* CRevisionGraphWnd::GetHitGlyph (CPoint point) const
437 float glyphSize = GLYPH_SIZE * m_fZoomFactor;
439 CSyncPointer<const CRevisionGraphState::TVisibleGlyphs>
440 visibleGlyphs (m_state.GetVisibleGlyphs());
442 for (size_t i = 0, count = visibleGlyphs->size(); i < count; ++i)
444 const CRevisionGraphState::SVisibleGlyph* entry = &(*visibleGlyphs)[i];
446 float xRel = point.x - entry->leftTop.X;
447 float yRel = point.y - entry->leftTop.Y;
449 if ( (xRel >= 0) && (xRel < glyphSize)
450 && (yRel >= 0) && (yRel < glyphSize))
452 return entry;
456 return nullptr;
458 #endif
459 void CRevisionGraphWnd::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
461 SCROLLINFO sinfo = {0};
462 sinfo.cbSize = sizeof(SCROLLINFO);
463 GetScrollInfo(SB_HORZ, &sinfo);
465 // Determine the new position of scroll box.
466 switch (nSBCode)
468 case SB_LEFT: // Scroll to far left.
469 sinfo.nPos = sinfo.nMin;
470 break;
471 case SB_RIGHT: // Scroll to far right.
472 sinfo.nPos = sinfo.nMax;
473 break;
474 case SB_ENDSCROLL: // End scroll.
475 break;
476 case SB_LINELEFT: // Scroll left.
477 if (sinfo.nPos > sinfo.nMin)
478 sinfo.nPos--;
479 break;
480 case SB_LINERIGHT: // Scroll right.
481 if (sinfo.nPos < sinfo.nMax)
482 ++sinfo.nPos;
483 break;
484 case SB_PAGELEFT: // Scroll one page left.
486 if (sinfo.nPos > sinfo.nMin)
487 sinfo.nPos = max(sinfo.nMin, sinfo.nPos - (int) sinfo.nPage);
489 break;
490 case SB_PAGERIGHT: // Scroll one page right.
492 if (sinfo.nPos < sinfo.nMax)
493 sinfo.nPos = min(sinfo.nMax, sinfo.nPos + (int) sinfo.nPage);
495 break;
496 case SB_THUMBPOSITION: // Scroll to absolute position. nPos is the position
497 sinfo.nPos = sinfo.nTrackPos; // of the scroll box at the end of the drag operation.
498 break;
499 case SB_THUMBTRACK: // Drag scroll box to specified position. nPos is the
500 sinfo.nPos = sinfo.nTrackPos; // position that the scroll box has been dragged to.
501 break;
503 SetScrollInfo(SB_HORZ, &sinfo);
504 Invalidate (FALSE);
505 __super::OnHScroll(nSBCode, nPos, pScrollBar);
508 void CRevisionGraphWnd::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
510 SCROLLINFO sinfo = {0};
511 sinfo.cbSize = sizeof(SCROLLINFO);
512 GetScrollInfo(SB_VERT, &sinfo);
514 // Determine the new position of scroll box.
515 switch (nSBCode)
517 case SB_LEFT: // Scroll to far left.
518 sinfo.nPos = sinfo.nMin;
519 break;
520 case SB_RIGHT: // Scroll to far right.
521 sinfo.nPos = sinfo.nMax;
522 break;
523 case SB_ENDSCROLL: // End scroll.
524 break;
525 case SB_LINELEFT: // Scroll left.
526 if (sinfo.nPos > sinfo.nMin)
527 sinfo.nPos--;
528 break;
529 case SB_LINERIGHT: // Scroll right.
530 if (sinfo.nPos < sinfo.nMax)
531 ++sinfo.nPos;
532 break;
533 case SB_PAGELEFT: // Scroll one page left.
535 if (sinfo.nPos > sinfo.nMin)
536 sinfo.nPos = max(sinfo.nMin, sinfo.nPos - (int) sinfo.nPage);
538 break;
539 case SB_PAGERIGHT: // Scroll one page right.
541 if (sinfo.nPos < sinfo.nMax)
542 sinfo.nPos = min(sinfo.nMax, sinfo.nPos + (int) sinfo.nPage);
544 break;
545 case SB_THUMBPOSITION: // Scroll to absolute position. nPos is the position
546 sinfo.nPos = sinfo.nTrackPos; // of the scroll box at the end of the drag operation.
547 break;
548 case SB_THUMBTRACK: // Drag scroll box to specified position. nPos is the
549 sinfo.nPos = sinfo.nTrackPos; // position that the scroll box has been dragged to.
550 break;
552 SetScrollInfo(SB_VERT, &sinfo);
553 Invalidate(FALSE);
554 __super::OnVScroll(nSBCode, nPos, pScrollBar);
557 void CRevisionGraphWnd::OnSize(UINT nType, int cx, int cy)
559 __super::OnSize(nType, cx, cy);
560 SetScrollbars(GetScrollPos(SB_VERT), GetScrollPos(SB_HORZ));
561 Invalidate(FALSE);
564 void CRevisionGraphWnd::OnLButtonDown(UINT nFlags, CPoint point)
566 if (IsUpdateJobRunning())
567 return __super::OnLButtonDown(nFlags, point);
569 // CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());
571 SetFocus();
572 bool bHit = false;
573 bool bControl = !!(GetKeyState(VK_CONTROL)&0x8000);
574 bool bOverview = m_bShowOverview && m_OverviewRect.PtInRect(point);
575 if (! bOverview)
577 #if 0
578 const CRevisionGraphState::SVisibleGlyph* hitGlyph
579 = GetHitGlyph (point);
581 if (hitGlyph)
583 ToggleNodeFlag (hitGlyph->node, hitGlyph->state);
584 return __super::OnLButtonDown(nFlags, point);
586 #endif
587 auto nodeIndex = GetHitNode(point);
588 if (nodeIndex)
590 if (bControl)
592 if (m_SelectedEntry1 == nodeIndex)
594 if (m_SelectedEntry2)
596 m_SelectedEntry1 = m_SelectedEntry2;
597 m_SelectedEntry2 = nullptr;
599 else
600 m_SelectedEntry1 = nullptr;
602 else if (m_SelectedEntry2 == nodeIndex)
603 m_SelectedEntry2 = nullptr;
604 else if (m_SelectedEntry1)
605 m_SelectedEntry2 = nodeIndex;
606 else
607 m_SelectedEntry1 = nodeIndex;
609 else
611 if (m_SelectedEntry1 == nodeIndex)
612 m_SelectedEntry1 = nullptr;
613 else
614 m_SelectedEntry1 = nodeIndex;
615 m_SelectedEntry2 = nullptr;
617 bHit = true;
618 Invalidate(FALSE);
622 if ((!bHit)&&(!bControl)&&(!bOverview))
624 m_SelectedEntry1 = nullptr;
625 m_SelectedEntry2 = nullptr;
626 m_bIsCanvasMove = true;
627 Invalidate(FALSE);
628 if (m_bShowOverview && m_OverviewRect.PtInRect(point))
629 m_bIsCanvasMove = false;
631 m_ptMoveCanvas = point;
633 UINT uEnable = MF_BYCOMMAND;
634 if (m_SelectedEntry1 && m_SelectedEntry2)
635 uEnable |= MF_ENABLED;
636 else
637 uEnable |= MF_GRAYED;
639 auto hMenu = GetParent()->GetMenu()->m_hMenu;
640 EnableMenuItem(hMenu, ID_VIEW_COMPAREREVISIONS, uEnable);
641 EnableMenuItem(hMenu, ID_VIEW_UNIFIEDDIFF, uEnable);
643 uEnable = MF_BYCOMMAND;
644 if (m_SelectedEntry1 && !m_SelectedEntry2)
645 uEnable |= MF_ENABLED;
646 else
647 uEnable |= MF_GRAYED;
649 EnableMenuItem(hMenu, ID_VIEW_UNIFIEDDIFFOFHEADREVISIONS, uEnable);
650 EnableMenuItem(hMenu, ID_VIEW_COMPAREHEADREVISIONS, uEnable);
652 __super::OnLButtonDown(nFlags, point);
655 void CRevisionGraphWnd::OnCaptureChanged(CWnd *pWnd)
657 __super::OnCaptureChanged(pWnd);
660 void CRevisionGraphWnd::OnLButtonUp(UINT nFlags, CPoint point)
662 if (!m_bIsCanvasMove)
663 return; // we don't have a rubberband, so no zooming necessary
665 m_bIsCanvasMove = false;
666 ReleaseCapture();
667 if (IsUpdateJobRunning())
668 return __super::OnLButtonUp(nFlags, point);
670 // zooming is finished
671 m_ptRubberEnd = CPoint(0,0);
672 CRect rect = GetClientRect();
673 int x = abs(m_ptMoveCanvas.x - point.x);
674 int y = abs(m_ptMoveCanvas.y - point.y);
676 if ((x < 20)&&(y < 20))
678 // too small zoom rectangle
679 // assume zooming by accident
680 Invalidate(FALSE);
681 __super::OnLButtonUp(nFlags, point);
682 return;
685 float xfact = float(rect.Width())/float(x);
686 float yfact = float(rect.Height())/float(y);
687 float fact = max(yfact, xfact);
689 // find out where to scroll to
690 x = min(m_ptMoveCanvas.x, point.x) + GetScrollPos(SB_HORZ);
691 y = min(m_ptMoveCanvas.y, point.y) + GetScrollPos(SB_VERT);
693 float fZoomfactor = m_fZoomFactor*fact;
694 if (fZoomfactor > 10 * MAX_ZOOM)
696 // with such a big zoomfactor, the user
697 // most likely zoomed by accident
698 Invalidate(FALSE);
699 __super::OnLButtonUp(nFlags, point);
700 return;
702 if (fZoomfactor > MAX_ZOOM)
704 fZoomfactor = MAX_ZOOM;
705 fact = fZoomfactor/m_fZoomFactor;
708 auto pDlg = static_cast<CRevisionGraphDlg*>(GetParent());
709 if (pDlg)
711 m_fZoomFactor = fZoomfactor;
712 pDlg->DoZoom (m_fZoomFactor);
713 SetScrollbars(int(float(y)*fact), int(float(x)*fact));
715 __super::OnLButtonUp(nFlags, point);
718 bool CRevisionGraphWnd::CancelMouseZoom()
720 bool bRet = m_bIsCanvasMove;
721 ReleaseCapture();
722 if (m_bIsCanvasMove)
723 Invalidate(FALSE);
724 m_bIsCanvasMove = false;
725 m_ptRubberEnd = CPoint(0,0);
726 return bRet;
729 INT_PTR CRevisionGraphWnd::OnToolHitTest(CPoint point, TOOLINFO* pTI) const
731 if (IsUpdateJobRunning())
732 return -1;
734 auto nodeIndex = GetHitNode(point);
735 if (m_tooltipIndex != nodeIndex)
737 // force tooltip to be updated
739 m_tooltipIndex = nodeIndex;
740 return -1;
743 if (!nodeIndex)
744 return -1;
746 // if ((GetHoverGlyphs (point) != 0) || (GetHitGlyph (point) != nullptr))
747 // return -1;
749 pTI->hwnd = this->m_hWnd;
750 CWnd::GetClientRect(&pTI->rect);
751 pTI->uFlags |= TTF_ALWAYSTIP | TTF_IDISHWND;
752 pTI->uId = (UINT_PTR)m_hWnd;
753 pTI->lpszText = LPSTR_TEXTCALLBACK;
755 return 1;
758 BOOL CRevisionGraphWnd::OnToolTipNotify(UINT /*id*/, NMHDR *pNMHDR, LRESULT *pResult)
760 if (pNMHDR->idFrom != (UINT_PTR)m_hWnd)
761 return FALSE;
763 POINT point;
764 DWORD ptW = GetMessagePos();
765 point.x = GET_X_LPARAM(ptW);
766 point.y = GET_Y_LPARAM(ptW);
767 ScreenToClient(&point);
769 CString strTipText = TooltipText (GetHitNode (point));
771 *pResult = 0;
772 if (strTipText.IsEmpty())
773 return TRUE;
775 CSize tooltipSize = UsableTooltipRect();
776 strTipText = DisplayableText (strTipText, tooltipSize);
778 // need to handle both ANSI and UNICODE versions of the message
779 if (pNMHDR->code == TTN_NEEDTEXTA)
781 TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;
782 ::SendMessage(pNMHDR->hwndFrom, TTM_SETMAXTIPWIDTH, 0, tooltipSize.cx);
783 pTTTA->lpszText = m_szTip;
784 WideCharToMultiByte(CP_ACP, 0, strTipText, -1, m_szTip, strTipText.GetLength()+1, 0, 0);
786 else
788 TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;
789 ::SendMessage(pNMHDR->hwndFrom, TTM_SETMAXTIPWIDTH, 0, tooltipSize.cx);
790 lstrcpyn(m_wszTip, strTipText, strTipText.GetLength()+1);
791 pTTTW->lpszText = m_wszTip;
794 // show the tooltip for 32 seconds. A higher value than 32767 won't work
795 // even though it's nowhere documented!
796 ::SendMessage(pNMHDR->hwndFrom, TTM_SETDELAYTIME, TTDT_AUTOPOP, 32767);
797 return TRUE; // message was handled
800 CSize CRevisionGraphWnd::UsableTooltipRect()
802 // get screen size
804 int screenWidth = GetSystemMetrics(SM_CXSCREEN);
805 int screenHeight = GetSystemMetrics(SM_CYSCREEN);
807 // get current mouse position
809 CPoint cursorPos;
810 if (GetCursorPos (&cursorPos) == FALSE)
812 // we could not determine the mouse position
813 // use screen / 2 minus some safety margin
815 return CSize (screenWidth / 2 - 20, screenHeight / 2 - 20);
818 // tool tip will display in the biggest sector beside the cursor
819 // deduct some safety margin (for the mouse cursor itself
821 CSize biggestSector
822 ( max (screenWidth - cursorPos.x - 40, cursorPos.x - 24)
823 , max (screenHeight - cursorPos.y - 40, cursorPos.y - 24));
825 return biggestSector;
828 CString CRevisionGraphWnd::DisplayableText ( const CString& wholeText
829 , const CSize& tooltipSize)
831 CDC* dc = GetDC();
832 if (!dc)
834 // no access to the device context -> truncate hard at 1000 chars
836 return wholeText.GetLength() >= MAX_TT_LENGTH_DEFAULT
837 ? wholeText.Left(MAX_TT_LENGTH_DEFAULT - 4) + L" ..."
838 : wholeText;
841 // select the tooltip font
843 NONCLIENTMETRICS metrics;
844 metrics.cbSize = sizeof (metrics);
845 SystemParametersInfo (SPI_GETNONCLIENTMETRICS, metrics.cbSize, &metrics, 0);
847 CFont font;
848 font.CreateFontIndirect(&metrics.lfStatusFont);
849 CFont* pOldFont = dc->SelectObject (&font);
851 // split into lines and fill the tooltip rect
853 CString result;
855 int remainingHeight = tooltipSize.cy;
856 int pos = 0;
857 while (pos < wholeText.GetLength())
859 // extract a whole line
861 int nextPos = wholeText.Find ('\n', pos);
862 if (nextPos < 0)
863 nextPos = wholeText.GetLength();
865 CString line = wholeText.Mid (pos, nextPos-pos+1);
867 // find a way to make it fit
869 CSize size = dc->GetTextExtent (line);
870 while (size.cx > tooltipSize.cx)
872 line.Delete (line.GetLength()-1);
873 int nextPos2 = line.ReverseFind (' ');
874 if (nextPos2 < 0)
875 break;
877 line.Delete (nextPos2+1, line.GetLength() - pos-1);
878 size = dc->GetTextExtent (line);
881 // enough room for the new line?
883 remainingHeight -= size.cy;
884 if (remainingHeight <= size.cy)
886 result += L"...";
887 break;
890 // add the line
892 result += line;
893 pos += line.GetLength();
896 // relase temp. resources
898 dc->SelectObject (pOldFont);
899 ReleaseDC(dc);
901 // ready
903 return result;
906 CString CRevisionGraphWnd::TooltipText(ogdf::node index)
908 if(index)
910 CString str;
911 CGitHash hash = m_logEntries[index->index()];
912 GitRevLoglist* rev = this->m_LogCache.GetCacheData(hash);
913 str += rev->m_CommitHash.ToString();
914 str += L'\n';
915 str += rev->GetAuthorName() + L' ' + rev->GetAuthorEmail();
916 str += L' ';
917 str += rev->GetAuthorDate().Format(L"%Y-%m-%d %H:%M");
918 str += L"\n\n" + rev->GetSubject();
919 str += L'\n';
920 str += rev->GetBody();
921 return str;
922 }else
923 return CString();
926 void CRevisionGraphWnd::SaveGraphAs(CString sSavePath)
928 CString extension = CPathUtils::GetFileExtFromPath(sSavePath);
929 if (extension.CompareNoCase(L".wmf") == 0)
931 // save the graph as an enhanced metafile
932 CMetaFileDC wmfDC;
933 wmfDC.CreateEnhanced(nullptr, sSavePath, nullptr, L"TortoiseGit\0Revision Graph\0\0");
934 float fZoom = m_fZoomFactor;
935 m_fZoomFactor = DEFAULT_ZOOM;
936 DoZoom(m_fZoomFactor);
937 CRect rect;
938 rect = GetViewRect();
939 GraphicsDevice dev;
940 dev.pDC = &wmfDC;
941 DrawGraph(dev, rect, 0, 0, true);
942 HENHMETAFILE hemf = wmfDC.CloseEnhanced();
943 DeleteEnhMetaFile(hemf);
944 m_fZoomFactor = fZoom;
945 DoZoom(m_fZoomFactor);
947 else if (extension.CompareNoCase(L".svg") == 0)
949 // save the graph as a scalable vector graphic
950 SVG svg;
951 float fZoom = m_fZoomFactor;
952 m_fZoomFactor = DEFAULT_ZOOM;
953 DoZoom(m_fZoomFactor);
954 CRect rect;
955 rect = GetViewRect();
956 svg.SetViewSize(rect.Width(), rect.Height());
957 GraphicsDevice dev;
958 dev.pSVG = &svg;
959 DrawGraph(dev, rect, 0, 0, true);
960 svg.Save(sSavePath);
961 m_fZoomFactor = fZoom;
962 DoZoom(m_fZoomFactor);
964 else if (extension.CompareNoCase(L".gv") == 0)
966 Graphviz graphviz;
967 float fZoom = m_fZoomFactor;
968 m_fZoomFactor = DEFAULT_ZOOM;
969 DoZoom(m_fZoomFactor);
970 CRect rect;
971 rect = GetViewRect();
972 GraphicsDevice dev;
973 dev.pGraphviz = &graphviz;
974 DrawGraph(dev, rect, 0, 0, true);
975 graphviz.Save(sSavePath);
976 m_fZoomFactor = fZoom;
977 DoZoom(m_fZoomFactor);
979 else
981 // save the graph as a pixel picture instead of a vector picture
982 // create dc to paint on
985 CString sErrormessage;
986 CWindowDC ddc(this);
987 CDC dc;
988 if (!dc.CreateCompatibleDC(&ddc))
990 CFormatMessageWrapper errorDetails;
991 if( errorDetails )
992 MessageBox(errorDetails, L"Error", MB_OK | MB_ICONINFORMATION);
994 return;
996 CRect rect;
997 rect = GetGraphRect();
998 rect.bottom = (LONG)(float(rect.Height()) * m_fZoomFactor);
999 rect.right = (LONG)(float(rect.Width()) * m_fZoomFactor);
1000 BITMAPINFO bmi = { 0 };
1001 HBITMAP hbm;
1002 LPBYTE pBits;
1003 // Fill out the fields you care about.
1004 bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
1005 bmi.bmiHeader.biWidth = rect.Width();
1006 bmi.bmiHeader.biHeight = rect.Height();
1007 bmi.bmiHeader.biPlanes = 1;
1008 bmi.bmiHeader.biBitCount = 24;
1009 bmi.bmiHeader.biCompression = BI_RGB;
1011 // Create the surface.
1012 hbm = CreateDIBSection(ddc.m_hDC, &bmi, DIB_RGB_COLORS,(void**)&pBits, nullptr, 0);
1013 if (!hbm)
1015 CMessageBox::Show(m_hWnd, IDS_REVGRAPH_ERR_NOMEMORY, IDS_APPNAME, MB_ICONERROR);
1016 return;
1018 HBITMAP oldbm = (HBITMAP)dc.SelectObject(hbm);
1019 // paint the whole graph
1020 GraphicsDevice dev;
1021 dev.pDC = &dc;
1022 DrawGraph(dev, rect, 0, 0, true);
1023 // now use GDI+ to save the picture
1024 CLSID encoderClsid;
1026 Bitmap bitmap(hbm, nullptr);
1027 if (bitmap.GetLastStatus()==Ok)
1029 // Get the CLSID of the encoder.
1030 int ret = 0;
1031 if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(L".png") == 0)
1032 ret = GetEncoderClsid(L"image/png", &encoderClsid);
1033 else if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(L".jpg") == 0)
1034 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1035 else if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(L".jpeg") == 0)
1036 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1037 else if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(L".bmp") == 0)
1038 ret = GetEncoderClsid(L"image/bmp", &encoderClsid);
1039 else if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(L".gif") == 0)
1040 ret = GetEncoderClsid(L"image/gif", &encoderClsid);
1041 else
1043 sSavePath += L".jpg";
1044 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1046 if (ret >= 0)
1048 CStringW tfile = CStringW(sSavePath);
1049 bitmap.Save(tfile, &encoderClsid, nullptr);
1051 else
1052 sErrormessage.Format(IDS_REVGRAPH_ERR_NOENCODER, (LPCTSTR)CPathUtils::GetFileExtFromPath(sSavePath));
1054 else
1055 sErrormessage.LoadString(IDS_REVGRAPH_ERR_NOBITMAP);
1057 dc.SelectObject(oldbm);
1058 DeleteObject(hbm);
1059 dc.DeleteDC();
1060 if (!sErrormessage.IsEmpty())
1061 ::MessageBox(m_hWnd, sErrormessage, L"TortoiseGit", MB_ICONERROR);
1063 catch (CException * pE)
1065 TCHAR szErrorMsg[2048] = { 0 };
1066 pE->GetErrorMessage(szErrorMsg, 2048);
1067 pE->Delete();
1068 ::MessageBox(m_hWnd, szErrorMsg, L"TortoiseGit", MB_ICONERROR);
1073 BOOL CRevisionGraphWnd::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
1075 if (IsUpdateJobRunning())
1076 return __super::OnMouseWheel(nFlags, zDelta, pt);
1078 if (GetKeyState(VK_CONTROL)&0x8000)
1080 float newZoom = m_fZoomFactor * (zDelta < 0 ? ZOOM_STEP : 1.0f/ZOOM_STEP);
1081 DoZoom (max (MIN_ZOOM, min (MAX_ZOOM, newZoom)));
1083 else
1085 int orientation = (GetKeyState(VK_SHIFT) & 0x8000) ? SB_HORZ : SB_VERT;
1086 int pos = GetScrollPos(orientation);
1087 pos -= (zDelta);
1088 SetScrollPos(orientation, pos);
1089 Invalidate(FALSE);
1091 return __super::OnMouseWheel(nFlags, zDelta, pt);
1094 void CRevisionGraphWnd::OnMouseHWheel(UINT nFlags, short zDelta, CPoint pt)
1096 if (IsUpdateJobRunning())
1097 return __super::OnMouseHWheel(nFlags, zDelta, pt);
1099 int orientation = (GetKeyState(VK_SHIFT) & 0x8000) ? SB_VERT : SB_HORZ;
1100 int pos = GetScrollPos(orientation);
1101 pos -= (zDelta);
1102 SetScrollPos(orientation, pos);
1103 Invalidate(FALSE);
1105 return __super::OnMouseHWheel(nFlags, zDelta, pt);
1108 bool CRevisionGraphWnd::UpdateSelectedEntry(ogdf::node clickedentry)
1110 if (!m_SelectedEntry1 && !clickedentry)
1111 return false;
1113 if (!m_SelectedEntry1)
1115 m_SelectedEntry1 = clickedentry;
1116 Invalidate(FALSE);
1118 if (!m_SelectedEntry2 && clickedentry != m_SelectedEntry1)
1120 m_SelectedEntry1 = clickedentry;
1121 Invalidate(FALSE);
1123 if (m_SelectedEntry1 && m_SelectedEntry2)
1125 if ((m_SelectedEntry2 != clickedentry)&&(m_SelectedEntry1 != clickedentry))
1126 return false;
1128 if (!m_SelectedEntry1)
1129 return false;
1131 return true;
1134 void CRevisionGraphWnd::AppendMenu
1135 ( CMenu& popup
1136 , UINT title
1137 , UINT command
1138 , UINT flags)
1140 // separate different groups / section within the context menu
1142 if (popup.GetMenuItemCount() > 0)
1144 UINT lastCommand = popup.GetMenuItemID (popup.GetMenuItemCount()-1);
1145 if ((lastCommand & GROUP_MASK) != (command & GROUP_MASK))
1146 popup.AppendMenu(MF_SEPARATOR, NULL);
1149 // actually add the new item
1151 CString titleString;
1152 titleString.LoadString (title);
1153 popup.AppendMenu (MF_STRING | flags, command, titleString);
1156 void CRevisionGraphWnd::AppendMenu(CMenu &popup, CString title, UINT command, CString *extra, CMenu *submenu)
1158 // separate different groups / section within the context menu
1159 if (popup.GetMenuItemCount() > 0)
1161 UINT lastCommand = popup.GetMenuItemID(popup.GetMenuItemCount() - 1);
1162 if ((lastCommand & GROUP_MASK) != (command & GROUP_MASK))
1163 popup.AppendMenu(MF_SEPARATOR, NULL);
1166 // actually add the new item
1167 MENUITEMINFO mii = { 0 };
1168 mii.cbSize = sizeof(MENUITEMINFO);
1169 mii.fMask = MIIM_STRING | MIIM_ID | (extra ? MIIM_DATA : 0) | (submenu ? MIIM_SUBMENU : 0);
1170 mii.wID = command;
1171 mii.hSubMenu = submenu ? submenu->m_hMenu : nullptr;
1172 mii.dwItemData = (ULONG_PTR)extra;
1173 mii.dwTypeData = title.GetBuffer();
1174 InsertMenuItem(popup, popup.GetMenuItemCount(), TRUE, &mii);
1175 title.ReleaseBuffer();
1178 void CRevisionGraphWnd::AddGraphOps (CMenu& /*popup*/, const CVisibleGraphNode * /*node*/)
1180 #if 0
1181 CSyncPointer<CGraphNodeStates> nodeStates (m_state.GetNodeStates());
1183 if (!node)
1185 DWORD state = nodeStates->GetCombinedFlags();
1186 if (state != 0)
1188 if (state & CGraphNodeStates::COLLAPSED_ALL)
1189 AppendMenu (popup, IDS_REVGRAPH_POPUP_EXPAND_ALL, ID_EXPAND_ALL);
1191 if (state & CGraphNodeStates::SPLIT_ALL)
1192 AppendMenu (popup, IDS_REVGRAPH_POPUP_JOIN_ALL, ID_JOIN_ALL);
1195 else
1197 DWORD state = nodeStates->GetFlags (node);
1199 if (node->GetSource() || (state & CGraphNodeStates::COLLAPSED_ABOVE))
1200 AppendMenu ( popup
1201 , (state & CGraphNodeStates::COLLAPSED_ABOVE)
1202 ? IDS_REVGRAPH_POPUP_EXPAND_ABOVE
1203 : IDS_REVGRAPH_POPUP_COLLAPSE_ABOVE
1204 , ID_GRAPH_EXPANDCOLLAPSE_ABOVE);
1206 if (node->GetFirstCopyTarget() || (state & CGraphNodeStates::COLLAPSED_RIGHT))
1207 AppendMenu ( popup
1208 , (state & CGraphNodeStates::COLLAPSED_RIGHT)
1209 ? IDS_REVGRAPH_POPUP_EXPAND_RIGHT
1210 : IDS_REVGRAPH_POPUP_COLLAPSE_RIGHT
1211 , ID_GRAPH_EXPANDCOLLAPSE_RIGHT);
1213 if (node->GetNext() || (state & CGraphNodeStates::COLLAPSED_BELOW))
1214 AppendMenu ( popup
1215 , (state & CGraphNodeStates::COLLAPSED_BELOW)
1216 ? IDS_REVGRAPH_POPUP_EXPAND_BELOW
1217 : IDS_REVGRAPH_POPUP_COLLAPSE_BELOW
1218 , ID_GRAPH_EXPANDCOLLAPSE_BELOW);
1220 if (node->GetSource() || (state & CGraphNodeStates::SPLIT_ABOVE))
1221 AppendMenu ( popup
1222 , (state & CGraphNodeStates::SPLIT_ABOVE)
1223 ? IDS_REVGRAPH_POPUP_JOIN_ABOVE
1224 : IDS_REVGRAPH_POPUP_SPLIT_ABOVE
1225 , ID_GRAPH_SPLITJOIN_ABOVE);
1227 if (node->GetFirstCopyTarget() || (state & CGraphNodeStates::SPLIT_RIGHT))
1228 AppendMenu ( popup
1229 , (state & CGraphNodeStates::SPLIT_RIGHT)
1230 ? IDS_REVGRAPH_POPUP_JOIN_RIGHT
1231 : IDS_REVGRAPH_POPUP_SPLIT_RIGHT
1232 , ID_GRAPH_SPLITJOIN_RIGHT);
1234 if (node->GetNext() || (state & CGraphNodeStates::SPLIT_BELOW))
1235 AppendMenu ( popup
1236 , (state & CGraphNodeStates::SPLIT_BELOW)
1237 ? IDS_REVGRAPH_POPUP_JOIN_BELOW
1238 : IDS_REVGRAPH_POPUP_SPLIT_BELOW
1239 , ID_GRAPH_SPLITJOIN_BELOW);
1241 #endif
1244 CString CRevisionGraphWnd::GetSelectedURL() const
1246 #if 0
1247 if (!m_SelectedEntry1)
1248 return CString();
1250 CString URL = m_state.GetRepositoryRoot()
1251 + CUnicodeUtils::GetUnicode (m_SelectedEntry1->GetPath().GetPath().c_str());
1252 URL = CUnicodeUtils::GetUnicode(CPathUtils::PathEscape(CUnicodeUtils::GetUTF8(URL)));
1254 return URL;
1255 #endif
1256 return CString();
1259 CString CRevisionGraphWnd::GetWCURL() const
1261 #if 0
1262 CTGitPath path (m_sPath);
1263 if (path.IsUrl())
1264 return CString();
1266 SVNInfo info;
1267 const SVNInfoData * status
1268 = info.GetFirstFileInfo (path, SVNRev(), SVNRev());
1270 return !status ? CString() : status->url;
1271 #endif
1272 return CString();
1275 void CRevisionGraphWnd::DoShowLog()
1277 if (!m_SelectedEntry1)
1278 return;
1280 CString sCmd;
1282 if(m_SelectedEntry2)
1283 sCmd.Format(L"/command:log %s /startrev:%s /endrev:%s",
1284 this->m_sPath.IsEmpty() ? L"" : (LPCTSTR)(L"/path:\"" + this->m_sPath + L'"'),
1285 (LPCTSTR)this->m_logEntries[m_SelectedEntry1->index()].ToString(),
1286 (LPCTSTR)this->m_logEntries[m_SelectedEntry2->index()].ToString());
1287 else
1288 sCmd.Format(L"/command:log %s /endrev:%s",
1289 (LPCTSTR)this->m_sPath.IsEmpty() ? L"" : (L"/path:\"" + this->m_sPath + L'"'),
1290 (LPCTSTR)this->m_logEntries[m_SelectedEntry1->index()].ToString());
1292 CAppUtils::RunTortoiseGitProc(sCmd);
1295 void CRevisionGraphWnd::DoCheckForModification()
1297 CChangedDlg dlg;
1298 dlg.m_pathList = CTGitPathList (CTGitPath (m_sPath));
1299 dlg.DoModal();
1302 void CRevisionGraphWnd::DoMergeTo()
1304 #if 0
1305 CString URL = GetSelectedURL();
1306 CString path = m_sPath;
1307 CBrowseFolder folderBrowser;
1308 folderBrowser.SetInfo(CString(MAKEINTRESOURCE(IDS_LOG_MERGETO)));
1309 if (folderBrowser.Show(GetSafeHwnd(), path, path) == CBrowseFolder::OK)
1311 CSVNProgressDlg dlg;
1312 dlg.SetCommand(CSVNProgressDlg::SVNProgress_Merge);
1313 dlg.SetPathList(CTGitPathList(CTGitPath(path)));
1314 dlg.SetUrl(URL);
1315 dlg.SetSecondUrl(URL);
1316 SVNRevRangeArray revarray;
1317 revarray.AddRevRange (m_SelectedEntry1->GetRevision()-1, svn_revnum_t(m_SelectedEntry1->GetRevision()));
1318 dlg.SetRevisionRanges(revarray);
1319 dlg.DoModal();
1321 #endif
1324 void CRevisionGraphWnd::DoUpdate()
1326 #if 0
1327 CSVNProgressDlg progDlg;
1328 progDlg.SetCommand (CSVNProgressDlg::SVNProgress_Update);
1329 progDlg.SetOptions (0); // don't ignore externals
1330 progDlg.SetPathList (CTGitPathList (CTGitPath (m_sPath)));
1331 progDlg.SetRevision (m_SelectedEntry1->GetRevision());
1332 progDlg.SetDepth();
1333 progDlg.DoModal();
1335 if (m_state.GetFetchedWCState())
1336 m_parent->UpdateFullHistory();
1337 #endif
1340 void CRevisionGraphWnd::DoSwitch(CString rev)
1342 CAppUtils::PerformSwitch(GetSafeHwnd(), rev);
1345 void CRevisionGraphWnd::DoSwitchToHead()
1347 #if 0
1348 CSVNProgressDlg progDlg;
1349 progDlg.SetCommand (CSVNProgressDlg::SVNProgress_Switch);
1350 progDlg.SetPathList (CTGitPathList (CTGitPath (m_sPath)));
1351 progDlg.SetUrl (GetSelectedURL());
1352 progDlg.SetRevision (SVNRev::REV_HEAD);
1353 progDlg.SetPegRevision (m_SelectedEntry1->GetRevision());
1354 progDlg.DoModal();
1356 if (m_state.GetFetchedWCState())
1357 m_parent->UpdateFullHistory();
1358 #endif
1361 void CRevisionGraphWnd::DoBrowseRepo()
1363 if (!m_SelectedEntry1)
1364 return;
1366 CString sCmd;
1367 sCmd.Format(L"/command:repobrowser %s /rev:%s",
1368 this->m_sPath.IsEmpty() ? L"" : (LPCTSTR)(L"/path:\"" + this->m_sPath + L'"'),
1369 (LPCTSTR)GetFriendRefName(m_SelectedEntry1));
1371 CAppUtils::RunTortoiseGitProc(sCmd);
1374 void CRevisionGraphWnd::ResetNodeFlags (DWORD /*flags*/)
1376 // m_state.GetNodeStates()->ResetFlags (flags);
1377 // m_parent->StartWorkerThread();
1380 void CRevisionGraphWnd::ToggleNodeFlag (const CVisibleGraphNode * /*node*/, DWORD /*flag*/)
1382 #if 0
1383 CSyncPointer<CGraphNodeStates> nodeStates (m_state.GetNodeStates());
1385 if (nodeStates->GetFlags (node) & flag)
1386 nodeStates->ResetFlags (node, flag);
1387 else
1388 nodeStates->SetFlags (node, flag);
1390 m_parent->StartWorkerThread();
1391 #endif
1394 void CRevisionGraphWnd::DoCopyRefs()
1396 if (!m_SelectedEntry1)
1397 return;
1399 STRING_VECTOR list = GetFriendRefNames(m_SelectedEntry1);
1400 CString text;
1401 if (list.empty())
1402 text = m_logEntries[m_SelectedEntry1->index()].ToString();
1403 for (size_t i = 0; i < list.size(); ++i)
1405 if (i > 0)
1406 text.Append(L"\r\n");
1407 text.Append(list[i]);
1409 CStringUtils::WriteAsciiStringToClipboard(text, m_hWnd);
1412 void CRevisionGraphWnd::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
1414 if (IsUpdateJobRunning())
1415 return;
1417 CPoint clientpoint = point;
1418 this->ScreenToClient(&clientpoint);
1420 auto nodeIndex = GetHitNode(clientpoint);
1422 if ( !UpdateSelectedEntry (nodeIndex))
1423 return;
1425 CMenu popup;
1426 if (!popup.CreatePopupMenu())
1427 return;
1429 bool bothPresent = (m_SelectedEntry2 && m_SelectedEntry1);
1431 AppendMenu (popup, IDS_REPOBROWSE_SHOWLOG, ID_SHOWLOG);
1433 STRING_VECTOR branchNames;
1434 STRING_VECTOR allRefNames;
1435 if (m_SelectedEntry1 && (m_SelectedEntry2 == nullptr))
1437 AppendMenu(popup, IDS_LOG_BROWSEREPO, ID_BROWSEREPO);
1439 CString currentBranch = g_Git.GetCurrentBranch();
1440 CGit::REF_TYPE refType = CGit::LOCAL_BRANCH;
1441 branchNames = GetFriendRefNames(m_SelectedEntry1, &currentBranch, &refType);
1442 if (branchNames.size() == 1)
1444 CString text;
1445 text.Format(L"%s \"%s\"", (LPCTSTR)CString(MAKEINTRESOURCE(IDS_SWITCH_BRANCH)), (LPCTSTR)branchNames[0]);
1446 AppendMenu(popup, text, ID_SWITCH, &branchNames[0]);
1448 else if (branchNames.size() > 1)
1450 CMenu switchMenu;
1451 switchMenu.CreatePopupMenu();
1452 for (size_t i = 0; i < branchNames.size(); ++i)
1453 AppendMenu(switchMenu, branchNames[i], ID_SWITCH + ((int)(i + 1) << 16), &branchNames[i]);
1454 AppendMenu(popup, CString(MAKEINTRESOURCE(IDS_SWITCH_BRANCH)), ID_SWITCH, nullptr, &switchMenu);
1457 AppendMenu(popup, IDS_COPY_REF_NAMES, ID_COPYREFS);
1459 allRefNames = GetFriendRefNames(m_SelectedEntry1, &currentBranch);
1460 if (allRefNames.size() == 1)
1462 CString str;
1463 str.LoadString(IDS_DELETE_BRANCHTAG_SHORT);
1464 str += L' ';
1465 str += allRefNames[0];
1466 AppendMenu(popup, str, ID_DELETE, &allRefNames[0]);
1468 else if (allRefNames.size() > 1)
1470 CString str;
1471 str.LoadString(IDS_DELETE_BRANCHTAG);
1472 CIconMenu submenu;
1473 submenu.CreatePopupMenu();
1474 for (size_t i = 0; i < allRefNames.size(); ++i)
1476 submenu.AppendMenuIcon(ID_DELETE + (i << 16), allRefNames[i]);
1477 submenu.SetMenuItemData(ID_DELETE + (i << 16), (ULONG_PTR)&allRefNames[i]);
1479 submenu.AppendMenuIcon(ID_DELETE + (allRefNames.size() << 16), IDS_ALL);
1480 submenu.SetMenuItemData(ID_DELETE + (allRefNames.size() << 16), (ULONG_PTR)MAKEINTRESOURCE(IDS_ALL));
1482 AppendMenu(popup, str, ID_DELETE, nullptr, &submenu);
1485 AppendMenu(popup, IDS_REVGRAPH_POPUP_COMPAREHEADS, ID_COMPAREHEADS);
1486 AppendMenu(popup, IDS_REVGRAPH_POPUP_UNIDIFFHEADS, ID_UNIDIFFHEADS);
1488 AppendMenu(popup, IDS_LOG_POPUP_COMPARE, ID_COMPAREWT);
1491 if (bothPresent)
1493 AppendMenu (popup, IDS_REVGRAPH_POPUP_COMPAREREVS, ID_COMPAREREVS);
1494 AppendMenu (popup, IDS_REVGRAPH_POPUP_UNIDIFFREVS, ID_UNIDIFFREVS);
1497 // AddGraphOps (popup, clickedentry);
1499 // if the context menu is invoked through the keyboard, we have to use
1500 // a calculated position on where to anchor the menu on
1501 if ((point.x == -1) && (point.y == -1))
1503 CRect rect = GetWindowRect();
1504 point = rect.CenterPoint();
1507 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RIGHTBUTTON, point.x, point.y, this);
1508 switch (cmd & 0xFFFF)
1510 case ID_COMPAREREVS:
1511 if (m_SelectedEntry1)
1512 CompareRevs(false);
1513 break;
1514 case ID_UNIDIFFREVS:
1515 if (m_SelectedEntry1)
1516 UnifiedDiffRevs(false);
1517 break;
1518 case ID_UNIDIFFHEADS:
1519 if (m_SelectedEntry1)
1520 UnifiedDiffRevs(true);
1521 break;
1522 case ID_SHOWLOG:
1523 DoShowLog();
1524 break;
1525 case ID_SWITCH:
1527 MENUITEMINFO mii = { 0 };
1528 mii.cbSize = sizeof(mii);
1529 mii.fMask |= MIIM_DATA;
1530 GetMenuItemInfo(popup, cmd, FALSE, &mii);
1531 CString *rev = (CString *)mii.dwItemData;
1532 if (rev)
1534 DoSwitch(*rev);
1535 m_parent->UpdateFullHistory();
1537 break;
1539 case ID_COPYREFS:
1540 DoCopyRefs();
1541 break;
1542 case ID_DELETE:
1544 MENUITEMINFO mii = { 0 };
1545 mii.cbSize = sizeof(mii);
1546 mii.fMask |= MIIM_DATA;
1547 GetMenuItemInfo(popup, cmd, FALSE, &mii);
1548 CString *rev = (CString*)mii.dwItemData;
1549 if (!rev)
1550 break;
1552 CString shortname;
1553 if (rev == (CString*)MAKEINTRESOURCE(IDS_ALL))
1555 bool nothingDeleted = true;
1556 for (const auto& ref : allRefNames)
1558 if (!CAppUtils::DeleteRef(this, ref))
1559 break;
1560 nothingDeleted = false;
1562 if (nothingDeleted)
1563 return;
1565 else if (!CAppUtils::DeleteRef(this, *rev))
1566 return;
1568 m_parent->UpdateFullHistory();
1569 break;
1571 case ID_BROWSEREPO:
1572 DoBrowseRepo();
1573 break;
1574 case ID_COMPAREHEADS:
1575 if (m_SelectedEntry1)
1576 CompareRevs(L"HEAD");
1577 break;
1578 case ID_COMPAREWT:
1579 if (m_SelectedEntry1)
1580 CompareRevs(CGitHash().ToString());
1581 break;
1583 #if 0
1584 case ID_COMPAREREVS:
1585 if (m_SelectedEntry1)
1586 CompareRevs(false);
1587 break;
1588 case ID_UNIDIFFREVS:
1589 if (m_SelectedEntry1)
1590 UnifiedDiffRevs(false);
1591 break;
1592 case ID_UNIDIFFHEADS:
1593 if (m_SelectedEntry1)
1594 UnifiedDiffRevs(true);
1595 break;
1596 case ID_SHOWLOG:
1597 DoShowLog();
1598 break;
1599 case ID_CFM:
1600 DoCheckForModification();
1601 break;
1602 case ID_MERGETO:
1603 DoMergeTo();
1604 break;
1605 case ID_UPDATE:
1606 DoUpdate();
1607 break;
1608 case ID_SWITCHTOHEAD:
1609 DoSwitchToHead();
1610 break;
1611 case ID_EXPAND_ALL:
1612 ResetNodeFlags (CGraphNodeStates::COLLAPSED_ALL);
1613 break;
1614 case ID_JOIN_ALL:
1615 ResetNodeFlags (CGraphNodeStates::SPLIT_ALL);
1616 break;
1617 case ID_GRAPH_EXPANDCOLLAPSE_ABOVE:
1618 ToggleNodeFlag (clickedentry, CGraphNodeStates::COLLAPSED_ABOVE);
1619 break;
1620 case ID_GRAPH_EXPANDCOLLAPSE_RIGHT:
1621 ToggleNodeFlag (clickedentry, CGraphNodeStates::COLLAPSED_RIGHT);
1622 break;
1623 case ID_GRAPH_EXPANDCOLLAPSE_BELOW:
1624 ToggleNodeFlag (clickedentry, CGraphNodeStates::COLLAPSED_BELOW);
1625 break;
1626 case ID_GRAPH_SPLITJOIN_ABOVE:
1627 ToggleNodeFlag (clickedentry, CGraphNodeStates::SPLIT_ABOVE);
1628 break;
1629 case ID_GRAPH_SPLITJOIN_RIGHT:
1630 ToggleNodeFlag (clickedentry, CGraphNodeStates::SPLIT_RIGHT);
1631 break;
1632 case ID_GRAPH_SPLITJOIN_BELOW:
1633 ToggleNodeFlag (clickedentry, CGraphNodeStates::SPLIT_BELOW);
1634 break;
1635 #endif
1639 void CRevisionGraphWnd::OnMouseMove(UINT nFlags, CPoint point)
1641 if (IsUpdateJobRunning())
1642 return __super::OnMouseMove(nFlags, point);
1643 if (!m_bIsCanvasMove)
1645 if (m_bShowOverview && (m_OverviewRect.PtInRect(point))&&(nFlags & MK_LBUTTON))
1647 // scrolling
1648 int x = (int)((point.x-m_OverviewRect.left - (m_OverviewPosRect.Width()/2)) / m_previewZoom * m_fZoomFactor);
1649 int y = (int)((point.y - m_OverviewRect.top - (m_OverviewPosRect.Height()/2)) / m_previewZoom * m_fZoomFactor);
1650 x = max(0, x);
1651 y = max(0, y);
1652 SetScrollbars(y, x);
1653 Invalidate(FALSE);
1654 return __super::OnMouseMove(nFlags, point);
1656 else
1658 // update screen if we hover over a different
1659 // node than during the last redraw
1661 CPoint clientPoint = point;
1662 GetCursorPos (&clientPoint);
1663 ScreenToClient (&clientPoint);
1665 #if 0
1666 const CRevisionGraphState::SVisibleGlyph* hitGlyph
1667 = GetHitGlyph (clientPoint);
1668 const CFullGraphNode* glyphNode
1669 = hitGlyph ? hitGlyph->node->GetBase() : nullptr;
1671 const CFullGraphNode* hoverNode = nullptr;
1672 if (m_hoverIndex != NO_INDEX)
1674 CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());
1675 if (m_hoverIndex < nodeList->GetCount())
1676 hoverNode = nodeList->GetNode (m_hoverIndex).node->GetBase();
1679 //bool onHoverNodeGlyph = hoverNode && (glyphNode == hoverNode);
1680 if ( !m_hoverIndex
1681 && ( (m_hoverIndex != GetHitNode (clientPoint))))
1683 m_showHoverGlyphs = false;
1685 KillTimer (GLYPH_HOVER_EVENT);
1686 SetTimer(GLYPH_HOVER_EVENT, GLYPH_HOVER_DELAY, nullptr);
1688 Invalidate(FALSE);
1690 #endif
1691 return __super::OnMouseMove(nFlags, point);
1694 SetCapture();
1696 int pos_h = GetScrollPos(SB_HORZ);
1697 pos_h -= point.x - m_ptMoveCanvas.x;
1698 SetScrollPos(SB_HORZ, pos_h);
1700 int pos_v = GetScrollPos(SB_VERT);
1701 pos_v -= point.y - m_ptMoveCanvas.y;
1702 SetScrollPos(SB_VERT, pos_v);
1704 m_ptMoveCanvas = point;
1706 this->Invalidate();
1708 __super::OnMouseMove(nFlags, point);
1711 BOOL CRevisionGraphWnd::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
1713 CRect viewRect = GetViewRect();
1715 LPTSTR cursorID = IDC_ARROW;
1716 HINSTANCE resourceHandle = nullptr;
1718 if ((nHitTest == HTCLIENT)&&(pWnd == this)&&(viewRect.Width())&&(viewRect.Height())&&(message))
1720 POINT pt;
1721 if (GetCursorPos(&pt))
1723 ScreenToClient(&pt);
1724 if (m_OverviewPosRect.PtInRect(pt))
1726 resourceHandle = AfxGetResourceHandle();
1727 cursorID = (GetKeyState(VK_LBUTTON) & 0x8000)
1728 ? MAKEINTRESOURCE(IDC_PANCURDOWN)
1729 : MAKEINTRESOURCE(IDC_PANCUR);
1731 if (m_bIsCanvasMove)
1732 cursorID = IDC_HAND;
1736 HCURSOR hCur = LoadCursor(resourceHandle, cursorID);
1737 if (GetCursor() != hCur)
1738 SetCursor (hCur);
1740 return TRUE;
1743 void CRevisionGraphWnd::OnTimer (UINT_PTR nIDEvent)
1745 if (nIDEvent == GLYPH_HOVER_EVENT)
1747 KillTimer (GLYPH_HOVER_EVENT);
1749 m_showHoverGlyphs = true;
1750 Invalidate (FALSE);
1752 else
1753 __super::OnTimer (nIDEvent);
1756 LRESULT CRevisionGraphWnd::OnWorkerThreadDone(WPARAM, LPARAM)
1758 // handle potential race condition between PostMessage and leaving job:
1759 // the background job may not have exited, yet
1761 if (updateJob.get())
1762 updateJob->GetResult();
1764 InitView();
1765 BuildPreview();
1767 if (m_HeadNode)
1769 SCROLLINFO sinfo = { 0 };
1770 sinfo.cbSize = sizeof(SCROLLINFO);
1771 if (GetScrollInfo(SB_HORZ, &sinfo))
1773 sinfo.nPos = (int)min(max(sinfo.nMin, m_GraphAttr.x(m_HeadNode) - m_GraphAttr.width(m_HeadNode) / 2), sinfo.nMax);
1774 SetScrollInfo(SB_HORZ, &sinfo);
1776 if (GetScrollInfo(SB_VERT, &sinfo))
1778 sinfo.nPos = (int)min(max(sinfo.nMin, m_GraphAttr.y(m_HeadNode) - m_GraphAttr.height(m_HeadNode) / 2), sinfo.nMax);
1779 SetScrollInfo(SB_VERT, &sinfo);
1783 Invalidate(FALSE);
1785 if (m_parent && !m_parent->GetOutputFile().IsEmpty())
1787 // save the graph to the output file and exit
1788 SaveGraphAs(m_parent->GetOutputFile());
1789 PostQuitMessage(0);
1791 return 0;
1794 void CRevisionGraphWnd::SetDlgTitle (bool /*offline*/)
1796 #if 0
1797 if (m_sTitle.IsEmpty())
1798 GetParent()->GetWindowText(m_sTitle);
1800 CString newTitle;
1801 if (offline)
1802 newTitle.Format (IDS_REVGRAPH_DLGTITLEOFFLINE, (LPCTSTR)m_sTitle);
1803 else
1804 newTitle = m_sTitle;
1806 CAppUtils::SetWindowTitle(GetParent()->GetSafeHwnd(), m_sPath, newTitle);
1807 #endif
1810 ULONG CRevisionGraphWnd::GetGestureStatus(CPoint /*ptTouch*/)
1812 return 0;