1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2012 - TortoiseSVN
4 // Copyright (C) 2012-2013 - 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 "SVNInfo.h"
32 //#include "SVNDiff.h"
33 #include "RevisionGraphDlg.h"
34 //#include "CachedLogInfo.h"
35 //#include "RevisionIndex.h"
36 //#include "RepositoryInfo.h"
37 #include "BrowseFolder.h"
38 #include "GitProgressDlg.h"
39 #include "ChangedDlg.h"
40 //#include "RevisionGraph/StandardLayout.h"
41 //#include "RevisionGraph/UpsideDownLayout.h"
43 #include "FormatMessageWrapper.h"
46 #pragma warning(disable: 4100) // unreferenced formal parameter
47 #include <ogdf/planarity/PlanarizationLayout.h>
48 #include <ogdf/planarity/VariableEmbeddingInserter.h>
49 #include <ogdf/planarity/FastPlanarSubgraph.h>
50 #include <ogdf/orthogonal/OrthoLayout.h>
51 #include <ogdf/planarity/EmbedderMinDepthMaxFaceLayers.h>
57 static char THIS_FILE
[] = __FILE__
;
60 using namespace Gdiplus
;
63 #if (_WIN32_WINNT < 0x0600)
64 #define WM_MOUSEHWHEEL 0x020E
68 enum RevisionGraphContextMenuCommands
70 // needs to start with 1, since 0 is the return value if *nothing* is clicked on in the context menu
75 ID_COMPAREREVS
= 0x100,
84 ID_EXPAND_ALL
= 0x500,
86 ID_GRAPH_EXPANDCOLLAPSE_ABOVE
= 0x600,
87 ID_GRAPH_EXPANDCOLLAPSE_RIGHT
,
88 ID_GRAPH_EXPANDCOLLAPSE_BELOW
,
89 ID_GRAPH_SPLITJOIN_ABOVE
,
90 ID_GRAPH_SPLITJOIN_RIGHT
,
91 ID_GRAPH_SPLITJOIN_BELOW
,
94 CRevisionGraphWnd::CRevisionGraphWnd()
96 , m_SelectedEntry1(NULL
)
97 , m_SelectedEntry2(NULL
)
100 , m_bTweakTrunkColors(true)
101 , m_bTweakTagsColors(true)
102 , m_fZoomFactor(DEFAULT_ZOOM
)
104 , m_ptMoveCanvas(0,0)
105 , m_bShowOverview(false)
107 , m_hoverIndex (NULL
)
109 // , m_tooltipIndex ((index_t)NO_INDEX)
110 , m_showHoverGlyphs (false)
111 , m_bIsCanvasMove(false)
116 , m_logEntries(&m_LogCache
)
117 , m_bCurrentBranch(false)
119 memset(&m_lfBaseFont
, 0, sizeof(LOGFONT
));
120 std::fill_n(m_apFonts
, MAXFONTS
, (CFont
*)NULL
);
123 HINSTANCE hInst
= AfxGetInstanceHandle();
124 #define REVGRAPH_CLASSNAME _T("Revgraph_windowclass")
125 if (!(::GetClassInfo(hInst
, REVGRAPH_CLASSNAME
, &wndcls
)))
127 // otherwise we need to register a new class
128 wndcls
.style
= CS_DBLCLKS
| CS_OWNDC
;
129 wndcls
.lpfnWndProc
= ::DefWindowProc
;
130 wndcls
.cbClsExtra
= wndcls
.cbWndExtra
= 0;
131 wndcls
.hInstance
= hInst
;
133 wndcls
.hCursor
= AfxGetApp()->LoadStandardCursor(IDC_ARROW
);
134 wndcls
.hbrBackground
= (HBRUSH
) (COLOR_WINDOW
+ 1);
135 wndcls
.lpszMenuName
= NULL
;
136 wndcls
.lpszClassName
= REVGRAPH_CLASSNAME
;
138 RegisterClass(&wndcls
);
141 m_bTweakTrunkColors
= CRegDWORD(_T("Software\\TortoiseGit\\RevisionGraph\\TweakTrunkColors"), TRUE
) != FALSE
;
142 m_bTweakTagsColors
= CRegDWORD(_T("Software\\TortoiseGit\\RevisionGraph\\TweakTagsColors"), TRUE
) != FALSE
;
146 m_GraphAttr
.init(this->m_Graph
, ogdf::GraphAttributes::nodeGraphics
| ogdf::GraphAttributes::edgeGraphics
|
147 ogdf:: GraphAttributes::nodeLabel
| ogdf::GraphAttributes::nodeColor
|
148 ogdf::GraphAttributes::edgeColor
| ogdf::GraphAttributes::edgeStyle
|
149 ogdf::GraphAttributes::nodeStyle
| ogdf::GraphAttributes::nodeTemplate
);
151 m_SugiyamLayout
.setRanking(::new ogdf::OptimalRanking());
152 m_SugiyamLayout
.setCrossMin(::new ogdf::MedianHeuristic());
154 double pi
= 3.1415926;
155 m_ArrowCos
= cos(pi
/8);
156 m_ArrowSin
= sin(pi
/8);
157 this->m_ArrowSize
= 8;
159 ogdf::node one
= this->m_Graph
.newNode();
160 ogdf::node two
= this->m_Graph
.newNode();
161 ogdf::node three
= this->m_Graph
.newNode();
162 ogdf::node four
= this->m_Graph
.newNode();
165 m_GraphAttr
.width(one
)=100;
166 m_GraphAttr
.height(one
)=200;
167 m_GraphAttr
.width(two
)=100;
168 m_GraphAttr
.height(two
)=100;
169 m_GraphAttr
.width(three
)=100;
170 m_GraphAttr
.height(three
)=20;
171 m_GraphAttr
.width(four
)=100;
172 m_GraphAttr
.height(four
)=20;
174 m_GraphAttr
.labelNode(one
)="One";
175 m_GraphAttr
.labelNode(two
)="Two";
176 m_GraphAttr
.labelNode(three
)="three";
178 this->m_Graph
.newEdge(one
, two
);
179 this->m_Graph
.newEdge(one
, three
);
180 this->m_Graph
.newEdge(two
, four
);
181 this->m_Graph
.newEdge(three
, four
);
184 FastHierarchyLayout
*pOHL
= ::new FastHierarchyLayout
;
185 //It will auto delte when m_SugiyamLayout destory
187 pOHL
->layerDistance(30.0);
188 pOHL
->nodeDistance(25.0);
190 m_SugiyamLayout
.setLayout(pOHL
);
193 //this->m_OHL.layerDistance(30.0);
194 //this->m_OHL.nodeDistance(25.0);
195 //this->m_OHL.weightBalancing(0.8);
196 m_SugiyamLayout
.setLayout(&m_OHL
);
197 m_SugiyamLayout
.call(m_GraphAttr
);
200 PlanarizationLayout pl
;
202 FastPlanarSubgraph
*ps
= ::new FastPlanarSubgraph
;
204 VariableEmbeddingInserter
*ves
= ::new VariableEmbeddingInserter
;
205 ves
->removeReinsert(EdgeInsertionModule::rrAll
);
209 EmbedderMinDepthMaxFaceLayers
*emb
= ::new EmbedderMinDepthMaxFaceLayers
;
212 OrthoLayout
*ol
=::new OrthoLayout
;
213 ol
->separation(20.0);
216 ol
->preferedDir(OrthoDir::odEast
);
217 pl
.setPlanarLayouter(ol
);
219 pl
.call(m_GraphAttr
);
222 forall_nodes(v
,m_Graph
) {
224 TRACE(_T("node x %f y %f %f %f\n"),/* m_GraphAttr.idNode(v), */
227 m_GraphAttr
.width(v
),
228 m_GraphAttr
.height(v
)
233 forall_edges(e
, m_Graph
)
235 // get connection and point position
236 const DPolyline
&dpl
= this->m_GraphAttr
.bends(e
);
238 ListConstIterator
<DPoint
> it
;
239 for(it
= dpl
.begin(); it
.valid(); ++it
)
241 TRACE(_T("edge %f %f\n"), (*it
).m_x
, (*it
).m_y
);
244 m_GraphAttr
.writeGML("test.gml");
248 CRevisionGraphWnd::~CRevisionGraphWnd()
250 for (int i
= 0; i
< MAXFONTS
; ++i
)
252 if (m_apFonts
[i
] != NULL
)
254 m_apFonts
[i
]->DeleteObject();
263 void CRevisionGraphWnd::DoDataExchange(CDataExchange
* pDX
)
265 CWnd::DoDataExchange(pDX
);
269 BEGIN_MESSAGE_MAP(CRevisionGraphWnd
, CWnd
)
276 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW
, 0, 0xFFFF, OnToolTipNotify
)
277 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA
, 0, 0xFFFF, OnToolTipNotify
)
285 ON_MESSAGE(WM_WORKERTHREADDONE
,OnWorkerThreadDone
)
286 ON_WM_CAPTURECHANGED()
289 void CRevisionGraphWnd::Init(CWnd
* pParent
, LPRECT rect
)
292 HINSTANCE hInst
= AfxGetInstanceHandle();
293 #define REVGRAPH_CLASSNAME _T("Revgraph_windowclass")
294 if (!(::GetClassInfo(hInst
, REVGRAPH_CLASSNAME
, &wndcls
)))
296 // otherwise we need to register a new class
297 wndcls
.style
= CS_DBLCLKS
| CS_OWNDC
;
298 wndcls
.lpfnWndProc
= ::DefWindowProc
;
299 wndcls
.cbClsExtra
= wndcls
.cbWndExtra
= 0;
300 wndcls
.hInstance
= hInst
;
302 wndcls
.hCursor
= AfxGetApp()->LoadStandardCursor(IDC_ARROW
);
303 wndcls
.hbrBackground
= (HBRUSH
) (COLOR_WINDOW
+ 1);
304 wndcls
.lpszMenuName
= NULL
;
305 wndcls
.lpszClassName
= REVGRAPH_CLASSNAME
;
307 RegisterClass(&wndcls
);
310 if (!IsWindow(m_hWnd
))
311 CreateEx(WS_EX_CLIENTEDGE
, REVGRAPH_CLASSNAME
, _T("RevGraph"), WS_CHILD
|WS_VISIBLE
|WS_TABSTOP
, *rect
, pParent
, 0);
312 m_pDlgTip
= new CToolTipCtrl
;
313 if(!m_pDlgTip
->Create(this))
315 // CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Unable to add tooltip!\n");
319 memset(&m_lfBaseFont
, 0, sizeof(m_lfBaseFont
));
320 m_lfBaseFont
.lfHeight
= 0;
321 m_lfBaseFont
.lfWeight
= FW_NORMAL
;
322 m_lfBaseFont
.lfItalic
= FALSE
;
323 m_lfBaseFont
.lfCharSet
= DEFAULT_CHARSET
;
324 m_lfBaseFont
.lfOutPrecision
= OUT_DEFAULT_PRECIS
;
325 m_lfBaseFont
.lfClipPrecision
= CLIP_DEFAULT_PRECIS
;
326 m_lfBaseFont
.lfQuality
= DEFAULT_QUALITY
;
327 m_lfBaseFont
.lfPitchAndFamily
= DEFAULT_PITCH
;
329 m_dwTicks
= GetTickCount();
331 m_parent
= dynamic_cast<CRevisionGraphDlg
*>(pParent
);
334 CPoint
CRevisionGraphWnd::GetLogCoordinates (CPoint point
) const
336 // translate point into logical coordinates
338 int nVScrollPos
= GetScrollPos(SB_VERT
);
339 int nHScrollPos
= GetScrollPos(SB_HORZ
);
341 return CPoint ( (int)((point
.x
+ nHScrollPos
) / m_fZoomFactor
)
342 , (int)((point
.y
+ nVScrollPos
) / m_fZoomFactor
));
345 node
CRevisionGraphWnd::GetHitNode (CPoint point
, CSize
/*border*/) const
350 CSyncPointer
<const ILayoutNodeList
> nodeList (m_state
.GetNodes());
352 return index_t(NO_INDEX
);
354 // search the nodes for one at that grid position
356 return nodeList
->GetAt (GetLogCoordinates (point
), border
);
360 forall_nodes(v
,m_Graph
)
362 RectF
noderect (GetNodeRect (v
, CPoint(GetScrollPos(SB_HORZ
), GetScrollPos(SB_VERT
))));
363 if(point
.x
>noderect
.X
&& point
.x
<(noderect
.X
+noderect
.Width
) &&
364 point
.y
>noderect
.Y
&& point
.y
<(noderect
.Y
+noderect
.Height
))
372 DWORD
CRevisionGraphWnd::GetHoverGlyphs (CPoint
/*point*/) const
374 // if there is no layout, there will be no nodes,
378 CSyncPointer
<const ILayoutNodeList
> nodeList (m_state
.GetNodes());
382 // get node at point or node that is close enough
383 // so that point may hit a glyph area
385 index_t nodeIndex
= GetHitNode(point
);
386 if (nodeIndex
== NO_INDEX
)
387 nodeIndex
= GetHitNode(point
, CSize (GLYPH_SIZE
, GLYPH_SIZE
/ 2));
389 if (nodeIndex
>= nodeList
->GetCount())
392 ILayoutNodeList::SNode node
= nodeList
->GetNode (nodeIndex
);
393 const CVisibleGraphNode
* base
= node
.node
;
395 // what glyphs should be shown depending on position of point
396 // relative to the node rect?
398 CPoint logCoordinates
= GetLogCoordinates (point
);
400 CPoint center
= r
.CenterPoint();
402 CRect
rightGlyphArea ( r
.right
- GLYPH_SIZE
, center
.y
- GLYPH_SIZE
/ 2
403 , r
.right
+ GLYPH_SIZE
, center
.y
+ GLYPH_SIZE
/ 2);
404 CRect
topGlyphArea ( center
.x
- GLYPH_SIZE
, r
.top
- GLYPH_SIZE
/ 2
405 , center
.x
+ GLYPH_SIZE
, r
.top
+ GLYPH_SIZE
/ 2);
406 CRect
bottomGlyphArea ( center
.x
- GLYPH_SIZE
, r
.bottom
- GLYPH_SIZE
/ 2
407 , center
.x
+ GLYPH_SIZE
, r
.bottom
+ GLYPH_SIZE
/ 2);
410 = m_state
.GetOptions()->GetOption
<CUpsideDownLayout
>()->IsActive();
414 std::swap (topGlyphArea
.top
, bottomGlyphArea
.top
);
415 std::swap (topGlyphArea
.bottom
, bottomGlyphArea
.bottom
);
419 if (rightGlyphArea
.PtInRect (logCoordinates
))
420 result
= base
->GetFirstCopyTarget() != NULL
421 ? CGraphNodeStates::COLLAPSED_RIGHT
| CGraphNodeStates::SPLIT_RIGHT
424 if (topGlyphArea
.PtInRect (logCoordinates
))
425 result
= base
->GetSource() != NULL
426 ? CGraphNodeStates::COLLAPSED_ABOVE
| CGraphNodeStates::SPLIT_ABOVE
429 if (bottomGlyphArea
.PtInRect (logCoordinates
))
430 result
= base
->GetNext() != NULL
431 ? CGraphNodeStates::COLLAPSED_BELOW
| CGraphNodeStates::SPLIT_BELOW
434 // if some nodes have already been split, don't allow collapsing etc.
436 CSyncPointer
<const CGraphNodeStates
> nodeStates (m_state
.GetNodeStates());
437 if (result
& nodeStates
->GetFlags (base
))
443 const CRevisionGraphState::SVisibleGlyph
* CRevisionGraphWnd::GetHitGlyph (CPoint point
) const
445 float glyphSize
= GLYPH_SIZE
* m_fZoomFactor
;
447 CSyncPointer
<const CRevisionGraphState::TVisibleGlyphs
>
448 visibleGlyphs (m_state
.GetVisibleGlyphs());
450 for (size_t i
= 0, count
= visibleGlyphs
->size(); i
< count
; ++i
)
452 const CRevisionGraphState::SVisibleGlyph
* entry
= &(*visibleGlyphs
)[i
];
454 float xRel
= point
.x
- entry
->leftTop
.X
;
455 float yRel
= point
.y
- entry
->leftTop
.Y
;
457 if ( (xRel
>= 0) && (xRel
< glyphSize
)
458 && (yRel
>= 0) && (yRel
< glyphSize
))
467 void CRevisionGraphWnd::OnHScroll(UINT nSBCode
, UINT nPos
, CScrollBar
* pScrollBar
)
469 SCROLLINFO sinfo
= {0};
470 sinfo
.cbSize
= sizeof(SCROLLINFO
);
471 GetScrollInfo(SB_HORZ
, &sinfo
);
473 // Determine the new position of scroll box.
476 case SB_LEFT
: // Scroll to far left.
477 sinfo
.nPos
= sinfo
.nMin
;
479 case SB_RIGHT
: // Scroll to far right.
480 sinfo
.nPos
= sinfo
.nMax
;
482 case SB_ENDSCROLL
: // End scroll.
484 case SB_LINELEFT
: // Scroll left.
485 if (sinfo
.nPos
> sinfo
.nMin
)
488 case SB_LINERIGHT
: // Scroll right.
489 if (sinfo
.nPos
< sinfo
.nMax
)
492 case SB_PAGELEFT
: // Scroll one page left.
494 if (sinfo
.nPos
> sinfo
.nMin
)
495 sinfo
.nPos
= max(sinfo
.nMin
, sinfo
.nPos
- (int) sinfo
.nPage
);
498 case SB_PAGERIGHT
: // Scroll one page right.
500 if (sinfo
.nPos
< sinfo
.nMax
)
501 sinfo
.nPos
= min(sinfo
.nMax
, sinfo
.nPos
+ (int) sinfo
.nPage
);
504 case SB_THUMBPOSITION
: // Scroll to absolute position. nPos is the position
505 sinfo
.nPos
= sinfo
.nTrackPos
; // of the scroll box at the end of the drag operation.
507 case SB_THUMBTRACK
: // Drag scroll box to specified position. nPos is the
508 sinfo
.nPos
= sinfo
.nTrackPos
; // position that the scroll box has been dragged to.
511 SetScrollInfo(SB_HORZ
, &sinfo
);
513 __super::OnHScroll(nSBCode
, nPos
, pScrollBar
);
516 void CRevisionGraphWnd::OnVScroll(UINT nSBCode
, UINT nPos
, CScrollBar
* pScrollBar
)
518 SCROLLINFO sinfo
= {0};
519 sinfo
.cbSize
= sizeof(SCROLLINFO
);
520 GetScrollInfo(SB_VERT
, &sinfo
);
522 // Determine the new position of scroll box.
525 case SB_LEFT
: // Scroll to far left.
526 sinfo
.nPos
= sinfo
.nMin
;
528 case SB_RIGHT
: // Scroll to far right.
529 sinfo
.nPos
= sinfo
.nMax
;
531 case SB_ENDSCROLL
: // End scroll.
533 case SB_LINELEFT
: // Scroll left.
534 if (sinfo
.nPos
> sinfo
.nMin
)
537 case SB_LINERIGHT
: // Scroll right.
538 if (sinfo
.nPos
< sinfo
.nMax
)
541 case SB_PAGELEFT
: // Scroll one page left.
543 if (sinfo
.nPos
> sinfo
.nMin
)
544 sinfo
.nPos
= max(sinfo
.nMin
, sinfo
.nPos
- (int) sinfo
.nPage
);
547 case SB_PAGERIGHT
: // Scroll one page right.
549 if (sinfo
.nPos
< sinfo
.nMax
)
550 sinfo
.nPos
= min(sinfo
.nMax
, sinfo
.nPos
+ (int) sinfo
.nPage
);
553 case SB_THUMBPOSITION
: // Scroll to absolute position. nPos is the position
554 sinfo
.nPos
= sinfo
.nTrackPos
; // of the scroll box at the end of the drag operation.
556 case SB_THUMBTRACK
: // Drag scroll box to specified position. nPos is the
557 sinfo
.nPos
= sinfo
.nTrackPos
; // position that the scroll box has been dragged to.
560 SetScrollInfo(SB_VERT
, &sinfo
);
562 __super::OnVScroll(nSBCode
, nPos
, pScrollBar
);
565 void CRevisionGraphWnd::OnSize(UINT nType
, int cx
, int cy
)
567 __super::OnSize(nType
, cx
, cy
);
568 SetScrollbars(GetScrollPos(SB_VERT
), GetScrollPos(SB_HORZ
));
572 void CRevisionGraphWnd::OnLButtonDown(UINT nFlags
, CPoint point
)
575 if (IsUpdateJobRunning())
576 return __super::OnLButtonDown(nFlags
, point
);
578 // CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());
582 bool bControl
= !!(GetKeyState(VK_CONTROL
)&0x8000);
583 bool bOverview
= m_bShowOverview
&& m_OverviewRect
.PtInRect(point
);
587 const CRevisionGraphState::SVisibleGlyph
* hitGlyph
588 = GetHitGlyph (point
);
590 if (hitGlyph
!= NULL
)
592 ToggleNodeFlag (hitGlyph
->node
, hitGlyph
->state
);
593 return __super::OnLButtonDown(nFlags
, point
);
596 node nodeIndex
= GetHitNode (point
);
597 if (nodeIndex
!= NULL
)
601 if (m_SelectedEntry1
== nodeIndex
)
603 if (m_SelectedEntry2
)
605 m_SelectedEntry1
= m_SelectedEntry2
;
606 m_SelectedEntry2
= NULL
;
609 m_SelectedEntry1
= NULL
;
611 else if (m_SelectedEntry2
== nodeIndex
)
612 m_SelectedEntry2
= NULL
;
613 else if (m_SelectedEntry1
)
614 m_SelectedEntry2
= nodeIndex
;
616 m_SelectedEntry1
= nodeIndex
;
620 if (m_SelectedEntry1
== nodeIndex
)
621 m_SelectedEntry1
= NULL
;
623 m_SelectedEntry1
= nodeIndex
;
624 m_SelectedEntry2
= NULL
;
631 if ((!bHit
)&&(!bControl
)&&(!bOverview
))
633 m_SelectedEntry1
= NULL
;
634 m_SelectedEntry2
= NULL
;
635 m_bIsCanvasMove
= true;
637 if (m_bShowOverview
&& m_OverviewRect
.PtInRect(point
))
638 m_bIsCanvasMove
= false;
640 m_ptMoveCanvas
= point
;
642 UINT uEnable
= MF_BYCOMMAND
;
643 if ((m_SelectedEntry1
!= NULL
)&&(m_SelectedEntry2
!= NULL
))
644 uEnable
|= MF_ENABLED
;
646 uEnable
|= MF_GRAYED
;
648 EnableMenuItem(GetParent()->GetMenu()->m_hMenu
, ID_VIEW_COMPAREREVISIONS
, uEnable
);
649 EnableMenuItem(GetParent()->GetMenu()->m_hMenu
, ID_VIEW_UNIFIEDDIFF
, uEnable
);
651 uEnable
= MF_BYCOMMAND
;
652 if ((m_SelectedEntry1
!= NULL
)&&(m_SelectedEntry2
== NULL
))
653 uEnable
|= MF_ENABLED
;
655 uEnable
|= MF_GRAYED
;
657 EnableMenuItem(GetParent()->GetMenu()->m_hMenu
, ID_VIEW_UNIFIEDDIFFOFHEADREVISIONS
, uEnable
);
658 EnableMenuItem(GetParent()->GetMenu()->m_hMenu
, ID_VIEW_COMPAREHEADREVISIONS
, uEnable
);
660 __super::OnLButtonDown(nFlags
, point
);
663 void CRevisionGraphWnd::OnCaptureChanged(CWnd
*pWnd
)
665 __super::OnCaptureChanged(pWnd
);
668 void CRevisionGraphWnd::OnLButtonUp(UINT nFlags
, CPoint point
)
670 if (!m_bIsCanvasMove
)
671 return; // we don't have a rubberband, so no zooming necessary
673 m_bIsCanvasMove
= false;
675 if (IsUpdateJobRunning())
676 return __super::OnLButtonUp(nFlags
, point
);
678 // zooming is finished
679 m_ptRubberEnd
= CPoint(0,0);
680 CRect rect
= GetClientRect();
681 int x
= abs(m_ptMoveCanvas
.x
- point
.x
);
682 int y
= abs(m_ptMoveCanvas
.y
- point
.y
);
684 if ((x
< 20)&&(y
< 20))
686 // too small zoom rectangle
687 // assume zooming by accident
689 __super::OnLButtonUp(nFlags
, point
);
693 float xfact
= float(rect
.Width())/float(x
);
694 float yfact
= float(rect
.Height())/float(y
);
695 float fact
= max(yfact
, xfact
);
697 // find out where to scroll to
698 x
= min(m_ptMoveCanvas
.x
, point
.x
) + GetScrollPos(SB_HORZ
);
699 y
= min(m_ptMoveCanvas
.y
, point
.y
) + GetScrollPos(SB_VERT
);
701 float fZoomfactor
= m_fZoomFactor
*fact
;
702 if (fZoomfactor
> 10 * MAX_ZOOM
)
704 // with such a big zoomfactor, the user
705 // most likely zoomed by accident
707 __super::OnLButtonUp(nFlags
, point
);
710 if (fZoomfactor
> MAX_ZOOM
)
712 fZoomfactor
= MAX_ZOOM
;
713 fact
= fZoomfactor
/m_fZoomFactor
;
716 CRevisionGraphDlg
* pDlg
= (CRevisionGraphDlg
*)GetParent();
719 m_fZoomFactor
= fZoomfactor
;
720 pDlg
->DoZoom (m_fZoomFactor
);
721 SetScrollbars(int(float(y
)*fact
), int(float(x
)*fact
));
723 __super::OnLButtonUp(nFlags
, point
);
726 bool CRevisionGraphWnd::CancelMouseZoom()
728 bool bRet
= m_bIsCanvasMove
;
732 m_bIsCanvasMove
= false;
733 m_ptRubberEnd
= CPoint(0,0);
737 INT_PTR
CRevisionGraphWnd::OnToolHitTest(CPoint point
, TOOLINFO
* pTI
) const
740 if (IsUpdateJobRunning())
743 node nodeIndex
= GetHitNode (point
);
744 if (m_tooltipIndex
!= nodeIndex
)
746 // force tooltip to be updated
748 m_tooltipIndex
= nodeIndex
;
752 if (nodeIndex
== NULL
)
755 // if ((GetHoverGlyphs (point) != 0) || (GetHitGlyph (point) != NULL))
758 pTI
->hwnd
= this->m_hWnd
;
759 CWnd::GetClientRect(&pTI
->rect
);
760 pTI
->uFlags
|= TTF_ALWAYSTIP
| TTF_IDISHWND
;
761 pTI
->uId
= (UINT_PTR
)m_hWnd
;
762 pTI
->lpszText
= LPSTR_TEXTCALLBACK
;
767 BOOL
CRevisionGraphWnd::OnToolTipNotify(UINT
/*id*/, NMHDR
*pNMHDR
, LRESULT
*pResult
)
769 if (pNMHDR
->idFrom
!= (UINT_PTR
)m_hWnd
)
773 DWORD ptW
= GetMessagePos();
774 point
.x
= GET_X_LPARAM(ptW
);
775 point
.y
= GET_Y_LPARAM(ptW
);
776 ScreenToClient(&point
);
778 CString strTipText
= TooltipText (GetHitNode (point
));
781 if (strTipText
.IsEmpty())
784 CSize tooltipSize
= UsableTooltipRect();
785 strTipText
= DisplayableText (strTipText
, tooltipSize
);
787 // need to handle both ANSI and UNICODE versions of the message
788 if (pNMHDR
->code
== TTN_NEEDTEXTA
)
790 TOOLTIPTEXTA
* pTTTA
= (TOOLTIPTEXTA
*)pNMHDR
;
791 ::SendMessage(pNMHDR
->hwndFrom
, TTM_SETMAXTIPWIDTH
, 0, tooltipSize
.cx
);
792 pTTTA
->lpszText
= m_szTip
;
793 WideCharToMultiByte(CP_ACP
, 0, strTipText
, -1, m_szTip
, strTipText
.GetLength()+1, 0, 0);
797 TOOLTIPTEXTW
* pTTTW
= (TOOLTIPTEXTW
*)pNMHDR
;
798 ::SendMessage(pNMHDR
->hwndFrom
, TTM_SETMAXTIPWIDTH
, 0, tooltipSize
.cx
);
799 lstrcpyn(m_wszTip
, strTipText
, strTipText
.GetLength()+1);
800 pTTTW
->lpszText
= m_wszTip
;
803 // show the tooltip for 32 seconds. A higher value than 32767 won't work
804 // even though it's nowhere documented!
805 ::SendMessage(pNMHDR
->hwndFrom
, TTM_SETDELAYTIME
, TTDT_AUTOPOP
, 32767);
806 return TRUE
; // message was handled
809 CSize
CRevisionGraphWnd::UsableTooltipRect()
813 int screenWidth
= GetSystemMetrics(SM_CXSCREEN
);
814 int screenHeight
= GetSystemMetrics(SM_CYSCREEN
);
816 // get current mouse position
819 if (GetCursorPos (&cursorPos
) == FALSE
)
821 // we could not determine the mouse position
822 // use screen / 2 minus some safety margin
824 return CSize (screenWidth
/ 2 - 20, screenHeight
/ 2 - 20);
827 // tool tip will display in the biggest sector beside the cursor
828 // deduct some safety margin (for the mouse cursor itself
831 ( max (screenWidth
- cursorPos
.x
- 40, cursorPos
.x
- 24)
832 , max (screenHeight
- cursorPos
.y
- 40, cursorPos
.y
- 24));
834 return biggestSector
;
837 CString
CRevisionGraphWnd::DisplayableText ( const CString
& wholeText
838 , const CSize
& tooltipSize
)
843 // no access to the device context -> truncate hard at 1000 chars
845 return wholeText
.GetLength() >= MAX_TT_LENGTH_DEFAULT
846 ? wholeText
.Left (MAX_TT_LENGTH_DEFAULT
-4) + _T(" ...")
850 // select the tooltip font
852 NONCLIENTMETRICS metrics
;
853 metrics
.cbSize
= sizeof (metrics
);
854 if (!SysInfo::Instance().IsVistaOrLater())
856 metrics
.cbSize
-= sizeof(int); // subtract the size of the iPaddedBorderWidth member which is not available on XP
858 SystemParametersInfo (SPI_GETNONCLIENTMETRICS
, metrics
.cbSize
, &metrics
, 0);
861 font
.CreateFontIndirect(&metrics
.lfStatusFont
);
862 CFont
* pOldFont
= dc
->SelectObject (&font
);
864 // split into lines and fill the tooltip rect
868 int remainingHeight
= tooltipSize
.cy
;
870 while (pos
< wholeText
.GetLength())
872 // extract a whole line
874 int nextPos
= wholeText
.Find ('\n', pos
);
876 nextPos
= wholeText
.GetLength();
878 CString line
= wholeText
.Mid (pos
, nextPos
-pos
+1);
880 // find a way to make it fit
882 CSize size
= dc
->GetTextExtent (line
);
883 while (size
.cx
> tooltipSize
.cx
)
885 line
.Delete (line
.GetLength()-1);
886 int nextPos2
= line
.ReverseFind (' ');
890 line
.Delete (nextPos2
+1, line
.GetLength() - pos
-1);
891 size
= dc
->GetTextExtent (line
);
894 // enough room for the new line?
896 remainingHeight
-= size
.cy
;
897 if (remainingHeight
<= size
.cy
)
906 pos
+= line
.GetLength();
909 // relase temp. resources
911 dc
->SelectObject (pOldFont
);
919 CString
CRevisionGraphWnd::TooltipText(node index
)
924 CGitHash hash
= m_logEntries
[index
->index()];
925 GitRev
*rev
= this->m_LogCache
.GetCacheData(hash
);
926 str
+= rev
->m_CommitHash
.ToString();
928 str
+= rev
->GetAuthorName() +_T(" ") + rev
->GetAuthorEmail();
930 str
+= rev
->GetAuthorDate().Format(_T("%Y-%m-%d %H:%M"));
931 str
+= _T("\n\n")+rev
->GetSubject();
933 str
+= rev
->GetBody();
940 void CRevisionGraphWnd::SaveGraphAs(CString sSavePath
)
943 CString extension
= CPathUtils::GetFileExtFromPath(sSavePath
);
944 if (extension
.CompareNoCase(_T(".wmf"))==0)
946 // save the graph as an enhanced metafile
948 wmfDC
.CreateEnhanced(NULL
, sSavePath
, NULL
, _T("TortoiseGit\0Revision Graph\0\0"));
949 float fZoom
= m_fZoomFactor
;
950 m_fZoomFactor
= DEFAULT_ZOOM
;
951 DoZoom(m_fZoomFactor
);
953 rect
= GetViewRect();
956 DrawGraph(dev
, rect
, 0, 0, true);
957 HENHMETAFILE hemf
= wmfDC
.CloseEnhanced();
958 DeleteEnhMetaFile(hemf
);
959 m_fZoomFactor
= fZoom
;
960 DoZoom(m_fZoomFactor
);
962 else if (extension
.CompareNoCase(_T(".svg"))==0)
964 // save the graph as a scalable vector graphic
966 float fZoom
= m_fZoomFactor
;
967 m_fZoomFactor
= DEFAULT_ZOOM
;
968 DoZoom(m_fZoomFactor
);
970 rect
= GetViewRect();
971 svg
.SetViewSize(rect
.Width(), rect
.Height());
974 DrawGraph(dev
, rect
, 0, 0, true);
976 m_fZoomFactor
= fZoom
;
977 DoZoom(m_fZoomFactor
);
979 else if (extension
.CompareNoCase(_T(".gv")) == 0)
982 float fZoom
= m_fZoomFactor
;
983 m_fZoomFactor
= DEFAULT_ZOOM
;
984 DoZoom(m_fZoomFactor
);
986 rect
= GetViewRect();
988 dev
.pGraphviz
= &graphviz
;
989 DrawGraph(dev
, rect
, 0, 0, true);
990 graphviz
.Save(sSavePath
);
991 m_fZoomFactor
= fZoom
;
992 DoZoom(m_fZoomFactor
);
996 // save the graph as a pixel picture instead of a vector picture
997 // create dc to paint on
1000 CString sErrormessage
;
1001 CWindowDC
ddc(this);
1003 if (!dc
.CreateCompatibleDC(&ddc
))
1005 CFormatMessageWrapper errorDetails
;
1007 MessageBox( errorDetails
, _T("Error"), MB_OK
| MB_ICONINFORMATION
);
1012 rect
= GetGraphRect();
1013 rect
.bottom
= (LONG
)(float(rect
.Height()) * m_fZoomFactor
);
1014 rect
.right
= (LONG
)(float(rect
.Width()) * m_fZoomFactor
);
1018 // Initialize header to 0s.
1019 SecureZeroMemory(&bmi
, sizeof(bmi
));
1020 // Fill out the fields you care about.
1021 bmi
.bmiHeader
.biSize
= sizeof(BITMAPINFOHEADER
);
1022 bmi
.bmiHeader
.biWidth
= rect
.Width();
1023 bmi
.bmiHeader
.biHeight
= rect
.Height();
1024 bmi
.bmiHeader
.biPlanes
= 1;
1025 bmi
.bmiHeader
.biBitCount
= 24;
1026 bmi
.bmiHeader
.biCompression
= BI_RGB
;
1028 // Create the surface.
1029 hbm
= CreateDIBSection(ddc
.m_hDC
, &bmi
, DIB_RGB_COLORS
,(void **)&pBits
, NULL
, 0);
1032 CMessageBox::Show(m_hWnd
, IDS_REVGRAPH_ERR_NOMEMORY
, IDS_APPNAME
, MB_ICONERROR
);
1035 HBITMAP oldbm
= (HBITMAP
)dc
.SelectObject(hbm
);
1036 // paint the whole graph
1039 DrawGraph(dev
, rect
, 0, 0, true);
1040 // now use GDI+ to save the picture
1043 Bitmap
bitmap(hbm
, NULL
);
1044 if (bitmap
.GetLastStatus()==Ok
)
1046 // Get the CLSID of the encoder.
1048 if (CPathUtils::GetFileExtFromPath(sSavePath
).CompareNoCase(_T(".png"))==0)
1049 ret
= GetEncoderClsid(L
"image/png", &encoderClsid
);
1050 else if (CPathUtils::GetFileExtFromPath(sSavePath
).CompareNoCase(_T(".jpg"))==0)
1051 ret
= GetEncoderClsid(L
"image/jpeg", &encoderClsid
);
1052 else if (CPathUtils::GetFileExtFromPath(sSavePath
).CompareNoCase(_T(".jpeg"))==0)
1053 ret
= GetEncoderClsid(L
"image/jpeg", &encoderClsid
);
1054 else if (CPathUtils::GetFileExtFromPath(sSavePath
).CompareNoCase(_T(".bmp"))==0)
1055 ret
= GetEncoderClsid(L
"image/bmp", &encoderClsid
);
1056 else if (CPathUtils::GetFileExtFromPath(sSavePath
).CompareNoCase(_T(".gif"))==0)
1057 ret
= GetEncoderClsid(L
"image/gif", &encoderClsid
);
1060 sSavePath
+= _T(".jpg");
1061 ret
= GetEncoderClsid(L
"image/jpeg", &encoderClsid
);
1065 CStringW tfile
= CStringW(sSavePath
);
1066 bitmap
.Save(tfile
, &encoderClsid
, NULL
);
1070 sErrormessage
.Format(IDS_REVGRAPH_ERR_NOENCODER
, (LPCTSTR
)CPathUtils::GetFileExtFromPath(sSavePath
));
1075 sErrormessage
.LoadString(IDS_REVGRAPH_ERR_NOBITMAP
);
1078 dc
.SelectObject(oldbm
);
1081 if (!sErrormessage
.IsEmpty())
1083 ::MessageBox(m_hWnd
, sErrormessage
, _T("TortoiseGit"), MB_ICONERROR
);
1086 catch (CException
* pE
)
1088 TCHAR szErrorMsg
[2048];
1089 pE
->GetErrorMessage(szErrorMsg
, 2048);
1091 ::MessageBox(m_hWnd
, szErrorMsg
, _T("TortoiseGit"), MB_ICONERROR
);
1097 BOOL
CRevisionGraphWnd::OnMouseWheel(UINT nFlags
, short zDelta
, CPoint pt
)
1099 if (IsUpdateJobRunning())
1100 return __super::OnMouseWheel(nFlags
, zDelta
, pt
);
1102 if (GetKeyState(VK_CONTROL
)&0x8000)
1104 float newZoom
= m_fZoomFactor
* (zDelta
< 0 ? ZOOM_STEP
: 1.0f
/ZOOM_STEP
);
1105 DoZoom (max (MIN_ZOOM
, min (MAX_ZOOM
, newZoom
)));
1109 int orientation
= GetKeyState(VK_SHIFT
)&0x8000 ? SB_HORZ
: SB_VERT
;
1110 int pos
= GetScrollPos(orientation
);
1112 SetScrollPos(orientation
, pos
);
1115 return __super::OnMouseWheel(nFlags
, zDelta
, pt
);
1118 void CRevisionGraphWnd::OnMouseHWheel(UINT nFlags
, short zDelta
, CPoint pt
)
1120 if (IsUpdateJobRunning())
1121 return __super::OnMouseHWheel(nFlags
, zDelta
, pt
);
1123 int orientation
= GetKeyState(VK_SHIFT
)&0x8000 ? SB_VERT
: SB_HORZ
;
1124 int pos
= GetScrollPos(orientation
);
1126 SetScrollPos(orientation
, pos
);
1129 return __super::OnMouseHWheel(nFlags
, zDelta
, pt
);
1132 bool CRevisionGraphWnd::UpdateSelectedEntry (node clickedentry
)
1134 if ((m_SelectedEntry1
== NULL
)&&(clickedentry
== NULL
))
1137 if (m_SelectedEntry1
== NULL
)
1139 m_SelectedEntry1
= clickedentry
;
1142 if ((m_SelectedEntry2
== NULL
)&&(clickedentry
!= m_SelectedEntry1
))
1144 m_SelectedEntry1
= clickedentry
;
1147 if (m_SelectedEntry1
&& m_SelectedEntry2
)
1149 if ((m_SelectedEntry2
!= clickedentry
)&&(m_SelectedEntry1
!= clickedentry
))
1152 if (m_SelectedEntry1
== NULL
)
1158 void CRevisionGraphWnd::AppendMenu
1164 // separate different groups / section within the context menu
1166 if (popup
.GetMenuItemCount() > 0)
1168 UINT lastCommand
= popup
.GetMenuItemID (popup
.GetMenuItemCount()-1);
1169 if ((lastCommand
& GROUP_MASK
) != (command
& GROUP_MASK
))
1170 popup
.AppendMenu(MF_SEPARATOR
, NULL
);
1173 // actually add the new item
1175 CString titleString
;
1176 titleString
.LoadString (title
);
1177 popup
.AppendMenu (MF_STRING
| flags
, command
, titleString
);
1180 void CRevisionGraphWnd::AddGitOps (CMenu
& popup
)
1182 bool bothPresent
= (m_SelectedEntry2
&& m_SelectedEntry1
);
1184 AppendMenu (popup
, IDS_REPOBROWSE_SHOWLOG
, ID_SHOWLOG
);
1186 if (m_SelectedEntry1
&& (m_SelectedEntry2
== NULL
))
1188 //AppendMenu (popup, IDS_SWITCH_TO_THIS, ID_SWITCH);
1189 AppendMenu(popup
, IDS_REVGRAPH_POPUP_COMPAREHEADS
, ID_COMPAREHEADS
);
1190 AppendMenu(popup
, IDS_REVGRAPH_POPUP_UNIDIFFHEADS
, ID_UNIDIFFHEADS
);
1195 AppendMenu (popup
, IDS_REVGRAPH_POPUP_COMPAREREVS
, ID_COMPAREREVS
);
1196 AppendMenu (popup
, IDS_REVGRAPH_POPUP_UNIDIFFREVS
, ID_UNIDIFFREVS
);
1201 void CRevisionGraphWnd::AddGraphOps (CMenu
& /*popup*/, const CVisibleGraphNode
* /*node*/)
1204 CSyncPointer
<CGraphNodeStates
> nodeStates (m_state
.GetNodeStates());
1208 DWORD state
= nodeStates
->GetCombinedFlags();
1211 if (state
& CGraphNodeStates::COLLAPSED_ALL
)
1212 AppendMenu (popup
, IDS_REVGRAPH_POPUP_EXPAND_ALL
, ID_EXPAND_ALL
);
1214 if (state
& CGraphNodeStates::SPLIT_ALL
)
1215 AppendMenu (popup
, IDS_REVGRAPH_POPUP_JOIN_ALL
, ID_JOIN_ALL
);
1220 DWORD state
= nodeStates
->GetFlags (node
);
1222 if (node
->GetSource() || (state
& CGraphNodeStates::COLLAPSED_ABOVE
))
1224 , (state
& CGraphNodeStates::COLLAPSED_ABOVE
)
1225 ? IDS_REVGRAPH_POPUP_EXPAND_ABOVE
1226 : IDS_REVGRAPH_POPUP_COLLAPSE_ABOVE
1227 , ID_GRAPH_EXPANDCOLLAPSE_ABOVE
);
1229 if (node
->GetFirstCopyTarget() || (state
& CGraphNodeStates::COLLAPSED_RIGHT
))
1231 , (state
& CGraphNodeStates::COLLAPSED_RIGHT
)
1232 ? IDS_REVGRAPH_POPUP_EXPAND_RIGHT
1233 : IDS_REVGRAPH_POPUP_COLLAPSE_RIGHT
1234 , ID_GRAPH_EXPANDCOLLAPSE_RIGHT
);
1236 if (node
->GetNext() || (state
& CGraphNodeStates::COLLAPSED_BELOW
))
1238 , (state
& CGraphNodeStates::COLLAPSED_BELOW
)
1239 ? IDS_REVGRAPH_POPUP_EXPAND_BELOW
1240 : IDS_REVGRAPH_POPUP_COLLAPSE_BELOW
1241 , ID_GRAPH_EXPANDCOLLAPSE_BELOW
);
1243 if (node
->GetSource() || (state
& CGraphNodeStates::SPLIT_ABOVE
))
1245 , (state
& CGraphNodeStates::SPLIT_ABOVE
)
1246 ? IDS_REVGRAPH_POPUP_JOIN_ABOVE
1247 : IDS_REVGRAPH_POPUP_SPLIT_ABOVE
1248 , ID_GRAPH_SPLITJOIN_ABOVE
);
1250 if (node
->GetFirstCopyTarget() || (state
& CGraphNodeStates::SPLIT_RIGHT
))
1252 , (state
& CGraphNodeStates::SPLIT_RIGHT
)
1253 ? IDS_REVGRAPH_POPUP_JOIN_RIGHT
1254 : IDS_REVGRAPH_POPUP_SPLIT_RIGHT
1255 , ID_GRAPH_SPLITJOIN_RIGHT
);
1257 if (node
->GetNext() || (state
& CGraphNodeStates::SPLIT_BELOW
))
1259 , (state
& CGraphNodeStates::SPLIT_BELOW
)
1260 ? IDS_REVGRAPH_POPUP_JOIN_BELOW
1261 : IDS_REVGRAPH_POPUP_SPLIT_BELOW
1262 , ID_GRAPH_SPLITJOIN_BELOW
);
1267 CString
CRevisionGraphWnd::GetSelectedURL() const
1270 if (m_SelectedEntry1
== NULL
)
1273 CString URL
= m_state
.GetRepositoryRoot()
1274 + CUnicodeUtils::GetUnicode (m_SelectedEntry1
->GetPath().GetPath().c_str());
1275 URL
= CUnicodeUtils::GetUnicode(CPathUtils::PathEscape(CUnicodeUtils::GetUTF8(URL
)));
1282 CString
CRevisionGraphWnd::GetWCURL() const
1285 CTGitPath
path (m_sPath
);
1290 const SVNInfoData
* status
1291 = info
.GetFirstFileInfo (path
, SVNRev(), SVNRev());
1293 return status
== NULL
? CString() : status
->url
;
1298 void CRevisionGraphWnd::DoShowLog()
1301 if(m_SelectedEntry1
== NULL
)
1306 if(m_SelectedEntry2
!= NULL
)
1307 sCmd
.Format(_T("/command:log %s /startrev:%s /endrev:%s"),
1308 this->m_sPath
.IsEmpty() ? _T("") : (_T("/path:\"") + this->m_sPath
+ _T("\"")),
1309 this->m_logEntries
[m_SelectedEntry1
->index()].ToString(),
1310 this->m_logEntries
[m_SelectedEntry2
->index()].ToString());
1312 sCmd
.Format(_T("/command:log %s /endrev:%s"),
1313 this->m_sPath
.IsEmpty() ? _T("") : (_T("/path:\"") + this->m_sPath
+ _T("\"")),
1314 this->m_logEntries
[m_SelectedEntry1
->index()].ToString());
1316 CAppUtils::RunTortoiseGitProc(sCmd
);
1320 void CRevisionGraphWnd::DoCheckForModification()
1323 dlg
.m_pathList
= CTGitPathList (CTGitPath (m_sPath
));
1327 void CRevisionGraphWnd::DoMergeTo()
1330 CString URL
= GetSelectedURL();
1331 CString path
= m_sPath
;
1332 CBrowseFolder folderBrowser
;
1333 folderBrowser
.SetInfo(CString(MAKEINTRESOURCE(IDS_LOG_MERGETO
)));
1334 if (folderBrowser
.Show(GetSafeHwnd(), path
, path
) == CBrowseFolder::OK
)
1336 CSVNProgressDlg dlg
;
1337 dlg
.SetCommand(CSVNProgressDlg::SVNProgress_Merge
);
1338 dlg
.SetPathList(CTGitPathList(CTGitPath(path
)));
1340 dlg
.SetSecondUrl(URL
);
1341 SVNRevRangeArray revarray
;
1342 revarray
.AddRevRange (m_SelectedEntry1
->GetRevision()-1, svn_revnum_t(m_SelectedEntry1
->GetRevision()));
1343 dlg
.SetRevisionRanges(revarray
);
1349 void CRevisionGraphWnd::DoUpdate()
1352 CSVNProgressDlg progDlg
;
1353 progDlg
.SetCommand (CSVNProgressDlg::SVNProgress_Update
);
1354 progDlg
.SetOptions (0); // don't ignore externals
1355 progDlg
.SetPathList (CTGitPathList (CTGitPath (m_sPath
)));
1356 progDlg
.SetRevision (m_SelectedEntry1
->GetRevision());
1360 if (m_state
.GetFetchedWCState())
1361 m_parent
->UpdateFullHistory();
1365 void CRevisionGraphWnd::DoSwitch()
1368 CSVNProgressDlg progDlg
;
1369 progDlg
.SetCommand (CSVNProgressDlg::SVNProgress_Switch
);
1370 progDlg
.SetPathList (CTGitPathList (CTGitPath (m_sPath
)));
1371 progDlg
.SetUrl (GetSelectedURL());
1372 progDlg
.SetRevision (m_SelectedEntry1
->GetRevision());
1375 if (m_state
.GetFetchedWCState())
1376 m_parent
->UpdateFullHistory();
1380 void CRevisionGraphWnd::DoSwitchToHead()
1383 CSVNProgressDlg progDlg
;
1384 progDlg
.SetCommand (CSVNProgressDlg::SVNProgress_Switch
);
1385 progDlg
.SetPathList (CTGitPathList (CTGitPath (m_sPath
)));
1386 progDlg
.SetUrl (GetSelectedURL());
1387 progDlg
.SetRevision (SVNRev::REV_HEAD
);
1388 progDlg
.SetPegRevision (m_SelectedEntry1
->GetRevision());
1391 if (m_state
.GetFetchedWCState())
1392 m_parent
->UpdateFullHistory();
1396 void CRevisionGraphWnd::DoBrowseRepo()
1400 sCmd
.Format(_T("/command:repobrowser /path:\"%s\" /rev:%d"),
1401 (LPCTSTR
)GetSelectedURL(), m_SelectedEntry1
->GetRevision());
1403 CAppUtils::RunTortoiseProc(sCmd
);
1407 void CRevisionGraphWnd::ResetNodeFlags (DWORD
/*flags*/)
1409 // m_state.GetNodeStates()->ResetFlags (flags);
1410 // m_parent->StartWorkerThread();
1413 void CRevisionGraphWnd::ToggleNodeFlag (const CVisibleGraphNode
* /*node*/, DWORD
/*flag*/)
1416 CSyncPointer
<CGraphNodeStates
> nodeStates (m_state
.GetNodeStates());
1418 if (nodeStates
->GetFlags (node
) & flag
)
1419 nodeStates
->ResetFlags (node
, flag
);
1421 nodeStates
->SetFlags (node
, flag
);
1423 m_parent
->StartWorkerThread();
1427 void CRevisionGraphWnd::DoCopyUrl()
1429 CStringUtils::WriteAsciiStringToClipboard(GetSelectedURL(), m_hWnd
);
1432 void CRevisionGraphWnd::OnContextMenu(CWnd
* /*pWnd*/, CPoint point
)
1434 if (IsUpdateJobRunning())
1437 CPoint clientpoint
= point
;
1438 this->ScreenToClient(&clientpoint
);
1440 node nodeIndex
= GetHitNode (clientpoint
);
1442 if ( !UpdateSelectedEntry (nodeIndex
))
1448 if (!popup
.CreatePopupMenu())
1452 // AddGraphOps (popup, clickedentry);
1454 // if the context menu is invoked through the keyboard, we have to use
1455 // a calculated position on where to anchor the menu on
1456 if ((point
.x
== -1) && (point
.y
== -1))
1458 CRect rect
= GetWindowRect();
1459 point
= rect
.CenterPoint();
1462 int cmd
= popup
.TrackPopupMenu(TPM_RETURNCMD
| TPM_LEFTALIGN
| TPM_NONOTIFY
| TPM_RIGHTBUTTON
, point
.x
, point
.y
, this, 0);
1465 case ID_COMPAREREVS
:
1466 if (m_SelectedEntry1
!= NULL
)
1469 case ID_UNIDIFFREVS
:
1470 if (m_SelectedEntry1
!= NULL
)
1471 UnifiedDiffRevs(false);
1473 case ID_UNIDIFFHEADS
:
1474 if (m_SelectedEntry1
!= NULL
)
1475 UnifiedDiffRevs(true);
1483 case ID_COMPAREHEADS
:
1484 if (m_SelectedEntry1
!= NULL
)
1490 case ID_COMPAREREVS
:
1491 if (m_SelectedEntry1
!= NULL
)
1494 case ID_UNIDIFFREVS
:
1495 if (m_SelectedEntry1
!= NULL
)
1496 UnifiedDiffRevs(false);
1498 case ID_UNIDIFFHEADS
:
1499 if (m_SelectedEntry1
!= NULL
)
1500 UnifiedDiffRevs(true);
1506 DoCheckForModification();
1514 case ID_SWITCHTOHEAD
:
1527 ResetNodeFlags (CGraphNodeStates::COLLAPSED_ALL
);
1530 ResetNodeFlags (CGraphNodeStates::SPLIT_ALL
);
1532 case ID_GRAPH_EXPANDCOLLAPSE_ABOVE
:
1533 ToggleNodeFlag (clickedentry
, CGraphNodeStates::COLLAPSED_ABOVE
);
1535 case ID_GRAPH_EXPANDCOLLAPSE_RIGHT
:
1536 ToggleNodeFlag (clickedentry
, CGraphNodeStates::COLLAPSED_RIGHT
);
1538 case ID_GRAPH_EXPANDCOLLAPSE_BELOW
:
1539 ToggleNodeFlag (clickedentry
, CGraphNodeStates::COLLAPSED_BELOW
);
1541 case ID_GRAPH_SPLITJOIN_ABOVE
:
1542 ToggleNodeFlag (clickedentry
, CGraphNodeStates::SPLIT_ABOVE
);
1544 case ID_GRAPH_SPLITJOIN_RIGHT
:
1545 ToggleNodeFlag (clickedentry
, CGraphNodeStates::SPLIT_RIGHT
);
1547 case ID_GRAPH_SPLITJOIN_BELOW
:
1548 ToggleNodeFlag (clickedentry
, CGraphNodeStates::SPLIT_BELOW
);
1555 void CRevisionGraphWnd::OnMouseMove(UINT nFlags
, CPoint point
)
1558 if (IsUpdateJobRunning())
1560 return __super::OnMouseMove(nFlags
, point
);
1562 if (!m_bIsCanvasMove
)
1564 if (m_bShowOverview
&& (m_OverviewRect
.PtInRect(point
))&&(nFlags
& MK_LBUTTON
))
1567 CRect viewRect
= GetViewRect();
1568 int x
= (int)((point
.x
-m_OverviewRect
.left
- (m_OverviewPosRect
.Width()/2)) / m_previewZoom
* m_fZoomFactor
);
1569 int y
= (int)((point
.y
- m_OverviewRect
.top
- (m_OverviewPosRect
.Height()/2)) / m_previewZoom
* m_fZoomFactor
);
1572 SetScrollbars(y
, x
);
1574 return __super::OnMouseMove(nFlags
, point
);
1578 // update screen if we hover over a different
1579 // node than during the last redraw
1581 CPoint clientPoint
= point
;
1582 GetCursorPos (&clientPoint
);
1583 ScreenToClient (&clientPoint
);
1586 const CRevisionGraphState::SVisibleGlyph
* hitGlyph
1587 = GetHitGlyph (clientPoint
);
1588 const CFullGraphNode
* glyphNode
1589 = hitGlyph
? hitGlyph
->node
->GetBase() : NULL
;
1591 const CFullGraphNode
* hoverNode
= NULL
;
1592 if (m_hoverIndex
!= NO_INDEX
)
1594 CSyncPointer
<const ILayoutNodeList
> nodeList (m_state
.GetNodes());
1595 if (m_hoverIndex
< nodeList
->GetCount())
1596 hoverNode
= nodeList
->GetNode (m_hoverIndex
).node
->GetBase();
1599 //bool onHoverNodeGlyph = (hoverNode != NULL) && (glyphNode == hoverNode);
1601 && ( (m_hoverIndex
!= GetHitNode (clientPoint
))))
1603 m_showHoverGlyphs
= false;
1605 KillTimer (GLYPH_HOVER_EVENT
);
1606 SetTimer (GLYPH_HOVER_EVENT
, GLYPH_HOVER_DELAY
, NULL
);
1611 return __super::OnMouseMove(nFlags
, point
);
1616 int pos_h
= GetScrollPos(SB_HORZ
);
1617 pos_h
-= point
.x
- m_ptMoveCanvas
.x
;
1618 SetScrollPos(SB_HORZ
, pos_h
);
1620 int pos_v
= GetScrollPos(SB_VERT
);
1621 pos_v
-= point
.y
- m_ptMoveCanvas
.y
;
1622 SetScrollPos(SB_VERT
, pos_v
);
1624 m_ptMoveCanvas
= point
;
1628 __super::OnMouseMove(nFlags
, point
);
1631 BOOL
CRevisionGraphWnd::OnSetCursor(CWnd
* pWnd
, UINT nHitTest
, UINT message
)
1633 CRect viewRect
= GetViewRect();
1635 LPTSTR cursorID
= IDC_ARROW
;
1636 HINSTANCE resourceHandle
= NULL
;
1638 if ((nHitTest
== HTCLIENT
)&&(pWnd
== this)&&(viewRect
.Width())&&(viewRect
.Height())&&(message
))
1641 if (GetCursorPos(&pt
))
1643 ScreenToClient(&pt
);
1644 if (m_OverviewPosRect
.PtInRect(pt
))
1646 resourceHandle
= AfxGetResourceHandle();
1647 cursorID
= GetKeyState(VK_LBUTTON
) & 0x8000
1648 ? MAKEINTRESOURCE(IDC_PANCURDOWN
)
1649 : MAKEINTRESOURCE(IDC_PANCUR
);
1651 if (m_bIsCanvasMove
)
1652 cursorID
= IDC_HAND
;
1656 HCURSOR hCur
= LoadCursor(resourceHandle
, MAKEINTRESOURCE(cursorID
));
1657 if (GetCursor() != hCur
)
1663 void CRevisionGraphWnd::OnTimer (UINT_PTR nIDEvent
)
1665 if (nIDEvent
== GLYPH_HOVER_EVENT
)
1667 KillTimer (GLYPH_HOVER_EVENT
);
1669 m_showHoverGlyphs
= true;
1674 __super::OnTimer (nIDEvent
);
1678 LRESULT
CRevisionGraphWnd::OnWorkerThreadDone(WPARAM
, LPARAM
)
1680 // handle potential race condition between PostMessage and leaving job:
1681 // the background job may not have exited, yet
1683 if (updateJob
.get())
1684 updateJob
->GetResult();
1689 SCROLLINFO sinfo
= {0};
1690 sinfo
.cbSize
= sizeof(SCROLLINFO
);
1691 GetScrollInfo(SB_HORZ
, &sinfo
);
1692 sinfo
.nPos
= sinfo
.nMax
;
1693 SetScrollInfo(SB_HORZ
, &sinfo
);
1699 LogCache::CRepositoryInfo
& cachedProperties
1700 = svn
.GetLogCachePool()->GetRepositoryInfo();
1702 CSyncPointer
<const CFullHistory
> fullHistoy (m_state
.GetFullHistory());
1703 if (fullHistoy
.get() != NULL
)
1705 SetDlgTitle (cachedProperties
.IsOffline
1706 ( fullHistoy
->GetRepositoryUUID()
1707 , fullHistoy
->GetRepositoryRoot()
1711 if (m_parent
&& !m_parent
->GetOutputFile().IsEmpty())
1713 // save the graph to the output file and exit
1714 SaveGraphAs(m_parent
->GetOutputFile());
1721 void CRevisionGraphWnd::SetDlgTitle (bool /*offline*/)
1724 if (m_sTitle
.IsEmpty())
1725 GetParent()->GetWindowText(m_sTitle
);
1729 newTitle
.Format (IDS_REVGRAPH_DLGTITLEOFFLINE
, (LPCTSTR
)m_sTitle
);
1731 newTitle
= m_sTitle
;
1733 CAppUtils::SetWindowTitle(GetParent()->GetSafeHwnd(), m_sPath
, newTitle
);