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.
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 "RevisionGraph/StandardLayout.h"
36 //#include "RevisionGraph/UpsideDownLayout.h"
37 #include "FormatMessageWrapper.h"
38 #include "GitRevLoglist.h"
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>
52 static char THIS_FILE
[] = __FILE__
;
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
64 ID_COMPAREREVS
= 0x100,
75 ID_EXPAND_ALL
= 0x500,
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()
87 , m_SelectedEntry1(nullptr)
88 , m_SelectedEntry2(nullptr)
92 , m_bTweakTrunkColors(true)
93 , m_bTweakTagsColors(true)
94 , m_fZoomFactor(DEFAULT_ZOOM
)
97 , m_bShowOverview(false)
99 , m_hoverIndex(nullptr)
101 , m_tooltipIndex(nullptr)
102 , m_showHoverGlyphs (false)
103 , m_bIsCanvasMove(false)
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);
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
;
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;
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
);
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
);
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
);
193 PlanarizationLayout pl
;
195 FastPlanarSubgraph
*ps
= ::new FastPlanarSubgraph
;
197 VariableEmbeddingInserter
*ves
= ::new VariableEmbeddingInserter
;
198 ves
->removeReinsert(EdgeInsertionModule::rrAll
);
202 EmbedderMinDepthMaxFaceLayers
*emb
= ::new EmbedderMinDepthMaxFaceLayers
;
205 OrthoLayout
*ol
=::new OrthoLayout
;
206 ol
->separation(20.0);
209 ol
->preferedDir(OrthoDir::odEast
);
210 pl
.setPlanarLayouter(ol
);
212 pl
.call(m_GraphAttr
);
215 forall_nodes(v
,m_Graph
) {
216 TRACE(L
"node x %f y %f %f %f\n",/* m_GraphAttr.idNode(v), */
219 m_GraphAttr
.width(v
),
220 m_GraphAttr
.height(v
)
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");
240 CRevisionGraphWnd::~CRevisionGraphWnd()
242 for (int i
= 0; i
< MAXFONTS
; ++i
)
246 m_apFonts
[i
]->DeleteObject();
249 m_apFonts
[i
] = nullptr;
255 void CRevisionGraphWnd::DoDataExchange(CDataExchange
* pDX
)
257 CWnd::DoDataExchange(pDX
);
261 BEGIN_MESSAGE_MAP(CRevisionGraphWnd
, CWnd
)
268 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW
, 0, 0xFFFF, OnToolTipNotify
)
269 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA
, 0, 0xFFFF, OnToolTipNotify
)
277 ON_MESSAGE(WM_WORKERTHREADDONE
,OnWorkerThreadDone
)
278 ON_WM_CAPTURECHANGED()
281 void CRevisionGraphWnd::Init(CWnd
* pParent
, LPRECT rect
)
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");
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
342 CSyncPointer
<const ILayoutNodeList
> nodeList (m_state
.GetNodes());
344 return index_t(NO_INDEX
);
346 // search the nodes for one at that grid position
348 return nodeList
->GetAt (GetLogCoordinates (point
), border
);
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
))
364 DWORD
CRevisionGraphWnd::GetHoverGlyphs (CPoint
/*point*/) const
366 // if there is no layout, there will be no nodes,
370 CSyncPointer
<const ILayoutNodeList
> nodeList (m_state
.GetNodes());
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())
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
);
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);
402 = m_state
.GetOptions()->GetOption
<CUpsideDownLayout
>()->IsActive();
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
416 if (topGlyphArea
.PtInRect (logCoordinates
))
417 result
= base
->GetSource()
418 ? CGraphNodeStates::COLLAPSED_ABOVE
| CGraphNodeStates::SPLIT_ABOVE
421 if (bottomGlyphArea
.PtInRect (logCoordinates
))
422 result
= base
->GetNext()
423 ? CGraphNodeStates::COLLAPSED_BELOW
| CGraphNodeStates::SPLIT_BELOW
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
))
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
))
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.
468 case SB_LEFT
: // Scroll to far left.
469 sinfo
.nPos
= sinfo
.nMin
;
471 case SB_RIGHT
: // Scroll to far right.
472 sinfo
.nPos
= sinfo
.nMax
;
474 case SB_ENDSCROLL
: // End scroll.
476 case SB_LINELEFT
: // Scroll left.
477 if (sinfo
.nPos
> sinfo
.nMin
)
480 case SB_LINERIGHT
: // Scroll right.
481 if (sinfo
.nPos
< sinfo
.nMax
)
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
);
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
);
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.
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.
503 SetScrollInfo(SB_HORZ
, &sinfo
);
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.
517 case SB_LEFT
: // Scroll to far left.
518 sinfo
.nPos
= sinfo
.nMin
;
520 case SB_RIGHT
: // Scroll to far right.
521 sinfo
.nPos
= sinfo
.nMax
;
523 case SB_ENDSCROLL
: // End scroll.
525 case SB_LINELEFT
: // Scroll left.
526 if (sinfo
.nPos
> sinfo
.nMin
)
529 case SB_LINERIGHT
: // Scroll right.
530 if (sinfo
.nPos
< sinfo
.nMax
)
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
);
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
);
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.
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.
552 SetScrollInfo(SB_VERT
, &sinfo
);
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
));
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());
573 bool bControl
= !!(GetKeyState(VK_CONTROL
)&0x8000);
574 bool bOverview
= m_bShowOverview
&& m_OverviewRect
.PtInRect(point
);
578 const CRevisionGraphState::SVisibleGlyph
* hitGlyph
579 = GetHitGlyph (point
);
583 ToggleNodeFlag (hitGlyph
->node
, hitGlyph
->state
);
584 return __super::OnLButtonDown(nFlags
, point
);
587 auto nodeIndex
= GetHitNode(point
);
592 if (m_SelectedEntry1
== nodeIndex
)
594 if (m_SelectedEntry2
)
596 m_SelectedEntry1
= m_SelectedEntry2
;
597 m_SelectedEntry2
= nullptr;
600 m_SelectedEntry1
= nullptr;
602 else if (m_SelectedEntry2
== nodeIndex
)
603 m_SelectedEntry2
= nullptr;
604 else if (m_SelectedEntry1
)
605 m_SelectedEntry2
= nodeIndex
;
607 m_SelectedEntry1
= nodeIndex
;
611 if (m_SelectedEntry1
== nodeIndex
)
612 m_SelectedEntry1
= nullptr;
614 m_SelectedEntry1
= nodeIndex
;
615 m_SelectedEntry2
= nullptr;
622 if ((!bHit
)&&(!bControl
)&&(!bOverview
))
624 m_SelectedEntry1
= nullptr;
625 m_SelectedEntry2
= nullptr;
626 m_bIsCanvasMove
= true;
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
;
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
;
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;
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
681 __super::OnLButtonUp(nFlags
, point
);
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
699 __super::OnLButtonUp(nFlags
, point
);
702 if (fZoomfactor
> MAX_ZOOM
)
704 fZoomfactor
= MAX_ZOOM
;
705 fact
= fZoomfactor
/m_fZoomFactor
;
708 auto pDlg
= static_cast<CRevisionGraphDlg
*>(GetParent());
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
;
724 m_bIsCanvasMove
= false;
725 m_ptRubberEnd
= CPoint(0,0);
729 INT_PTR
CRevisionGraphWnd::OnToolHitTest(CPoint point
, TOOLINFO
* pTI
) const
731 if (IsUpdateJobRunning())
734 auto nodeIndex
= GetHitNode(point
);
735 if (m_tooltipIndex
!= nodeIndex
)
737 // force tooltip to be updated
739 m_tooltipIndex
= nodeIndex
;
746 // if ((GetHoverGlyphs (point) != 0) || (GetHitGlyph (point) != nullptr))
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
;
758 BOOL
CRevisionGraphWnd::OnToolTipNotify(UINT
/*id*/, NMHDR
*pNMHDR
, LRESULT
*pResult
)
760 if (pNMHDR
->idFrom
!= (UINT_PTR
)m_hWnd
)
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
));
772 if (strTipText
.IsEmpty())
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);
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()
804 int screenWidth
= GetSystemMetrics(SM_CXSCREEN
);
805 int screenHeight
= GetSystemMetrics(SM_CYSCREEN
);
807 // get current mouse position
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
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
)
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
" ..."
841 // select the tooltip font
843 NONCLIENTMETRICS metrics
;
844 metrics
.cbSize
= sizeof (metrics
);
845 SystemParametersInfo (SPI_GETNONCLIENTMETRICS
, metrics
.cbSize
, &metrics
, 0);
848 font
.CreateFontIndirect(&metrics
.lfStatusFont
);
849 CFont
* pOldFont
= dc
->SelectObject (&font
);
851 // split into lines and fill the tooltip rect
855 int remainingHeight
= tooltipSize
.cy
;
857 while (pos
< wholeText
.GetLength())
859 // extract a whole line
861 int nextPos
= wholeText
.Find ('\n', pos
);
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 (' ');
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
)
893 pos
+= line
.GetLength();
896 // relase temp. resources
898 dc
->SelectObject (pOldFont
);
906 CString
CRevisionGraphWnd::TooltipText(ogdf::node index
)
911 CGitHash hash
= m_logEntries
[index
->index()];
912 GitRevLoglist
* rev
= this->m_LogCache
.GetCacheData(hash
);
913 str
+= rev
->m_CommitHash
.ToString();
915 str
+= rev
->GetAuthorName() + L
' ' + rev
->GetAuthorEmail();
917 str
+= rev
->GetAuthorDate().Format(L
"%Y-%m-%d %H:%M");
918 str
+= L
"\n\n" + rev
->GetSubject();
920 str
+= rev
->GetBody();
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
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
);
938 rect
= GetViewRect();
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
951 float fZoom
= m_fZoomFactor
;
952 m_fZoomFactor
= DEFAULT_ZOOM
;
953 DoZoom(m_fZoomFactor
);
955 rect
= GetViewRect();
956 svg
.SetViewSize(rect
.Width(), rect
.Height());
959 DrawGraph(dev
, rect
, 0, 0, true);
961 m_fZoomFactor
= fZoom
;
962 DoZoom(m_fZoomFactor
);
964 else if (extension
.CompareNoCase(L
".gv") == 0)
967 float fZoom
= m_fZoomFactor
;
968 m_fZoomFactor
= DEFAULT_ZOOM
;
969 DoZoom(m_fZoomFactor
);
971 rect
= GetViewRect();
973 dev
.pGraphviz
= &graphviz
;
974 DrawGraph(dev
, rect
, 0, 0, true);
975 graphviz
.Save(sSavePath
);
976 m_fZoomFactor
= fZoom
;
977 DoZoom(m_fZoomFactor
);
981 // save the graph as a pixel picture instead of a vector picture
982 // create dc to paint on
985 CString sErrormessage
;
988 if (!dc
.CreateCompatibleDC(&ddc
))
990 CFormatMessageWrapper errorDetails
;
992 MessageBox(errorDetails
, L
"Error", MB_OK
| MB_ICONINFORMATION
);
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 };
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);
1015 CMessageBox::Show(m_hWnd
, IDS_REVGRAPH_ERR_NOMEMORY
, IDS_APPNAME
, MB_ICONERROR
);
1018 HBITMAP oldbm
= (HBITMAP
)dc
.SelectObject(hbm
);
1019 // paint the whole graph
1022 DrawGraph(dev
, rect
, 0, 0, true);
1023 // now use GDI+ to save the picture
1026 Bitmap
bitmap(hbm
, nullptr);
1027 if (bitmap
.GetLastStatus()==Ok
)
1029 // Get the CLSID of the encoder.
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
);
1043 sSavePath
+= L
".jpg";
1044 ret
= GetEncoderClsid(L
"image/jpeg", &encoderClsid
);
1048 CStringW tfile
= CStringW(sSavePath
);
1049 bitmap
.Save(tfile
, &encoderClsid
, nullptr);
1052 sErrormessage
.Format(IDS_REVGRAPH_ERR_NOENCODER
, (LPCTSTR
)CPathUtils::GetFileExtFromPath(sSavePath
));
1055 sErrormessage
.LoadString(IDS_REVGRAPH_ERR_NOBITMAP
);
1057 dc
.SelectObject(oldbm
);
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);
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
)));
1085 int orientation
= (GetKeyState(VK_SHIFT
) & 0x8000) ? SB_HORZ
: SB_VERT
;
1086 int pos
= GetScrollPos(orientation
);
1088 SetScrollPos(orientation
, pos
);
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
);
1102 SetScrollPos(orientation
, pos
);
1105 return __super::OnMouseHWheel(nFlags
, zDelta
, pt
);
1108 bool CRevisionGraphWnd::UpdateSelectedEntry(ogdf::node clickedentry
)
1110 if (!m_SelectedEntry1
&& !clickedentry
)
1113 if (!m_SelectedEntry1
)
1115 m_SelectedEntry1
= clickedentry
;
1118 if (!m_SelectedEntry2
&& clickedentry
!= m_SelectedEntry1
)
1120 m_SelectedEntry1
= clickedentry
;
1123 if (m_SelectedEntry1
&& m_SelectedEntry2
)
1125 if ((m_SelectedEntry2
!= clickedentry
)&&(m_SelectedEntry1
!= clickedentry
))
1128 if (!m_SelectedEntry1
)
1134 void CRevisionGraphWnd::AppendMenu
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);
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*/)
1181 CSyncPointer
<CGraphNodeStates
> nodeStates (m_state
.GetNodeStates());
1185 DWORD state
= nodeStates
->GetCombinedFlags();
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
);
1197 DWORD state
= nodeStates
->GetFlags (node
);
1199 if (node
->GetSource() || (state
& CGraphNodeStates::COLLAPSED_ABOVE
))
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
))
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
))
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
))
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
))
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
))
1236 , (state
& CGraphNodeStates::SPLIT_BELOW
)
1237 ? IDS_REVGRAPH_POPUP_JOIN_BELOW
1238 : IDS_REVGRAPH_POPUP_SPLIT_BELOW
1239 , ID_GRAPH_SPLITJOIN_BELOW
);
1244 CString
CRevisionGraphWnd::GetSelectedURL() const
1247 if (!m_SelectedEntry1
)
1250 CString URL
= m_state
.GetRepositoryRoot()
1251 + CUnicodeUtils::GetUnicode (m_SelectedEntry1
->GetPath().GetPath().c_str());
1252 URL
= CUnicodeUtils::GetUnicode(CPathUtils::PathEscape(CUnicodeUtils::GetUTF8(URL
)));
1259 CString
CRevisionGraphWnd::GetWCURL() const
1262 CTGitPath
path (m_sPath
);
1267 const SVNInfoData
* status
1268 = info
.GetFirstFileInfo (path
, SVNRev(), SVNRev());
1270 return !status
? CString() : status
->url
;
1275 void CRevisionGraphWnd::DoShowLog()
1277 if (!m_SelectedEntry1
)
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());
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()
1298 dlg
.m_pathList
= CTGitPathList (CTGitPath (m_sPath
));
1302 void CRevisionGraphWnd::DoMergeTo()
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
)));
1315 dlg
.SetSecondUrl(URL
);
1316 SVNRevRangeArray revarray
;
1317 revarray
.AddRevRange (m_SelectedEntry1
->GetRevision()-1, svn_revnum_t(m_SelectedEntry1
->GetRevision()));
1318 dlg
.SetRevisionRanges(revarray
);
1324 void CRevisionGraphWnd::DoUpdate()
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());
1335 if (m_state
.GetFetchedWCState())
1336 m_parent
->UpdateFullHistory();
1340 void CRevisionGraphWnd::DoSwitch(CString rev
)
1342 CAppUtils::PerformSwitch(GetSafeHwnd(), rev
);
1345 void CRevisionGraphWnd::DoSwitchToHead()
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());
1356 if (m_state
.GetFetchedWCState())
1357 m_parent
->UpdateFullHistory();
1361 void CRevisionGraphWnd::DoBrowseRepo()
1363 if (!m_SelectedEntry1
)
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*/)
1383 CSyncPointer
<CGraphNodeStates
> nodeStates (m_state
.GetNodeStates());
1385 if (nodeStates
->GetFlags (node
) & flag
)
1386 nodeStates
->ResetFlags (node
, flag
);
1388 nodeStates
->SetFlags (node
, flag
);
1390 m_parent
->StartWorkerThread();
1394 void CRevisionGraphWnd::DoCopyRefs()
1396 if (!m_SelectedEntry1
)
1399 STRING_VECTOR list
= GetFriendRefNames(m_SelectedEntry1
);
1402 text
= m_logEntries
[m_SelectedEntry1
->index()].ToString();
1403 for (size_t i
= 0; i
< list
.size(); ++i
)
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())
1417 CPoint clientpoint
= point
;
1418 this->ScreenToClient(&clientpoint
);
1420 auto nodeIndex
= GetHitNode(clientpoint
);
1422 if ( !UpdateSelectedEntry (nodeIndex
))
1426 if (!popup
.CreatePopupMenu())
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
, ¤tBranch
, &refType
);
1442 if (branchNames
.size() == 1)
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)
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
, ¤tBranch
);
1460 if (allRefNames
.size() == 1)
1463 str
.LoadString(IDS_DELETE_BRANCHTAG_SHORT
);
1465 str
+= allRefNames
[0];
1466 AppendMenu(popup
, str
, ID_DELETE
, &allRefNames
[0]);
1468 else if (allRefNames
.size() > 1)
1471 str
.LoadString(IDS_DELETE_BRANCHTAG
);
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
);
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
)
1514 case ID_UNIDIFFREVS
:
1515 if (m_SelectedEntry1
)
1516 UnifiedDiffRevs(false);
1518 case ID_UNIDIFFHEADS
:
1519 if (m_SelectedEntry1
)
1520 UnifiedDiffRevs(true);
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
;
1535 m_parent
->UpdateFullHistory();
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
;
1553 if (rev
== (CString
*)MAKEINTRESOURCE(IDS_ALL
))
1555 bool nothingDeleted
= true;
1556 for (const auto& ref
: allRefNames
)
1558 if (!CAppUtils::DeleteRef(this, ref
))
1560 nothingDeleted
= false;
1565 else if (!CAppUtils::DeleteRef(this, *rev
))
1568 m_parent
->UpdateFullHistory();
1574 case ID_COMPAREHEADS
:
1575 if (m_SelectedEntry1
)
1576 CompareRevs(L
"HEAD");
1579 if (m_SelectedEntry1
)
1580 CompareRevs(CGitHash().ToString());
1584 case ID_COMPAREREVS
:
1585 if (m_SelectedEntry1
)
1588 case ID_UNIDIFFREVS
:
1589 if (m_SelectedEntry1
)
1590 UnifiedDiffRevs(false);
1592 case ID_UNIDIFFHEADS
:
1593 if (m_SelectedEntry1
)
1594 UnifiedDiffRevs(true);
1600 DoCheckForModification();
1608 case ID_SWITCHTOHEAD
:
1612 ResetNodeFlags (CGraphNodeStates::COLLAPSED_ALL
);
1615 ResetNodeFlags (CGraphNodeStates::SPLIT_ALL
);
1617 case ID_GRAPH_EXPANDCOLLAPSE_ABOVE
:
1618 ToggleNodeFlag (clickedentry
, CGraphNodeStates::COLLAPSED_ABOVE
);
1620 case ID_GRAPH_EXPANDCOLLAPSE_RIGHT
:
1621 ToggleNodeFlag (clickedentry
, CGraphNodeStates::COLLAPSED_RIGHT
);
1623 case ID_GRAPH_EXPANDCOLLAPSE_BELOW
:
1624 ToggleNodeFlag (clickedentry
, CGraphNodeStates::COLLAPSED_BELOW
);
1626 case ID_GRAPH_SPLITJOIN_ABOVE
:
1627 ToggleNodeFlag (clickedentry
, CGraphNodeStates::SPLIT_ABOVE
);
1629 case ID_GRAPH_SPLITJOIN_RIGHT
:
1630 ToggleNodeFlag (clickedentry
, CGraphNodeStates::SPLIT_RIGHT
);
1632 case ID_GRAPH_SPLITJOIN_BELOW
:
1633 ToggleNodeFlag (clickedentry
, CGraphNodeStates::SPLIT_BELOW
);
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
))
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
);
1652 SetScrollbars(y
, x
);
1654 return __super::OnMouseMove(nFlags
, point
);
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
);
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);
1681 && ( (m_hoverIndex
!= GetHitNode (clientPoint
))))
1683 m_showHoverGlyphs
= false;
1685 KillTimer (GLYPH_HOVER_EVENT
);
1686 SetTimer(GLYPH_HOVER_EVENT
, GLYPH_HOVER_DELAY
, nullptr);
1691 return __super::OnMouseMove(nFlags
, point
);
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
;
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
))
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
)
1743 void CRevisionGraphWnd::OnTimer (UINT_PTR nIDEvent
)
1745 if (nIDEvent
== GLYPH_HOVER_EVENT
)
1747 KillTimer (GLYPH_HOVER_EVENT
);
1749 m_showHoverGlyphs
= true;
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();
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
);
1785 if (m_parent
&& !m_parent
->GetOutputFile().IsEmpty())
1787 // save the graph to the output file and exit
1788 SaveGraphAs(m_parent
->GetOutputFile());
1794 void CRevisionGraphWnd::SetDlgTitle (bool /*offline*/)
1797 if (m_sTitle
.IsEmpty())
1798 GetParent()->GetWindowText(m_sTitle
);
1802 newTitle
.Format (IDS_REVGRAPH_DLGTITLEOFFLINE
, (LPCTSTR
)m_sTitle
);
1804 newTitle
= m_sTitle
;
1806 CAppUtils::SetWindowTitle(GetParent()->GetSafeHwnd(), m_sPath
, newTitle
);
1810 ULONG
CRevisionGraphWnd::GetGestureStatus(CPoint
/*ptTouch*/)