Fix typos
[TortoiseGit.git] / src / TortoiseProc / RevisionGraph / RevisionGraphWnd.cpp
bloba8817fff4d542153355e749c52143685b7074524
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2012, 2015 - TortoiseSVN
4 // Copyright (C) 2012-2023 - 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 "FormatMessageWrapper.h"
36 #include "GitRevLoglist.h"
38 #pragma warning(push)
39 #pragma warning(disable: 4100) // unreferenced formal parameter
40 #include <ogdf/planarity/PlanarizationLayout.h>
41 #include <ogdf/planarity/VariableEmbeddingInserter.h>
42 #include <ogdf/planarity/planar_subgraph_fast/PlanarSubgraphPQTree.h>
43 #include <ogdf/orthogonal/OrthoLayout.h>
44 #include <ogdf/planarity/EmbedderMinDepthMaxFaceLayers.h>
45 #pragma warning(pop)
47 #ifdef _DEBUG
48 #define new DEBUG_NEW
49 #undef THIS_FILE
50 static char THIS_FILE[] = __FILE__;
51 #endif
53 using namespace Gdiplus;
55 enum RevisionGraphContextMenuCommands
57 // needs to start with 1, since 0 is the return value if *nothing* is clicked on in the context menu
58 GROUP_MASK = 0xff00,
59 ID_SHOWLOG = 1,
60 ID_CFM = 2,
61 ID_BROWSEREPO,
62 ID_COMPAREREVS = 0x100,
63 ID_COMPAREHEADS,
64 ID_COMPAREWT,
65 ID_UNIDIFFREVS,
66 ID_UNIDIFFHEADS,
67 ID_MERGETO = 0x300,
68 ID_UPDATE,
69 ID_SWITCHTOHEAD,
70 ID_SWITCH,
71 ID_DELETE,
72 ID_SWITCHTOREV,
73 ID_COPYREFS = 0x400,
74 ID_EXPAND_ALL = 0x500,
75 ID_JOIN_ALL,
76 ID_GRAPH_EXPANDCOLLAPSE_ABOVE = 0x600,
77 ID_GRAPH_EXPANDCOLLAPSE_RIGHT,
78 ID_GRAPH_EXPANDCOLLAPSE_BELOW,
79 ID_GRAPH_SPLITJOIN_ABOVE,
80 ID_GRAPH_SPLITJOIN_RIGHT,
81 ID_GRAPH_SPLITJOIN_BELOW,
84 CRevisionGraphWnd::CRevisionGraphWnd()
85 : CWnd()
86 , m_nFontSize(12)
87 , m_bTweakTrunkColors(true)
88 , m_bTweakTagsColors(true)
89 , m_fZoomFactor(DEFAULT_ZOOM)
90 , m_bShowOverview(false)
91 , m_showHoverGlyphs (false)
92 , m_bIsCanvasMove(false)
93 , m_previewWidth(0)
94 , m_previewHeight(0)
95 , m_previewZoom(1)
96 , m_logEntries(&m_LogCache)
97 , m_bCurrentBranch(false)
98 , m_bLocalBranches(FALSE)
99 , m_bShowBranchingsMerges(false)
100 , m_bShowAllTags(true)
101 , m_bArrowPointToMerges(false)
103 WNDCLASS wndcls;
104 HINSTANCE hInst = AfxGetInstanceHandle();
105 #define REVGRAPH_CLASSNAME L"Revgraph_windowclass"
106 if (!(::GetClassInfo(hInst, REVGRAPH_CLASSNAME, &wndcls)))
108 // otherwise we need to register a new class
109 wndcls.style = CS_DBLCLKS | CS_OWNDC;
110 wndcls.lpfnWndProc = ::DefWindowProc;
111 wndcls.cbClsExtra = wndcls.cbWndExtra = 0;
112 wndcls.hInstance = hInst;
113 wndcls.hIcon = nullptr;
114 wndcls.hCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
115 wndcls.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1);
116 wndcls.lpszMenuName = nullptr;
117 wndcls.lpszClassName = REVGRAPH_CLASSNAME;
119 RegisterClass(&wndcls);
122 m_bTweakTrunkColors = CRegDWORD(L"Software\\TortoiseGit\\RevisionGraph\\TweakTrunkColors", TRUE) != FALSE;
123 m_bTweakTagsColors = CRegDWORD(L"Software\\TortoiseGit\\RevisionGraph\\TweakTagsColors", TRUE) != FALSE;
124 m_szTip[0] = '\0';
125 m_wszTip[0] = L'\0';
127 m_GraphAttr.init(this->m_Graph, ogdf::GraphAttributes::nodeGraphics | ogdf::GraphAttributes::edgeGraphics);
129 m_SugiyamLayout.setRanking(::new ogdf::OptimalRanking());
130 m_SugiyamLayout.setCrossMin(::new ogdf::MedianHeuristic());
132 double pi = 3.1415926;
133 m_ArrowCos = cos(pi/8);
134 m_ArrowSin = sin(pi/8);
135 this->m_ArrowSize = 8;
137 auto pOHL = ::new ogdf::FastHierarchyLayout;
138 //It will auto delte when m_SugiyamLayout destroy
140 pOHL->layerDistance(30.0);
141 pOHL->nodeDistance(25.0);
143 m_SugiyamLayout.setLayout(pOHL);
146 CRevisionGraphWnd::~CRevisionGraphWnd()
148 delete m_pDlgTip;
149 m_Graph.clear();
152 void CRevisionGraphWnd::DoDataExchange(CDataExchange* pDX)
154 CWnd::DoDataExchange(pDX);
158 BEGIN_MESSAGE_MAP(CRevisionGraphWnd, CWnd)
159 ON_WM_PAINT()
160 ON_WM_ERASEBKGND()
161 ON_WM_HSCROLL()
162 ON_WM_VSCROLL()
163 ON_WM_SIZE()
164 ON_WM_LBUTTONDOWN()
165 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipNotify)
166 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipNotify)
167 ON_WM_MOUSEWHEEL()
168 ON_WM_MOUSEHWHEEL()
169 ON_WM_CONTEXTMENU()
170 ON_WM_MOUSEMOVE()
171 ON_WM_LBUTTONUP()
172 ON_WM_SETCURSOR()
173 ON_WM_TIMER()
174 ON_MESSAGE(WM_WORKERTHREADDONE,OnWorkerThreadDone)
175 ON_WM_CAPTURECHANGED()
176 END_MESSAGE_MAP()
178 void CRevisionGraphWnd::Init(CWnd * pParent, LPRECT rect)
180 WNDCLASS wndcls;
181 HINSTANCE hInst = AfxGetInstanceHandle();
182 #define REVGRAPH_CLASSNAME L"Revgraph_windowclass"
183 if (!(::GetClassInfo(hInst, REVGRAPH_CLASSNAME, &wndcls)))
185 // otherwise we need to register a new class
186 wndcls.style = CS_DBLCLKS | CS_OWNDC;
187 wndcls.lpfnWndProc = ::DefWindowProc;
188 wndcls.cbClsExtra = wndcls.cbWndExtra = 0;
189 wndcls.hInstance = hInst;
190 wndcls.hIcon = nullptr;
191 wndcls.hCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
192 wndcls.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1);
193 wndcls.lpszMenuName = nullptr;
194 wndcls.lpszClassName = REVGRAPH_CLASSNAME;
196 RegisterClass(&wndcls);
199 if (!IsWindow(m_hWnd))
200 CreateEx(WS_EX_CLIENTEDGE, REVGRAPH_CLASSNAME, L"RevGraph", WS_CHILD|WS_VISIBLE|WS_TABSTOP, *rect, pParent, 0);
201 m_pDlgTip = new CToolTipCtrl;
202 if(!m_pDlgTip->Create(this))
204 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Unable to add tooltip!\n");
206 EnableToolTips();
208 m_ullTicks = GetTickCount64();
210 m_parent = dynamic_cast<CRevisionGraphDlg*>(pParent);
213 CPoint CRevisionGraphWnd::GetLogCoordinates (CPoint point) const
215 // translate point into logical coordinates
217 int nVScrollPos = GetScrollPos(SB_VERT);
218 int nHScrollPos = GetScrollPos(SB_HORZ);
220 return CPoint(static_cast<int>((point.x + nHScrollPos) / m_fZoomFactor),
221 static_cast<int>((point.y + nVScrollPos) / m_fZoomFactor));
224 ogdf::node CRevisionGraphWnd::GetHitNode(CPoint point, CSize /*border*/) const
226 #if 0
227 // any nodes at all?
229 CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());
230 if (!nodeList)
231 return index_t(NO_INDEX);
233 // search the nodes for one at that grid position
235 return nodeList->GetAt (GetLogCoordinates (point), border);
236 #endif
238 for (auto v : m_Graph.nodes)
240 RectF noderect (GetNodeRect (v, CPoint(GetScrollPos(SB_HORZ), GetScrollPos(SB_VERT))));
241 if(point.x>noderect.X && point.x <(noderect.X+noderect.Width) &&
242 point.y>noderect.Y && point.y <(noderect.Y+noderect.Height))
244 return v;
247 return nullptr;
250 DWORD CRevisionGraphWnd::GetHoverGlyphs (CPoint /*point*/) const
252 // if there is no layout, there will be no nodes,
253 // hence, no glyphs
254 DWORD result = 0;
255 #if 0
256 CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());
257 if (!nodeList)
258 return 0;
260 // get node at point or node that is close enough
261 // so that point may hit a glyph area
263 index_t nodeIndex = GetHitNode(point);
264 if (nodeIndex == NO_INDEX)
265 nodeIndex = GetHitNode(point, CSize (GLYPH_SIZE, GLYPH_SIZE / 2));
267 if (nodeIndex >= nodeList->GetCount())
268 return 0;
270 ILayoutNodeList::SNode node = nodeList->GetNode (nodeIndex);
271 const CVisibleGraphNode* base = node.node;
273 // what glyphs should be shown depending on position of point
274 // relative to the node rect?
276 CPoint logCoordinates = GetLogCoordinates (point);
277 CRect r = node.rect;
278 CPoint center = r.CenterPoint();
280 CRect rightGlyphArea ( r.right - GLYPH_SIZE, center.y - GLYPH_SIZE / 2
281 , r.right + GLYPH_SIZE, center.y + GLYPH_SIZE / 2);
282 CRect topGlyphArea ( center.x - GLYPH_SIZE, r.top - GLYPH_SIZE / 2
283 , center.x + GLYPH_SIZE, r.top + GLYPH_SIZE / 2);
284 CRect bottomGlyphArea ( center.x - GLYPH_SIZE, r.bottom - GLYPH_SIZE / 2
285 , center.x + GLYPH_SIZE, r.bottom + GLYPH_SIZE / 2);
287 bool upsideDown
288 = m_state.GetOptions()->GetOption<CUpsideDownLayout>()->IsActive();
290 if (upsideDown)
292 std::swap (topGlyphArea.top, bottomGlyphArea.top);
293 std::swap (topGlyphArea.bottom, bottomGlyphArea.bottom);
297 if (rightGlyphArea.PtInRect (logCoordinates))
298 result = base->GetFirstCopyTarget()
299 ? CGraphNodeStates::COLLAPSED_RIGHT | CGraphNodeStates::SPLIT_RIGHT
300 : 0;
302 if (topGlyphArea.PtInRect (logCoordinates))
303 result = base->GetSource()
304 ? CGraphNodeStates::COLLAPSED_ABOVE | CGraphNodeStates::SPLIT_ABOVE
305 : 0;
307 if (bottomGlyphArea.PtInRect (logCoordinates))
308 result = base->GetNext()
309 ? CGraphNodeStates::COLLAPSED_BELOW | CGraphNodeStates::SPLIT_BELOW
310 : 0;
312 // if some nodes have already been split, don't allow collapsing etc.
314 CSyncPointer<const CGraphNodeStates> nodeStates (m_state.GetNodeStates());
315 if (result & nodeStates->GetFlags (base))
316 result = 0;
317 #endif
318 return result;
320 #if 0
321 const CRevisionGraphState::SVisibleGlyph* CRevisionGraphWnd::GetHitGlyph (CPoint point) const
323 float glyphSize = GLYPH_SIZE * m_fZoomFactor;
325 CSyncPointer<const CRevisionGraphState::TVisibleGlyphs>
326 visibleGlyphs (m_state.GetVisibleGlyphs());
328 for (size_t i = 0, count = visibleGlyphs->size(); i < count; ++i)
330 const CRevisionGraphState::SVisibleGlyph* entry = &(*visibleGlyphs)[i];
332 float xRel = point.x - entry->leftTop.X;
333 float yRel = point.y - entry->leftTop.Y;
335 if ( (xRel >= 0) && (xRel < glyphSize)
336 && (yRel >= 0) && (yRel < glyphSize))
338 return entry;
342 return nullptr;
344 #endif
345 void CRevisionGraphWnd::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
347 SCROLLINFO sinfo = {0};
348 sinfo.cbSize = sizeof(SCROLLINFO);
349 GetScrollInfo(SB_HORZ, &sinfo);
351 // Determine the new position of scroll box.
352 switch (nSBCode)
354 case SB_LEFT: // Scroll to far left.
355 sinfo.nPos = sinfo.nMin;
356 break;
357 case SB_RIGHT: // Scroll to far right.
358 sinfo.nPos = sinfo.nMax;
359 break;
360 case SB_ENDSCROLL: // End scroll.
361 break;
362 case SB_LINELEFT: // Scroll left.
363 if (sinfo.nPos > sinfo.nMin)
364 sinfo.nPos--;
365 break;
366 case SB_LINERIGHT: // Scroll right.
367 if (sinfo.nPos < sinfo.nMax)
368 ++sinfo.nPos;
369 break;
370 case SB_PAGELEFT: // Scroll one page left.
372 if (sinfo.nPos > sinfo.nMin)
373 sinfo.nPos = max(sinfo.nMin, sinfo.nPos - static_cast<int>(sinfo.nPage));
375 break;
376 case SB_PAGERIGHT: // Scroll one page right.
378 if (sinfo.nPos < sinfo.nMax)
379 sinfo.nPos = min(sinfo.nMax, sinfo.nPos + static_cast<int>(sinfo.nPage));
381 break;
382 case SB_THUMBPOSITION: // Scroll to absolute position. nPos is the position
383 sinfo.nPos = sinfo.nTrackPos; // of the scroll box at the end of the drag operation.
384 break;
385 case SB_THUMBTRACK: // Drag scroll box to specified position. nPos is the
386 sinfo.nPos = sinfo.nTrackPos; // position that the scroll box has been dragged to.
387 break;
389 SetScrollInfo(SB_HORZ, &sinfo);
390 Invalidate (FALSE);
391 __super::OnHScroll(nSBCode, nPos, pScrollBar);
394 void CRevisionGraphWnd::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
396 SCROLLINFO sinfo = {0};
397 sinfo.cbSize = sizeof(SCROLLINFO);
398 GetScrollInfo(SB_VERT, &sinfo);
400 // Determine the new position of scroll box.
401 switch (nSBCode)
403 case SB_LEFT: // Scroll to far left.
404 sinfo.nPos = sinfo.nMin;
405 break;
406 case SB_RIGHT: // Scroll to far right.
407 sinfo.nPos = sinfo.nMax;
408 break;
409 case SB_ENDSCROLL: // End scroll.
410 break;
411 case SB_LINELEFT: // Scroll left.
412 if (sinfo.nPos > sinfo.nMin)
413 sinfo.nPos--;
414 break;
415 case SB_LINERIGHT: // Scroll right.
416 if (sinfo.nPos < sinfo.nMax)
417 ++sinfo.nPos;
418 break;
419 case SB_PAGELEFT: // Scroll one page left.
421 if (sinfo.nPos > sinfo.nMin)
422 sinfo.nPos = max(sinfo.nMin, sinfo.nPos - static_cast<int>(sinfo.nPage));
424 break;
425 case SB_PAGERIGHT: // Scroll one page right.
427 if (sinfo.nPos < sinfo.nMax)
428 sinfo.nPos = min(sinfo.nMax, sinfo.nPos + static_cast<int>(sinfo.nPage));
430 break;
431 case SB_THUMBPOSITION: // Scroll to absolute position. nPos is the position
432 sinfo.nPos = sinfo.nTrackPos; // of the scroll box at the end of the drag operation.
433 break;
434 case SB_THUMBTRACK: // Drag scroll box to specified position. nPos is the
435 sinfo.nPos = sinfo.nTrackPos; // position that the scroll box has been dragged to.
436 break;
438 SetScrollInfo(SB_VERT, &sinfo);
439 Invalidate(FALSE);
440 __super::OnVScroll(nSBCode, nPos, pScrollBar);
443 void CRevisionGraphWnd::OnSize(UINT nType, int cx, int cy)
445 __super::OnSize(nType, cx, cy);
446 SetScrollbars(GetScrollPos(SB_VERT), GetScrollPos(SB_HORZ));
447 Invalidate(FALSE);
450 void CRevisionGraphWnd::OnLButtonDown(UINT nFlags, CPoint point)
452 if (IsUpdateJobRunning())
453 return __super::OnLButtonDown(nFlags, point);
455 // CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());
457 SetFocus();
458 bool bHit = false;
459 bool bControl = !!(GetKeyState(VK_CONTROL)&0x8000);
460 bool bOverview = m_bShowOverview && m_OverviewRect.PtInRect(point);
461 if (! bOverview)
463 #if 0
464 const CRevisionGraphState::SVisibleGlyph* hitGlyph
465 = GetHitGlyph (point);
467 if (hitGlyph)
469 ToggleNodeFlag (hitGlyph->node, hitGlyph->state);
470 return __super::OnLButtonDown(nFlags, point);
472 #endif
473 auto nodeIndex = GetHitNode(point);
474 if (nodeIndex)
476 if (bControl)
478 if (m_SelectedEntry1 == nodeIndex)
480 if (m_SelectedEntry2)
482 m_SelectedEntry1 = m_SelectedEntry2;
483 m_SelectedEntry2 = nullptr;
485 else
486 m_SelectedEntry1 = nullptr;
488 else if (m_SelectedEntry2 == nodeIndex)
489 m_SelectedEntry2 = nullptr;
490 else if (m_SelectedEntry1)
491 m_SelectedEntry2 = nodeIndex;
492 else
493 m_SelectedEntry1 = nodeIndex;
495 else
497 if (m_SelectedEntry1 == nodeIndex)
498 m_SelectedEntry1 = nullptr;
499 else
500 m_SelectedEntry1 = nodeIndex;
501 m_SelectedEntry2 = nullptr;
503 bHit = true;
504 Invalidate(FALSE);
508 if ((!bHit)&&(!bControl)&&(!bOverview))
510 m_SelectedEntry1 = nullptr;
511 m_SelectedEntry2 = nullptr;
512 m_bIsCanvasMove = true;
513 Invalidate(FALSE);
514 if (m_bShowOverview && m_OverviewRect.PtInRect(point))
515 m_bIsCanvasMove = false;
517 m_ptMoveCanvas = point;
519 UINT uEnable = MF_BYCOMMAND;
520 if (m_SelectedEntry1 && m_SelectedEntry2)
521 uEnable |= MF_ENABLED;
522 else
523 uEnable |= MF_GRAYED;
525 auto hMenu = GetParent()->GetMenu()->m_hMenu;
526 EnableMenuItem(hMenu, ID_VIEW_COMPAREREVISIONS, uEnable);
527 EnableMenuItem(hMenu, ID_VIEW_UNIFIEDDIFF, uEnable);
529 uEnable = MF_BYCOMMAND;
530 if (m_SelectedEntry1 && !m_SelectedEntry2)
531 uEnable |= MF_ENABLED;
532 else
533 uEnable |= MF_GRAYED;
535 EnableMenuItem(hMenu, ID_VIEW_UNIFIEDDIFFOFHEADREVISIONS, uEnable);
536 EnableMenuItem(hMenu, ID_VIEW_COMPAREHEADREVISIONS, uEnable);
538 __super::OnLButtonDown(nFlags, point);
541 void CRevisionGraphWnd::OnCaptureChanged(CWnd *pWnd)
543 __super::OnCaptureChanged(pWnd);
546 void CRevisionGraphWnd::OnLButtonUp(UINT nFlags, CPoint point)
548 if (!m_bIsCanvasMove)
549 return; // we don't have a rubberband, so no zooming necessary
551 m_bIsCanvasMove = false;
552 ReleaseCapture();
553 if (IsUpdateJobRunning())
554 return __super::OnLButtonUp(nFlags, point);
556 // zooming is finished
557 m_ptRubberEnd = CPoint(0,0);
558 CRect rect = GetClientRect();
559 int x = abs(m_ptMoveCanvas.x - point.x);
560 int y = abs(m_ptMoveCanvas.y - point.y);
562 if ((x < 20)&&(y < 20))
564 // too small zoom rectangle
565 // assume zooming by accident
566 Invalidate(FALSE);
567 __super::OnLButtonUp(nFlags, point);
568 return;
571 float xfact = float(rect.Width())/float(x);
572 float yfact = float(rect.Height())/float(y);
573 float fact = max(yfact, xfact);
575 // find out where to scroll to
576 x = min(m_ptMoveCanvas.x, point.x) + GetScrollPos(SB_HORZ);
577 y = min(m_ptMoveCanvas.y, point.y) + GetScrollPos(SB_VERT);
579 float fZoomfactor = m_fZoomFactor*fact;
580 if (fZoomfactor > 10 * MAX_ZOOM)
582 // with such a big zoomfactor, the user
583 // most likely zoomed by accident
584 Invalidate(FALSE);
585 __super::OnLButtonUp(nFlags, point);
586 return;
588 if (fZoomfactor > MAX_ZOOM)
590 fZoomfactor = MAX_ZOOM;
591 fact = fZoomfactor/m_fZoomFactor;
594 auto pDlg = static_cast<CRevisionGraphDlg*>(GetParent());
595 if (pDlg)
597 m_fZoomFactor = fZoomfactor;
598 pDlg->DoZoom (m_fZoomFactor);
599 SetScrollbars(int(float(y)*fact), int(float(x)*fact));
601 __super::OnLButtonUp(nFlags, point);
604 bool CRevisionGraphWnd::CancelMouseZoom()
606 bool bRet = m_bIsCanvasMove;
607 ReleaseCapture();
608 if (m_bIsCanvasMove)
609 Invalidate(FALSE);
610 m_bIsCanvasMove = false;
611 m_ptRubberEnd = CPoint(0,0);
612 return bRet;
615 INT_PTR CRevisionGraphWnd::OnToolHitTest(CPoint point, TOOLINFO* pTI) const
617 if (IsUpdateJobRunning())
618 return -1;
620 auto nodeIndex = GetHitNode(point);
621 if (m_tooltipIndex != nodeIndex)
623 // force tooltip to be updated
625 m_tooltipIndex = nodeIndex;
626 return -1;
629 if (!nodeIndex)
630 return -1;
632 // if ((GetHoverGlyphs (point) != 0) || (GetHitGlyph (point) != nullptr))
633 // return -1;
635 pTI->hwnd = this->m_hWnd;
636 CWnd::GetClientRect(&pTI->rect);
637 pTI->uFlags |= TTF_ALWAYSTIP | TTF_IDISHWND;
638 pTI->uId = reinterpret_cast<UINT_PTR>(m_hWnd);
639 pTI->lpszText = LPSTR_TEXTCALLBACK;
641 return 1;
644 BOOL CRevisionGraphWnd::OnToolTipNotify(UINT /*id*/, NMHDR *pNMHDR, LRESULT *pResult)
646 if (pNMHDR->idFrom != reinterpret_cast<UINT_PTR>(m_hWnd))
647 return FALSE;
649 POINT point;
650 DWORD ptW = GetMessagePos();
651 point.x = GET_X_LPARAM(ptW);
652 point.y = GET_Y_LPARAM(ptW);
653 ScreenToClient(&point);
655 CString strTipText = TooltipText (GetHitNode (point));
657 *pResult = 0;
658 if (strTipText.IsEmpty())
659 return TRUE;
661 CSize tooltipSize = UsableTooltipRect();
662 strTipText = DisplayableText (strTipText, tooltipSize);
664 // need to handle both ANSI and UNICODE versions of the message
665 if (pNMHDR->code == TTN_NEEDTEXTA)
667 auto pTTTA = reinterpret_cast<TOOLTIPTEXTA*>(pNMHDR);
668 ::SendMessage(pNMHDR->hwndFrom, TTM_SETMAXTIPWIDTH, 0, tooltipSize.cx);
669 pTTTA->lpszText = m_szTip;
670 WideCharToMultiByte(CP_ACP, 0, strTipText, -1, m_szTip, strTipText.GetLength()+1, 0, 0);
672 else
674 auto pTTTW = reinterpret_cast<TOOLTIPTEXTW*>(pNMHDR);
675 ::SendMessage(pNMHDR->hwndFrom, TTM_SETMAXTIPWIDTH, 0, tooltipSize.cx);
676 lstrcpyn(m_wszTip, strTipText, strTipText.GetLength()+1);
677 pTTTW->lpszText = m_wszTip;
680 // show the tooltip for 32 seconds. A higher value than 32767 won't work
681 // even though it's nowhere documented!
682 ::SendMessage(pNMHDR->hwndFrom, TTM_SETDELAYTIME, TTDT_AUTOPOP, 32767);
683 return TRUE; // message was handled
686 CSize CRevisionGraphWnd::UsableTooltipRect()
688 // get screen size
690 int screenWidth = GetSystemMetrics(SM_CXSCREEN);
691 int screenHeight = GetSystemMetrics(SM_CYSCREEN);
693 // get current mouse position
695 CPoint cursorPos;
696 if (GetCursorPos (&cursorPos) == FALSE)
698 // we could not determine the mouse position
699 // use screen / 2 minus some safety margin
701 return CSize (screenWidth / 2 - 20, screenHeight / 2 - 20);
704 // tool tip will display in the biggest sector beside the cursor
705 // deduct some safety margin (for the mouse cursor itself
707 CSize biggestSector
708 ( max (screenWidth - cursorPos.x - 40, cursorPos.x - 24)
709 , max (screenHeight - cursorPos.y - 40, cursorPos.y - 24));
711 return biggestSector;
714 CString CRevisionGraphWnd::DisplayableText ( const CString& wholeText
715 , const CSize& tooltipSize)
717 CDC* dc = GetDC();
718 if (!dc)
720 // no access to the device context -> truncate hard at 1000 chars
722 return wholeText.GetLength() >= MAX_TT_LENGTH_DEFAULT
723 ? wholeText.Left(MAX_TT_LENGTH_DEFAULT - 4) + L" ..."
724 : wholeText;
727 // select the tooltip font
729 NONCLIENTMETRICS metrics;
730 metrics.cbSize = sizeof (metrics);
731 SystemParametersInfo (SPI_GETNONCLIENTMETRICS, metrics.cbSize, &metrics, 0);
733 CFont font;
734 font.CreateFontIndirect(&metrics.lfStatusFont);
735 CFont* pOldFont = dc->SelectObject (&font);
737 // split into lines and fill the tooltip rect
739 CString result;
741 int remainingHeight = tooltipSize.cy;
742 int pos = 0;
743 while (pos < wholeText.GetLength())
745 // extract a whole line
747 int nextPos = wholeText.Find ('\n', pos);
748 if (nextPos < 0)
749 nextPos = wholeText.GetLength();
751 CString line = wholeText.Mid (pos, nextPos-pos+1);
753 // find a way to make it fit
755 CSize size = dc->GetTextExtent (line);
756 while (size.cx > tooltipSize.cx)
758 line.Delete (line.GetLength()-1);
759 int nextPos2 = line.ReverseFind (' ');
760 if (nextPos2 < 0)
761 break;
763 line.Delete (nextPos2+1, line.GetLength() - pos-1);
764 size = dc->GetTextExtent (line);
767 // enough room for the new line?
769 remainingHeight -= size.cy;
770 if (remainingHeight <= size.cy)
772 result += L"...";
773 break;
776 // add the line
778 result += line;
779 pos += line.GetLength();
782 // relase temp. resources
784 dc->SelectObject (pOldFont);
785 ReleaseDC(dc);
787 // ready
789 return result;
792 CString CRevisionGraphWnd::TooltipText(ogdf::node index)
794 if(index)
796 CString str;
797 CGitHash hash = m_logEntries[index->index()];
798 GitRevLoglist* rev = this->m_LogCache.GetCacheData(hash);
799 str += rev->m_CommitHash.ToString();
800 str += L'\n';
801 str += rev->GetAuthorName() + L" <" + rev->GetAuthorEmail() + L'>';
802 str += L' ';
803 str += rev->GetAuthorDate().Format(L"%Y-%m-%d %H:%M");
804 str += L"\n\n" + rev->GetSubject();
805 str += L'\n';
806 str += rev->GetBody();
807 if (str.GetLength() > 8000)
809 str.Truncate(8000);
810 str += L"...";
812 return str;
813 }else
814 return CString();
817 void CRevisionGraphWnd::SaveGraphAs(CString sSavePath)
819 CString extension = CPathUtils::GetFileExtFromPath(sSavePath);
820 if (extension.CompareNoCase(L".wmf") == 0)
822 // save the graph as an enhanced metafile
823 CMetaFileDC wmfDC;
824 wmfDC.CreateEnhanced(nullptr, sSavePath, nullptr, L"TortoiseGit\0Revision Graph\0\0");
825 float fZoom = m_fZoomFactor;
826 m_fZoomFactor = DEFAULT_ZOOM;
827 DoZoom(m_fZoomFactor);
828 CRect rect;
829 rect = GetViewRect();
830 GraphicsDevice dev;
831 dev.pDC = &wmfDC;
832 DrawGraph(dev, rect, 0, 0, true);
833 HENHMETAFILE hemf = wmfDC.CloseEnhanced();
834 DeleteEnhMetaFile(hemf);
835 m_fZoomFactor = fZoom;
836 DoZoom(m_fZoomFactor);
838 else if (extension.CompareNoCase(L".svg") == 0)
840 // save the graph as a scalable vector graphic
841 SVG svg;
842 float fZoom = m_fZoomFactor;
843 m_fZoomFactor = DEFAULT_ZOOM;
844 DoZoom(m_fZoomFactor);
845 CRect rect;
846 rect = GetViewRect();
847 svg.SetViewSize(rect.Width(), rect.Height());
848 GraphicsDevice dev;
849 dev.pSVG = &svg;
850 DrawGraph(dev, rect, 0, 0, true);
851 svg.Save(sSavePath);
852 m_fZoomFactor = fZoom;
853 DoZoom(m_fZoomFactor);
855 else if (extension.CompareNoCase(L".gv") == 0)
857 Graphviz graphviz;
858 float fZoom = m_fZoomFactor;
859 m_fZoomFactor = DEFAULT_ZOOM;
860 DoZoom(m_fZoomFactor);
861 CRect rect;
862 rect = GetViewRect();
863 GraphicsDevice dev;
864 dev.pGraphviz = &graphviz;
865 DrawGraph(dev, rect, 0, 0, true);
866 graphviz.Save(sSavePath);
867 m_fZoomFactor = fZoom;
868 DoZoom(m_fZoomFactor);
870 else
872 // save the graph as a pixel picture instead of a vector picture
873 // create dc to paint on
876 CString sErrormessage;
877 CWindowDC ddc(this);
878 CDC dc;
879 if (!dc.CreateCompatibleDC(&ddc))
881 CFormatMessageWrapper errorDetails;
882 if( errorDetails )
883 MessageBox(errorDetails, L"Error", MB_OK | MB_ICONINFORMATION);
885 return;
887 CRect rect;
888 rect = GetGraphRect();
889 rect.bottom = static_cast<long>(float(rect.Height()) * m_fZoomFactor);
890 rect.right = static_cast<long>(float(rect.Width()) * m_fZoomFactor);
891 BITMAPINFO bmi = { 0 };
892 HBITMAP hbm;
893 LPBYTE pBits;
894 // Fill out the fields you care about.
895 bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
896 bmi.bmiHeader.biWidth = rect.Width();
897 bmi.bmiHeader.biHeight = rect.Height();
898 bmi.bmiHeader.biPlanes = 1;
899 bmi.bmiHeader.biBitCount = 24;
900 bmi.bmiHeader.biCompression = BI_RGB;
902 // Create the surface.
903 hbm = CreateDIBSection(ddc.m_hDC, &bmi, DIB_RGB_COLORS, reinterpret_cast<void**>(&pBits), nullptr, 0);
904 if (!hbm)
906 CMessageBox::Show(m_hWnd, IDS_REVGRAPH_ERR_NOMEMORY, IDS_APPNAME, MB_ICONERROR);
907 return;
909 HBITMAP oldbm = static_cast<HBITMAP>(dc.SelectObject(hbm));
910 // paint the whole graph
911 GraphicsDevice dev;
912 dev.pDC = &dc;
913 DrawGraph(dev, rect, 0, 0, true);
914 // now use GDI+ to save the picture
915 CLSID encoderClsid;
917 Bitmap bitmap(hbm, nullptr);
918 if (bitmap.GetLastStatus()==Ok)
920 // Get the CLSID of the encoder.
921 int ret = 0;
922 if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(L".png") == 0)
923 ret = GetEncoderClsid(L"image/png", &encoderClsid);
924 else if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(L".jpg") == 0)
925 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
926 else if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(L".jpeg") == 0)
927 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
928 else if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(L".bmp") == 0)
929 ret = GetEncoderClsid(L"image/bmp", &encoderClsid);
930 else if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(L".gif") == 0)
931 ret = GetEncoderClsid(L"image/gif", &encoderClsid);
932 else
934 sSavePath += L".jpg";
935 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
937 if (ret >= 0)
939 CStringW tfile = CStringW(sSavePath);
940 bitmap.Save(tfile, &encoderClsid, nullptr);
942 else
943 sErrormessage.Format(IDS_REVGRAPH_ERR_NOENCODER, static_cast<LPCWSTR>(CPathUtils::GetFileExtFromPath(sSavePath)));
945 else
946 sErrormessage.LoadString(IDS_REVGRAPH_ERR_NOBITMAP);
948 dc.SelectObject(oldbm);
949 DeleteObject(hbm);
950 dc.DeleteDC();
951 if (!sErrormessage.IsEmpty())
952 ::MessageBox(m_hWnd, sErrormessage, L"TortoiseGit", MB_ICONERROR);
954 catch (CException * pE)
956 wchar_t szErrorMsg[2048] = { 0 };
957 pE->GetErrorMessage(szErrorMsg, 2048);
958 pE->Delete();
959 ::MessageBox(m_hWnd, szErrorMsg, L"TortoiseGit", MB_ICONERROR);
964 BOOL CRevisionGraphWnd::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
966 if (IsUpdateJobRunning())
967 return __super::OnMouseWheel(nFlags, zDelta, pt);
969 if (GetKeyState(VK_CONTROL)&0x8000)
971 float newZoom = m_fZoomFactor * (zDelta < 0 ? ZOOM_STEP : 1.0f/ZOOM_STEP);
972 DoZoom (max (MIN_ZOOM, min (MAX_ZOOM, newZoom)));
974 else
976 int orientation = (GetKeyState(VK_SHIFT) & 0x8000) ? SB_HORZ : SB_VERT;
977 int pos = GetScrollPos(orientation);
978 pos -= (zDelta);
979 SetScrollPos(orientation, pos);
980 Invalidate(FALSE);
982 return __super::OnMouseWheel(nFlags, zDelta, pt);
985 void CRevisionGraphWnd::OnMouseHWheel(UINT nFlags, short zDelta, CPoint pt)
987 if (IsUpdateJobRunning())
988 return __super::OnMouseHWheel(nFlags, zDelta, pt);
990 int orientation = (GetKeyState(VK_SHIFT) & 0x8000) ? SB_VERT : SB_HORZ;
991 int pos = GetScrollPos(orientation);
992 pos -= (zDelta);
993 SetScrollPos(orientation, pos);
994 Invalidate(FALSE);
996 return __super::OnMouseHWheel(nFlags, zDelta, pt);
999 bool CRevisionGraphWnd::UpdateSelectedEntry(ogdf::node clickedentry)
1001 if (!m_SelectedEntry1 && !clickedentry)
1002 return false;
1004 if (!m_SelectedEntry1)
1006 m_SelectedEntry1 = clickedentry;
1007 Invalidate(FALSE);
1009 if (!m_SelectedEntry2 && clickedentry != m_SelectedEntry1)
1011 m_SelectedEntry1 = clickedentry;
1012 Invalidate(FALSE);
1014 if (m_SelectedEntry1 && m_SelectedEntry2)
1016 if ((m_SelectedEntry2 != clickedentry)&&(m_SelectedEntry1 != clickedentry))
1017 return false;
1019 if (!m_SelectedEntry1)
1020 return false;
1022 return true;
1025 void CRevisionGraphWnd::AppendMenu
1026 ( CMenu& popup
1027 , UINT title
1028 , UINT command
1029 , UINT flags)
1031 // separate different groups / section within the context menu
1033 if (popup.GetMenuItemCount() > 0)
1035 UINT lastCommand = popup.GetMenuItemID (popup.GetMenuItemCount()-1);
1036 if ((lastCommand & GROUP_MASK) != (command & GROUP_MASK))
1037 popup.AppendMenu(MF_SEPARATOR, NULL);
1040 // actually add the new item
1042 CString titleString;
1043 titleString.LoadString (title);
1044 popup.AppendMenu (MF_STRING | flags, command, titleString);
1047 void CRevisionGraphWnd::AppendMenu(CMenu &popup, CString title, UINT command, CString *extra, CMenu *submenu)
1049 // separate different groups / section within the context menu
1050 if (popup.GetMenuItemCount() > 0)
1052 UINT lastCommand = popup.GetMenuItemID(popup.GetMenuItemCount() - 1);
1053 if ((lastCommand & GROUP_MASK) != (command & GROUP_MASK))
1054 popup.AppendMenu(MF_SEPARATOR, NULL);
1057 // actually add the new item
1058 MENUITEMINFO mii = { 0 };
1059 mii.cbSize = sizeof(MENUITEMINFO);
1060 mii.fMask = MIIM_STRING | MIIM_ID | (extra ? MIIM_DATA : 0) | (submenu ? MIIM_SUBMENU : 0);
1061 mii.wID = command;
1062 mii.hSubMenu = submenu ? submenu->m_hMenu : nullptr;
1063 mii.dwItemData = reinterpret_cast<ULONG_PTR>(extra);
1064 mii.dwTypeData = title.GetBuffer();
1065 InsertMenuItem(popup, popup.GetMenuItemCount(), TRUE, &mii);
1066 title.ReleaseBuffer();
1069 void CRevisionGraphWnd::DoShowLog()
1071 if (!m_SelectedEntry1)
1072 return;
1074 CString sCmd;
1076 if(m_SelectedEntry2)
1077 sCmd.Format(L"/command:log %s /startrev:%s /endrev:%s",
1078 this->m_sPath.IsEmpty() ? L"" : static_cast<LPCWSTR>(L"/path:\"" + this->m_sPath + L'"'),
1079 static_cast<LPCWSTR>(this->m_logEntries[m_SelectedEntry1->index()].ToString()),
1080 static_cast<LPCWSTR>(this->m_logEntries[m_SelectedEntry2->index()].ToString()));
1081 else
1082 sCmd.Format(L"/command:log %s /endrev:%s",
1083 static_cast<LPCWSTR>(this->m_sPath.IsEmpty() ? CString() : (L"/path:\"" + this->m_sPath + L'"')),
1084 static_cast<LPCWSTR>(this->m_logEntries[m_SelectedEntry1->index()].ToString()));
1086 CAppUtils::RunTortoiseGitProc(sCmd);
1089 void CRevisionGraphWnd::DoSwitch(CString rev)
1091 CAppUtils::PerformSwitch(GetSafeHwnd(), rev);
1094 void CRevisionGraphWnd::DoBrowseRepo()
1096 if (!m_SelectedEntry1)
1097 return;
1099 CString sCmd;
1100 sCmd.Format(L"/command:repobrowser %s /rev:%s",
1101 this->m_sPath.IsEmpty() ? L"" : static_cast<LPCWSTR>(L"/path:\"" + this->m_sPath + L'"'),
1102 static_cast<LPCWSTR>(GetFriendRefName(m_SelectedEntry1)));
1104 CAppUtils::RunTortoiseGitProc(sCmd);
1107 void CRevisionGraphWnd::DoCopyRefs()
1109 if (!m_SelectedEntry1)
1110 return;
1112 STRING_VECTOR list = GetFriendRefNames(m_SelectedEntry1);
1113 CString text;
1114 if (list.empty())
1115 text = m_logEntries[m_SelectedEntry1->index()].ToString();
1116 for (size_t i = 0; i < list.size(); ++i)
1118 if (i > 0)
1119 text.Append(L"\r\n");
1120 text.Append(list[i]);
1122 CStringUtils::WriteAsciiStringToClipboard(text, m_hWnd);
1125 void CRevisionGraphWnd::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
1127 if (IsUpdateJobRunning())
1128 return;
1130 CPoint clientpoint = point;
1131 this->ScreenToClient(&clientpoint);
1133 auto nodeIndex = GetHitNode(clientpoint);
1135 if ( !UpdateSelectedEntry (nodeIndex))
1136 return;
1138 CMenu popup;
1139 if (!popup.CreatePopupMenu())
1140 return;
1142 bool bothPresent = (m_SelectedEntry2 && m_SelectedEntry1);
1144 AppendMenu (popup, IDS_REPOBROWSE_SHOWLOG, ID_SHOWLOG);
1146 STRING_VECTOR branchNames;
1147 STRING_VECTOR allRefNames;
1148 STRING_VECTOR remoteBranchNames;
1149 if (m_SelectedEntry1 && (m_SelectedEntry2 == nullptr))
1151 AppendMenu(popup, IDS_LOG_BROWSEREPO, ID_BROWSEREPO);
1153 CString currentBranch = g_Git.GetCurrentBranch();
1154 CGit::REF_TYPE refType = CGit::LOCAL_BRANCH;
1155 branchNames = GetFriendRefNames(m_SelectedEntry1, &currentBranch, &refType);
1156 if (branchNames.size() == 1)
1158 CString text;
1159 text.Format(L"%s \"%s\"", static_cast<LPCWSTR>(CString(MAKEINTRESOURCE(IDS_SWITCH_BRANCH))), static_cast<LPCWSTR>(branchNames[0]));
1160 AppendMenu(popup, text, ID_SWITCH, &branchNames[0]);
1162 else if (branchNames.size() > 1)
1164 CMenu switchMenu;
1165 switchMenu.CreatePopupMenu();
1166 for (size_t i = 0; i < branchNames.size(); ++i)
1167 AppendMenu(switchMenu, branchNames[i], ID_SWITCH + (static_cast<int>(i + 1) << 16), &branchNames[i]);
1168 AppendMenu(popup, CString(MAKEINTRESOURCE(IDS_SWITCH_BRANCH)), ID_SWITCH, nullptr, &switchMenu);
1170 else
1172 for (auto arefType : { CGit::REMOTE_BRANCH, CGit::TAG, CGit::ANNOTATED_TAG })
1174 remoteBranchNames = GetFriendRefNames(m_SelectedEntry1, &currentBranch, &arefType);
1175 if (remoteBranchNames.size() >= 1)
1177 CString temp;
1178 temp.LoadString(IDS_SWITCH_TO_THIS);
1179 AppendMenu(popup, temp, ID_SWITCHTOREV, &remoteBranchNames[0]);
1180 break;
1185 AppendMenu(popup, IDS_COPY_REF_NAMES, ID_COPYREFS);
1187 allRefNames = GetFriendRefNames(m_SelectedEntry1, &currentBranch);
1188 if (allRefNames.size() == 1)
1190 CString str;
1191 str.LoadString(IDS_DELETE_BRANCHTAG_SHORT);
1192 str += L' ';
1193 str += allRefNames[0];
1194 AppendMenu(popup, str, ID_DELETE, &allRefNames[0]);
1196 else if (allRefNames.size() > 1)
1198 CString str;
1199 str.LoadString(IDS_DELETE_BRANCHTAG);
1200 CIconMenu submenu;
1201 submenu.CreatePopupMenu();
1202 for (size_t i = 0; i < allRefNames.size(); ++i)
1204 submenu.AppendMenuIcon(ID_DELETE + (i << 16), allRefNames[i]);
1205 submenu.SetMenuItemData(ID_DELETE + (i << 16), reinterpret_cast<ULONG_PTR>(&allRefNames[i]));
1207 submenu.AppendMenuIcon(ID_DELETE + (allRefNames.size() << 16), IDS_ALL);
1208 submenu.SetMenuItemData(ID_DELETE + (allRefNames.size() << 16), reinterpret_cast<ULONG_PTR>(MAKEINTRESOURCE(IDS_ALL)));
1210 AppendMenu(popup, str, ID_DELETE, nullptr, &submenu);
1213 AppendMenu(popup, IDS_REVGRAPH_POPUP_COMPAREHEADS, ID_COMPAREHEADS);
1214 AppendMenu(popup, IDS_REVGRAPH_POPUP_UNIDIFFHEADS, ID_UNIDIFFHEADS);
1216 AppendMenu(popup, IDS_LOG_POPUP_COMPARE, ID_COMPAREWT);
1219 if (bothPresent)
1221 AppendMenu (popup, IDS_REVGRAPH_POPUP_COMPAREREVS, ID_COMPAREREVS);
1222 AppendMenu (popup, IDS_REVGRAPH_POPUP_UNIDIFFREVS, ID_UNIDIFFREVS);
1225 // if the context menu is invoked through the keyboard, we have to use
1226 // a calculated position on where to anchor the menu on
1227 if ((point.x == -1) && (point.y == -1))
1229 CRect rect = GetWindowRect();
1230 point = rect.CenterPoint();
1233 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RIGHTBUTTON, point.x, point.y, this);
1234 switch (cmd & 0xFFFF)
1236 case ID_COMPAREREVS:
1237 if (m_SelectedEntry1)
1238 CompareRevs(L"");
1239 break;
1240 case ID_UNIDIFFREVS:
1241 if (m_SelectedEntry1)
1242 UnifiedDiffRevs(false);
1243 break;
1244 case ID_UNIDIFFHEADS:
1245 if (m_SelectedEntry1)
1246 UnifiedDiffRevs(true);
1247 break;
1248 case ID_SHOWLOG:
1249 DoShowLog();
1250 break;
1251 case ID_SWITCH:
1253 MENUITEMINFO mii = { 0 };
1254 mii.cbSize = sizeof(mii);
1255 mii.fMask |= MIIM_DATA;
1256 GetMenuItemInfo(popup, cmd, FALSE, &mii);
1257 auto rev = reinterpret_cast<CString*>(mii.dwItemData);
1258 if (rev)
1260 DoSwitch(*rev);
1261 m_parent->UpdateFullHistory();
1263 break;
1265 case ID_SWITCHTOREV:
1267 MENUITEMINFO mii = { 0 };
1268 mii.cbSize = sizeof(mii);
1269 mii.fMask |= MIIM_DATA;
1270 GetMenuItemInfo(popup, cmd, FALSE, &mii);
1271 auto rev = reinterpret_cast<CString*>(mii.dwItemData);
1272 CAppUtils::Switch(GetSafeHwnd(), *rev);
1273 m_parent->UpdateFullHistory();
1275 break;
1276 case ID_COPYREFS:
1277 DoCopyRefs();
1278 break;
1279 case ID_DELETE:
1281 MENUITEMINFO mii = { 0 };
1282 mii.cbSize = sizeof(mii);
1283 mii.fMask |= MIIM_DATA;
1284 GetMenuItemInfo(popup, cmd, FALSE, &mii);
1285 auto rev = reinterpret_cast<CString*>(mii.dwItemData);
1286 if (!rev)
1287 break;
1289 CString shortname;
1290 if (rev == reinterpret_cast<CString*>(MAKEINTRESOURCE(IDS_ALL)))
1292 bool nothingDeleted = true;
1293 for (const auto& ref : allRefNames)
1295 if (!CAppUtils::DeleteRef(this, ref))
1296 break;
1297 nothingDeleted = false;
1299 if (nothingDeleted)
1300 return;
1302 else if (!CAppUtils::DeleteRef(this, *rev))
1303 return;
1305 m_parent->UpdateFullHistory();
1306 break;
1308 case ID_BROWSEREPO:
1309 DoBrowseRepo();
1310 break;
1311 case ID_COMPAREHEADS:
1312 if (m_SelectedEntry1)
1313 CompareRevs(L"HEAD");
1314 break;
1315 case ID_COMPAREWT:
1316 if (m_SelectedEntry1)
1317 CompareRevs(CGitHash().ToString());
1318 break;
1322 void CRevisionGraphWnd::OnMouseMove(UINT nFlags, CPoint point)
1324 if (IsUpdateJobRunning())
1325 return __super::OnMouseMove(nFlags, point);
1326 if (!m_bIsCanvasMove)
1328 if (m_bShowOverview && (m_OverviewRect.PtInRect(point))&&(nFlags & MK_LBUTTON))
1330 // scrolling
1331 int x = static_cast<int>((point.x - m_OverviewRect.left - (m_OverviewPosRect.Width() / 2)) / m_previewZoom * m_fZoomFactor);
1332 int y = static_cast<int>((point.y - m_OverviewRect.top - (m_OverviewPosRect.Height() / 2)) / m_previewZoom * m_fZoomFactor);
1333 x = max(0, x);
1334 y = max(0, y);
1335 SetScrollbars(y, x);
1336 Invalidate(FALSE);
1337 return __super::OnMouseMove(nFlags, point);
1339 else
1341 // update screen if we hover over a different
1342 // node than during the last redraw
1344 CPoint clientPoint = point;
1345 GetCursorPos (&clientPoint);
1346 ScreenToClient (&clientPoint);
1348 return __super::OnMouseMove(nFlags, point);
1351 SetCapture();
1353 int pos_h = GetScrollPos(SB_HORZ);
1354 pos_h -= point.x - m_ptMoveCanvas.x;
1355 SetScrollPos(SB_HORZ, pos_h);
1357 int pos_v = GetScrollPos(SB_VERT);
1358 pos_v -= point.y - m_ptMoveCanvas.y;
1359 SetScrollPos(SB_VERT, pos_v);
1361 m_ptMoveCanvas = point;
1363 this->Invalidate();
1365 __super::OnMouseMove(nFlags, point);
1368 BOOL CRevisionGraphWnd::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
1370 CRect viewRect = GetViewRect();
1372 LPWSTR cursorID = IDC_ARROW;
1373 HINSTANCE resourceHandle = nullptr;
1375 if ((nHitTest == HTCLIENT)&&(pWnd == this)&&(viewRect.Width())&&(viewRect.Height())&&(message))
1377 POINT pt;
1378 if (GetCursorPos(&pt))
1380 ScreenToClient(&pt);
1381 if (m_OverviewPosRect.PtInRect(pt) || m_bIsCanvasMove)
1383 resourceHandle = AfxGetResourceHandle();
1384 cursorID = (GetKeyState(VK_LBUTTON) & 0x8000)
1385 ? MAKEINTRESOURCE(IDC_PANCURDOWN)
1386 : MAKEINTRESOURCE(IDC_PANCUR);
1391 HCURSOR hCur = LoadCursor(resourceHandle, cursorID);
1392 if (GetCursor() != hCur)
1393 SetCursor (hCur);
1395 return TRUE;
1398 void CRevisionGraphWnd::OnTimer (UINT_PTR nIDEvent)
1400 if (nIDEvent == GLYPH_HOVER_EVENT)
1402 KillTimer (GLYPH_HOVER_EVENT);
1404 m_showHoverGlyphs = true;
1405 Invalidate (FALSE);
1407 else
1408 __super::OnTimer (nIDEvent);
1411 LRESULT CRevisionGraphWnd::OnWorkerThreadDone(WPARAM, LPARAM)
1413 // handle potential race condition between PostMessage and leaving job:
1414 // the background job may not have exited, yet
1416 if (updateJob.get())
1417 updateJob->GetResult();
1419 InitView();
1420 BuildPreview();
1422 if (m_HeadNode)
1424 SCROLLINFO sinfo = { 0 };
1425 sinfo.cbSize = sizeof(SCROLLINFO);
1426 if (GetScrollInfo(SB_HORZ, &sinfo))
1428 sinfo.nPos = min(max(sinfo.nMin, static_cast<int>(m_GraphAttr.x(m_HeadNode) - m_GraphAttr.width(m_HeadNode) / 2)), sinfo.nMax);
1429 SetScrollInfo(SB_HORZ, &sinfo);
1431 if (GetScrollInfo(SB_VERT, &sinfo))
1433 sinfo.nPos = min(max(sinfo.nMin, static_cast<int>(m_GraphAttr.y(m_HeadNode) - m_GraphAttr.height(m_HeadNode) / 2)), sinfo.nMax);
1434 SetScrollInfo(SB_VERT, &sinfo);
1438 Invalidate(FALSE);
1440 if (m_parent && !m_parent->GetOutputFile().IsEmpty())
1442 // save the graph to the output file and exit
1443 SaveGraphAs(m_parent->GetOutputFile());
1444 PostQuitMessage(0);
1446 return 0;
1449 ULONG CRevisionGraphWnd::GetGestureStatus(CPoint /*ptTouch*/)
1451 return 0;
1454 void CRevisionGraphWnd::ScrollTo(int i, bool select)
1456 ogdf::node v = nullptr;
1457 for (auto vIT : m_Graph.nodes)
1459 if (vIT->index() == i)
1461 v = vIT;
1462 break;
1465 if (!v)
1466 return;
1468 SCROLLINFO sinfo = { 0 };
1469 sinfo.cbSize = sizeof(SCROLLINFO);
1470 if (GetScrollInfo(SB_HORZ, &sinfo))
1472 sinfo.nPos = min(max(sinfo.nMin, static_cast<int>(m_GraphAttr.x(v) - m_GraphAttr.width(v) / 2)), sinfo.nMax);
1473 SetScrollInfo(SB_HORZ, &sinfo);
1475 if (GetScrollInfo(SB_VERT, &sinfo))
1477 sinfo.nPos = min(max(sinfo.nMin, static_cast<int>(m_GraphAttr.y(v) - m_GraphAttr.height(v) / 2 - max(1.0f, 25 * m_fZoomFactor))), sinfo.nMax);
1478 SetScrollInfo(SB_VERT, &sinfo);
1481 if (!select)
1482 return;
1484 m_SelectedEntry1 = v;
1485 m_SelectedEntry2 = nullptr;