If there are submodule changes in the file list when resolving conflicts while rebasi...
[TortoiseGit.git] / src / TortoiseProc / RevisionGraph / RevisionGraphWnd.cpp
blobad1da1b68b75a0f82d8fb2483e47a6f13c5d6572
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.
20 #include "stdafx.h"
21 #include "TortoiseProc.h"
22 #include "Revisiongraphwnd.h"
23 #include "MessageBox.h"
24 #include "Git.h"
25 #include "AppUtils.h"
26 #include "PathUtils.h"
27 #include "StringUtils.h"
28 #include "TempFile.h"
29 #include "UnicodeUtils.h"
30 #include "TGitPath.h"
31 //#include "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"
42 #include "SysInfo.h"
43 #include "FormatMessageWrapper.h"
45 #pragma warning(push)
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>
52 #pragma warning(pop)
54 #ifdef _DEBUG
55 #define new DEBUG_NEW
56 #undef THIS_FILE
57 static char THIS_FILE[] = __FILE__;
58 #endif
60 using namespace Gdiplus;
61 using namespace ogdf;
63 #if (_WIN32_WINNT < 0x0600)
64 #define WM_MOUSEHWHEEL 0x020E
65 #endif
68 enum RevisionGraphContextMenuCommands
70 // needs to start with 1, since 0 is the return value if *nothing* is clicked on in the context menu
71 GROUP_MASK = 0xff00,
72 ID_SHOWLOG = 1,
73 ID_CFM = 2,
74 ID_BROWSEREPO,
75 ID_COMPAREREVS = 0x100,
76 ID_COMPAREHEADS,
77 ID_UNIDIFFREVS,
78 ID_UNIDIFFHEADS,
79 ID_MERGETO = 0x300,
80 ID_UPDATE,
81 ID_SWITCHTOHEAD,
82 ID_SWITCH,
83 ID_COPYURL = 0x400,
84 ID_EXPAND_ALL = 0x500,
85 ID_JOIN_ALL,
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()
95 : CWnd()
96 , m_SelectedEntry1(NULL)
97 , m_SelectedEntry2(NULL)
98 , m_pDlgTip(NULL)
99 , m_nFontSize(12)
100 , m_bTweakTrunkColors(true)
101 , m_bTweakTagsColors(true)
102 , m_fZoomFactor(DEFAULT_ZOOM)
103 , m_ptRubberEnd(0,0)
104 , m_ptMoveCanvas(0,0)
105 , m_bShowOverview(false)
106 , m_parent (NULL)
107 , m_hoverIndex (NULL)
108 , m_hoverGlyphs (0)
109 // , m_tooltipIndex ((index_t)NO_INDEX)
110 , m_showHoverGlyphs (false)
111 , m_bIsCanvasMove(false)
112 , m_previewWidth(0)
113 , m_previewHeight(0)
114 , m_previewZoom(1)
115 , m_dwTicks(0)
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);
122 WNDCLASS wndcls;
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;
132 wndcls.hIcon = NULL;
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;
143 m_szTip[0] = 0;
144 m_wszTip[0] = 0;
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;
158 #if 0
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);
183 #endif
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);
192 #if 0
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);
198 #endif
199 #if 0
200 PlanarizationLayout pl;
202 FastPlanarSubgraph *ps = ::new FastPlanarSubgraph;
203 ps->runs(100);
204 VariableEmbeddingInserter *ves = ::new VariableEmbeddingInserter;
205 ves->removeReinsert(EdgeInsertionModule::rrAll);
206 pl.setSubgraph(ps);
207 pl.setInserter(ves);
209 EmbedderMinDepthMaxFaceLayers *emb = ::new EmbedderMinDepthMaxFaceLayers;
210 pl.setEmbedder(emb);
212 OrthoLayout *ol =::new OrthoLayout;
213 ol->separation(20.0);
214 ol->cOverhang(0.4);
215 ol->setOptions(2+4);
216 ol->preferedDir(OrthoDir::odEast);
217 pl.setPlanarLayouter(ol);
219 pl.call(m_GraphAttr);
221 node v;
222 forall_nodes(v,m_Graph) {
224 TRACE(_T("node x %f y %f %f %f\n"),/* m_GraphAttr.idNode(v), */
225 m_GraphAttr.x(v),
226 m_GraphAttr.y(v),
227 m_GraphAttr.width(v),
228 m_GraphAttr.height(v)
232 edge e;
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");
245 #endif
248 CRevisionGraphWnd::~CRevisionGraphWnd()
250 for (int i = 0; i < MAXFONTS; ++i)
252 if (m_apFonts[i] != NULL)
254 m_apFonts[i]->DeleteObject();
255 delete m_apFonts[i];
257 m_apFonts[i] = NULL;
259 delete m_pDlgTip;
260 m_Graph.clear();
263 void CRevisionGraphWnd::DoDataExchange(CDataExchange* pDX)
265 CWnd::DoDataExchange(pDX);
269 BEGIN_MESSAGE_MAP(CRevisionGraphWnd, CWnd)
270 ON_WM_PAINT()
271 ON_WM_ERASEBKGND()
272 ON_WM_HSCROLL()
273 ON_WM_VSCROLL()
274 ON_WM_SIZE()
275 ON_WM_LBUTTONDOWN()
276 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipNotify)
277 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipNotify)
278 ON_WM_MOUSEWHEEL()
279 ON_WM_MOUSEHWHEEL()
280 ON_WM_CONTEXTMENU()
281 ON_WM_MOUSEMOVE()
282 ON_WM_LBUTTONUP()
283 ON_WM_SETCURSOR()
284 ON_WM_TIMER()
285 ON_MESSAGE(WM_WORKERTHREADDONE,OnWorkerThreadDone)
286 ON_WM_CAPTURECHANGED()
287 END_MESSAGE_MAP()
289 void CRevisionGraphWnd::Init(CWnd * pParent, LPRECT rect)
291 WNDCLASS wndcls;
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;
301 wndcls.hIcon = NULL;
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");
317 EnableToolTips();
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
347 #if 0
348 // any nodes at all?
350 CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());
351 if (!nodeList)
352 return index_t(NO_INDEX);
354 // search the nodes for one at that grid position
356 return nodeList->GetAt (GetLogCoordinates (point), border);
357 #endif
359 node v;
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))
366 return v;
369 return NULL;
372 DWORD CRevisionGraphWnd::GetHoverGlyphs (CPoint /*point*/) const
374 // if there is no layout, there will be no nodes,
375 // hence, no glyphs
376 DWORD result = 0;
377 #if 0
378 CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());
379 if (!nodeList)
380 return 0;
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())
390 return 0;
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);
399 CRect r = node.rect;
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);
409 bool upsideDown
410 = m_state.GetOptions()->GetOption<CUpsideDownLayout>()->IsActive();
412 if (upsideDown)
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
422 : 0;
424 if (topGlyphArea.PtInRect (logCoordinates))
425 result = base->GetSource() != NULL
426 ? CGraphNodeStates::COLLAPSED_ABOVE | CGraphNodeStates::SPLIT_ABOVE
427 : 0;
429 if (bottomGlyphArea.PtInRect (logCoordinates))
430 result = base->GetNext() != NULL
431 ? CGraphNodeStates::COLLAPSED_BELOW | CGraphNodeStates::SPLIT_BELOW
432 : 0;
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))
438 result = 0;
439 #endif
440 return result;
442 #if 0
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))
460 return entry;
464 return NULL;
466 #endif
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.
474 switch (nSBCode)
476 case SB_LEFT: // Scroll to far left.
477 sinfo.nPos = sinfo.nMin;
478 break;
479 case SB_RIGHT: // Scroll to far right.
480 sinfo.nPos = sinfo.nMax;
481 break;
482 case SB_ENDSCROLL: // End scroll.
483 break;
484 case SB_LINELEFT: // Scroll left.
485 if (sinfo.nPos > sinfo.nMin)
486 sinfo.nPos--;
487 break;
488 case SB_LINERIGHT: // Scroll right.
489 if (sinfo.nPos < sinfo.nMax)
490 ++sinfo.nPos;
491 break;
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);
497 break;
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);
503 break;
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.
506 break;
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.
509 break;
511 SetScrollInfo(SB_HORZ, &sinfo);
512 Invalidate (FALSE);
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.
523 switch (nSBCode)
525 case SB_LEFT: // Scroll to far left.
526 sinfo.nPos = sinfo.nMin;
527 break;
528 case SB_RIGHT: // Scroll to far right.
529 sinfo.nPos = sinfo.nMax;
530 break;
531 case SB_ENDSCROLL: // End scroll.
532 break;
533 case SB_LINELEFT: // Scroll left.
534 if (sinfo.nPos > sinfo.nMin)
535 sinfo.nPos--;
536 break;
537 case SB_LINERIGHT: // Scroll right.
538 if (sinfo.nPos < sinfo.nMax)
539 ++sinfo.nPos;
540 break;
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);
546 break;
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);
552 break;
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.
555 break;
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.
558 break;
560 SetScrollInfo(SB_VERT, &sinfo);
561 Invalidate(FALSE);
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));
569 Invalidate(FALSE);
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());
580 SetFocus();
581 bool bHit = false;
582 bool bControl = !!(GetKeyState(VK_CONTROL)&0x8000);
583 bool bOverview = m_bShowOverview && m_OverviewRect.PtInRect(point);
584 if (! bOverview)
586 #if 0
587 const CRevisionGraphState::SVisibleGlyph* hitGlyph
588 = GetHitGlyph (point);
590 if (hitGlyph != NULL)
592 ToggleNodeFlag (hitGlyph->node, hitGlyph->state);
593 return __super::OnLButtonDown(nFlags, point);
595 #endif
596 node nodeIndex = GetHitNode (point);
597 if (nodeIndex != NULL)
599 if (bControl)
601 if (m_SelectedEntry1 == nodeIndex)
603 if (m_SelectedEntry2)
605 m_SelectedEntry1 = m_SelectedEntry2;
606 m_SelectedEntry2 = NULL;
608 else
609 m_SelectedEntry1 = NULL;
611 else if (m_SelectedEntry2 == nodeIndex)
612 m_SelectedEntry2 = NULL;
613 else if (m_SelectedEntry1)
614 m_SelectedEntry2 = nodeIndex;
615 else
616 m_SelectedEntry1 = nodeIndex;
618 else
620 if (m_SelectedEntry1 == nodeIndex)
621 m_SelectedEntry1 = NULL;
622 else
623 m_SelectedEntry1 = nodeIndex;
624 m_SelectedEntry2 = NULL;
626 bHit = true;
627 Invalidate(FALSE);
631 if ((!bHit)&&(!bControl)&&(!bOverview))
633 m_SelectedEntry1 = NULL;
634 m_SelectedEntry2 = NULL;
635 m_bIsCanvasMove = true;
636 Invalidate(FALSE);
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;
645 else
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;
654 else
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;
674 ReleaseCapture();
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
688 Invalidate(FALSE);
689 __super::OnLButtonUp(nFlags, point);
690 return;
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
706 Invalidate(FALSE);
707 __super::OnLButtonUp(nFlags, point);
708 return;
710 if (fZoomfactor > MAX_ZOOM)
712 fZoomfactor = MAX_ZOOM;
713 fact = fZoomfactor/m_fZoomFactor;
716 CRevisionGraphDlg * pDlg = (CRevisionGraphDlg*)GetParent();
717 if (pDlg)
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;
729 ReleaseCapture();
730 if (m_bIsCanvasMove)
731 Invalidate(FALSE);
732 m_bIsCanvasMove = false;
733 m_ptRubberEnd = CPoint(0,0);
734 return bRet;
737 INT_PTR CRevisionGraphWnd::OnToolHitTest(CPoint point, TOOLINFO* pTI) const
740 if (IsUpdateJobRunning())
741 return -1;
743 node nodeIndex = GetHitNode (point);
744 if (m_tooltipIndex != nodeIndex)
746 // force tooltip to be updated
748 m_tooltipIndex = nodeIndex;
749 return -1;
752 if (nodeIndex == NULL)
753 return -1;
755 // if ((GetHoverGlyphs (point) != 0) || (GetHitGlyph (point) != NULL))
756 // return -1;
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;
764 return 1;
767 BOOL CRevisionGraphWnd::OnToolTipNotify(UINT /*id*/, NMHDR *pNMHDR, LRESULT *pResult)
769 if (pNMHDR->idFrom != (UINT_PTR)m_hWnd)
770 return FALSE;
772 POINT point;
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));
780 *pResult = 0;
781 if (strTipText.IsEmpty())
782 return TRUE;
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);
795 else
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()
811 // get screen size
813 int screenWidth = GetSystemMetrics(SM_CXSCREEN);
814 int screenHeight = GetSystemMetrics(SM_CYSCREEN);
816 // get current mouse position
818 CPoint cursorPos;
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
830 CSize biggestSector
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)
840 CDC* dc = GetDC();
841 if (dc == NULL)
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(" ...")
847 : wholeText;
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);
860 CFont font;
861 font.CreateFontIndirect(&metrics.lfStatusFont);
862 CFont* pOldFont = dc->SelectObject (&font);
864 // split into lines and fill the tooltip rect
866 CString result;
868 int remainingHeight = tooltipSize.cy;
869 int pos = 0;
870 while (pos < wholeText.GetLength())
872 // extract a whole line
874 int nextPos = wholeText.Find ('\n', pos);
875 if (nextPos < 0)
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 (' ');
887 if (nextPos2 < 0)
888 break;
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)
899 result += _T("...");
900 break;
903 // add the line
905 result += line;
906 pos += line.GetLength();
909 // relase temp. resources
911 dc->SelectObject (pOldFont);
912 ReleaseDC(dc);
914 // ready
916 return result;
919 CString CRevisionGraphWnd::TooltipText(node index)
921 if(index)
923 CString str;
924 CGitHash hash = m_logEntries[index->index()];
925 GitRev *rev = this->m_LogCache.GetCacheData(hash);
926 str += rev->m_CommitHash.ToString();
927 str += _T("\n");
928 str += rev->GetAuthorName() +_T(" ") + rev->GetAuthorEmail();
929 str += _T(" ");
930 str += rev->GetAuthorDate().Format(_T("%Y-%m-%d %H:%M"));
931 str += _T("\n\n")+rev->GetSubject();
932 str += _T("\n");
933 str += rev->GetBody();
934 return str;
936 }else
937 return CString();
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
947 CMetaFileDC wmfDC;
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);
952 CRect rect;
953 rect = GetViewRect();
954 GraphicsDevice dev;
955 dev.pDC = &wmfDC;
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
965 SVG svg;
966 float fZoom = m_fZoomFactor;
967 m_fZoomFactor = DEFAULT_ZOOM;
968 DoZoom(m_fZoomFactor);
969 CRect rect;
970 rect = GetViewRect();
971 svg.SetViewSize(rect.Width(), rect.Height());
972 GraphicsDevice dev;
973 dev.pSVG = &svg;
974 DrawGraph(dev, rect, 0, 0, true);
975 svg.Save(sSavePath);
976 m_fZoomFactor = fZoom;
977 DoZoom(m_fZoomFactor);
979 else if (extension.CompareNoCase(_T(".gv")) == 0)
981 Graphviz graphviz;
982 float fZoom = m_fZoomFactor;
983 m_fZoomFactor = DEFAULT_ZOOM;
984 DoZoom(m_fZoomFactor);
985 CRect rect;
986 rect = GetViewRect();
987 GraphicsDevice dev;
988 dev.pGraphviz = &graphviz;
989 DrawGraph(dev, rect, 0, 0, true);
990 graphviz.Save(sSavePath);
991 m_fZoomFactor = fZoom;
992 DoZoom(m_fZoomFactor);
994 else
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);
1002 CDC dc;
1003 if (!dc.CreateCompatibleDC(&ddc))
1005 CFormatMessageWrapper errorDetails;
1006 if( errorDetails )
1007 MessageBox( errorDetails, _T("Error"), MB_OK | MB_ICONINFORMATION );
1009 return;
1011 CRect rect;
1012 rect = GetGraphRect();
1013 rect.bottom = (LONG)(float(rect.Height()) * m_fZoomFactor);
1014 rect.right = (LONG)(float(rect.Width()) * m_fZoomFactor);
1015 BITMAPINFO bmi;
1016 HBITMAP hbm;
1017 LPBYTE pBits;
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);
1030 if (hbm==0)
1032 CMessageBox::Show(m_hWnd, IDS_REVGRAPH_ERR_NOMEMORY, IDS_APPNAME, MB_ICONERROR);
1033 return;
1035 HBITMAP oldbm = (HBITMAP)dc.SelectObject(hbm);
1036 // paint the whole graph
1037 GraphicsDevice dev;
1038 dev.pDC = &dc;
1039 DrawGraph(dev, rect, 0, 0, true);
1040 // now use GDI+ to save the picture
1041 CLSID encoderClsid;
1043 Bitmap bitmap(hbm, NULL);
1044 if (bitmap.GetLastStatus()==Ok)
1046 // Get the CLSID of the encoder.
1047 int ret = 0;
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);
1058 else
1060 sSavePath += _T(".jpg");
1061 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1063 if (ret >= 0)
1065 CStringW tfile = CStringW(sSavePath);
1066 bitmap.Save(tfile, &encoderClsid, NULL);
1068 else
1070 sErrormessage.Format(IDS_REVGRAPH_ERR_NOENCODER, (LPCTSTR)CPathUtils::GetFileExtFromPath(sSavePath));
1073 else
1075 sErrormessage.LoadString(IDS_REVGRAPH_ERR_NOBITMAP);
1078 dc.SelectObject(oldbm);
1079 DeleteObject(hbm);
1080 dc.DeleteDC();
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);
1090 pE->Delete();
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)));
1107 else
1109 int orientation = GetKeyState(VK_SHIFT)&0x8000 ? SB_HORZ : SB_VERT;
1110 int pos = GetScrollPos(orientation);
1111 pos -= (zDelta);
1112 SetScrollPos(orientation, pos);
1113 Invalidate(FALSE);
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);
1125 pos -= (zDelta);
1126 SetScrollPos(orientation, pos);
1127 Invalidate(FALSE);
1129 return __super::OnMouseHWheel(nFlags, zDelta, pt);
1132 bool CRevisionGraphWnd::UpdateSelectedEntry (node clickedentry)
1134 if ((m_SelectedEntry1 == NULL)&&(clickedentry == NULL))
1135 return false;
1137 if (m_SelectedEntry1 == NULL)
1139 m_SelectedEntry1 = clickedentry;
1140 Invalidate(FALSE);
1142 if ((m_SelectedEntry2 == NULL)&&(clickedentry != m_SelectedEntry1))
1144 m_SelectedEntry1 = clickedentry;
1145 Invalidate(FALSE);
1147 if (m_SelectedEntry1 && m_SelectedEntry2)
1149 if ((m_SelectedEntry2 != clickedentry)&&(m_SelectedEntry1 != clickedentry))
1150 return false;
1152 if (m_SelectedEntry1 == NULL)
1153 return false;
1155 return true;
1158 void CRevisionGraphWnd::AppendMenu
1159 ( CMenu& popup
1160 , UINT title
1161 , UINT command
1162 , UINT flags)
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);
1193 if (bothPresent)
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*/)
1203 #if 0
1204 CSyncPointer<CGraphNodeStates> nodeStates (m_state.GetNodeStates());
1206 if (node == NULL)
1208 DWORD state = nodeStates->GetCombinedFlags();
1209 if (state != 0)
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);
1218 else
1220 DWORD state = nodeStates->GetFlags (node);
1222 if (node->GetSource() || (state & CGraphNodeStates::COLLAPSED_ABOVE))
1223 AppendMenu ( popup
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))
1230 AppendMenu ( popup
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))
1237 AppendMenu ( popup
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))
1244 AppendMenu ( popup
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))
1251 AppendMenu ( popup
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))
1258 AppendMenu ( popup
1259 , (state & CGraphNodeStates::SPLIT_BELOW)
1260 ? IDS_REVGRAPH_POPUP_JOIN_BELOW
1261 : IDS_REVGRAPH_POPUP_SPLIT_BELOW
1262 , ID_GRAPH_SPLITJOIN_BELOW);
1264 #endif
1267 CString CRevisionGraphWnd::GetSelectedURL() const
1269 #if 0
1270 if (m_SelectedEntry1 == NULL)
1271 return CString();
1273 CString URL = m_state.GetRepositoryRoot()
1274 + CUnicodeUtils::GetUnicode (m_SelectedEntry1->GetPath().GetPath().c_str());
1275 URL = CUnicodeUtils::GetUnicode(CPathUtils::PathEscape(CUnicodeUtils::GetUTF8(URL)));
1277 return URL;
1278 #endif
1279 return CString();
1282 CString CRevisionGraphWnd::GetWCURL() const
1284 #if 0
1285 CTGitPath path (m_sPath);
1286 if (path.IsUrl())
1287 return CString();
1289 SVNInfo info;
1290 const SVNInfoData * status
1291 = info.GetFirstFileInfo (path, SVNRev(), SVNRev());
1293 return status == NULL ? CString() : status->url;
1294 #endif
1295 return CString();
1298 void CRevisionGraphWnd::DoShowLog()
1301 if(m_SelectedEntry1 == NULL)
1302 return;
1304 CString sCmd;
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());
1311 else
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()
1322 CChangedDlg dlg;
1323 dlg.m_pathList = CTGitPathList (CTGitPath (m_sPath));
1324 dlg.DoModal();
1327 void CRevisionGraphWnd::DoMergeTo()
1329 #if 0
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)));
1339 dlg.SetUrl(URL);
1340 dlg.SetSecondUrl(URL);
1341 SVNRevRangeArray revarray;
1342 revarray.AddRevRange (m_SelectedEntry1->GetRevision()-1, svn_revnum_t(m_SelectedEntry1->GetRevision()));
1343 dlg.SetRevisionRanges(revarray);
1344 dlg.DoModal();
1346 #endif
1349 void CRevisionGraphWnd::DoUpdate()
1351 #if 0
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());
1357 progDlg.SetDepth();
1358 progDlg.DoModal();
1360 if (m_state.GetFetchedWCState())
1361 m_parent->UpdateFullHistory();
1362 #endif
1365 void CRevisionGraphWnd::DoSwitch()
1367 #if 0
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());
1373 progDlg.DoModal();
1375 if (m_state.GetFetchedWCState())
1376 m_parent->UpdateFullHistory();
1377 #endif
1380 void CRevisionGraphWnd::DoSwitchToHead()
1382 #if 0
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());
1389 progDlg.DoModal();
1391 if (m_state.GetFetchedWCState())
1392 m_parent->UpdateFullHistory();
1393 #endif
1396 void CRevisionGraphWnd::DoBrowseRepo()
1398 #if 0
1399 CString sCmd;
1400 sCmd.Format(_T("/command:repobrowser /path:\"%s\" /rev:%d"),
1401 (LPCTSTR)GetSelectedURL(), m_SelectedEntry1->GetRevision());
1403 CAppUtils::RunTortoiseProc(sCmd);
1404 #endif
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*/)
1415 #if 0
1416 CSyncPointer<CGraphNodeStates> nodeStates (m_state.GetNodeStates());
1418 if (nodeStates->GetFlags (node) & flag)
1419 nodeStates->ResetFlags (node, flag);
1420 else
1421 nodeStates->SetFlags (node, flag);
1423 m_parent->StartWorkerThread();
1424 #endif
1427 void CRevisionGraphWnd::DoCopyUrl()
1429 CStringUtils::WriteAsciiStringToClipboard(GetSelectedURL(), m_hWnd);
1432 void CRevisionGraphWnd::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
1434 if (IsUpdateJobRunning())
1435 return;
1437 CPoint clientpoint = point;
1438 this->ScreenToClient(&clientpoint);
1440 node nodeIndex = GetHitNode (clientpoint);
1442 if ( !UpdateSelectedEntry (nodeIndex))
1444 return;
1447 CMenu popup;
1448 if (!popup.CreatePopupMenu())
1449 return;
1451 AddGitOps (popup);
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);
1463 switch (cmd)
1465 case ID_COMPAREREVS:
1466 if (m_SelectedEntry1 != NULL)
1467 CompareRevs(false);
1468 break;
1469 case ID_UNIDIFFREVS:
1470 if (m_SelectedEntry1 != NULL)
1471 UnifiedDiffRevs(false);
1472 break;
1473 case ID_UNIDIFFHEADS:
1474 if (m_SelectedEntry1 != NULL)
1475 UnifiedDiffRevs(true);
1476 break;
1477 case ID_SHOWLOG:
1478 DoShowLog();
1479 break;
1480 case ID_SWITCH:
1481 DoSwitch();
1482 break;
1483 case ID_COMPAREHEADS:
1484 if (m_SelectedEntry1 != NULL)
1485 CompareRevs(true);
1486 break;
1489 #if 0
1490 case ID_COMPAREREVS:
1491 if (m_SelectedEntry1 != NULL)
1492 CompareRevs(false);
1493 break;
1494 case ID_UNIDIFFREVS:
1495 if (m_SelectedEntry1 != NULL)
1496 UnifiedDiffRevs(false);
1497 break;
1498 case ID_UNIDIFFHEADS:
1499 if (m_SelectedEntry1 != NULL)
1500 UnifiedDiffRevs(true);
1501 break;
1502 case ID_SHOWLOG:
1503 DoShowLog();
1504 break;
1505 case ID_CFM:
1506 DoCheckForModification();
1507 break;
1508 case ID_MERGETO:
1509 DoMergeTo();
1510 break;
1511 case ID_UPDATE:
1512 DoUpdate();
1513 break;
1514 case ID_SWITCHTOHEAD:
1515 DoSwitchToHead();
1516 break;
1517 case ID_SWITCH:
1518 DoSwitch();
1519 break;
1520 case ID_COPYURL:
1521 DoCopyUrl();
1522 break;
1523 case ID_BROWSEREPO:
1524 DoBrowseRepo();
1525 break;
1526 case ID_EXPAND_ALL:
1527 ResetNodeFlags (CGraphNodeStates::COLLAPSED_ALL);
1528 break;
1529 case ID_JOIN_ALL:
1530 ResetNodeFlags (CGraphNodeStates::SPLIT_ALL);
1531 break;
1532 case ID_GRAPH_EXPANDCOLLAPSE_ABOVE:
1533 ToggleNodeFlag (clickedentry, CGraphNodeStates::COLLAPSED_ABOVE);
1534 break;
1535 case ID_GRAPH_EXPANDCOLLAPSE_RIGHT:
1536 ToggleNodeFlag (clickedentry, CGraphNodeStates::COLLAPSED_RIGHT);
1537 break;
1538 case ID_GRAPH_EXPANDCOLLAPSE_BELOW:
1539 ToggleNodeFlag (clickedentry, CGraphNodeStates::COLLAPSED_BELOW);
1540 break;
1541 case ID_GRAPH_SPLITJOIN_ABOVE:
1542 ToggleNodeFlag (clickedentry, CGraphNodeStates::SPLIT_ABOVE);
1543 break;
1544 case ID_GRAPH_SPLITJOIN_RIGHT:
1545 ToggleNodeFlag (clickedentry, CGraphNodeStates::SPLIT_RIGHT);
1546 break;
1547 case ID_GRAPH_SPLITJOIN_BELOW:
1548 ToggleNodeFlag (clickedentry, CGraphNodeStates::SPLIT_BELOW);
1549 break;
1550 #endif
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))
1566 // scrolling
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);
1570 x = max(0, x);
1571 y = max(0, y);
1572 SetScrollbars(y, x);
1573 Invalidate(FALSE);
1574 return __super::OnMouseMove(nFlags, point);
1576 else
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);
1585 #if 0
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);
1600 if ( !m_hoverIndex
1601 && ( (m_hoverIndex != GetHitNode (clientPoint))))
1603 m_showHoverGlyphs = false;
1605 KillTimer (GLYPH_HOVER_EVENT);
1606 SetTimer (GLYPH_HOVER_EVENT, GLYPH_HOVER_DELAY, NULL);
1608 Invalidate(FALSE);
1610 #endif
1611 return __super::OnMouseMove(nFlags, point);
1614 SetCapture();
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;
1626 this->Invalidate();
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))
1640 POINT pt;
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)
1658 SetCursor (hCur);
1660 return TRUE;
1663 void CRevisionGraphWnd::OnTimer (UINT_PTR nIDEvent)
1665 if (nIDEvent == GLYPH_HOVER_EVENT)
1667 KillTimer (GLYPH_HOVER_EVENT);
1669 m_showHoverGlyphs = true;
1670 Invalidate (FALSE);
1672 else
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();
1686 InitView();
1687 BuildPreview();
1689 SCROLLINFO sinfo = {0};
1690 sinfo.cbSize = sizeof(SCROLLINFO);
1691 GetScrollInfo(SB_HORZ, &sinfo);
1692 sinfo.nPos = sinfo.nMax;
1693 SetScrollInfo(SB_HORZ, &sinfo);
1695 Invalidate(FALSE);
1697 #if 0
1698 SVN svn;
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()
1708 , false));
1711 if (m_parent && !m_parent->GetOutputFile().IsEmpty())
1713 // save the graph to the output file and exit
1714 SaveGraphAs(m_parent->GetOutputFile());
1715 PostQuitMessage(0);
1717 #endif
1718 return 0;
1721 void CRevisionGraphWnd::SetDlgTitle (bool /*offline*/)
1723 #if 0
1724 if (m_sTitle.IsEmpty())
1725 GetParent()->GetWindowText(m_sTitle);
1727 CString newTitle;
1728 if (offline)
1729 newTitle.Format (IDS_REVGRAPH_DLGTITLEOFFLINE, (LPCTSTR)m_sTitle);
1730 else
1731 newTitle = m_sTitle;
1733 CAppUtils::SetWindowTitle(GetParent()->GetSafeHwnd(), m_sPath, newTitle);
1734 #endif