Cleanup
[TortoiseGit.git] / src / TortoiseProc / RevisionGraph / RevisionGraphWnd.cpp
blobfca88a185a508a12f9227530b36c78c6f997ed9e
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2012 - TortoiseSVN
4 // Copyright (C) 2012 - 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 #include <ogdf/planarity/PlanarizationLayout.h>
46 #include <ogdf/planarity/VariableEmbeddingInserter.h>
47 #include <ogdf/planarity/FastPlanarSubgraph.h>
48 #include <ogdf/orthogonal/OrthoLayout.h>
49 #include <ogdf/planarity/EmbedderMinDepthMaxFaceLayers.h>
51 #ifdef _DEBUG
52 #define new DEBUG_NEW
53 #undef THIS_FILE
54 static char THIS_FILE[] = __FILE__;
55 #endif
57 using namespace Gdiplus;
58 using namespace ogdf;
60 #if (_WIN32_WINNT < 0x0600)
61 #define WM_MOUSEHWHEEL 0x020E
62 #endif
65 enum RevisionGraphContextMenuCommands
67 // needs to start with 1, since 0 is the return value if *nothing* is clicked on in the context menu
68 GROUP_MASK = 0xff00,
69 ID_SHOWLOG = 1,
70 ID_CFM = 2,
71 ID_BROWSEREPO,
72 ID_COMPAREREVS = 0x100,
73 ID_COMPAREHEADS,
74 ID_UNIDIFFREVS,
75 ID_UNIDIFFHEADS,
76 ID_MERGETO = 0x300,
77 ID_UPDATE,
78 ID_SWITCHTOHEAD,
79 ID_SWITCH,
80 ID_COPYURL = 0x400,
81 ID_EXPAND_ALL = 0x500,
82 ID_JOIN_ALL,
83 ID_GRAPH_EXPANDCOLLAPSE_ABOVE = 0x600,
84 ID_GRAPH_EXPANDCOLLAPSE_RIGHT,
85 ID_GRAPH_EXPANDCOLLAPSE_BELOW,
86 ID_GRAPH_SPLITJOIN_ABOVE,
87 ID_GRAPH_SPLITJOIN_RIGHT,
88 ID_GRAPH_SPLITJOIN_BELOW,
91 CRevisionGraphWnd::CRevisionGraphWnd()
92 : CWnd()
93 , m_SelectedEntry1(NULL)
94 , m_SelectedEntry2(NULL)
95 , m_pDlgTip(NULL)
96 , m_nFontSize(12)
97 , m_bTweakTrunkColors(true)
98 , m_bTweakTagsColors(true)
99 , m_fZoomFactor(DEFAULT_ZOOM)
100 , m_ptRubberEnd(0,0)
101 , m_ptRubberStart(0,0)
102 , m_bShowOverview(false)
103 , m_parent (NULL)
104 , m_hoverIndex (NULL)
105 , m_hoverGlyphs (0)
106 // , m_tooltipIndex ((index_t)NO_INDEX)
107 , m_showHoverGlyphs (false)
108 , m_bIsRubberBand(false)
109 , m_previewWidth(0)
110 , m_previewHeight(0)
111 , m_previewZoom(1)
112 , m_dwTicks(0)
113 , m_logEntries(&m_LogCache)
114 , m_bCurrentBranch(false)
116 memset(&m_lfBaseFont, 0, sizeof(LOGFONT));
117 std::fill_n(m_apFonts, MAXFONTS, (CFont*)NULL);
119 WNDCLASS wndcls;
120 HINSTANCE hInst = AfxGetInstanceHandle();
121 #define REVGRAPH_CLASSNAME _T("Revgraph_windowclass")
122 if (!(::GetClassInfo(hInst, REVGRAPH_CLASSNAME, &wndcls)))
124 // otherwise we need to register a new class
125 wndcls.style = CS_DBLCLKS | CS_OWNDC;
126 wndcls.lpfnWndProc = ::DefWindowProc;
127 wndcls.cbClsExtra = wndcls.cbWndExtra = 0;
128 wndcls.hInstance = hInst;
129 wndcls.hIcon = NULL;
130 wndcls.hCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
131 wndcls.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
132 wndcls.lpszMenuName = NULL;
133 wndcls.lpszClassName = REVGRAPH_CLASSNAME;
135 RegisterClass(&wndcls);
138 m_bTweakTrunkColors = CRegDWORD(_T("Software\\TortoiseGit\\RevisionGraph\\TweakTrunkColors"), TRUE) != FALSE;
139 m_bTweakTagsColors = CRegDWORD(_T("Software\\TortoiseGit\\RevisionGraph\\TweakTagsColors"), TRUE) != FALSE;
140 m_szTip[0] = 0;
141 m_wszTip[0] = 0;
143 m_GraphAttr.init(this->m_Graph, ogdf::GraphAttributes::nodeGraphics | ogdf::GraphAttributes::edgeGraphics |
144 ogdf:: GraphAttributes::nodeLabel | ogdf::GraphAttributes::nodeColor |
145 ogdf::GraphAttributes::edgeColor | ogdf::GraphAttributes::edgeStyle |
146 ogdf::GraphAttributes::nodeStyle | ogdf::GraphAttributes::nodeTemplate);
148 m_SugiyamLayout.setRanking(::new ogdf::OptimalRanking());
149 m_SugiyamLayout.setCrossMin(::new ogdf::MedianHeuristic());
151 double pi = 3.1415926;
152 m_ArrowCos = cos(pi/8);
153 m_ArrowSin = sin(pi/8);
154 this->m_ArrowSize = 8;
155 #if 0
156 ogdf::node one = this->m_Graph.newNode();
157 ogdf::node two = this->m_Graph.newNode();
158 ogdf::node three = this->m_Graph.newNode();
159 ogdf::node four = this->m_Graph.newNode();
162 m_GraphAttr.width(one)=100;
163 m_GraphAttr.height(one)=200;
164 m_GraphAttr.width(two)=100;
165 m_GraphAttr.height(two)=100;
166 m_GraphAttr.width(three)=100;
167 m_GraphAttr.height(three)=20;
168 m_GraphAttr.width(four)=100;
169 m_GraphAttr.height(four)=20;
171 m_GraphAttr.labelNode(one)="One";
172 m_GraphAttr.labelNode(two)="Two";
173 m_GraphAttr.labelNode(three)="three";
175 this->m_Graph.newEdge(one, two);
176 this->m_Graph.newEdge(one, three);
177 this->m_Graph.newEdge(two, four);
178 this->m_Graph.newEdge(three, four);
180 #endif
181 FastHierarchyLayout *pOHL = ::new FastHierarchyLayout;
182 //It will auto delte when m_SugiyamLayout destory
184 pOHL->layerDistance(30.0);
185 pOHL->nodeDistance(25.0);
187 m_SugiyamLayout.setLayout(pOHL);
189 #if 0
190 //this->m_OHL.layerDistance(30.0);
191 //this->m_OHL.nodeDistance(25.0);
192 //this->m_OHL.weightBalancing(0.8);
193 m_SugiyamLayout.setLayout(&m_OHL);
194 m_SugiyamLayout.call(m_GraphAttr);
195 #endif
196 #if 0
197 PlanarizationLayout pl;
199 FastPlanarSubgraph *ps = ::new FastPlanarSubgraph;
200 ps->runs(100);
201 VariableEmbeddingInserter *ves = ::new VariableEmbeddingInserter;
202 ves->removeReinsert(EdgeInsertionModule::rrAll);
203 pl.setSubgraph(ps);
204 pl.setInserter(ves);
206 EmbedderMinDepthMaxFaceLayers *emb = ::new EmbedderMinDepthMaxFaceLayers;
207 pl.setEmbedder(emb);
209 OrthoLayout *ol =::new OrthoLayout;
210 ol->separation(20.0);
211 ol->cOverhang(0.4);
212 ol->setOptions(2+4);
213 ol->preferedDir(OrthoDir::odEast);
214 pl.setPlanarLayouter(ol);
216 pl.call(m_GraphAttr);
218 node v;
219 forall_nodes(v,m_Graph) {
221 TRACE(_T("node x %f y %f %f %f\n"),/* m_GraphAttr.idNode(v), */
222 m_GraphAttr.x(v),
223 m_GraphAttr.y(v),
224 m_GraphAttr.width(v),
225 m_GraphAttr.height(v)
229 edge e;
230 forall_edges(e, m_Graph)
232 // get connection and point position
233 const DPolyline &dpl = this->m_GraphAttr.bends(e);
235 ListConstIterator<DPoint> it;
236 for(it = dpl.begin(); it.valid(); ++it)
238 TRACE(_T("edge %f %f\n"), (*it).m_x, (*it).m_y);
241 m_GraphAttr.writeGML("test.gml");
242 #endif
245 CRevisionGraphWnd::~CRevisionGraphWnd()
247 for (int i=0; i<MAXFONTS; i++)
249 if (m_apFonts[i] != NULL)
251 m_apFonts[i]->DeleteObject();
252 delete m_apFonts[i];
254 m_apFonts[i] = NULL;
256 delete m_pDlgTip;
257 m_Graph.clear();
260 void CRevisionGraphWnd::DoDataExchange(CDataExchange* pDX)
262 CWnd::DoDataExchange(pDX);
266 BEGIN_MESSAGE_MAP(CRevisionGraphWnd, CWnd)
267 ON_WM_PAINT()
268 ON_WM_ERASEBKGND()
269 ON_WM_HSCROLL()
270 ON_WM_VSCROLL()
271 ON_WM_SIZE()
272 ON_WM_LBUTTONDOWN()
273 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipNotify)
274 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipNotify)
275 ON_WM_MOUSEWHEEL()
276 ON_WM_MOUSEHWHEEL()
277 ON_WM_CONTEXTMENU()
278 ON_WM_MOUSEMOVE()
279 ON_WM_LBUTTONUP()
280 ON_WM_SETCURSOR()
281 ON_WM_TIMER()
282 ON_MESSAGE(WM_WORKERTHREADDONE,OnWorkerThreadDone)
283 ON_WM_CAPTURECHANGED()
284 END_MESSAGE_MAP()
286 void CRevisionGraphWnd::Init(CWnd * pParent, LPRECT rect)
288 WNDCLASS wndcls;
289 HINSTANCE hInst = AfxGetInstanceHandle();
290 #define REVGRAPH_CLASSNAME _T("Revgraph_windowclass")
291 if (!(::GetClassInfo(hInst, REVGRAPH_CLASSNAME, &wndcls)))
293 // otherwise we need to register a new class
294 wndcls.style = CS_DBLCLKS | CS_OWNDC;
295 wndcls.lpfnWndProc = ::DefWindowProc;
296 wndcls.cbClsExtra = wndcls.cbWndExtra = 0;
297 wndcls.hInstance = hInst;
298 wndcls.hIcon = NULL;
299 wndcls.hCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
300 wndcls.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
301 wndcls.lpszMenuName = NULL;
302 wndcls.lpszClassName = REVGRAPH_CLASSNAME;
304 RegisterClass(&wndcls);
307 if (!IsWindow(m_hWnd))
308 CreateEx(WS_EX_CLIENTEDGE, REVGRAPH_CLASSNAME, _T("RevGraph"), WS_CHILD|WS_VISIBLE|WS_TABSTOP, *rect, pParent, 0);
309 m_pDlgTip = new CToolTipCtrl;
310 if(!m_pDlgTip->Create(this))
312 // CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Unable to add tooltip!\n");
314 EnableToolTips();
316 memset(&m_lfBaseFont, 0, sizeof(m_lfBaseFont));
317 m_lfBaseFont.lfHeight = 0;
318 m_lfBaseFont.lfWeight = FW_NORMAL;
319 m_lfBaseFont.lfItalic = FALSE;
320 m_lfBaseFont.lfCharSet = DEFAULT_CHARSET;
321 m_lfBaseFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
322 m_lfBaseFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
323 m_lfBaseFont.lfQuality = DEFAULT_QUALITY;
324 m_lfBaseFont.lfPitchAndFamily = DEFAULT_PITCH;
326 m_dwTicks = GetTickCount();
328 m_parent = dynamic_cast<CRevisionGraphDlg*>(pParent);
331 CPoint CRevisionGraphWnd::GetLogCoordinates (CPoint point) const
333 // translate point into logical coordinates
335 int nVScrollPos = GetScrollPos(SB_VERT);
336 int nHScrollPos = GetScrollPos(SB_HORZ);
338 return CPoint ( (int)((point.x + nHScrollPos) / m_fZoomFactor)
339 , (int)((point.y + nVScrollPos) / m_fZoomFactor));
342 node CRevisionGraphWnd::GetHitNode (CPoint point, CSize /*border*/) const
344 #if 0
345 // any nodes at all?
347 CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());
348 if (!nodeList)
349 return index_t(NO_INDEX);
351 // search the nodes for one at that grid position
353 return nodeList->GetAt (GetLogCoordinates (point), border);
354 #endif
356 node v;
357 forall_nodes(v,m_Graph)
359 RectF noderect (GetNodeRect (v, CPoint(GetScrollPos(SB_HORZ), GetScrollPos(SB_VERT))));
360 if(point.x>noderect.X && point.x <(noderect.X+noderect.Width) &&
361 point.y>noderect.Y && point.y <(noderect.Y+noderect.Height))
363 return v;
366 return NULL;
369 DWORD CRevisionGraphWnd::GetHoverGlyphs (CPoint /*point*/) const
371 // if there is no layout, there will be no nodes,
372 // hence, no glyphs
373 DWORD result = 0;
374 #if 0
375 CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());
376 if (!nodeList)
377 return 0;
379 // get node at point or node that is close enough
380 // so that point may hit a glyph area
382 index_t nodeIndex = GetHitNode(point);
383 if (nodeIndex == NO_INDEX)
384 nodeIndex = GetHitNode(point, CSize (GLYPH_SIZE, GLYPH_SIZE / 2));
386 if (nodeIndex >= nodeList->GetCount())
387 return 0;
389 ILayoutNodeList::SNode node = nodeList->GetNode (nodeIndex);
390 const CVisibleGraphNode* base = node.node;
392 // what glyphs should be shown depending on position of point
393 // relative to the node rect?
395 CPoint logCoordinates = GetLogCoordinates (point);
396 CRect r = node.rect;
397 CPoint center = r.CenterPoint();
399 CRect rightGlyphArea ( r.right - GLYPH_SIZE, center.y - GLYPH_SIZE / 2
400 , r.right + GLYPH_SIZE, center.y + GLYPH_SIZE / 2);
401 CRect topGlyphArea ( center.x - GLYPH_SIZE, r.top - GLYPH_SIZE / 2
402 , center.x + GLYPH_SIZE, r.top + GLYPH_SIZE / 2);
403 CRect bottomGlyphArea ( center.x - GLYPH_SIZE, r.bottom - GLYPH_SIZE / 2
404 , center.x + GLYPH_SIZE, r.bottom + GLYPH_SIZE / 2);
406 bool upsideDown
407 = m_state.GetOptions()->GetOption<CUpsideDownLayout>()->IsActive();
409 if (upsideDown)
411 std::swap (topGlyphArea.top, bottomGlyphArea.top);
412 std::swap (topGlyphArea.bottom, bottomGlyphArea.bottom);
416 if (rightGlyphArea.PtInRect (logCoordinates))
417 result = base->GetFirstCopyTarget() != NULL
418 ? CGraphNodeStates::COLLAPSED_RIGHT | CGraphNodeStates::SPLIT_RIGHT
419 : 0;
421 if (topGlyphArea.PtInRect (logCoordinates))
422 result = base->GetSource() != NULL
423 ? CGraphNodeStates::COLLAPSED_ABOVE | CGraphNodeStates::SPLIT_ABOVE
424 : 0;
426 if (bottomGlyphArea.PtInRect (logCoordinates))
427 result = base->GetNext() != NULL
428 ? CGraphNodeStates::COLLAPSED_BELOW | CGraphNodeStates::SPLIT_BELOW
429 : 0;
431 // if some nodes have already been split, don't allow collapsing etc.
433 CSyncPointer<const CGraphNodeStates> nodeStates (m_state.GetNodeStates());
434 if (result & nodeStates->GetFlags (base))
435 result = 0;
436 #endif
437 return result;
439 #if 0
440 const CRevisionGraphState::SVisibleGlyph* CRevisionGraphWnd::GetHitGlyph (CPoint point) const
442 float glyphSize = GLYPH_SIZE * m_fZoomFactor;
444 CSyncPointer<const CRevisionGraphState::TVisibleGlyphs>
445 visibleGlyphs (m_state.GetVisibleGlyphs());
447 for (size_t i = 0, count = visibleGlyphs->size(); i < count; ++i)
449 const CRevisionGraphState::SVisibleGlyph* entry = &(*visibleGlyphs)[i];
451 float xRel = point.x - entry->leftTop.X;
452 float yRel = point.y - entry->leftTop.Y;
454 if ( (xRel >= 0) && (xRel < glyphSize)
455 && (yRel >= 0) && (yRel < glyphSize))
457 return entry;
461 return NULL;
463 #endif
464 void CRevisionGraphWnd::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
466 SCROLLINFO sinfo = {0};
467 sinfo.cbSize = sizeof(SCROLLINFO);
468 GetScrollInfo(SB_HORZ, &sinfo);
470 // Determine the new position of scroll box.
471 switch (nSBCode)
473 case SB_LEFT: // Scroll to far left.
474 sinfo.nPos = sinfo.nMin;
475 break;
476 case SB_RIGHT: // Scroll to far right.
477 sinfo.nPos = sinfo.nMax;
478 break;
479 case SB_ENDSCROLL: // End scroll.
480 break;
481 case SB_LINELEFT: // Scroll left.
482 if (sinfo.nPos > sinfo.nMin)
483 sinfo.nPos--;
484 break;
485 case SB_LINERIGHT: // Scroll right.
486 if (sinfo.nPos < sinfo.nMax)
487 sinfo.nPos++;
488 break;
489 case SB_PAGELEFT: // Scroll one page left.
491 if (sinfo.nPos > sinfo.nMin)
492 sinfo.nPos = max(sinfo.nMin, sinfo.nPos - (int) sinfo.nPage);
494 break;
495 case SB_PAGERIGHT: // Scroll one page right.
497 if (sinfo.nPos < sinfo.nMax)
498 sinfo.nPos = min(sinfo.nMax, sinfo.nPos + (int) sinfo.nPage);
500 break;
501 case SB_THUMBPOSITION: // Scroll to absolute position. nPos is the position
502 sinfo.nPos = sinfo.nTrackPos; // of the scroll box at the end of the drag operation.
503 break;
504 case SB_THUMBTRACK: // Drag scroll box to specified position. nPos is the
505 sinfo.nPos = sinfo.nTrackPos; // position that the scroll box has been dragged to.
506 break;
508 SetScrollInfo(SB_HORZ, &sinfo);
509 Invalidate (FALSE);
510 __super::OnHScroll(nSBCode, nPos, pScrollBar);
513 void CRevisionGraphWnd::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
515 SCROLLINFO sinfo = {0};
516 sinfo.cbSize = sizeof(SCROLLINFO);
517 GetScrollInfo(SB_VERT, &sinfo);
519 // Determine the new position of scroll box.
520 switch (nSBCode)
522 case SB_LEFT: // Scroll to far left.
523 sinfo.nPos = sinfo.nMin;
524 break;
525 case SB_RIGHT: // Scroll to far right.
526 sinfo.nPos = sinfo.nMax;
527 break;
528 case SB_ENDSCROLL: // End scroll.
529 break;
530 case SB_LINELEFT: // Scroll left.
531 if (sinfo.nPos > sinfo.nMin)
532 sinfo.nPos--;
533 break;
534 case SB_LINERIGHT: // Scroll right.
535 if (sinfo.nPos < sinfo.nMax)
536 sinfo.nPos++;
537 break;
538 case SB_PAGELEFT: // Scroll one page left.
540 if (sinfo.nPos > sinfo.nMin)
541 sinfo.nPos = max(sinfo.nMin, sinfo.nPos - (int) sinfo.nPage);
543 break;
544 case SB_PAGERIGHT: // Scroll one page right.
546 if (sinfo.nPos < sinfo.nMax)
547 sinfo.nPos = min(sinfo.nMax, sinfo.nPos + (int) sinfo.nPage);
549 break;
550 case SB_THUMBPOSITION: // Scroll to absolute position. nPos is the position
551 sinfo.nPos = sinfo.nTrackPos; // of the scroll box at the end of the drag operation.
552 break;
553 case SB_THUMBTRACK: // Drag scroll box to specified position. nPos is the
554 sinfo.nPos = sinfo.nTrackPos; // position that the scroll box has been dragged to.
555 break;
557 SetScrollInfo(SB_VERT, &sinfo);
558 Invalidate(FALSE);
559 __super::OnVScroll(nSBCode, nPos, pScrollBar);
562 void CRevisionGraphWnd::OnSize(UINT nType, int cx, int cy)
564 __super::OnSize(nType, cx, cy);
565 SetScrollbars(GetScrollPos(SB_VERT), GetScrollPos(SB_HORZ));
566 Invalidate(FALSE);
569 void CRevisionGraphWnd::OnLButtonDown(UINT nFlags, CPoint point)
572 if (IsUpdateJobRunning())
573 return __super::OnLButtonDown(nFlags, point);
575 // CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());
577 SetFocus();
578 bool bHit = false;
579 bool bControl = !!(GetKeyState(VK_CONTROL)&0x8000);
580 bool bOverview = m_bShowOverview && m_OverviewRect.PtInRect(point);
581 if (! bOverview)
583 #if 0
584 const CRevisionGraphState::SVisibleGlyph* hitGlyph
585 = GetHitGlyph (point);
587 if (hitGlyph != NULL)
589 ToggleNodeFlag (hitGlyph->node, hitGlyph->state);
590 return __super::OnLButtonDown(nFlags, point);
592 #endif
593 node nodeIndex = GetHitNode (point);
594 if (nodeIndex != NULL)
596 if (bControl)
598 if (m_SelectedEntry1 == nodeIndex)
600 if (m_SelectedEntry2)
602 m_SelectedEntry1 = m_SelectedEntry2;
603 m_SelectedEntry2 = NULL;
605 else
606 m_SelectedEntry1 = NULL;
608 else if (m_SelectedEntry2 == nodeIndex)
609 m_SelectedEntry2 = NULL;
610 else if (m_SelectedEntry1)
611 m_SelectedEntry2 = nodeIndex;
612 else
613 m_SelectedEntry1 = nodeIndex;
615 else
617 if (m_SelectedEntry1 == nodeIndex)
618 m_SelectedEntry1 = NULL;
619 else
620 m_SelectedEntry1 = nodeIndex;
621 m_SelectedEntry2 = NULL;
623 bHit = true;
624 Invalidate(FALSE);
628 if ((!bHit)&&(!bControl)&&(!bOverview))
630 m_SelectedEntry1 = NULL;
631 m_SelectedEntry2 = NULL;
632 m_bIsRubberBand = true;
633 Invalidate(FALSE);
634 if (m_bShowOverview && m_OverviewRect.PtInRect(point))
635 m_bIsRubberBand = false;
637 m_ptRubberStart = point;
639 UINT uEnable = MF_BYCOMMAND;
640 if ((m_SelectedEntry1 != NULL)&&(m_SelectedEntry2 != NULL))
641 uEnable |= MF_ENABLED;
642 else
643 uEnable |= MF_GRAYED;
645 EnableMenuItem(GetParent()->GetMenu()->m_hMenu, ID_VIEW_COMPAREREVISIONS, uEnable);
646 EnableMenuItem(GetParent()->GetMenu()->m_hMenu, ID_VIEW_UNIFIEDDIFF, uEnable);
648 uEnable = MF_BYCOMMAND;
649 if ((m_SelectedEntry1 != NULL)&&(m_SelectedEntry2 == NULL))
650 uEnable |= MF_ENABLED;
651 else
652 uEnable |= MF_GRAYED;
654 EnableMenuItem(GetParent()->GetMenu()->m_hMenu, ID_VIEW_UNIFIEDDIFFOFHEADREVISIONS, uEnable);
655 EnableMenuItem(GetParent()->GetMenu()->m_hMenu, ID_VIEW_COMPAREHEADREVISIONS, uEnable);
657 __super::OnLButtonDown(nFlags, point);
660 void CRevisionGraphWnd::OnCaptureChanged(CWnd *pWnd)
662 m_bIsRubberBand = false;
663 m_ptRubberEnd = CPoint(0,0);
665 __super::OnCaptureChanged(pWnd);
668 void CRevisionGraphWnd::OnLButtonUp(UINT nFlags, CPoint point)
670 if (!m_bIsRubberBand)
671 return; // we don't have a rubberband, so no zooming necessary
673 m_bIsRubberBand = 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_ptRubberStart.x - point.x);
682 int y = abs(m_ptRubberStart.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_ptRubberStart.x, point.x) + GetScrollPos(SB_HORZ);
699 y = min(m_ptRubberStart.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_bIsRubberBand;
729 ReleaseCapture();
730 if (m_bIsRubberBand)
731 Invalidate(FALSE);
732 m_bIsRubberBand = 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);
980 else
982 // save the graph as a pixel picture instead of a vector picture
983 // create dc to paint on
986 CString sErrormessage;
987 CWindowDC ddc(this);
988 CDC dc;
989 if (!dc.CreateCompatibleDC(&ddc))
991 CFormatMessageWrapper errorDetails;
992 if( errorDetails )
993 MessageBox( errorDetails, _T("Error"), MB_OK | MB_ICONINFORMATION );
995 return;
997 CRect rect;
998 rect = GetGraphRect();
999 rect.bottom = (LONG)(float(rect.Height()) * m_fZoomFactor);
1000 rect.right = (LONG)(float(rect.Width()) * m_fZoomFactor);
1001 BITMAPINFO bmi;
1002 HBITMAP hbm;
1003 LPBYTE pBits;
1004 // Initialize header to 0s.
1005 SecureZeroMemory(&bmi, sizeof(bmi));
1006 // Fill out the fields you care about.
1007 bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
1008 bmi.bmiHeader.biWidth = rect.Width();
1009 bmi.bmiHeader.biHeight = rect.Height();
1010 bmi.bmiHeader.biPlanes = 1;
1011 bmi.bmiHeader.biBitCount = 24;
1012 bmi.bmiHeader.biCompression = BI_RGB;
1014 // Create the surface.
1015 hbm = CreateDIBSection(ddc.m_hDC, &bmi, DIB_RGB_COLORS,(void **)&pBits, NULL, 0);
1016 if (hbm==0)
1018 CMessageBox::Show(m_hWnd, IDS_REVGRAPH_ERR_NOMEMORY, IDS_APPNAME, MB_ICONERROR);
1019 return;
1021 HBITMAP oldbm = (HBITMAP)dc.SelectObject(hbm);
1022 // paint the whole graph
1023 GraphicsDevice dev;
1024 dev.pDC = &dc;
1025 DrawGraph(dev, rect, 0, 0, true);
1026 // now use GDI+ to save the picture
1027 CLSID encoderClsid;
1029 Bitmap bitmap(hbm, NULL);
1030 if (bitmap.GetLastStatus()==Ok)
1032 // Get the CLSID of the encoder.
1033 int ret = 0;
1034 if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(_T(".png"))==0)
1035 ret = GetEncoderClsid(L"image/png", &encoderClsid);
1036 else if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(_T(".jpg"))==0)
1037 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1038 else if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(_T(".jpeg"))==0)
1039 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1040 else if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(_T(".bmp"))==0)
1041 ret = GetEncoderClsid(L"image/bmp", &encoderClsid);
1042 else if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(_T(".gif"))==0)
1043 ret = GetEncoderClsid(L"image/gif", &encoderClsid);
1044 else
1046 sSavePath += _T(".jpg");
1047 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1049 if (ret >= 0)
1051 CStringW tfile = CStringW(sSavePath);
1052 bitmap.Save(tfile, &encoderClsid, NULL);
1054 else
1056 sErrormessage.Format(IDS_REVGRAPH_ERR_NOENCODER, (LPCTSTR)CPathUtils::GetFileExtFromPath(sSavePath));
1059 else
1061 sErrormessage.LoadString(IDS_REVGRAPH_ERR_NOBITMAP);
1064 dc.SelectObject(oldbm);
1065 DeleteObject(hbm);
1066 dc.DeleteDC();
1067 if (!sErrormessage.IsEmpty())
1069 ::MessageBox(m_hWnd, sErrormessage, _T("TortoiseGit"), MB_ICONERROR);
1072 catch (CException * pE)
1074 TCHAR szErrorMsg[2048];
1075 pE->GetErrorMessage(szErrorMsg, 2048);
1076 pE->Delete();
1077 ::MessageBox(m_hWnd, szErrorMsg, _T("TortoiseGit"), MB_ICONERROR);
1083 BOOL CRevisionGraphWnd::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
1085 if (IsUpdateJobRunning())
1086 return __super::OnMouseWheel(nFlags, zDelta, pt);
1088 if (GetKeyState(VK_CONTROL)&0x8000)
1090 float newZoom = m_fZoomFactor * (zDelta < 0 ? ZOOM_STEP : 1.0f/ZOOM_STEP);
1091 DoZoom (max (MIN_ZOOM, min (MAX_ZOOM, newZoom)));
1093 else
1095 int orientation = GetKeyState(VK_SHIFT)&0x8000 ? SB_HORZ : SB_VERT;
1096 int pos = GetScrollPos(orientation);
1097 pos -= (zDelta);
1098 SetScrollPos(orientation, pos);
1099 Invalidate(FALSE);
1101 return __super::OnMouseWheel(nFlags, zDelta, pt);
1104 void CRevisionGraphWnd::OnMouseHWheel(UINT nFlags, short zDelta, CPoint pt)
1106 if (IsUpdateJobRunning())
1107 return __super::OnMouseHWheel(nFlags, zDelta, pt);
1109 int orientation = GetKeyState(VK_SHIFT)&0x8000 ? SB_VERT : SB_HORZ;
1110 int pos = GetScrollPos(orientation);
1111 pos -= (zDelta);
1112 SetScrollPos(orientation, pos);
1113 Invalidate(FALSE);
1115 return __super::OnMouseHWheel(nFlags, zDelta, pt);
1118 bool CRevisionGraphWnd::UpdateSelectedEntry (node clickedentry)
1120 if ((m_SelectedEntry1 == NULL)&&(clickedentry == NULL))
1121 return false;
1123 if (m_SelectedEntry1 == NULL)
1125 m_SelectedEntry1 = clickedentry;
1126 Invalidate(FALSE);
1128 if ((m_SelectedEntry2 == NULL)&&(clickedentry != m_SelectedEntry1))
1130 m_SelectedEntry1 = clickedentry;
1131 Invalidate(FALSE);
1133 if (m_SelectedEntry1 && m_SelectedEntry2)
1135 if ((m_SelectedEntry2 != clickedentry)&&(m_SelectedEntry1 != clickedentry))
1136 return false;
1138 if (m_SelectedEntry1 == NULL)
1139 return false;
1141 return true;
1144 void CRevisionGraphWnd::AppendMenu
1145 ( CMenu& popup
1146 , UINT title
1147 , UINT command
1148 , UINT flags)
1150 // separate different groups / section within the context menu
1152 if (popup.GetMenuItemCount() > 0)
1154 UINT lastCommand = popup.GetMenuItemID (popup.GetMenuItemCount()-1);
1155 if ((lastCommand & GROUP_MASK) != (command & GROUP_MASK))
1156 popup.AppendMenu(MF_SEPARATOR, NULL);
1159 // actually add the new item
1161 CString titleString;
1162 titleString.LoadString (title);
1163 popup.AppendMenu (MF_STRING | flags, command, titleString);
1166 void CRevisionGraphWnd::AddGitOps (CMenu& popup)
1168 bool bothPresent = (m_SelectedEntry2 && m_SelectedEntry1);
1170 AppendMenu (popup, IDS_REPOBROWSE_SHOWLOG, ID_SHOWLOG);
1172 if (m_SelectedEntry1 && (m_SelectedEntry2 == NULL))
1174 //AppendMenu (popup, IDS_REVGRAPH_POPUP_SWITCH, ID_SWITCH);
1175 AppendMenu(popup, IDS_REVGRAPH_POPUP_COMPAREHEADS, ID_COMPAREHEADS);
1176 AppendMenu(popup, IDS_REVGRAPH_POPUP_UNIDIFFHEADS, ID_UNIDIFFHEADS);
1179 if (bothPresent)
1181 AppendMenu (popup, IDS_REVGRAPH_POPUP_COMPAREREVS, ID_COMPAREREVS);
1182 AppendMenu (popup, IDS_REVGRAPH_POPUP_UNIDIFFREVS, ID_UNIDIFFREVS);
1187 void CRevisionGraphWnd::AddGraphOps (CMenu& /*popup*/, const CVisibleGraphNode * /*node*/)
1189 #if 0
1190 CSyncPointer<CGraphNodeStates> nodeStates (m_state.GetNodeStates());
1192 if (node == NULL)
1194 DWORD state = nodeStates->GetCombinedFlags();
1195 if (state != 0)
1197 if (state & CGraphNodeStates::COLLAPSED_ALL)
1198 AppendMenu (popup, IDS_REVGRAPH_POPUP_EXPAND_ALL, ID_EXPAND_ALL);
1200 if (state & CGraphNodeStates::SPLIT_ALL)
1201 AppendMenu (popup, IDS_REVGRAPH_POPUP_JOIN_ALL, ID_JOIN_ALL);
1204 else
1206 DWORD state = nodeStates->GetFlags (node);
1208 if (node->GetSource() || (state & CGraphNodeStates::COLLAPSED_ABOVE))
1209 AppendMenu ( popup
1210 , (state & CGraphNodeStates::COLLAPSED_ABOVE)
1211 ? IDS_REVGRAPH_POPUP_EXPAND_ABOVE
1212 : IDS_REVGRAPH_POPUP_COLLAPSE_ABOVE
1213 , ID_GRAPH_EXPANDCOLLAPSE_ABOVE);
1215 if (node->GetFirstCopyTarget() || (state & CGraphNodeStates::COLLAPSED_RIGHT))
1216 AppendMenu ( popup
1217 , (state & CGraphNodeStates::COLLAPSED_RIGHT)
1218 ? IDS_REVGRAPH_POPUP_EXPAND_RIGHT
1219 : IDS_REVGRAPH_POPUP_COLLAPSE_RIGHT
1220 , ID_GRAPH_EXPANDCOLLAPSE_RIGHT);
1222 if (node->GetNext() || (state & CGraphNodeStates::COLLAPSED_BELOW))
1223 AppendMenu ( popup
1224 , (state & CGraphNodeStates::COLLAPSED_BELOW)
1225 ? IDS_REVGRAPH_POPUP_EXPAND_BELOW
1226 : IDS_REVGRAPH_POPUP_COLLAPSE_BELOW
1227 , ID_GRAPH_EXPANDCOLLAPSE_BELOW);
1229 if (node->GetSource() || (state & CGraphNodeStates::SPLIT_ABOVE))
1230 AppendMenu ( popup
1231 , (state & CGraphNodeStates::SPLIT_ABOVE)
1232 ? IDS_REVGRAPH_POPUP_JOIN_ABOVE
1233 : IDS_REVGRAPH_POPUP_SPLIT_ABOVE
1234 , ID_GRAPH_SPLITJOIN_ABOVE);
1236 if (node->GetFirstCopyTarget() || (state & CGraphNodeStates::SPLIT_RIGHT))
1237 AppendMenu ( popup
1238 , (state & CGraphNodeStates::SPLIT_RIGHT)
1239 ? IDS_REVGRAPH_POPUP_JOIN_RIGHT
1240 : IDS_REVGRAPH_POPUP_SPLIT_RIGHT
1241 , ID_GRAPH_SPLITJOIN_RIGHT);
1243 if (node->GetNext() || (state & CGraphNodeStates::SPLIT_BELOW))
1244 AppendMenu ( popup
1245 , (state & CGraphNodeStates::SPLIT_BELOW)
1246 ? IDS_REVGRAPH_POPUP_JOIN_BELOW
1247 : IDS_REVGRAPH_POPUP_SPLIT_BELOW
1248 , ID_GRAPH_SPLITJOIN_BELOW);
1250 #endif
1253 CString CRevisionGraphWnd::GetSelectedURL() const
1255 #if 0
1256 if (m_SelectedEntry1 == NULL)
1257 return CString();
1259 CString URL = m_state.GetRepositoryRoot()
1260 + CUnicodeUtils::GetUnicode (m_SelectedEntry1->GetPath().GetPath().c_str());
1261 URL = CUnicodeUtils::GetUnicode(CPathUtils::PathEscape(CUnicodeUtils::GetUTF8(URL)));
1263 return URL;
1264 #endif
1265 return CString();
1268 CString CRevisionGraphWnd::GetWCURL() const
1270 #if 0
1271 CTGitPath path (m_sPath);
1272 if (path.IsUrl())
1273 return CString();
1275 SVNInfo info;
1276 const SVNInfoData * status
1277 = info.GetFirstFileInfo (path, SVNRev(), SVNRev());
1279 return status == NULL ? CString() : status->url;
1280 #endif
1281 return CString();
1284 void CRevisionGraphWnd::DoShowLog()
1287 if(m_SelectedEntry1 == NULL)
1288 return;
1290 CString sCmd;
1292 if(m_SelectedEntry2 != NULL)
1293 sCmd.Format(_T("/command:log %s /endrev:%s /startrev:%s"),
1294 this->m_sPath.IsEmpty() ? _T("") : (_T("/path:\"") + this->m_sPath + _T("\"")),
1295 this->m_logEntries[m_SelectedEntry1->index()].ToString(),
1296 this->m_logEntries[m_SelectedEntry2->index()].ToString());
1297 else
1298 sCmd.Format(_T("/command:log %s /endrev:%s"),
1299 this->m_sPath.IsEmpty() ? _T("") : (_T("/path:\"") + this->m_sPath + _T("\"")),
1300 this->m_logEntries[m_SelectedEntry1->index()].ToString());
1302 CAppUtils::RunTortoiseProc(sCmd);
1306 void CRevisionGraphWnd::DoCheckForModification()
1308 CChangedDlg dlg;
1309 dlg.m_pathList = CTGitPathList (CTGitPath (m_sPath));
1310 dlg.DoModal();
1313 void CRevisionGraphWnd::DoMergeTo()
1315 #if 0
1316 CString URL = GetSelectedURL();
1317 CString path = m_sPath;
1318 CBrowseFolder folderBrowser;
1319 folderBrowser.SetInfo(CString(MAKEINTRESOURCE(IDS_LOG_MERGETO)));
1320 if (folderBrowser.Show(GetSafeHwnd(), path, path) == CBrowseFolder::OK)
1322 CSVNProgressDlg dlg;
1323 dlg.SetCommand(CSVNProgressDlg::SVNProgress_Merge);
1324 dlg.SetPathList(CTGitPathList(CTGitPath(path)));
1325 dlg.SetUrl(URL);
1326 dlg.SetSecondUrl(URL);
1327 SVNRevRangeArray revarray;
1328 revarray.AddRevRange (m_SelectedEntry1->GetRevision()-1, svn_revnum_t(m_SelectedEntry1->GetRevision()));
1329 dlg.SetRevisionRanges(revarray);
1330 dlg.DoModal();
1332 #endif
1335 void CRevisionGraphWnd::DoUpdate()
1337 #if 0
1338 CSVNProgressDlg progDlg;
1339 progDlg.SetCommand (CSVNProgressDlg::SVNProgress_Update);
1340 progDlg.SetOptions (0); // don't ignore externals
1341 progDlg.SetPathList (CTGitPathList (CTGitPath (m_sPath)));
1342 progDlg.SetRevision (m_SelectedEntry1->GetRevision());
1343 progDlg.SetDepth();
1344 progDlg.DoModal();
1346 if (m_state.GetFetchedWCState())
1347 m_parent->UpdateFullHistory();
1348 #endif
1351 void CRevisionGraphWnd::DoSwitch()
1353 #if 0
1354 CSVNProgressDlg progDlg;
1355 progDlg.SetCommand (CSVNProgressDlg::SVNProgress_Switch);
1356 progDlg.SetPathList (CTGitPathList (CTGitPath (m_sPath)));
1357 progDlg.SetUrl (GetSelectedURL());
1358 progDlg.SetRevision (m_SelectedEntry1->GetRevision());
1359 progDlg.DoModal();
1361 if (m_state.GetFetchedWCState())
1362 m_parent->UpdateFullHistory();
1363 #endif
1366 void CRevisionGraphWnd::DoSwitchToHead()
1368 #if 0
1369 CSVNProgressDlg progDlg;
1370 progDlg.SetCommand (CSVNProgressDlg::SVNProgress_Switch);
1371 progDlg.SetPathList (CTGitPathList (CTGitPath (m_sPath)));
1372 progDlg.SetUrl (GetSelectedURL());
1373 progDlg.SetRevision (SVNRev::REV_HEAD);
1374 progDlg.SetPegRevision (m_SelectedEntry1->GetRevision());
1375 progDlg.DoModal();
1377 if (m_state.GetFetchedWCState())
1378 m_parent->UpdateFullHistory();
1379 #endif
1382 void CRevisionGraphWnd::DoBrowseRepo()
1384 #if 0
1385 CString sCmd;
1386 sCmd.Format(_T("/command:repobrowser /path:\"%s\" /rev:%d"),
1387 (LPCTSTR)GetSelectedURL(), m_SelectedEntry1->GetRevision());
1389 CAppUtils::RunTortoiseProc(sCmd);
1390 #endif
1393 void CRevisionGraphWnd::ResetNodeFlags (DWORD /*flags*/)
1395 // m_state.GetNodeStates()->ResetFlags (flags);
1396 // m_parent->StartWorkerThread();
1399 void CRevisionGraphWnd::ToggleNodeFlag (const CVisibleGraphNode * /*node*/, DWORD /*flag*/)
1401 #if 0
1402 CSyncPointer<CGraphNodeStates> nodeStates (m_state.GetNodeStates());
1404 if (nodeStates->GetFlags (node) & flag)
1405 nodeStates->ResetFlags (node, flag);
1406 else
1407 nodeStates->SetFlags (node, flag);
1409 m_parent->StartWorkerThread();
1410 #endif
1413 void CRevisionGraphWnd::DoCopyUrl()
1415 CStringUtils::WriteAsciiStringToClipboard(GetSelectedURL(), m_hWnd);
1418 void CRevisionGraphWnd::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
1420 if (IsUpdateJobRunning())
1421 return;
1423 CPoint clientpoint = point;
1424 this->ScreenToClient(&clientpoint);
1426 node nodeIndex = GetHitNode (clientpoint);
1428 if ( !UpdateSelectedEntry (nodeIndex))
1430 return;
1433 CMenu popup;
1434 if (!popup.CreatePopupMenu())
1435 return;
1437 AddGitOps (popup);
1438 // AddGraphOps (popup, clickedentry);
1440 // if the context menu is invoked through the keyboard, we have to use
1441 // a calculated position on where to anchor the menu on
1442 if ((point.x == -1) && (point.y == -1))
1444 CRect rect = GetWindowRect();
1445 point = rect.CenterPoint();
1448 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RIGHTBUTTON, point.x, point.y, this, 0);
1449 switch (cmd)
1451 case ID_COMPAREREVS:
1452 if (m_SelectedEntry1 != NULL)
1453 CompareRevs(false);
1454 break;
1455 case ID_UNIDIFFREVS:
1456 if (m_SelectedEntry1 != NULL)
1457 UnifiedDiffRevs(false);
1458 break;
1459 case ID_UNIDIFFHEADS:
1460 if (m_SelectedEntry1 != NULL)
1461 UnifiedDiffRevs(true);
1462 break;
1463 case ID_SHOWLOG:
1464 DoShowLog();
1465 break;
1466 case ID_SWITCH:
1467 DoSwitch();
1468 break;
1469 case ID_COMPAREHEADS:
1470 if (m_SelectedEntry1 != NULL)
1471 CompareRevs(true);
1472 break;
1475 #if 0
1476 case ID_COMPAREREVS:
1477 if (m_SelectedEntry1 != NULL)
1478 CompareRevs(false);
1479 break;
1480 case ID_UNIDIFFREVS:
1481 if (m_SelectedEntry1 != NULL)
1482 UnifiedDiffRevs(false);
1483 break;
1484 case ID_UNIDIFFHEADS:
1485 if (m_SelectedEntry1 != NULL)
1486 UnifiedDiffRevs(true);
1487 break;
1488 case ID_SHOWLOG:
1489 DoShowLog();
1490 break;
1491 case ID_CFM:
1492 DoCheckForModification();
1493 break;
1494 case ID_MERGETO:
1495 DoMergeTo();
1496 break;
1497 case ID_UPDATE:
1498 DoUpdate();
1499 break;
1500 case ID_SWITCHTOHEAD:
1501 DoSwitchToHead();
1502 break;
1503 case ID_SWITCH:
1504 DoSwitch();
1505 break;
1506 case ID_COPYURL:
1507 DoCopyUrl();
1508 break;
1509 case ID_BROWSEREPO:
1510 DoBrowseRepo();
1511 break;
1512 case ID_EXPAND_ALL:
1513 ResetNodeFlags (CGraphNodeStates::COLLAPSED_ALL);
1514 break;
1515 case ID_JOIN_ALL:
1516 ResetNodeFlags (CGraphNodeStates::SPLIT_ALL);
1517 break;
1518 case ID_GRAPH_EXPANDCOLLAPSE_ABOVE:
1519 ToggleNodeFlag (clickedentry, CGraphNodeStates::COLLAPSED_ABOVE);
1520 break;
1521 case ID_GRAPH_EXPANDCOLLAPSE_RIGHT:
1522 ToggleNodeFlag (clickedentry, CGraphNodeStates::COLLAPSED_RIGHT);
1523 break;
1524 case ID_GRAPH_EXPANDCOLLAPSE_BELOW:
1525 ToggleNodeFlag (clickedentry, CGraphNodeStates::COLLAPSED_BELOW);
1526 break;
1527 case ID_GRAPH_SPLITJOIN_ABOVE:
1528 ToggleNodeFlag (clickedentry, CGraphNodeStates::SPLIT_ABOVE);
1529 break;
1530 case ID_GRAPH_SPLITJOIN_RIGHT:
1531 ToggleNodeFlag (clickedentry, CGraphNodeStates::SPLIT_RIGHT);
1532 break;
1533 case ID_GRAPH_SPLITJOIN_BELOW:
1534 ToggleNodeFlag (clickedentry, CGraphNodeStates::SPLIT_BELOW);
1535 break;
1536 #endif
1541 void CRevisionGraphWnd::OnMouseMove(UINT nFlags, CPoint point)
1544 if (IsUpdateJobRunning())
1546 return __super::OnMouseMove(nFlags, point);
1548 if (!m_bIsRubberBand)
1550 if (m_bShowOverview && (m_OverviewRect.PtInRect(point))&&(nFlags & MK_LBUTTON))
1552 // scrolling
1553 CRect viewRect = GetViewRect();
1554 int x = (int)((point.x-m_OverviewRect.left - (m_OverviewPosRect.Width()/2)) / m_previewZoom * m_fZoomFactor);
1555 int y = (int)((point.y - m_OverviewRect.top - (m_OverviewPosRect.Height()/2)) / m_previewZoom * m_fZoomFactor);
1556 x = max(0, x);
1557 y = max(0, y);
1558 SetScrollbars(y, x);
1559 Invalidate(FALSE);
1560 return __super::OnMouseMove(nFlags, point);
1562 else
1564 // update screen if we hover over a different
1565 // node than during the last redraw
1567 CPoint clientPoint = point;
1568 GetCursorPos (&clientPoint);
1569 ScreenToClient (&clientPoint);
1571 #if 0
1572 const CRevisionGraphState::SVisibleGlyph* hitGlyph
1573 = GetHitGlyph (clientPoint);
1574 const CFullGraphNode* glyphNode
1575 = hitGlyph ? hitGlyph->node->GetBase() : NULL;
1577 const CFullGraphNode* hoverNode = NULL;
1578 if (m_hoverIndex != NO_INDEX)
1580 CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());
1581 if (m_hoverIndex < nodeList->GetCount())
1582 hoverNode = nodeList->GetNode (m_hoverIndex).node->GetBase();
1585 //bool onHoverNodeGlyph = (hoverNode != NULL) && (glyphNode == hoverNode);
1586 if ( !m_hoverIndex
1587 && ( (m_hoverIndex != GetHitNode (clientPoint))))
1589 m_showHoverGlyphs = false;
1591 KillTimer (GLYPH_HOVER_EVENT);
1592 SetTimer (GLYPH_HOVER_EVENT, GLYPH_HOVER_DELAY, NULL);
1594 Invalidate(FALSE);
1596 #endif
1597 return __super::OnMouseMove(nFlags, point);
1600 #if 0
1601 if ((abs(m_ptRubberStart.x - point.x) < 2)&&(abs(m_ptRubberStart.y - point.y) < 2))
1603 return __super::OnMouseMove(nFlags, point);
1606 SetCapture();
1608 if ((m_ptRubberEnd.x != 0)||(m_ptRubberEnd.y != 0))
1609 DrawRubberBand();
1610 m_ptRubberEnd = point;
1611 CRect rect = GetClientRect();
1612 m_ptRubberEnd.x = max(m_ptRubberEnd.x, rect.left);
1613 m_ptRubberEnd.x = min(m_ptRubberEnd.x, rect.right);
1614 m_ptRubberEnd.y = max(m_ptRubberEnd.y, rect.top);
1615 m_ptRubberEnd.y = min(m_ptRubberEnd.y, rect.bottom);
1616 DrawRubberBand();
1617 #endif
1618 __super::OnMouseMove(nFlags, point);
1621 BOOL CRevisionGraphWnd::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
1623 CRect viewRect = GetViewRect();
1625 LPTSTR cursorID = IDC_ARROW;
1626 HINSTANCE resourceHandle = NULL;
1628 if ((nHitTest == HTCLIENT)&&(pWnd == this)&&(viewRect.Width())&&(viewRect.Height())&&(message))
1630 POINT pt;
1631 if (GetCursorPos(&pt))
1633 ScreenToClient(&pt);
1634 if (m_OverviewPosRect.PtInRect(pt))
1636 resourceHandle = AfxGetResourceHandle();
1637 cursorID = GetKeyState(VK_LBUTTON) & 0x8000
1638 ? MAKEINTRESOURCE(IDC_PANCURDOWN)
1639 : MAKEINTRESOURCE(IDC_PANCUR);
1644 HCURSOR hCur = LoadCursor(resourceHandle, MAKEINTRESOURCE(cursorID));
1645 if (GetCursor() != hCur)
1646 SetCursor (hCur);
1648 return TRUE;
1651 void CRevisionGraphWnd::OnTimer (UINT_PTR nIDEvent)
1653 if (nIDEvent == GLYPH_HOVER_EVENT)
1655 KillTimer (GLYPH_HOVER_EVENT);
1657 m_showHoverGlyphs = true;
1658 Invalidate (FALSE);
1660 else
1662 __super::OnTimer (nIDEvent);
1666 LRESULT CRevisionGraphWnd::OnWorkerThreadDone(WPARAM, LPARAM)
1668 // handle potential race condition between PostMessage and leaving job:
1669 // the background job may not have exited, yet
1671 if (updateJob.get())
1672 updateJob->GetResult();
1674 InitView();
1675 BuildPreview();
1677 SCROLLINFO sinfo = {0};
1678 sinfo.cbSize = sizeof(SCROLLINFO);
1679 GetScrollInfo(SB_HORZ, &sinfo);
1680 sinfo.nPos = sinfo.nMax;
1681 SetScrollInfo(SB_HORZ, &sinfo);
1683 Invalidate(FALSE);
1685 #if 0
1686 SVN svn;
1687 LogCache::CRepositoryInfo& cachedProperties
1688 = svn.GetLogCachePool()->GetRepositoryInfo();
1690 CSyncPointer<const CFullHistory> fullHistoy (m_state.GetFullHistory());
1691 if (fullHistoy.get() != NULL)
1693 SetDlgTitle (cachedProperties.IsOffline
1694 ( fullHistoy->GetRepositoryUUID()
1695 , fullHistoy->GetRepositoryRoot()
1696 , false));
1699 if (m_parent && !m_parent->GetOutputFile().IsEmpty())
1701 // save the graph to the output file and exit
1702 SaveGraphAs(m_parent->GetOutputFile());
1703 PostQuitMessage(0);
1705 #endif
1706 return 0;
1709 void CRevisionGraphWnd::SetDlgTitle (bool /*offline*/)
1711 #if 0
1712 if (m_sTitle.IsEmpty())
1713 GetParent()->GetWindowText(m_sTitle);
1715 CString newTitle;
1716 if (offline)
1717 newTitle.Format (IDS_REVGRAPH_DLGTITLEOFFLINE, (LPCTSTR)m_sTitle);
1718 else
1719 newTitle = m_sTitle;
1721 CAppUtils::SetWindowTitle(GetParent()->GetSafeHwnd(), m_sPath, newTitle);
1722 #endif