OSDN Git Service

BrowseRefs: Added option to delete branch or tag.
[tortoisegit/TortoiseGitJp.git] / src / TortoiseProc / RevisionGraph / RevisionGraphWnd.cpp
1 // TortoiseSVN - a Windows shell extension for easy version control\r
2 \r
3 // Copyright (C) 2003-2009 - TortoiseSVN\r
4 \r
5 // This program is free software; you can redistribute it and/or\r
6 // modify it under the terms of the GNU General Public License\r
7 // as published by the Free Software Foundation; either version 2\r
8 // of the License, or (at your option) any later version.\r
9 \r
10 // This program is distributed in the hope that it will be useful,\r
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of\r
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
13 // GNU General Public License for more details.\r
14 \r
15 // You should have received a copy of the GNU General Public License\r
16 // along with this program; if not, write to the Free Software Foundation,\r
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\r
18 //\r
19 #include "stdafx.h"\r
20 #include "TortoiseProc.h"\r
21 #include "Revisiongraphwnd.h"\r
22 #include "MessageBox.h"\r
23 #include "SVN.h"\r
24 #include "AppUtils.h"\r
25 #include "PathUtils.h"\r
26 #include "TempFile.h"\r
27 #include "UnicodeUtils.h"\r
28 #include "TSVNPath.h"\r
29 #include "SVNInfo.h"\r
30 #include "SVNDiff.h"\r
31 #include "RevisionGraphDlg.h"\r
32 #include "CachedLogInfo.h"\r
33 #include "RevisionIndex.h"\r
34 #include "RepositoryInfo.h"\r
35 #include "BrowseFolder.h"\r
36 #include "SVNProgressDlg.h"\r
37 #include "ChangedDlg.h"\r
38 #include "RevisionGraph/StandardLayout.h"\r
39 #include "RevisionGraph/UpsideDownLayout.h"\r
40 \r
41 #ifdef _DEBUG\r
42 #define new DEBUG_NEW\r
43 #undef THIS_FILE\r
44 static char THIS_FILE[] = __FILE__;\r
45 #endif\r
46 \r
47 using namespace Gdiplus;\r
48 \r
49 enum RevisionGraphContextMenuCommands\r
50 {\r
51         // needs to start with 1, since 0 is the return value if *nothing* is clicked on in the context menu\r
52     GROUP_MASK = 0xff00,\r
53         ID_SHOWLOG = 1,\r
54     ID_CFM = 2,\r
55     ID_BROWSEREPO,\r
56         ID_COMPAREREVS = 0x100,\r
57         ID_COMPAREHEADS,\r
58         ID_UNIDIFFREVS,\r
59         ID_UNIDIFFHEADS,\r
60         ID_MERGETO = 0x300,\r
61     ID_UPDATE,\r
62     ID_SWITCHTOHEAD,\r
63     ID_SWITCH,\r
64     ID_EXPAND_ALL = 0x400,\r
65     ID_JOIN_ALL,\r
66     ID_GRAPH_EXPANDCOLLAPSE_ABOVE = 0x500,\r
67     ID_GRAPH_EXPANDCOLLAPSE_RIGHT,\r
68     ID_GRAPH_EXPANDCOLLAPSE_BELOW,\r
69     ID_GRAPH_SPLITJOIN_ABOVE,\r
70     ID_GRAPH_SPLITJOIN_RIGHT,\r
71     ID_GRAPH_SPLITJOIN_BELOW\r
72 };\r
73 \r
74 CRevisionGraphWnd::CRevisionGraphWnd()\r
75         : CWnd()\r
76         , m_SelectedEntry1(NULL)\r
77         , m_SelectedEntry2(NULL)\r
78         , m_bThreadRunning(TRUE)\r
79     , m_pProgress(NULL)\r
80         , m_pDlgTip(NULL)\r
81         , m_nFontSize(12)\r
82     , m_bTweakTrunkColors(true)\r
83     , m_bTweakTagsColors(true)\r
84         , m_fZoomFactor(1.0)\r
85         , m_ptRubberEnd(0,0)\r
86         , m_ptRubberStart(0,0)\r
87         , m_bShowOverview(false)\r
88     , m_parent (NULL)\r
89     , m_hoverIndex ((index_t)NO_INDEX)\r
90     , m_hoverGlyphs (0)\r
91     , m_tooltipIndex ((index_t)NO_INDEX)\r
92     , m_showHoverGlyphs (false)\r
93 {\r
94         memset(&m_lfBaseFont, 0, sizeof(LOGFONT));      \r
95         for (int i=0; i<MAXFONTS; i++)\r
96         {\r
97                 m_apFonts[i] = NULL;\r
98         }\r
99 \r
100         WNDCLASS wndcls;\r
101         HINSTANCE hInst = AfxGetInstanceHandle();\r
102 #define REVGRAPH_CLASSNAME _T("Revgraph_windowclass")\r
103         if (!(::GetClassInfo(hInst, REVGRAPH_CLASSNAME, &wndcls)))\r
104         {\r
105                 // otherwise we need to register a new class\r
106                 wndcls.style            = CS_DBLCLKS | CS_OWNDC;\r
107                 wndcls.lpfnWndProc      = ::DefWindowProc;\r
108                 wndcls.cbClsExtra       = wndcls.cbWndExtra = 0;\r
109                 wndcls.hInstance        = hInst;\r
110                 wndcls.hIcon            = NULL;\r
111                 wndcls.hCursor          = AfxGetApp()->LoadStandardCursor(IDC_ARROW);\r
112                 wndcls.hbrBackground    = (HBRUSH) (COLOR_WINDOW + 1);\r
113                 wndcls.lpszMenuName     = NULL;\r
114                 wndcls.lpszClassName    = REVGRAPH_CLASSNAME;\r
115 \r
116                 RegisterClass(&wndcls);\r
117         }\r
118 \r
119         m_bTweakTrunkColors = CRegDWORD(_T("Software\\TortoiseSVN\\RevisionGraph\\TweakTrunkColors"), TRUE) != FALSE;\r
120         m_bTweakTagsColors = CRegDWORD(_T("Software\\TortoiseSVN\\RevisionGraph\\TweakTagsColors"), TRUE) != FALSE;\r
121 }\r
122 \r
123 CRevisionGraphWnd::~CRevisionGraphWnd()\r
124 {\r
125         for (int i=0; i<MAXFONTS; i++)\r
126         {\r
127                 if (m_apFonts[i] != NULL)\r
128                 {\r
129                         m_apFonts[i]->DeleteObject();\r
130                         delete m_apFonts[i];\r
131                 }\r
132                 m_apFonts[i] = NULL;\r
133         }\r
134         if (m_pDlgTip)\r
135                 delete m_pDlgTip;\r
136 }\r
137 \r
138 void CRevisionGraphWnd::DoDataExchange(CDataExchange* pDX)\r
139 {\r
140         CWnd::DoDataExchange(pDX);\r
141 }\r
142 \r
143 \r
144 BEGIN_MESSAGE_MAP(CRevisionGraphWnd, CWnd)\r
145         ON_WM_PAINT()\r
146         ON_WM_ERASEBKGND()\r
147         ON_WM_HSCROLL()\r
148         ON_WM_VSCROLL()\r
149         ON_WM_SIZE()\r
150         ON_WM_LBUTTONDOWN()\r
151         ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipNotify)\r
152         ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipNotify)\r
153         ON_WM_MOUSEWHEEL()\r
154         ON_WM_CONTEXTMENU()\r
155         ON_WM_MOUSEMOVE()\r
156         ON_WM_LBUTTONUP()\r
157         ON_WM_SETCURSOR()\r
158     ON_WM_TIMER()\r
159     ON_MESSAGE(WM_WORKERTHREADDONE,OnWorkerThreadDone)\r
160 END_MESSAGE_MAP()\r
161 \r
162 void CRevisionGraphWnd::Init(CWnd * pParent, LPRECT rect)\r
163 {\r
164         WNDCLASS wndcls;\r
165         HINSTANCE hInst = AfxGetInstanceHandle();\r
166 #define REVGRAPH_CLASSNAME _T("Revgraph_windowclass")\r
167         if (!(::GetClassInfo(hInst, REVGRAPH_CLASSNAME, &wndcls)))\r
168         {\r
169                 // otherwise we need to register a new class\r
170                 wndcls.style            = CS_DBLCLKS | CS_OWNDC;\r
171                 wndcls.lpfnWndProc      = ::DefWindowProc;\r
172                 wndcls.cbClsExtra       = wndcls.cbWndExtra = 0;\r
173                 wndcls.hInstance        = hInst;\r
174                 wndcls.hIcon            = NULL;\r
175                 wndcls.hCursor          = AfxGetApp()->LoadStandardCursor(IDC_ARROW);\r
176                 wndcls.hbrBackground    = (HBRUSH) (COLOR_WINDOW + 1);\r
177                 wndcls.lpszMenuName     = NULL;\r
178                 wndcls.lpszClassName    = REVGRAPH_CLASSNAME;\r
179 \r
180                 RegisterClass(&wndcls);\r
181         }\r
182 \r
183         if (!IsWindow(m_hWnd))\r
184                 CreateEx(WS_EX_CLIENTEDGE, REVGRAPH_CLASSNAME, _T("RevGraph"), WS_CHILD|WS_VISIBLE|WS_TABSTOP, *rect, pParent, 0);\r
185         m_pDlgTip = new CToolTipCtrl;\r
186         if(!m_pDlgTip->Create(this))\r
187         {\r
188                 TRACE("Unable to add tooltip!\n");\r
189         }\r
190         EnableToolTips();\r
191 \r
192         memset(&m_lfBaseFont, 0, sizeof(m_lfBaseFont));\r
193         m_lfBaseFont.lfHeight = 0;\r
194         m_lfBaseFont.lfWeight = FW_NORMAL;\r
195         m_lfBaseFont.lfItalic = FALSE;\r
196         m_lfBaseFont.lfCharSet = DEFAULT_CHARSET;\r
197         m_lfBaseFont.lfOutPrecision = OUT_DEFAULT_PRECIS;\r
198         m_lfBaseFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;\r
199         m_lfBaseFont.lfQuality = DEFAULT_QUALITY;\r
200         m_lfBaseFont.lfPitchAndFamily = DEFAULT_PITCH;\r
201 \r
202         m_dwTicks = GetTickCount();\r
203 \r
204     m_parent = dynamic_cast<CRevisionGraphDlg*>(pParent);\r
205 }\r
206 \r
207 CPoint CRevisionGraphWnd::GetLogCoordinates (CPoint point) const\r
208 {\r
209     // translate point into logical coordinates\r
210 \r
211     int nVScrollPos = GetScrollPos(SB_VERT);\r
212     int nHScrollPos = GetScrollPos(SB_HORZ);\r
213 \r
214     return CPoint ( (int)((point.x + nHScrollPos) / m_fZoomFactor)\r
215                   , (int)((point.y + nVScrollPos) / m_fZoomFactor));\r
216 }\r
217 \r
218 index_t CRevisionGraphWnd::GetHitNode (CPoint point, CSize border) const\r
219 {\r
220     // any nodes at all?\r
221 \r
222     CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());\r
223     if (!nodeList)\r
224         return index_t(NO_INDEX);\r
225 \r
226     // search the nodes for one at that grid position\r
227 \r
228     return nodeList->GetAt (GetLogCoordinates (point), border);\r
229 }\r
230 \r
231 DWORD CRevisionGraphWnd::GetHoverGlyphs (CPoint point) const\r
232 {\r
233     // if there is no layout, there will be no nodes,\r
234     // hence, no glyphs\r
235 \r
236     CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());\r
237     if (!nodeList)\r
238         return 0;\r
239 \r
240     // get node at point or node that is close enough \r
241     // so that point may hit a glyph area\r
242 \r
243     index_t nodeIndex = GetHitNode(point);\r
244     if (nodeIndex == NO_INDEX)\r
245         nodeIndex = GetHitNode(point, CSize (GLYPH_SIZE, GLYPH_SIZE / 2));\r
246 \r
247     if (nodeIndex >= nodeList->GetCount())\r
248         return 0;\r
249 \r
250     ILayoutNodeList::SNode node = nodeList->GetNode (nodeIndex);\r
251     const CVisibleGraphNode* base = node.node;\r
252 \r
253     // what glyphs should be shown depending on position of point\r
254     // relative to the node rect?\r
255 \r
256     CPoint logCoordinates = GetLogCoordinates (point);\r
257     CRect r = node.rect;\r
258     CPoint center = r.CenterPoint();\r
259 \r
260     CRect rightGlyphArea ( r.right - GLYPH_SIZE, center.y - GLYPH_SIZE / 2\r
261                          , r.right + GLYPH_SIZE, center.y + GLYPH_SIZE / 2);\r
262     CRect topGlyphArea ( center.x - GLYPH_SIZE, r.top - GLYPH_SIZE / 2\r
263                        , center.x + GLYPH_SIZE, r.top + GLYPH_SIZE / 2);\r
264     CRect bottomGlyphArea ( center.x - GLYPH_SIZE, r.bottom - GLYPH_SIZE / 2\r
265                           , center.x + GLYPH_SIZE, r.bottom + GLYPH_SIZE / 2);\r
266 \r
267     bool upsideDown \r
268         = m_state.GetOptions()->GetOption<CUpsideDownLayout>()->IsActive();\r
269 \r
270     if (upsideDown)\r
271     {\r
272         std::swap (topGlyphArea.top, bottomGlyphArea.top);\r
273         std::swap (topGlyphArea.bottom, bottomGlyphArea.bottom);\r
274     }\r
275 \r
276     DWORD result = 0;\r
277     if (rightGlyphArea.PtInRect (logCoordinates))\r
278         result = base->GetFirstCopyTarget() != NULL\r
279                ? CGraphNodeStates::COLLAPSED_RIGHT | CGraphNodeStates::SPLIT_RIGHT\r
280                : 0;\r
281 \r
282     if (topGlyphArea.PtInRect (logCoordinates))\r
283         result = base->GetSource() != NULL\r
284                ? CGraphNodeStates::COLLAPSED_ABOVE | CGraphNodeStates::SPLIT_ABOVE\r
285                : 0;\r
286 \r
287     if (bottomGlyphArea.PtInRect (logCoordinates))\r
288         result = base->GetNext() != NULL\r
289                ? CGraphNodeStates::COLLAPSED_BELOW | CGraphNodeStates::SPLIT_BELOW\r
290                : 0;\r
291 \r
292     // if some nodes have already been split, don't allow collapsing etc.\r
293 \r
294     CSyncPointer<const CGraphNodeStates> nodeStates (m_state.GetNodeStates());\r
295     if (result & nodeStates->GetFlags (base))\r
296         result = 0;\r
297 \r
298     return result;\r
299 }\r
300     \r
301 const CRevisionGraphState::SVisibleGlyph* CRevisionGraphWnd::GetHitGlyph (CPoint point) const\r
302 {\r
303     float glyphSize = GLYPH_SIZE * m_fZoomFactor;\r
304 \r
305     CSyncPointer<const CRevisionGraphState::TVisibleGlyphs> \r
306         visibleGlyphs (m_state.GetVisibleGlyphs());\r
307 \r
308     for (size_t i = 0, count = visibleGlyphs->size(); i < count; ++i)\r
309     {\r
310         const CRevisionGraphState::SVisibleGlyph* entry = &(*visibleGlyphs)[i];\r
311 \r
312         float xRel = point.x - entry->leftTop.X;\r
313         float yRel = point.y - entry->leftTop.Y;\r
314 \r
315         if (   (xRel >= 0) && (xRel < glyphSize)\r
316             && (yRel >= 0) && (yRel < glyphSize))\r
317         {\r
318             return entry;\r
319         }\r
320     }\r
321 \r
322     return NULL;\r
323 }\r
324 \r
325 void CRevisionGraphWnd::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)\r
326 {\r
327         SCROLLINFO sinfo = {0};\r
328         sinfo.cbSize = sizeof(SCROLLINFO);\r
329         GetScrollInfo(SB_HORZ, &sinfo);\r
330 \r
331         // Determine the new position of scroll box.\r
332         switch (nSBCode)\r
333         {\r
334         case SB_LEFT:      // Scroll to far left.\r
335                 sinfo.nPos = sinfo.nMin;\r
336                 break;\r
337         case SB_RIGHT:      // Scroll to far right.\r
338                 sinfo.nPos = sinfo.nMax;\r
339                 break;\r
340         case SB_ENDSCROLL:   // End scroll.\r
341                 break;\r
342         case SB_LINELEFT:      // Scroll left.\r
343                 if (sinfo.nPos > sinfo.nMin)\r
344                         sinfo.nPos--;\r
345                 break;\r
346         case SB_LINERIGHT:   // Scroll right.\r
347                 if (sinfo.nPos < sinfo.nMax)\r
348                         sinfo.nPos++;\r
349                 break;\r
350         case SB_PAGELEFT:    // Scroll one page left.\r
351                 {\r
352                         if (sinfo.nPos > sinfo.nMin)\r
353                                 sinfo.nPos = max(sinfo.nMin, sinfo.nPos - (int) sinfo.nPage);\r
354                 }\r
355                 break;\r
356         case SB_PAGERIGHT:      // Scroll one page right.\r
357                 {\r
358                         if (sinfo.nPos < sinfo.nMax)\r
359                                 sinfo.nPos = min(sinfo.nMax, sinfo.nPos + (int) sinfo.nPage);\r
360                 }\r
361                 break;\r
362         case SB_THUMBPOSITION: // Scroll to absolute position. nPos is the position\r
363                 sinfo.nPos = sinfo.nTrackPos;      // of the scroll box at the end of the drag operation.\r
364                 break;\r
365         case SB_THUMBTRACK:   // Drag scroll box to specified position. nPos is the\r
366                 sinfo.nPos = sinfo.nTrackPos;     // position that the scroll box has been dragged to.\r
367                 break;\r
368         }\r
369         SetScrollInfo(SB_HORZ, &sinfo);\r
370         Invalidate (FALSE);\r
371         __super::OnHScroll(nSBCode, nPos, pScrollBar);\r
372 }\r
373 \r
374 void CRevisionGraphWnd::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)\r
375 {\r
376         SCROLLINFO sinfo = {0};\r
377         sinfo.cbSize = sizeof(SCROLLINFO);\r
378         GetScrollInfo(SB_VERT, &sinfo);\r
379 \r
380         // Determine the new position of scroll box.\r
381         switch (nSBCode)\r
382         {\r
383         case SB_LEFT:      // Scroll to far left.\r
384                 sinfo.nPos = sinfo.nMin;\r
385                 break;\r
386         case SB_RIGHT:      // Scroll to far right.\r
387                 sinfo.nPos = sinfo.nMax;\r
388                 break;\r
389         case SB_ENDSCROLL:   // End scroll.\r
390                 break;\r
391         case SB_LINELEFT:      // Scroll left.\r
392                 if (sinfo.nPos > sinfo.nMin)\r
393                         sinfo.nPos--;\r
394                 break;\r
395         case SB_LINERIGHT:   // Scroll right.\r
396                 if (sinfo.nPos < sinfo.nMax)\r
397                         sinfo.nPos++;\r
398                 break;\r
399         case SB_PAGELEFT:    // Scroll one page left.\r
400                 {\r
401                         if (sinfo.nPos > sinfo.nMin)\r
402                                 sinfo.nPos = max(sinfo.nMin, sinfo.nPos - (int) sinfo.nPage);\r
403                 }\r
404                 break;\r
405         case SB_PAGERIGHT:      // Scroll one page right.\r
406                 {\r
407                         if (sinfo.nPos < sinfo.nMax)\r
408                                 sinfo.nPos = min(sinfo.nMax, sinfo.nPos + (int) sinfo.nPage);\r
409                 }\r
410                 break;\r
411         case SB_THUMBPOSITION: // Scroll to absolute position. nPos is the position\r
412                 sinfo.nPos = sinfo.nTrackPos;      // of the scroll box at the end of the drag operation.\r
413                 break;\r
414         case SB_THUMBTRACK:   // Drag scroll box to specified position. nPos is the\r
415                 sinfo.nPos = sinfo.nTrackPos;     // position that the scroll box has been dragged to.\r
416                 break;\r
417         }\r
418         SetScrollInfo(SB_VERT, &sinfo);\r
419         Invalidate(FALSE);\r
420         __super::OnVScroll(nSBCode, nPos, pScrollBar);\r
421 }\r
422 \r
423 void CRevisionGraphWnd::OnSize(UINT nType, int cx, int cy)\r
424 {\r
425         __super::OnSize(nType, cx, cy);\r
426         SetScrollbars(GetScrollPos(SB_VERT), GetScrollPos(SB_HORZ));\r
427         Invalidate(FALSE);\r
428 }\r
429 \r
430 void CRevisionGraphWnd::OnLButtonDown(UINT nFlags, CPoint point)\r
431 {\r
432         if (m_bThreadRunning)\r
433                 return __super::OnLButtonDown(nFlags, point);\r
434 \r
435     CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());\r
436 \r
437         ATLTRACE("right clicked on x=%d y=%d\n", point.x, point.y);\r
438         SetFocus();\r
439         bool bHit = false;\r
440         bool bControl = !!(GetKeyState(VK_CONTROL)&0x8000);\r
441         if (!m_bShowOverview || !m_OverviewRect.PtInRect(point))\r
442         {\r
443         const CRevisionGraphState::SVisibleGlyph* hitGlyph \r
444             = GetHitGlyph (point);\r
445 \r
446         if (hitGlyph != NULL)\r
447         {\r
448             ToggleNodeFlag (hitGlyph->node, hitGlyph->state);\r
449                 return __super::OnLButtonDown(nFlags, point);\r
450         }\r
451         else\r
452         {\r
453             index_t nodeIndex = GetHitNode (point);\r
454                 if (nodeIndex != NO_INDEX)\r
455                 {\r
456                 const CVisibleGraphNode* reventry = nodeList->GetNode (nodeIndex).node;\r
457                         if (bControl)\r
458                         {\r
459                                 if (m_SelectedEntry1 == reventry)\r
460                                 {\r
461                                         if (m_SelectedEntry2)\r
462                                         {\r
463                                                 m_SelectedEntry1 = m_SelectedEntry2;\r
464                                                 m_SelectedEntry2 = NULL;\r
465                                         }\r
466                                         else\r
467                                                 m_SelectedEntry1 = NULL;\r
468                                 }\r
469                                 else if (m_SelectedEntry2 == reventry)\r
470                                         m_SelectedEntry2 = NULL;\r
471                                 else if (m_SelectedEntry1)\r
472                                         m_SelectedEntry2 = reventry;\r
473                                 else\r
474                                         m_SelectedEntry1 = reventry;\r
475                         }\r
476                         else\r
477                         {\r
478                                 if (m_SelectedEntry1 == reventry)\r
479                                         m_SelectedEntry1 = NULL;\r
480                                 else\r
481                                         m_SelectedEntry1 = reventry;\r
482                                 m_SelectedEntry2 = NULL;\r
483                         }\r
484                         bHit = true;\r
485                         Invalidate(FALSE);\r
486                 }\r
487         }\r
488     }\r
489 \r
490     if ((!bHit)&&(!bControl))\r
491         {\r
492                 m_SelectedEntry1 = NULL;\r
493                 m_SelectedEntry2 = NULL;\r
494                 m_bIsRubberBand = true;\r
495                 ATLTRACE("LButtonDown: x = %ld, y = %ld\n", point.x, point.y);\r
496                 Invalidate(FALSE);\r
497                 if (m_bShowOverview && m_OverviewRect.PtInRect(point))\r
498                         m_bIsRubberBand = false;\r
499         }\r
500         m_ptRubberStart = point;\r
501         \r
502         UINT uEnable = MF_BYCOMMAND;\r
503         if ((m_SelectedEntry1 != NULL)&&(m_SelectedEntry2 != NULL))\r
504                 uEnable |= MF_ENABLED;\r
505         else\r
506                 uEnable |= MF_GRAYED;\r
507 \r
508         EnableMenuItem(GetParent()->GetMenu()->m_hMenu, ID_VIEW_COMPAREREVISIONS, uEnable);\r
509         EnableMenuItem(GetParent()->GetMenu()->m_hMenu, ID_VIEW_COMPAREHEADREVISIONS, uEnable);\r
510         EnableMenuItem(GetParent()->GetMenu()->m_hMenu, ID_VIEW_UNIFIEDDIFF, uEnable);\r
511         EnableMenuItem(GetParent()->GetMenu()->m_hMenu, ID_VIEW_UNIFIEDDIFFOFHEADREVISIONS, uEnable);\r
512         \r
513         __super::OnLButtonDown(nFlags, point);\r
514 }\r
515 \r
516 void CRevisionGraphWnd::OnLButtonUp(UINT nFlags, CPoint point)\r
517 {\r
518         if (!m_bIsRubberBand)\r
519                 return;         // we don't have a rubberband, so no zooming necessary\r
520 \r
521         m_bIsRubberBand = false;\r
522         ReleaseCapture();\r
523         if (m_bThreadRunning)\r
524                 return __super::OnLButtonUp(nFlags, point);\r
525         // zooming is finished\r
526         m_ptRubberEnd = CPoint(0,0);\r
527         CRect rect = GetClientRect();\r
528         int x = abs(m_ptRubberStart.x - point.x);\r
529         int y = abs(m_ptRubberStart.y - point.y);\r
530 \r
531         if ((x < 20)&&(y < 20))\r
532         {\r
533                 // too small zoom rectangle\r
534                 // assume zooming by accident\r
535                 Invalidate(FALSE);\r
536                 __super::OnLButtonUp(nFlags, point);\r
537                 return;\r
538         }\r
539 \r
540         float xfact = float(rect.Width())/float(x);\r
541         float yfact = float(rect.Height())/float(y);\r
542         float fact = max(yfact, xfact);\r
543 \r
544         // find out where to scroll to\r
545         x = min(m_ptRubberStart.x, point.x) + GetScrollPos(SB_HORZ);\r
546         y = min(m_ptRubberStart.y, point.y) + GetScrollPos(SB_VERT);\r
547 \r
548         float fZoomfactor = m_fZoomFactor*fact;\r
549         if (fZoomfactor > 20.0)\r
550         {\r
551                 // with such a big zoomfactor, the user\r
552                 // most likely zoomed by accident\r
553                 Invalidate(FALSE);\r
554                 __super::OnLButtonUp(nFlags, point);\r
555                 return;\r
556         }\r
557         if (fZoomfactor > 2.0)\r
558         {\r
559                 fZoomfactor = 2.0;\r
560                 fact = fZoomfactor/m_fZoomFactor;\r
561         }\r
562 \r
563         CRevisionGraphDlg * pDlg = (CRevisionGraphDlg*)GetParent();\r
564         if (pDlg)\r
565         {\r
566                 m_fZoomFactor = fZoomfactor;\r
567                 pDlg->DoZoom (m_fZoomFactor);\r
568                 SetScrollbars(int(float(y)*fact), int(float(x)*fact));\r
569         }\r
570         __super::OnLButtonUp(nFlags, point);\r
571 }\r
572 \r
573 bool CRevisionGraphWnd::CancelMouseZoom()\r
574 {\r
575         bool bRet = m_bIsRubberBand;\r
576         ReleaseCapture();\r
577         if (m_bIsRubberBand)\r
578                 Invalidate(FALSE);\r
579         m_bIsRubberBand = false;\r
580         m_ptRubberEnd = CPoint(0,0);\r
581         return bRet;\r
582 }\r
583 \r
584 INT_PTR CRevisionGraphWnd::OnToolHitTest(CPoint point, TOOLINFO* pTI) const\r
585 {\r
586         if (m_bThreadRunning)\r
587                 return -1;\r
588 \r
589     index_t nodeIndex = GetHitNode (point);\r
590     if (m_tooltipIndex != nodeIndex)\r
591     {\r
592         // force tooltip to be updated\r
593 \r
594         m_tooltipIndex = nodeIndex;\r
595         return -1;\r
596     }\r
597 \r
598     if (nodeIndex == NO_INDEX)\r
599         return -1;\r
600 \r
601     if ((GetHoverGlyphs (point) != 0) || (GetHitGlyph (point) != NULL))\r
602         return -1;\r
603 \r
604         pTI->hwnd = this->m_hWnd;\r
605     CWnd::GetClientRect(&pTI->rect);\r
606         pTI->uFlags  |= TTF_ALWAYSTIP | TTF_IDISHWND;\r
607         pTI->uId = (UINT)m_hWnd;\r
608         pTI->lpszText = LPSTR_TEXTCALLBACK;\r
609 \r
610     return 1;\r
611 }\r
612 \r
613 BOOL CRevisionGraphWnd::OnToolTipNotify(UINT /*id*/, NMHDR *pNMHDR, LRESULT *pResult)\r
614 {\r
615     if (pNMHDR->idFrom != (UINT)m_hWnd)\r
616                 return FALSE;\r
617 \r
618     // need to handle both ANSI and UNICODE versions of the message\r
619         TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;\r
620         TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;\r
621 \r
622         POINT point;\r
623         DWORD ptW = GetMessagePos();\r
624         point.x = GET_X_LPARAM(ptW);\r
625         point.y = GET_Y_LPARAM(ptW);\r
626         ScreenToClient(&point);\r
627 \r
628     CString strTipText = TooltipText (GetHitNode (point));\r
629 \r
630         *pResult = 0;\r
631         if (strTipText.IsEmpty())\r
632                 return TRUE;\r
633                 \r
634     CSize tooltipSize = UsableTooltipRect();\r
635     strTipText = DisplayableText (strTipText, tooltipSize);\r
636 \r
637         if (pNMHDR->code == TTN_NEEDTEXTA)\r
638         {\r
639         ::SendMessage(pNMHDR->hwndFrom, TTM_SETMAXTIPWIDTH, 0, tooltipSize.cx);\r
640                 pTTTA->lpszText = m_szTip;\r
641                 WideCharToMultiByte(CP_ACP, 0, strTipText, -1, m_szTip, strTipText.GetLength()+1, 0, 0);\r
642         }\r
643         else\r
644         {\r
645                 ::SendMessage(pNMHDR->hwndFrom, TTM_SETMAXTIPWIDTH, 0, tooltipSize.cx);\r
646                 lstrcpyn(m_wszTip, strTipText, strTipText.GetLength()+1);\r
647                 pTTTW->lpszText = m_wszTip;\r
648         }\r
649 \r
650         // show the tooltip for 32 seconds. A higher value than 32767 won't work\r
651         // even though it's nowhere documented!\r
652         ::SendMessage(pNMHDR->hwndFrom, TTM_SETDELAYTIME, TTDT_AUTOPOP, 32767);\r
653         return TRUE;    // message was handled\r
654 }\r
655 \r
656 CSize CRevisionGraphWnd::UsableTooltipRect()\r
657 {\r
658     // get screen size\r
659 \r
660     int screenWidth = GetSystemMetrics(SM_CXSCREEN);\r
661     int screenHeight = GetSystemMetrics(SM_CYSCREEN);\r
662 \r
663     // get current mouse position\r
664 \r
665     CPoint cursorPos;\r
666     if (GetCursorPos (&cursorPos) == FALSE)\r
667     {\r
668         // we could not determine the mouse position \r
669         // use screen / 2 minus some safety margin\r
670 \r
671         return CSize (screenWidth / 2 - 20, screenHeight / 2 - 20);\r
672     }\r
673 \r
674     // tool tip will display in the biggest sector beside the cursor\r
675     // deduct some safety margin (for the mouse cursor itself\r
676 \r
677     CSize biggestSector\r
678         ( max (screenWidth - cursorPos.x - 40, cursorPos.x - 24)\r
679         , max (screenHeight - cursorPos.y - 40, cursorPos.y - 24));\r
680 \r
681     return biggestSector;\r
682 }\r
683 \r
684 CString CRevisionGraphWnd::DisplayableText ( const CString& wholeText\r
685                                            , const CSize& tooltipSize)\r
686 {\r
687     CDC* dc = GetDC();\r
688     if (dc == NULL)\r
689     {\r
690         // no access to the device context -> truncate hard at 1000 chars\r
691 \r
692         return wholeText.GetLength() >= MAX_TT_LENGTH_DEFAULT\r
693             ? wholeText.Left (MAX_TT_LENGTH_DEFAULT-4) + _T(" ...")\r
694             : wholeText;\r
695     }\r
696 \r
697     // select the tooltip font\r
698 \r
699     NONCLIENTMETRICS metrics;\r
700     metrics.cbSize = sizeof (metrics);\r
701     SystemParametersInfo (SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &metrics, 0);\r
702 \r
703     CFont font;\r
704     font.CreateFontIndirect(&metrics.lfStatusFont);\r
705     CFont* pOldFont = dc->SelectObject (&font); \r
706 \r
707     // split into lines and fill the tooltip rect\r
708 \r
709     CString result;\r
710 \r
711     int remainingHeight = tooltipSize.cy;\r
712     int pos = 0;\r
713     while (pos < wholeText.GetLength())\r
714     {\r
715         // extract a whole line\r
716 \r
717         int nextPos = wholeText.Find ('\n', pos);\r
718         if (nextPos < 0)\r
719             nextPos = wholeText.GetLength();\r
720 \r
721         CString line = wholeText.Mid (pos, nextPos-pos+1);\r
722 \r
723         // find a way to make it fit\r
724 \r
725         CSize size = dc->GetTextExtent (line);\r
726         while (size.cx > tooltipSize.cx)\r
727         {\r
728             line.Delete (line.GetLength()-1);\r
729             int nextPos2 = line.ReverseFind (' ');\r
730             if (nextPos2 < 0)\r
731                 break;\r
732 \r
733             line.Delete (pos+1, line.GetLength() - pos-1);\r
734             size = dc->GetTextExtent (line);\r
735         }\r
736 \r
737         // enough room for the new line?\r
738 \r
739         remainingHeight -= size.cy;\r
740         if (remainingHeight <= size.cy)\r
741         {\r
742             result += _T("...");\r
743             break;\r
744         }\r
745 \r
746         // add the line\r
747 \r
748         result += line;\r
749         pos += line.GetLength();\r
750     }\r
751         \r
752     // relase temp. resources\r
753 \r
754     dc->SelectObject (pOldFont);\r
755     ReleaseDC(dc);\r
756 \r
757     // ready\r
758 \r
759     return result;\r
760 }\r
761 \r
762 CString CRevisionGraphWnd::TooltipText (index_t index)\r
763 {\r
764     if (index != NO_INDEX)\r
765     {\r
766         CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());\r
767         return nodeList->GetToolTip (index);\r
768     }\r
769 \r
770     return CString();\r
771 }\r
772 \r
773 void CRevisionGraphWnd::SaveGraphAs(CString sSavePath)\r
774 {\r
775         CString extension = CPathUtils::GetFileExtFromPath(sSavePath);\r
776         if (extension.CompareNoCase(_T(".wmf"))==0)\r
777         {\r
778                 // save the graph as an enhanced metafile\r
779                 CMetaFileDC wmfDC;\r
780                 wmfDC.CreateEnhanced(NULL, sSavePath, NULL, _T("TortoiseSVN\0Revision Graph\0\0"));\r
781                 float fZoom = m_fZoomFactor;\r
782                 m_fZoomFactor = 1.0;\r
783                 DoZoom(m_fZoomFactor);\r
784                 CRect rect;\r
785                 rect = GetViewRect();\r
786                 DrawGraph(&wmfDC, rect, 0, 0, true);\r
787                 HENHMETAFILE hemf = wmfDC.CloseEnhanced();\r
788                 DeleteEnhMetaFile(hemf);\r
789                 m_fZoomFactor = fZoom;\r
790                 DoZoom(m_fZoomFactor);\r
791         }\r
792         else\r
793         {\r
794                 // save the graph as a pixel picture instead of a vector picture\r
795                 // create dc to paint on\r
796                 try\r
797                 {\r
798                         CString sErrormessage;\r
799                         CWindowDC ddc(this);\r
800                         CDC dc;\r
801                         if (!dc.CreateCompatibleDC(&ddc))\r
802                         {\r
803                                 LPVOID lpMsgBuf;\r
804                                 if (!FormatMessage( \r
805                                         FORMAT_MESSAGE_ALLOCATE_BUFFER | \r
806                                         FORMAT_MESSAGE_FROM_SYSTEM | \r
807                                         FORMAT_MESSAGE_IGNORE_INSERTS,\r
808                                         NULL,\r
809                                         GetLastError(),\r
810                                         MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language\r
811                                         (LPTSTR) &lpMsgBuf,\r
812                                         0,\r
813                                         NULL ))\r
814                                 {\r
815                                         return;\r
816                                 }\r
817                                 MessageBox( (LPCTSTR)lpMsgBuf, _T("Error"), MB_OK | MB_ICONINFORMATION );\r
818                                 LocalFree( lpMsgBuf );\r
819                                 return;\r
820                         }\r
821                         CRect rect;\r
822                         rect = GetGraphRect();\r
823                         rect.bottom = (LONG)(float(rect.Height()) * m_fZoomFactor);\r
824                         rect.right = (LONG)(float(rect.Width()) * m_fZoomFactor);\r
825                         BITMAPINFO bmi;\r
826                         HBITMAP hbm;\r
827                         LPBYTE pBits;\r
828                         // Initialize header to 0s.\r
829                         SecureZeroMemory(&bmi, sizeof(bmi));\r
830                         // Fill out the fields you care about.\r
831                         bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);\r
832                         bmi.bmiHeader.biWidth = rect.Width();\r
833                         bmi.bmiHeader.biHeight = rect.Height();\r
834                         bmi.bmiHeader.biPlanes = 1;\r
835                         bmi.bmiHeader.biBitCount = 24;\r
836                         bmi.bmiHeader.biCompression = BI_RGB;\r
837 \r
838                         // Create the surface.\r
839                         hbm = CreateDIBSection(ddc.m_hDC, &bmi, DIB_RGB_COLORS,(void **)&pBits, NULL, 0);\r
840                         if (hbm==0)\r
841                         {\r
842                                 CMessageBox::Show(m_hWnd, IDS_REVGRAPH_ERR_NOMEMORY, IDS_APPNAME, MB_ICONERROR);\r
843                                 return;\r
844                         }\r
845                         HBITMAP oldbm = (HBITMAP)dc.SelectObject(hbm);\r
846                         // paint the whole graph\r
847                         DrawGraph(&dc, rect, 0, 0, true);\r
848                         // now use GDI+ to save the picture\r
849                         CLSID   encoderClsid;\r
850                         {\r
851                                 Bitmap bitmap(hbm, NULL);\r
852                                 if (bitmap.GetLastStatus()==Ok)\r
853                                 {\r
854                                         // Get the CLSID of the encoder.\r
855                                         int ret = 0;\r
856                                         if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(_T(".png"))==0)\r
857                                                 ret = GetEncoderClsid(L"image/png", &encoderClsid);\r
858                                         else if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(_T(".jpg"))==0)\r
859                                                 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);\r
860                                         else if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(_T(".jpeg"))==0)\r
861                                                 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);\r
862                                         else if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(_T(".bmp"))==0)\r
863                                                 ret = GetEncoderClsid(L"image/bmp", &encoderClsid);\r
864                                         else if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(_T(".gif"))==0)\r
865                                                 ret = GetEncoderClsid(L"image/gif", &encoderClsid);\r
866                                         else\r
867                                         {\r
868                                                 sSavePath += _T(".jpg");\r
869                                                 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);\r
870                                         }\r
871                                         if (ret >= 0)\r
872                                         {\r
873                                                 CStringW tfile = CStringW(sSavePath);\r
874                                                 bitmap.Save(tfile, &encoderClsid, NULL);\r
875                                         }\r
876                                         else\r
877                                         {\r
878                                                 sErrormessage.Format(IDS_REVGRAPH_ERR_NOENCODER, (LPCTSTR)CPathUtils::GetFileExtFromPath(sSavePath));\r
879                                         }\r
880                                 }\r
881                                 else\r
882                                 {\r
883                                         sErrormessage.LoadString(IDS_REVGRAPH_ERR_NOBITMAP);\r
884                                 }\r
885                         }\r
886                         dc.SelectObject(oldbm);\r
887                         DeleteObject(hbm);\r
888                         dc.DeleteDC();\r
889                         if (!sErrormessage.IsEmpty())\r
890                         {\r
891                                 CMessageBox::Show(m_hWnd, sErrormessage, _T("TortoiseSVN"), MB_ICONERROR);\r
892                         }\r
893                 }\r
894                 catch (CException * pE)\r
895                 {\r
896                         TCHAR szErrorMsg[2048];\r
897                         pE->GetErrorMessage(szErrorMsg, 2048);\r
898                         CMessageBox::Show(m_hWnd, szErrorMsg, _T("TortoiseSVN"), MB_ICONERROR);\r
899                 }\r
900         }\r
901 }\r
902 \r
903 BOOL CRevisionGraphWnd::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)\r
904 {\r
905         if (m_bThreadRunning)\r
906                 return __super::OnMouseWheel(nFlags, zDelta, pt);\r
907         int orientation = GetKeyState(VK_CONTROL)&0x8000 ? SB_HORZ : SB_VERT;\r
908         int pos = GetScrollPos(orientation);\r
909         pos -= (zDelta);\r
910         SetScrollPos(orientation, pos);\r
911         Invalidate(FALSE);\r
912         return __super::OnMouseWheel(nFlags, zDelta, pt);\r
913 }\r
914 \r
915 bool CRevisionGraphWnd::UpdateSelectedEntry (const CVisibleGraphNode * clickedentry)\r
916 {\r
917         if ((m_SelectedEntry1 == NULL)&&(clickedentry == NULL))\r
918                 return false;\r
919 \r
920         if (m_SelectedEntry1 == NULL)\r
921         {\r
922                 m_SelectedEntry1 = clickedentry;\r
923                 Invalidate(FALSE);\r
924         }\r
925         if ((m_SelectedEntry2 == NULL)&&(clickedentry != m_SelectedEntry1))\r
926         {\r
927                 m_SelectedEntry1 = clickedentry;\r
928                 Invalidate(FALSE);\r
929         }\r
930         if (m_SelectedEntry1 && m_SelectedEntry2)\r
931         {\r
932                 if ((m_SelectedEntry2 != clickedentry)&&(m_SelectedEntry1 != clickedentry))\r
933                         return false;\r
934         }\r
935         if (m_SelectedEntry1 == NULL)\r
936                 return false;\r
937 \r
938     return true;\r
939 }\r
940 \r
941 void CRevisionGraphWnd::AppendMenu \r
942     ( CMenu& popup\r
943     , UINT title\r
944     , UINT command\r
945     , UINT flags)\r
946 {\r
947     // separate different groups / section within the context menu\r
948 \r
949     if (popup.GetMenuItemCount() > 0)\r
950     {\r
951         UINT lastCommand = popup.GetMenuItemID (popup.GetMenuItemCount()-1);\r
952         if ((lastCommand & GROUP_MASK) != (command & GROUP_MASK))\r
953                     popup.AppendMenu(MF_SEPARATOR, NULL);\r
954     }\r
955 \r
956     // actually add the new item\r
957 \r
958     CString titleString;\r
959     titleString.LoadString (title);\r
960     popup.AppendMenu (MF_STRING | flags, command, titleString);\r
961 }\r
962 \r
963 void CRevisionGraphWnd::AddSVNOps (CMenu& popup)\r
964 {\r
965     bool bothPresent =  (m_SelectedEntry1 != NULL)\r
966                      && !m_SelectedEntry1->GetClassification().Is (CNodeClassification::IS_DELETED)\r
967                      && (m_SelectedEntry2 != NULL)\r
968                      && !m_SelectedEntry2->GetClassification().Is (CNodeClassification::IS_DELETED);\r
969 \r
970     bool bSameURL =   (m_SelectedEntry2 && m_SelectedEntry1 \r
971                    && (m_SelectedEntry1->GetPath() == m_SelectedEntry2->GetPath()));\r
972 \r
973         if (m_SelectedEntry1 && (m_SelectedEntry2 == NULL))\r
974         {\r
975                 AppendMenu (popup, IDS_REPOBROWSE_SHOWLOG, ID_SHOWLOG);\r
976         if (!m_SelectedEntry1->GetClassification().Is (CNodeClassification::IS_MODIFIED_WC))\r
977             AppendMenu (popup, IDS_LOG_BROWSEREPO, ID_BROWSEREPO);\r
978                 if (PathIsDirectory(m_sPath))\r
979             if (m_SelectedEntry1->GetClassification().Is (CNodeClassification::IS_MODIFIED_WC))\r
980                         AppendMenu (popup, IDS_REVGRAPH_POPUP_CFM, ID_CFM);\r
981             else\r
982                         AppendMenu (popup, IDS_LOG_POPUP_MERGEREV, ID_MERGETO);\r
983 \r
984         if (!m_SelectedEntry1->GetClassification().Is (CNodeClassification::IS_WORKINGCOPY))\r
985             if (!CTSVNPath (m_sPath).IsUrl())\r
986                 if (GetWCURL() == GetSelectedURL())\r
987                 {\r
988                             AppendMenu (popup, IDS_REVGRAPH_POPUP_UPDATE, ID_UPDATE);\r
989                 }\r
990                 else\r
991                 {\r
992                     AppendMenu (popup, IDS_REVGRAPH_POPUP_SWITCHTOHEAD, ID_SWITCHTOHEAD);\r
993                     AppendMenu (popup, IDS_REVGRAPH_POPUP_SWITCH, ID_SWITCH);\r
994                 }\r
995 \r
996         }\r
997 \r
998         if (bothPresent)\r
999         {\r
1000         if (!m_SelectedEntry2->GetClassification().Is (CNodeClassification::IS_MODIFIED_WC))\r
1001         {\r
1002             // TODO: TSVN currently can't compare URL -> WC, but only vice versa)\r
1003 \r
1004                     AppendMenu (popup, IDS_REVGRAPH_POPUP_COMPAREREVS, ID_COMPAREREVS);\r
1005                 if (!bSameURL)\r
1006                     AppendMenu (popup, IDS_REVGRAPH_POPUP_COMPAREHEADS, ID_COMPAREHEADS);\r
1007         }\r
1008 \r
1009                 AppendMenu (popup, IDS_REVGRAPH_POPUP_UNIDIFFREVS, ID_UNIDIFFREVS);\r
1010             if (!bSameURL)\r
1011                 AppendMenu (popup, IDS_REVGRAPH_POPUP_UNIDIFFHEADS, ID_UNIDIFFHEADS);\r
1012         }\r
1013 }\r
1014 \r
1015 void CRevisionGraphWnd::AddGraphOps (CMenu& popup, const CVisibleGraphNode * node)\r
1016 {\r
1017     CSyncPointer<CGraphNodeStates> nodeStates (m_state.GetNodeStates());\r
1018 \r
1019     if (node == NULL)\r
1020     {\r
1021         DWORD state = nodeStates->GetCombinedFlags();\r
1022         if (state != 0)\r
1023         {\r
1024             if (state & CGraphNodeStates::COLLAPSED_ALL)\r
1025                         AppendMenu (popup, IDS_REVGRAPH_POPUP_EXPAND_ALL, ID_EXPAND_ALL);\r
1026 \r
1027             if (state & CGraphNodeStates::SPLIT_ALL)\r
1028                         AppendMenu (popup, IDS_REVGRAPH_POPUP_JOIN_ALL, ID_JOIN_ALL);\r
1029         }\r
1030     }\r
1031     else\r
1032     {\r
1033         DWORD state = nodeStates->GetFlags (node);\r
1034 \r
1035         if (node->GetSource() || (state & CGraphNodeStates::COLLAPSED_ABOVE))\r
1036                 AppendMenu ( popup\r
1037                        ,   (state & CGraphNodeStates::COLLAPSED_ABOVE) \r
1038                          ? IDS_REVGRAPH_POPUP_EXPAND_ABOVE \r
1039                          : IDS_REVGRAPH_POPUP_COLLAPSE_ABOVE\r
1040                        , ID_GRAPH_EXPANDCOLLAPSE_ABOVE);\r
1041 \r
1042         if (node->GetFirstCopyTarget() || (state & CGraphNodeStates::COLLAPSED_RIGHT))\r
1043                 AppendMenu ( popup\r
1044                        ,   (state & CGraphNodeStates::COLLAPSED_RIGHT) \r
1045                          ? IDS_REVGRAPH_POPUP_EXPAND_RIGHT \r
1046                          : IDS_REVGRAPH_POPUP_COLLAPSE_RIGHT\r
1047                        , ID_GRAPH_EXPANDCOLLAPSE_RIGHT);\r
1048 \r
1049         if (node->GetNext() || (state & CGraphNodeStates::COLLAPSED_BELOW))\r
1050                 AppendMenu ( popup\r
1051                        ,   (state & CGraphNodeStates::COLLAPSED_BELOW) \r
1052                          ? IDS_REVGRAPH_POPUP_EXPAND_BELOW \r
1053                          : IDS_REVGRAPH_POPUP_COLLAPSE_BELOW\r
1054                        , ID_GRAPH_EXPANDCOLLAPSE_BELOW);\r
1055 \r
1056         if (node->GetSource() || (state & CGraphNodeStates::SPLIT_ABOVE))\r
1057                 AppendMenu ( popup\r
1058                        ,   (state & CGraphNodeStates::SPLIT_ABOVE) \r
1059                          ? IDS_REVGRAPH_POPUP_JOIN_ABOVE \r
1060                          : IDS_REVGRAPH_POPUP_SPLIT_ABOVE\r
1061                        , ID_GRAPH_SPLITJOIN_ABOVE);\r
1062 \r
1063         if (node->GetFirstCopyTarget() || (state & CGraphNodeStates::SPLIT_RIGHT))\r
1064                 AppendMenu ( popup\r
1065                        ,   (state & CGraphNodeStates::SPLIT_RIGHT) \r
1066                          ? IDS_REVGRAPH_POPUP_JOIN_RIGHT \r
1067                          : IDS_REVGRAPH_POPUP_SPLIT_RIGHT\r
1068                        , ID_GRAPH_SPLITJOIN_RIGHT);\r
1069 \r
1070         if (node->GetNext() || (state & CGraphNodeStates::SPLIT_BELOW))\r
1071                 AppendMenu ( popup\r
1072                        ,   (state & CGraphNodeStates::SPLIT_BELOW) \r
1073                          ? IDS_REVGRAPH_POPUP_JOIN_BELOW \r
1074                          : IDS_REVGRAPH_POPUP_SPLIT_BELOW\r
1075                        , ID_GRAPH_SPLITJOIN_BELOW);\r
1076     }\r
1077 }\r
1078 \r
1079 CString CRevisionGraphWnd::GetSelectedURL() const\r
1080 {\r
1081     if (m_SelectedEntry1 == NULL)\r
1082         return CString();\r
1083 \r
1084         CString URL = m_state.GetRepositoryRoot() \r
1085                 + CUnicodeUtils::GetUnicode (m_SelectedEntry1->GetPath().GetPath().c_str());\r
1086         URL = CUnicodeUtils::GetUnicode(CPathUtils::PathEscape(CUnicodeUtils::GetUTF8(URL)));\r
1087 \r
1088     return URL;\r
1089 }\r
1090 \r
1091 CString CRevisionGraphWnd::GetWCURL() const\r
1092 {\r
1093     CTSVNPath path (m_sPath);\r
1094     if (path.IsUrl())\r
1095         return CString();\r
1096 \r
1097     SVNInfo info;\r
1098     const SVNInfoData * status \r
1099         = info.GetFirstFileInfo (path, SVNRev(), SVNRev());\r
1100 \r
1101     return status == NULL ? CString() : status->url;\r
1102 }\r
1103 \r
1104 void CRevisionGraphWnd::DoShowLog()\r
1105 {\r
1106         CString URL = GetSelectedURL();\r
1107 \r
1108     CString sCmd;\r
1109         sCmd.Format(_T("\"%s\" /command:log /path:\"%s\" /startrev:%ld"), \r
1110                 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")), \r
1111                 (LPCTSTR)URL,\r
1112         m_SelectedEntry1->GetRevision());\r
1113 \r
1114         if (!SVN::PathIsURL(CTSVNPath(m_sPath)))\r
1115         {\r
1116                 sCmd += _T(" /propspath:\"");\r
1117                 sCmd += m_sPath;\r
1118                 sCmd += _T("\"");\r
1119         }       \r
1120 \r
1121         CAppUtils::LaunchApplication(sCmd, NULL, false);\r
1122 }\r
1123 \r
1124 void CRevisionGraphWnd::DoCheckForModification()\r
1125 {\r
1126         CChangedDlg dlg;\r
1127         dlg.m_pathList = CTSVNPathList (CTSVNPath (m_sPath));\r
1128         dlg.DoModal();\r
1129 }\r
1130 \r
1131 void CRevisionGraphWnd::DoMergeTo()\r
1132 {\r
1133         CString URL = GetSelectedURL();\r
1134         CString path = m_sPath;\r
1135         CBrowseFolder folderBrowser;\r
1136         folderBrowser.SetInfo(CString(MAKEINTRESOURCE(IDS_LOG_MERGETO)));\r
1137         if (folderBrowser.Show(GetSafeHwnd(), path, path) == CBrowseFolder::OK)\r
1138         {\r
1139                 CSVNProgressDlg dlg;\r
1140                 dlg.SetCommand(CSVNProgressDlg::SVNProgress_Merge);\r
1141                 dlg.SetPathList(CTSVNPathList(CTSVNPath(path)));\r
1142                 dlg.SetUrl(URL);\r
1143                 dlg.SetSecondUrl(URL);\r
1144                 SVNRevRangeArray revarray;\r
1145                 revarray.AddRevRange(m_SelectedEntry1->GetRevision(), svn_revnum_t(m_SelectedEntry1->GetRevision())-1);\r
1146                 dlg.SetRevisionRanges(revarray);\r
1147                 dlg.DoModal();\r
1148         }\r
1149 }\r
1150 \r
1151 void CRevisionGraphWnd::DoUpdate()\r
1152 {\r
1153         CSVNProgressDlg progDlg;\r
1154     progDlg.SetCommand (CSVNProgressDlg::SVNProgress_Update);\r
1155         progDlg.SetOptions (0); // don't ignore externals\r
1156         progDlg.SetPathList (CTSVNPathList (CTSVNPath (m_sPath)));\r
1157         progDlg.SetRevision (m_SelectedEntry1->GetRevision());\r
1158         progDlg.SetDepth();\r
1159         progDlg.DoModal();\r
1160 \r
1161     if (m_state.GetFetchedWCState())\r
1162         m_parent->UpdateFullHistory();\r
1163 }\r
1164 \r
1165 void CRevisionGraphWnd::DoSwitch()\r
1166 {\r
1167         CSVNProgressDlg progDlg;\r
1168     progDlg.SetCommand (CSVNProgressDlg::SVNProgress_Switch);\r
1169         progDlg.SetPathList (CTSVNPathList (CTSVNPath (m_sPath)));\r
1170         progDlg.SetUrl (GetSelectedURL());\r
1171         progDlg.SetRevision (m_SelectedEntry1->GetRevision());\r
1172         progDlg.DoModal();\r
1173 \r
1174     if (m_state.GetFetchedWCState())\r
1175         m_parent->UpdateFullHistory();\r
1176 }\r
1177 \r
1178 void CRevisionGraphWnd::DoSwitchToHead()\r
1179 {\r
1180         CSVNProgressDlg progDlg;\r
1181     progDlg.SetCommand (CSVNProgressDlg::SVNProgress_Switch);\r
1182         progDlg.SetPathList (CTSVNPathList (CTSVNPath (m_sPath)));\r
1183         progDlg.SetUrl (GetSelectedURL());\r
1184     progDlg.SetRevision (SVNRev::REV_HEAD);\r
1185         progDlg.SetPegRevision (m_SelectedEntry1->GetRevision());\r
1186         progDlg.DoModal();\r
1187 \r
1188     if (m_state.GetFetchedWCState())\r
1189         m_parent->UpdateFullHistory();\r
1190 }\r
1191 \r
1192 void CRevisionGraphWnd::DoBrowseRepo()\r
1193 {\r
1194     CString sCmd;\r
1195     sCmd.Format(_T("%s /command:repobrowser /path:\"%s\" /rev:%d"),\r
1196       (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")),\r
1197       (LPCTSTR)GetSelectedURL(), m_SelectedEntry1->GetRevision());\r
1198 \r
1199     CAppUtils::LaunchApplication(sCmd, NULL, false);\r
1200 }\r
1201 \r
1202 void CRevisionGraphWnd::ResetNodeFlags (DWORD flags)\r
1203 {\r
1204     m_state.GetNodeStates()->ResetFlags (flags);\r
1205     m_parent->StartWorkerThread();\r
1206 }\r
1207 \r
1208 void CRevisionGraphWnd::ToggleNodeFlag (const CVisibleGraphNode *node, DWORD flag)\r
1209 {\r
1210     CSyncPointer<CGraphNodeStates> nodeStates (m_state.GetNodeStates());\r
1211 \r
1212     if (nodeStates->GetFlags (node) & flag)\r
1213         nodeStates->ResetFlags (node, flag);\r
1214     else\r
1215         nodeStates->SetFlags (node, flag);\r
1216 \r
1217     m_parent->StartWorkerThread();\r
1218 }\r
1219 \r
1220 void CRevisionGraphWnd::OnContextMenu(CWnd* /*pWnd*/, CPoint point)\r
1221 {\r
1222         if (m_bThreadRunning)\r
1223                 return;\r
1224 \r
1225     CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());\r
1226 \r
1227         CPoint clientpoint = point;\r
1228         this->ScreenToClient(&clientpoint);\r
1229         ATLTRACE("right clicked on x=%d y=%d\n", clientpoint.x, clientpoint.y);\r
1230 \r
1231     index_t nodeIndex = GetHitNode (clientpoint);\r
1232         const CVisibleGraphNode * clickedentry = NULL;\r
1233     if (nodeIndex != NO_INDEX)\r
1234     {\r
1235         clickedentry = nodeList->GetNode (nodeIndex).node;\r
1236     }\r
1237 \r
1238     if (   !UpdateSelectedEntry (clickedentry) \r
1239         && !m_state.GetNodeStates()->GetCombinedFlags())\r
1240                 return;\r
1241 \r
1242     CMenu popup;\r
1243         if (popup.CreatePopupMenu())\r
1244         {\r
1245         AddSVNOps (popup);\r
1246         AddGraphOps (popup, clickedentry);\r
1247 \r
1248                 // if the context menu is invoked through the keyboard, we have to use\r
1249                 // a calculated position on where to anchor the menu on\r
1250                 if ((point.x == -1) && (point.y == -1))\r
1251                 {\r
1252                         CRect rect = GetWindowRect();\r
1253                         point = rect.CenterPoint();\r
1254                 }\r
1255 \r
1256                 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);\r
1257                 switch (cmd)\r
1258                 {\r
1259                 case ID_COMPAREREVS:\r
1260                 if (m_SelectedEntry1 != NULL)\r
1261                         CompareRevs(false);\r
1262                         break;\r
1263                 case ID_COMPAREHEADS:\r
1264                 if (m_SelectedEntry1 != NULL)\r
1265                         CompareRevs(true);\r
1266                         break;\r
1267                 case ID_UNIDIFFREVS:\r
1268                 if (m_SelectedEntry1 != NULL)\r
1269                         UnifiedDiffRevs(false);\r
1270                         break;\r
1271                 case ID_UNIDIFFHEADS:\r
1272                 if (m_SelectedEntry1 != NULL)\r
1273                         UnifiedDiffRevs(true);\r
1274                         break;\r
1275                 case ID_SHOWLOG:\r
1276                         DoShowLog();\r
1277                         break;\r
1278         case ID_CFM:\r
1279             DoCheckForModification();\r
1280             break;\r
1281                 case ID_MERGETO:\r
1282             DoMergeTo();\r
1283                         break;\r
1284         case ID_UPDATE:\r
1285             DoUpdate();\r
1286             break;\r
1287         case ID_SWITCHTOHEAD:\r
1288             DoSwitchToHead();\r
1289             break;\r
1290         case ID_SWITCH:\r
1291             DoSwitch();\r
1292             break;\r
1293         case ID_BROWSEREPO:\r
1294             DoBrowseRepo();\r
1295             break;\r
1296         case ID_EXPAND_ALL:\r
1297             ResetNodeFlags (CGraphNodeStates::COLLAPSED_ALL);\r
1298             break;\r
1299         case ID_JOIN_ALL:\r
1300             ResetNodeFlags (CGraphNodeStates::SPLIT_ALL);\r
1301             break;\r
1302         case ID_GRAPH_EXPANDCOLLAPSE_ABOVE:\r
1303             ToggleNodeFlag (clickedentry, CGraphNodeStates::COLLAPSED_ABOVE);\r
1304             break;\r
1305         case ID_GRAPH_EXPANDCOLLAPSE_RIGHT:\r
1306             ToggleNodeFlag (clickedentry, CGraphNodeStates::COLLAPSED_RIGHT);\r
1307             break;\r
1308         case ID_GRAPH_EXPANDCOLLAPSE_BELOW:\r
1309             ToggleNodeFlag (clickedentry, CGraphNodeStates::COLLAPSED_BELOW);\r
1310             break;\r
1311         case ID_GRAPH_SPLITJOIN_ABOVE:\r
1312             ToggleNodeFlag (clickedentry, CGraphNodeStates::SPLIT_ABOVE);\r
1313             break;\r
1314         case ID_GRAPH_SPLITJOIN_RIGHT:\r
1315             ToggleNodeFlag (clickedentry, CGraphNodeStates::SPLIT_RIGHT);\r
1316             break;\r
1317         case ID_GRAPH_SPLITJOIN_BELOW:\r
1318             ToggleNodeFlag (clickedentry, CGraphNodeStates::SPLIT_BELOW);\r
1319             break;\r
1320                 }\r
1321         }\r
1322 }\r
1323 \r
1324 void CRevisionGraphWnd::OnMouseMove(UINT nFlags, CPoint point)\r
1325 {\r
1326         if (m_bThreadRunning)\r
1327         {\r
1328                 return __super::OnMouseMove(nFlags, point);\r
1329         }\r
1330         if (!m_bIsRubberBand)\r
1331         {\r
1332                 if (m_bShowOverview && (m_OverviewRect.PtInRect(point))&&(nFlags & MK_LBUTTON))\r
1333                 {\r
1334                         // scrolling\r
1335             CRect viewRect = GetViewRect();\r
1336                         int x = (int)((point.x-m_OverviewRect.left - (m_OverviewPosRect.Width()/2)) / m_previewZoom  * m_fZoomFactor);\r
1337                         int y = (int)((point.y - (m_OverviewPosRect.Height()/2)) / m_previewZoom  * m_fZoomFactor);\r
1338                         SetScrollbars(y, x);\r
1339                         Invalidate(FALSE);\r
1340                         return __super::OnMouseMove(nFlags, point);\r
1341                 }\r
1342                 else\r
1343         {\r
1344             // update screen if we hover over a different\r
1345             // node than during the last redraw\r
1346 \r
1347             CPoint clientPoint = point;\r
1348             GetCursorPos (&clientPoint);\r
1349             ScreenToClient (&clientPoint);\r
1350 \r
1351             const CRevisionGraphState::SVisibleGlyph* hitGlyph \r
1352                 = GetHitGlyph (clientPoint);\r
1353             const CFullGraphNode* glyphNode \r
1354                 = hitGlyph ? hitGlyph->node->GetBase() : NULL;\r
1355 \r
1356             const CFullGraphNode* hoverNode = NULL;\r
1357             if (m_hoverIndex != NO_INDEX)\r
1358             {\r
1359                 CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());\r
1360                 if (m_hoverIndex < nodeList->GetCount())\r
1361                     hoverNode = nodeList->GetNode (m_hoverIndex).node->GetBase();\r
1362             }\r
1363 \r
1364             bool onHoverNodeGlyph = (hoverNode != NULL) && (glyphNode == hoverNode);\r
1365             if (   !onHoverNodeGlyph \r
1366                 && (   (m_hoverIndex != GetHitNode (clientPoint))\r
1367                     || (m_hoverGlyphs != GetHoverGlyphs (clientPoint))))\r
1368             {\r
1369                 m_showHoverGlyphs = false;\r
1370 \r
1371                 KillTimer (GLYPH_HOVER_EVENT);\r
1372                 SetTimer (GLYPH_HOVER_EVENT, GLYPH_HOVER_DELAY, NULL);\r
1373 \r
1374                 Invalidate(FALSE);\r
1375             }\r
1376 \r
1377                         return __super::OnMouseMove(nFlags, point);\r
1378         }\r
1379         }\r
1380 \r
1381         if ((abs(m_ptRubberStart.x - point.x) < 2)&&(abs(m_ptRubberStart.y - point.y) < 2))\r
1382         {\r
1383                 return __super::OnMouseMove(nFlags, point);\r
1384         }\r
1385 \r
1386         SetCapture();\r
1387 \r
1388         if ((m_ptRubberEnd.x != 0)||(m_ptRubberEnd.y != 0))\r
1389                 DrawRubberBand();\r
1390         m_ptRubberEnd = point;\r
1391         CRect rect = GetClientRect();\r
1392         m_ptRubberEnd.x = max(m_ptRubberEnd.x, rect.left);\r
1393         m_ptRubberEnd.x = min(m_ptRubberEnd.x, rect.right);\r
1394         m_ptRubberEnd.y = max(m_ptRubberEnd.y, rect.top);\r
1395         m_ptRubberEnd.y = min(m_ptRubberEnd.y, rect.bottom);\r
1396         DrawRubberBand();\r
1397 \r
1398         __super::OnMouseMove(nFlags, point);\r
1399 }\r
1400 \r
1401 BOOL CRevisionGraphWnd::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)\r
1402 {\r
1403     CRect viewRect = GetViewRect();\r
1404 \r
1405     LPTSTR cursorID = IDC_ARROW;\r
1406     HINSTANCE resourceHandle = NULL;\r
1407 \r
1408         if ((nHitTest == HTCLIENT)&&(pWnd == this)&&(viewRect.Width())&&(viewRect.Height())&&(message))\r
1409         {\r
1410                 POINT pt;\r
1411                 if (GetCursorPos(&pt))\r
1412                 {\r
1413                         ScreenToClient(&pt);\r
1414                         if (m_OverviewPosRect.PtInRect(pt))\r
1415                         {\r
1416                 resourceHandle = AfxGetResourceHandle();\r
1417                 cursorID = GetKeyState(VK_LBUTTON) & 0x8000\r
1418                          ? MAKEINTRESOURCE(IDC_PANCURDOWN)\r
1419                          : MAKEINTRESOURCE(IDC_PANCUR);\r
1420                         }\r
1421                 }\r
1422         }\r
1423 \r
1424         HCURSOR hCur = LoadCursor(resourceHandle, MAKEINTRESOURCE(cursorID));\r
1425     if (GetCursor() != hCur)\r
1426             SetCursor (hCur);\r
1427 \r
1428         return TRUE;\r
1429 }\r
1430 \r
1431 void CRevisionGraphWnd::OnTimer (UINT_PTR nIDEvent)\r
1432 {\r
1433     if (nIDEvent == GLYPH_HOVER_EVENT)\r
1434     {\r
1435         KillTimer (GLYPH_HOVER_EVENT);\r
1436 \r
1437         m_showHoverGlyphs = true;\r
1438         Invalidate (FALSE);\r
1439     }\r
1440     else\r
1441     {\r
1442         __super::OnTimer (nIDEvent);\r
1443     }\r
1444 }\r
1445 \r
1446 LRESULT CRevisionGraphWnd::OnWorkerThreadDone(WPARAM, LPARAM)\r
1447 {\r
1448         InitView();\r
1449         BuildPreview();\r
1450     Invalidate(FALSE);\r
1451 \r
1452     SVN svn;\r
1453         LogCache::CRepositoryInfo& cachedProperties \r
1454         = svn.GetLogCachePool()->GetRepositoryInfo();\r
1455 \r
1456     CSyncPointer<const CFullHistory> fullHistoy (m_state.GetFullHistory());\r
1457     if (fullHistoy.get() != NULL)\r
1458     {\r
1459             SetDlgTitle (cachedProperties.IsOffline \r
1460             ( fullHistoy->GetRepositoryUUID()\r
1461             , fullHistoy->GetRepositoryRoot()\r
1462             , false));\r
1463     }\r
1464 \r
1465     return 0;\r
1466 }\r
1467 \r
1468 void CRevisionGraphWnd::SetDlgTitle (bool offline)\r
1469 {\r
1470         if (m_sTitle.IsEmpty())\r
1471                 GetParent()->GetWindowText(m_sTitle);\r
1472 \r
1473         CString newTitle;\r
1474         if (offline)\r
1475         newTitle.Format (IDS_REVGRAPH_DLGTITLEOFFLINE, (LPCTSTR)m_sTitle);\r
1476     else\r
1477         newTitle = m_sTitle;\r
1478 \r
1479         GetParent()->SetWindowText (newTitle);\r
1480 }\r
1481 \r