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.
21 #include "TortoiseProc.h"
22 #include "Revisiongraphwnd.h"
23 #include "MessageBox.h"
26 #include "PathUtils.h"
27 #include "StringUtils.h"
29 #include "UnicodeUtils.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"
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>
50 static char THIS_FILE
[] = __FILE__
;
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
62 ID_COMPAREREVS
= 0x100,
74 ID_EXPAND_ALL
= 0x500,
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()
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)
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)
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
;
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()
152 void CRevisionGraphWnd::DoDataExchange(CDataExchange
* pDX
)
154 CWnd::DoDataExchange(pDX
);
158 BEGIN_MESSAGE_MAP(CRevisionGraphWnd
, CWnd
)
165 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW
, 0, 0xFFFF, OnToolTipNotify
)
166 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA
, 0, 0xFFFF, OnToolTipNotify
)
174 ON_MESSAGE(WM_WORKERTHREADDONE
,OnWorkerThreadDone
)
175 ON_WM_CAPTURECHANGED()
178 void CRevisionGraphWnd::Init(CWnd
* pParent
, LPRECT rect
)
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");
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
229 CSyncPointer
<const ILayoutNodeList
> nodeList (m_state
.GetNodes());
231 return index_t(NO_INDEX
);
233 // search the nodes for one at that grid position
235 return nodeList
->GetAt (GetLogCoordinates (point
), border
);
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
))
250 DWORD
CRevisionGraphWnd::GetHoverGlyphs (CPoint
/*point*/) const
252 // if there is no layout, there will be no nodes,
256 CSyncPointer
<const ILayoutNodeList
> nodeList (m_state
.GetNodes());
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())
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
);
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);
288 = m_state
.GetOptions()->GetOption
<CUpsideDownLayout
>()->IsActive();
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
302 if (topGlyphArea
.PtInRect (logCoordinates
))
303 result
= base
->GetSource()
304 ? CGraphNodeStates::COLLAPSED_ABOVE
| CGraphNodeStates::SPLIT_ABOVE
307 if (bottomGlyphArea
.PtInRect (logCoordinates
))
308 result
= base
->GetNext()
309 ? CGraphNodeStates::COLLAPSED_BELOW
| CGraphNodeStates::SPLIT_BELOW
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
))
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
))
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.
354 case SB_LEFT
: // Scroll to far left.
355 sinfo
.nPos
= sinfo
.nMin
;
357 case SB_RIGHT
: // Scroll to far right.
358 sinfo
.nPos
= sinfo
.nMax
;
360 case SB_ENDSCROLL
: // End scroll.
362 case SB_LINELEFT
: // Scroll left.
363 if (sinfo
.nPos
> sinfo
.nMin
)
366 case SB_LINERIGHT
: // Scroll right.
367 if (sinfo
.nPos
< sinfo
.nMax
)
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
));
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
));
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.
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.
389 SetScrollInfo(SB_HORZ
, &sinfo
);
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.
403 case SB_LEFT
: // Scroll to far left.
404 sinfo
.nPos
= sinfo
.nMin
;
406 case SB_RIGHT
: // Scroll to far right.
407 sinfo
.nPos
= sinfo
.nMax
;
409 case SB_ENDSCROLL
: // End scroll.
411 case SB_LINELEFT
: // Scroll left.
412 if (sinfo
.nPos
> sinfo
.nMin
)
415 case SB_LINERIGHT
: // Scroll right.
416 if (sinfo
.nPos
< sinfo
.nMax
)
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
));
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
));
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.
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.
438 SetScrollInfo(SB_VERT
, &sinfo
);
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
));
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());
459 bool bControl
= !!(GetKeyState(VK_CONTROL
)&0x8000);
460 bool bOverview
= m_bShowOverview
&& m_OverviewRect
.PtInRect(point
);
464 const CRevisionGraphState::SVisibleGlyph
* hitGlyph
465 = GetHitGlyph (point
);
469 ToggleNodeFlag (hitGlyph
->node
, hitGlyph
->state
);
470 return __super::OnLButtonDown(nFlags
, point
);
473 auto nodeIndex
= GetHitNode(point
);
478 if (m_SelectedEntry1
== nodeIndex
)
480 if (m_SelectedEntry2
)
482 m_SelectedEntry1
= m_SelectedEntry2
;
483 m_SelectedEntry2
= nullptr;
486 m_SelectedEntry1
= nullptr;
488 else if (m_SelectedEntry2
== nodeIndex
)
489 m_SelectedEntry2
= nullptr;
490 else if (m_SelectedEntry1
)
491 m_SelectedEntry2
= nodeIndex
;
493 m_SelectedEntry1
= nodeIndex
;
497 if (m_SelectedEntry1
== nodeIndex
)
498 m_SelectedEntry1
= nullptr;
500 m_SelectedEntry1
= nodeIndex
;
501 m_SelectedEntry2
= nullptr;
508 if ((!bHit
)&&(!bControl
)&&(!bOverview
))
510 m_SelectedEntry1
= nullptr;
511 m_SelectedEntry2
= nullptr;
512 m_bIsCanvasMove
= true;
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
;
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
;
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;
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
567 __super::OnLButtonUp(nFlags
, point
);
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
585 __super::OnLButtonUp(nFlags
, point
);
588 if (fZoomfactor
> MAX_ZOOM
)
590 fZoomfactor
= MAX_ZOOM
;
591 fact
= fZoomfactor
/m_fZoomFactor
;
594 auto pDlg
= static_cast<CRevisionGraphDlg
*>(GetParent());
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
;
610 m_bIsCanvasMove
= false;
611 m_ptRubberEnd
= CPoint(0,0);
615 INT_PTR
CRevisionGraphWnd::OnToolHitTest(CPoint point
, TOOLINFO
* pTI
) const
617 if (IsUpdateJobRunning())
620 auto nodeIndex
= GetHitNode(point
);
621 if (m_tooltipIndex
!= nodeIndex
)
623 // force tooltip to be updated
625 m_tooltipIndex
= nodeIndex
;
632 // if ((GetHoverGlyphs (point) != 0) || (GetHitGlyph (point) != nullptr))
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
;
644 BOOL
CRevisionGraphWnd::OnToolTipNotify(UINT
/*id*/, NMHDR
*pNMHDR
, LRESULT
*pResult
)
646 if (pNMHDR
->idFrom
!= reinterpret_cast<UINT_PTR
>(m_hWnd
))
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
));
658 if (strTipText
.IsEmpty())
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);
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()
690 int screenWidth
= GetSystemMetrics(SM_CXSCREEN
);
691 int screenHeight
= GetSystemMetrics(SM_CYSCREEN
);
693 // get current mouse position
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
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
)
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
" ..."
727 // select the tooltip font
729 NONCLIENTMETRICS metrics
;
730 metrics
.cbSize
= sizeof (metrics
);
731 SystemParametersInfo (SPI_GETNONCLIENTMETRICS
, metrics
.cbSize
, &metrics
, 0);
734 font
.CreateFontIndirect(&metrics
.lfStatusFont
);
735 CFont
* pOldFont
= dc
->SelectObject (&font
);
737 // split into lines and fill the tooltip rect
741 int remainingHeight
= tooltipSize
.cy
;
743 while (pos
< wholeText
.GetLength())
745 // extract a whole line
747 int nextPos
= wholeText
.Find ('\n', pos
);
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 (' ');
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
)
779 pos
+= line
.GetLength();
782 // relase temp. resources
784 dc
->SelectObject (pOldFont
);
792 CString
CRevisionGraphWnd::TooltipText(ogdf::node index
)
797 CGitHash hash
= m_logEntries
[index
->index()];
798 GitRevLoglist
* rev
= this->m_LogCache
.GetCacheData(hash
);
799 str
+= rev
->m_CommitHash
.ToString();
801 str
+= rev
->GetAuthorName() + L
" <" + rev
->GetAuthorEmail() + L
'>';
803 str
+= rev
->GetAuthorDate().Format(L
"%Y-%m-%d %H:%M");
804 str
+= L
"\n\n" + rev
->GetSubject();
806 str
+= rev
->GetBody();
807 if (str
.GetLength() > 8000)
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
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
);
829 rect
= GetViewRect();
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
842 float fZoom
= m_fZoomFactor
;
843 m_fZoomFactor
= DEFAULT_ZOOM
;
844 DoZoom(m_fZoomFactor
);
846 rect
= GetViewRect();
847 svg
.SetViewSize(rect
.Width(), rect
.Height());
850 DrawGraph(dev
, rect
, 0, 0, true);
852 m_fZoomFactor
= fZoom
;
853 DoZoom(m_fZoomFactor
);
855 else if (extension
.CompareNoCase(L
".gv") == 0)
858 float fZoom
= m_fZoomFactor
;
859 m_fZoomFactor
= DEFAULT_ZOOM
;
860 DoZoom(m_fZoomFactor
);
862 rect
= GetViewRect();
864 dev
.pGraphviz
= &graphviz
;
865 DrawGraph(dev
, rect
, 0, 0, true);
866 graphviz
.Save(sSavePath
);
867 m_fZoomFactor
= fZoom
;
868 DoZoom(m_fZoomFactor
);
872 // save the graph as a pixel picture instead of a vector picture
873 // create dc to paint on
876 CString sErrormessage
;
879 if (!dc
.CreateCompatibleDC(&ddc
))
881 CFormatMessageWrapper errorDetails
;
883 MessageBox(errorDetails
, L
"Error", MB_OK
| MB_ICONINFORMATION
);
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 };
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);
906 CMessageBox::Show(m_hWnd
, IDS_REVGRAPH_ERR_NOMEMORY
, IDS_APPNAME
, MB_ICONERROR
);
909 HBITMAP oldbm
= static_cast<HBITMAP
>(dc
.SelectObject(hbm
));
910 // paint the whole graph
913 DrawGraph(dev
, rect
, 0, 0, true);
914 // now use GDI+ to save the picture
917 Bitmap
bitmap(hbm
, nullptr);
918 if (bitmap
.GetLastStatus()==Ok
)
920 // Get the CLSID of the encoder.
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
);
934 sSavePath
+= L
".jpg";
935 ret
= GetEncoderClsid(L
"image/jpeg", &encoderClsid
);
939 CStringW tfile
= CStringW(sSavePath
);
940 bitmap
.Save(tfile
, &encoderClsid
, nullptr);
943 sErrormessage
.Format(IDS_REVGRAPH_ERR_NOENCODER
, static_cast<LPCWSTR
>(CPathUtils::GetFileExtFromPath(sSavePath
)));
946 sErrormessage
.LoadString(IDS_REVGRAPH_ERR_NOBITMAP
);
948 dc
.SelectObject(oldbm
);
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);
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
)));
976 int orientation
= (GetKeyState(VK_SHIFT
) & 0x8000) ? SB_HORZ
: SB_VERT
;
977 int pos
= GetScrollPos(orientation
);
979 SetScrollPos(orientation
, pos
);
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
);
993 SetScrollPos(orientation
, pos
);
996 return __super::OnMouseHWheel(nFlags
, zDelta
, pt
);
999 bool CRevisionGraphWnd::UpdateSelectedEntry(ogdf::node clickedentry
)
1001 if (!m_SelectedEntry1
&& !clickedentry
)
1004 if (!m_SelectedEntry1
)
1006 m_SelectedEntry1
= clickedentry
;
1009 if (!m_SelectedEntry2
&& clickedentry
!= m_SelectedEntry1
)
1011 m_SelectedEntry1
= clickedentry
;
1014 if (m_SelectedEntry1
&& m_SelectedEntry2
)
1016 if ((m_SelectedEntry2
!= clickedentry
)&&(m_SelectedEntry1
!= clickedentry
))
1019 if (!m_SelectedEntry1
)
1025 void CRevisionGraphWnd::AppendMenu
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);
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
)
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()));
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
)
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
)
1112 STRING_VECTOR list
= GetFriendRefNames(m_SelectedEntry1
);
1115 text
= m_logEntries
[m_SelectedEntry1
->index()].ToString();
1116 for (size_t i
= 0; i
< list
.size(); ++i
)
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())
1130 CPoint clientpoint
= point
;
1131 this->ScreenToClient(&clientpoint
);
1133 auto nodeIndex
= GetHitNode(clientpoint
);
1135 if ( !UpdateSelectedEntry (nodeIndex
))
1139 if (!popup
.CreatePopupMenu())
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
, ¤tBranch
, &refType
);
1156 if (branchNames
.size() == 1)
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)
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
);
1172 for (auto arefType
: { CGit::REMOTE_BRANCH
, CGit::TAG
, CGit::ANNOTATED_TAG
})
1174 remoteBranchNames
= GetFriendRefNames(m_SelectedEntry1
, ¤tBranch
, &arefType
);
1175 if (remoteBranchNames
.size() >= 1)
1178 temp
.LoadString(IDS_SWITCH_TO_THIS
);
1179 AppendMenu(popup
, temp
, ID_SWITCHTOREV
, &remoteBranchNames
[0]);
1185 AppendMenu(popup
, IDS_COPY_REF_NAMES
, ID_COPYREFS
);
1187 allRefNames
= GetFriendRefNames(m_SelectedEntry1
, ¤tBranch
);
1188 if (allRefNames
.size() == 1)
1191 str
.LoadString(IDS_DELETE_BRANCHTAG_SHORT
);
1193 str
+= allRefNames
[0];
1194 AppendMenu(popup
, str
, ID_DELETE
, &allRefNames
[0]);
1196 else if (allRefNames
.size() > 1)
1199 str
.LoadString(IDS_DELETE_BRANCHTAG
);
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
);
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
)
1240 case ID_UNIDIFFREVS
:
1241 if (m_SelectedEntry1
)
1242 UnifiedDiffRevs(false);
1244 case ID_UNIDIFFHEADS
:
1245 if (m_SelectedEntry1
)
1246 UnifiedDiffRevs(true);
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
);
1261 m_parent
->UpdateFullHistory();
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();
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
);
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
))
1297 nothingDeleted
= false;
1302 else if (!CAppUtils::DeleteRef(this, *rev
))
1305 m_parent
->UpdateFullHistory();
1311 case ID_COMPAREHEADS
:
1312 if (m_SelectedEntry1
)
1313 CompareRevs(L
"HEAD");
1316 if (m_SelectedEntry1
)
1317 CompareRevs(CGitHash().ToString());
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
))
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
);
1335 SetScrollbars(y
, x
);
1337 return __super::OnMouseMove(nFlags
, point
);
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
);
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
;
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
))
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
)
1398 void CRevisionGraphWnd::OnTimer (UINT_PTR nIDEvent
)
1400 if (nIDEvent
== GLYPH_HOVER_EVENT
)
1402 KillTimer (GLYPH_HOVER_EVENT
);
1404 m_showHoverGlyphs
= true;
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();
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
);
1440 if (m_parent
&& !m_parent
->GetOutputFile().IsEmpty())
1442 // save the graph to the output file and exit
1443 SaveGraphAs(m_parent
->GetOutputFile());
1449 ULONG
CRevisionGraphWnd::GetGestureStatus(CPoint
/*ptTouch*/)
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
)
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
);
1484 m_SelectedEntry1
= v
;
1485 m_SelectedEntry2
= nullptr;