1 // TortoiseSVN - a Windows shell extension for easy version control
\r
3 // Copyright (C) 2003-2009 - TortoiseSVN
\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
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
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
20 #include "TortoiseProc.h"
\r
21 #include "Revisiongraphwnd.h"
\r
22 #include "MessageBox.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
42 #define new DEBUG_NEW
\r
44 static char THIS_FILE[] = __FILE__;
\r
47 using namespace Gdiplus;
\r
49 enum RevisionGraphContextMenuCommands
\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
56 ID_COMPAREREVS = 0x100,
\r
64 ID_EXPAND_ALL = 0x400,
\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
74 CRevisionGraphWnd::CRevisionGraphWnd()
\r
76 , m_SelectedEntry1(NULL)
\r
77 , m_SelectedEntry2(NULL)
\r
78 , m_bThreadRunning(TRUE)
\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
89 , m_hoverIndex ((index_t)NO_INDEX)
\r
91 , m_tooltipIndex ((index_t)NO_INDEX)
\r
92 , m_showHoverGlyphs (false)
\r
94 memset(&m_lfBaseFont, 0, sizeof(LOGFONT));
\r
95 for (int i=0; i<MAXFONTS; i++)
\r
97 m_apFonts[i] = NULL;
\r
101 HINSTANCE hInst = AfxGetInstanceHandle();
\r
102 #define REVGRAPH_CLASSNAME _T("Revgraph_windowclass")
\r
103 if (!(::GetClassInfo(hInst, REVGRAPH_CLASSNAME, &wndcls)))
\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
116 RegisterClass(&wndcls);
\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
123 CRevisionGraphWnd::~CRevisionGraphWnd()
\r
125 for (int i=0; i<MAXFONTS; i++)
\r
127 if (m_apFonts[i] != NULL)
\r
129 m_apFonts[i]->DeleteObject();
\r
130 delete m_apFonts[i];
\r
132 m_apFonts[i] = NULL;
\r
138 void CRevisionGraphWnd::DoDataExchange(CDataExchange* pDX)
\r
140 CWnd::DoDataExchange(pDX);
\r
144 BEGIN_MESSAGE_MAP(CRevisionGraphWnd, CWnd)
\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
154 ON_WM_CONTEXTMENU()
\r
159 ON_MESSAGE(WM_WORKERTHREADDONE,OnWorkerThreadDone)
\r
162 void CRevisionGraphWnd::Init(CWnd * pParent, LPRECT rect)
\r
165 HINSTANCE hInst = AfxGetInstanceHandle();
\r
166 #define REVGRAPH_CLASSNAME _T("Revgraph_windowclass")
\r
167 if (!(::GetClassInfo(hInst, REVGRAPH_CLASSNAME, &wndcls)))
\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
180 RegisterClass(&wndcls);
\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
188 TRACE("Unable to add tooltip!\n");
\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
202 m_dwTicks = GetTickCount();
\r
204 m_parent = dynamic_cast<CRevisionGraphDlg*>(pParent);
\r
207 CPoint CRevisionGraphWnd::GetLogCoordinates (CPoint point) const
\r
209 // translate point into logical coordinates
\r
211 int nVScrollPos = GetScrollPos(SB_VERT);
\r
212 int nHScrollPos = GetScrollPos(SB_HORZ);
\r
214 return CPoint ( (int)((point.x + nHScrollPos) / m_fZoomFactor)
\r
215 , (int)((point.y + nVScrollPos) / m_fZoomFactor));
\r
218 index_t CRevisionGraphWnd::GetHitNode (CPoint point, CSize border) const
\r
220 // any nodes at all?
\r
222 CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());
\r
224 return index_t(NO_INDEX);
\r
226 // search the nodes for one at that grid position
\r
228 return nodeList->GetAt (GetLogCoordinates (point), border);
\r
231 DWORD CRevisionGraphWnd::GetHoverGlyphs (CPoint point) const
\r
233 // if there is no layout, there will be no nodes,
\r
234 // hence, no glyphs
\r
236 CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());
\r
240 // get node at point or node that is close enough
\r
241 // so that point may hit a glyph area
\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
247 if (nodeIndex >= nodeList->GetCount())
\r
250 ILayoutNodeList::SNode node = nodeList->GetNode (nodeIndex);
\r
251 const CVisibleGraphNode* base = node.node;
\r
253 // what glyphs should be shown depending on position of point
\r
254 // relative to the node rect?
\r
256 CPoint logCoordinates = GetLogCoordinates (point);
\r
257 CRect r = node.rect;
\r
258 CPoint center = r.CenterPoint();
\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
268 = m_state.GetOptions()->GetOption<CUpsideDownLayout>()->IsActive();
\r
272 std::swap (topGlyphArea.top, bottomGlyphArea.top);
\r
273 std::swap (topGlyphArea.bottom, bottomGlyphArea.bottom);
\r
277 if (rightGlyphArea.PtInRect (logCoordinates))
\r
278 result = base->GetFirstCopyTarget() != NULL
\r
279 ? CGraphNodeStates::COLLAPSED_RIGHT | CGraphNodeStates::SPLIT_RIGHT
\r
282 if (topGlyphArea.PtInRect (logCoordinates))
\r
283 result = base->GetSource() != NULL
\r
284 ? CGraphNodeStates::COLLAPSED_ABOVE | CGraphNodeStates::SPLIT_ABOVE
\r
287 if (bottomGlyphArea.PtInRect (logCoordinates))
\r
288 result = base->GetNext() != NULL
\r
289 ? CGraphNodeStates::COLLAPSED_BELOW | CGraphNodeStates::SPLIT_BELOW
\r
292 // if some nodes have already been split, don't allow collapsing etc.
\r
294 CSyncPointer<const CGraphNodeStates> nodeStates (m_state.GetNodeStates());
\r
295 if (result & nodeStates->GetFlags (base))
\r
301 const CRevisionGraphState::SVisibleGlyph* CRevisionGraphWnd::GetHitGlyph (CPoint point) const
\r
303 float glyphSize = GLYPH_SIZE * m_fZoomFactor;
\r
305 CSyncPointer<const CRevisionGraphState::TVisibleGlyphs>
\r
306 visibleGlyphs (m_state.GetVisibleGlyphs());
\r
308 for (size_t i = 0, count = visibleGlyphs->size(); i < count; ++i)
\r
310 const CRevisionGraphState::SVisibleGlyph* entry = &(*visibleGlyphs)[i];
\r
312 float xRel = point.x - entry->leftTop.X;
\r
313 float yRel = point.y - entry->leftTop.Y;
\r
315 if ( (xRel >= 0) && (xRel < glyphSize)
\r
316 && (yRel >= 0) && (yRel < glyphSize))
\r
325 void CRevisionGraphWnd::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
\r
327 SCROLLINFO sinfo = {0};
\r
328 sinfo.cbSize = sizeof(SCROLLINFO);
\r
329 GetScrollInfo(SB_HORZ, &sinfo);
\r
331 // Determine the new position of scroll box.
\r
334 case SB_LEFT: // Scroll to far left.
\r
335 sinfo.nPos = sinfo.nMin;
\r
337 case SB_RIGHT: // Scroll to far right.
\r
338 sinfo.nPos = sinfo.nMax;
\r
340 case SB_ENDSCROLL: // End scroll.
\r
342 case SB_LINELEFT: // Scroll left.
\r
343 if (sinfo.nPos > sinfo.nMin)
\r
346 case SB_LINERIGHT: // Scroll right.
\r
347 if (sinfo.nPos < sinfo.nMax)
\r
350 case SB_PAGELEFT: // Scroll one page left.
\r
352 if (sinfo.nPos > sinfo.nMin)
\r
353 sinfo.nPos = max(sinfo.nMin, sinfo.nPos - (int) sinfo.nPage);
\r
356 case SB_PAGERIGHT: // Scroll one page right.
\r
358 if (sinfo.nPos < sinfo.nMax)
\r
359 sinfo.nPos = min(sinfo.nMax, sinfo.nPos + (int) sinfo.nPage);
\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
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
369 SetScrollInfo(SB_HORZ, &sinfo);
\r
370 Invalidate (FALSE);
\r
371 __super::OnHScroll(nSBCode, nPos, pScrollBar);
\r
374 void CRevisionGraphWnd::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
\r
376 SCROLLINFO sinfo = {0};
\r
377 sinfo.cbSize = sizeof(SCROLLINFO);
\r
378 GetScrollInfo(SB_VERT, &sinfo);
\r
380 // Determine the new position of scroll box.
\r
383 case SB_LEFT: // Scroll to far left.
\r
384 sinfo.nPos = sinfo.nMin;
\r
386 case SB_RIGHT: // Scroll to far right.
\r
387 sinfo.nPos = sinfo.nMax;
\r
389 case SB_ENDSCROLL: // End scroll.
\r
391 case SB_LINELEFT: // Scroll left.
\r
392 if (sinfo.nPos > sinfo.nMin)
\r
395 case SB_LINERIGHT: // Scroll right.
\r
396 if (sinfo.nPos < sinfo.nMax)
\r
399 case SB_PAGELEFT: // Scroll one page left.
\r
401 if (sinfo.nPos > sinfo.nMin)
\r
402 sinfo.nPos = max(sinfo.nMin, sinfo.nPos - (int) sinfo.nPage);
\r
405 case SB_PAGERIGHT: // Scroll one page right.
\r
407 if (sinfo.nPos < sinfo.nMax)
\r
408 sinfo.nPos = min(sinfo.nMax, sinfo.nPos + (int) sinfo.nPage);
\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
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
418 SetScrollInfo(SB_VERT, &sinfo);
\r
420 __super::OnVScroll(nSBCode, nPos, pScrollBar);
\r
423 void CRevisionGraphWnd::OnSize(UINT nType, int cx, int cy)
\r
425 __super::OnSize(nType, cx, cy);
\r
426 SetScrollbars(GetScrollPos(SB_VERT), GetScrollPos(SB_HORZ));
\r
430 void CRevisionGraphWnd::OnLButtonDown(UINT nFlags, CPoint point)
\r
432 if (m_bThreadRunning)
\r
433 return __super::OnLButtonDown(nFlags, point);
\r
435 CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());
\r
437 ATLTRACE("right clicked on x=%d y=%d\n", point.x, point.y);
\r
440 bool bControl = !!(GetKeyState(VK_CONTROL)&0x8000);
\r
441 if (!m_bShowOverview || !m_OverviewRect.PtInRect(point))
\r
443 const CRevisionGraphState::SVisibleGlyph* hitGlyph
\r
444 = GetHitGlyph (point);
\r
446 if (hitGlyph != NULL)
\r
448 ToggleNodeFlag (hitGlyph->node, hitGlyph->state);
\r
449 return __super::OnLButtonDown(nFlags, point);
\r
453 index_t nodeIndex = GetHitNode (point);
\r
454 if (nodeIndex != NO_INDEX)
\r
456 const CVisibleGraphNode* reventry = nodeList->GetNode (nodeIndex).node;
\r
459 if (m_SelectedEntry1 == reventry)
\r
461 if (m_SelectedEntry2)
\r
463 m_SelectedEntry1 = m_SelectedEntry2;
\r
464 m_SelectedEntry2 = NULL;
\r
467 m_SelectedEntry1 = NULL;
\r
469 else if (m_SelectedEntry2 == reventry)
\r
470 m_SelectedEntry2 = NULL;
\r
471 else if (m_SelectedEntry1)
\r
472 m_SelectedEntry2 = reventry;
\r
474 m_SelectedEntry1 = reventry;
\r
478 if (m_SelectedEntry1 == reventry)
\r
479 m_SelectedEntry1 = NULL;
\r
481 m_SelectedEntry1 = reventry;
\r
482 m_SelectedEntry2 = NULL;
\r
490 if ((!bHit)&&(!bControl))
\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
497 if (m_bShowOverview && m_OverviewRect.PtInRect(point))
\r
498 m_bIsRubberBand = false;
\r
500 m_ptRubberStart = point;
\r
502 UINT uEnable = MF_BYCOMMAND;
\r
503 if ((m_SelectedEntry1 != NULL)&&(m_SelectedEntry2 != NULL))
\r
504 uEnable |= MF_ENABLED;
\r
506 uEnable |= MF_GRAYED;
\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
513 __super::OnLButtonDown(nFlags, point);
\r
516 void CRevisionGraphWnd::OnLButtonUp(UINT nFlags, CPoint point)
\r
518 if (!m_bIsRubberBand)
\r
519 return; // we don't have a rubberband, so no zooming necessary
\r
521 m_bIsRubberBand = false;
\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
531 if ((x < 20)&&(y < 20))
\r
533 // too small zoom rectangle
\r
534 // assume zooming by accident
\r
536 __super::OnLButtonUp(nFlags, point);
\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
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
548 float fZoomfactor = m_fZoomFactor*fact;
\r
549 if (fZoomfactor > 20.0)
\r
551 // with such a big zoomfactor, the user
\r
552 // most likely zoomed by accident
\r
554 __super::OnLButtonUp(nFlags, point);
\r
557 if (fZoomfactor > 2.0)
\r
560 fact = fZoomfactor/m_fZoomFactor;
\r
563 CRevisionGraphDlg * pDlg = (CRevisionGraphDlg*)GetParent();
\r
566 m_fZoomFactor = fZoomfactor;
\r
567 pDlg->DoZoom (m_fZoomFactor);
\r
568 SetScrollbars(int(float(y)*fact), int(float(x)*fact));
\r
570 __super::OnLButtonUp(nFlags, point);
\r
573 bool CRevisionGraphWnd::CancelMouseZoom()
\r
575 bool bRet = m_bIsRubberBand;
\r
577 if (m_bIsRubberBand)
\r
579 m_bIsRubberBand = false;
\r
580 m_ptRubberEnd = CPoint(0,0);
\r
584 INT_PTR CRevisionGraphWnd::OnToolHitTest(CPoint point, TOOLINFO* pTI) const
\r
586 if (m_bThreadRunning)
\r
589 index_t nodeIndex = GetHitNode (point);
\r
590 if (m_tooltipIndex != nodeIndex)
\r
592 // force tooltip to be updated
\r
594 m_tooltipIndex = nodeIndex;
\r
598 if (nodeIndex == NO_INDEX)
\r
601 if ((GetHoverGlyphs (point) != 0) || (GetHitGlyph (point) != NULL))
\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
613 BOOL CRevisionGraphWnd::OnToolTipNotify(UINT /*id*/, NMHDR *pNMHDR, LRESULT *pResult)
\r
615 if (pNMHDR->idFrom != (UINT)m_hWnd)
\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
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
628 CString strTipText = TooltipText (GetHitNode (point));
\r
631 if (strTipText.IsEmpty())
\r
634 CSize tooltipSize = UsableTooltipRect();
\r
635 strTipText = DisplayableText (strTipText, tooltipSize);
\r
637 if (pNMHDR->code == TTN_NEEDTEXTA)
\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
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
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
656 CSize CRevisionGraphWnd::UsableTooltipRect()
\r
660 int screenWidth = GetSystemMetrics(SM_CXSCREEN);
\r
661 int screenHeight = GetSystemMetrics(SM_CYSCREEN);
\r
663 // get current mouse position
\r
666 if (GetCursorPos (&cursorPos) == FALSE)
\r
668 // we could not determine the mouse position
\r
669 // use screen / 2 minus some safety margin
\r
671 return CSize (screenWidth / 2 - 20, screenHeight / 2 - 20);
\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
677 CSize biggestSector
\r
678 ( max (screenWidth - cursorPos.x - 40, cursorPos.x - 24)
\r
679 , max (screenHeight - cursorPos.y - 40, cursorPos.y - 24));
\r
681 return biggestSector;
\r
684 CString CRevisionGraphWnd::DisplayableText ( const CString& wholeText
\r
685 , const CSize& tooltipSize)
\r
690 // no access to the device context -> truncate hard at 1000 chars
\r
692 return wholeText.GetLength() >= MAX_TT_LENGTH_DEFAULT
\r
693 ? wholeText.Left (MAX_TT_LENGTH_DEFAULT-4) + _T(" ...")
\r
697 // select the tooltip font
\r
699 NONCLIENTMETRICS metrics;
\r
700 metrics.cbSize = sizeof (metrics);
\r
701 SystemParametersInfo (SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &metrics, 0);
\r
704 font.CreateFontIndirect(&metrics.lfStatusFont);
\r
705 CFont* pOldFont = dc->SelectObject (&font);
\r
707 // split into lines and fill the tooltip rect
\r
711 int remainingHeight = tooltipSize.cy;
\r
713 while (pos < wholeText.GetLength())
\r
715 // extract a whole line
\r
717 int nextPos = wholeText.Find ('\n', pos);
\r
719 nextPos = wholeText.GetLength();
\r
721 CString line = wholeText.Mid (pos, nextPos-pos+1);
\r
723 // find a way to make it fit
\r
725 CSize size = dc->GetTextExtent (line);
\r
726 while (size.cx > tooltipSize.cx)
\r
728 line.Delete (line.GetLength()-1);
\r
729 int nextPos2 = line.ReverseFind (' ');
\r
733 line.Delete (pos+1, line.GetLength() - pos-1);
\r
734 size = dc->GetTextExtent (line);
\r
737 // enough room for the new line?
\r
739 remainingHeight -= size.cy;
\r
740 if (remainingHeight <= size.cy)
\r
742 result += _T("...");
\r
749 pos += line.GetLength();
\r
752 // relase temp. resources
\r
754 dc->SelectObject (pOldFont);
\r
762 CString CRevisionGraphWnd::TooltipText (index_t index)
\r
764 if (index != NO_INDEX)
\r
766 CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());
\r
767 return nodeList->GetToolTip (index);
\r
773 void CRevisionGraphWnd::SaveGraphAs(CString sSavePath)
\r
775 CString extension = CPathUtils::GetFileExtFromPath(sSavePath);
\r
776 if (extension.CompareNoCase(_T(".wmf"))==0)
\r
778 // save the graph as an enhanced metafile
\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
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
794 // save the graph as a pixel picture instead of a vector picture
\r
795 // create dc to paint on
\r
798 CString sErrormessage;
\r
799 CWindowDC ddc(this);
\r
801 if (!dc.CreateCompatibleDC(&ddc))
\r
804 if (!FormatMessage(
\r
805 FORMAT_MESSAGE_ALLOCATE_BUFFER |
\r
806 FORMAT_MESSAGE_FROM_SYSTEM |
\r
807 FORMAT_MESSAGE_IGNORE_INSERTS,
\r
810 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
\r
811 (LPTSTR) &lpMsgBuf,
\r
817 MessageBox( (LPCTSTR)lpMsgBuf, _T("Error"), MB_OK | MB_ICONINFORMATION );
\r
818 LocalFree( lpMsgBuf );
\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
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
838 // Create the surface.
\r
839 hbm = CreateDIBSection(ddc.m_hDC, &bmi, DIB_RGB_COLORS,(void **)&pBits, NULL, 0);
\r
842 CMessageBox::Show(m_hWnd, IDS_REVGRAPH_ERR_NOMEMORY, IDS_APPNAME, MB_ICONERROR);
\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
851 Bitmap bitmap(hbm, NULL);
\r
852 if (bitmap.GetLastStatus()==Ok)
\r
854 // Get the CLSID of the encoder.
\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
868 sSavePath += _T(".jpg");
\r
869 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
\r
873 CStringW tfile = CStringW(sSavePath);
\r
874 bitmap.Save(tfile, &encoderClsid, NULL);
\r
878 sErrormessage.Format(IDS_REVGRAPH_ERR_NOENCODER, (LPCTSTR)CPathUtils::GetFileExtFromPath(sSavePath));
\r
883 sErrormessage.LoadString(IDS_REVGRAPH_ERR_NOBITMAP);
\r
886 dc.SelectObject(oldbm);
\r
889 if (!sErrormessage.IsEmpty())
\r
891 CMessageBox::Show(m_hWnd, sErrormessage, _T("TortoiseSVN"), MB_ICONERROR);
\r
894 catch (CException * pE)
\r
896 TCHAR szErrorMsg[2048];
\r
897 pE->GetErrorMessage(szErrorMsg, 2048);
\r
898 CMessageBox::Show(m_hWnd, szErrorMsg, _T("TortoiseSVN"), MB_ICONERROR);
\r
903 BOOL CRevisionGraphWnd::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
\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
910 SetScrollPos(orientation, pos);
\r
912 return __super::OnMouseWheel(nFlags, zDelta, pt);
\r
915 bool CRevisionGraphWnd::UpdateSelectedEntry (const CVisibleGraphNode * clickedentry)
\r
917 if ((m_SelectedEntry1 == NULL)&&(clickedentry == NULL))
\r
920 if (m_SelectedEntry1 == NULL)
\r
922 m_SelectedEntry1 = clickedentry;
\r
925 if ((m_SelectedEntry2 == NULL)&&(clickedentry != m_SelectedEntry1))
\r
927 m_SelectedEntry1 = clickedentry;
\r
930 if (m_SelectedEntry1 && m_SelectedEntry2)
\r
932 if ((m_SelectedEntry2 != clickedentry)&&(m_SelectedEntry1 != clickedentry))
\r
935 if (m_SelectedEntry1 == NULL)
\r
941 void CRevisionGraphWnd::AppendMenu
\r
947 // separate different groups / section within the context menu
\r
949 if (popup.GetMenuItemCount() > 0)
\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
956 // actually add the new item
\r
958 CString titleString;
\r
959 titleString.LoadString (title);
\r
960 popup.AppendMenu (MF_STRING | flags, command, titleString);
\r
963 void CRevisionGraphWnd::AddSVNOps (CMenu& popup)
\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
970 bool bSameURL = (m_SelectedEntry2 && m_SelectedEntry1
\r
971 && (m_SelectedEntry1->GetPath() == m_SelectedEntry2->GetPath()));
\r
973 if (m_SelectedEntry1 && (m_SelectedEntry2 == NULL))
\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
982 AppendMenu (popup, IDS_LOG_POPUP_MERGEREV, ID_MERGETO);
\r
984 if (!m_SelectedEntry1->GetClassification().Is (CNodeClassification::IS_WORKINGCOPY))
\r
985 if (!CTSVNPath (m_sPath).IsUrl())
\r
986 if (GetWCURL() == GetSelectedURL())
\r
988 AppendMenu (popup, IDS_REVGRAPH_POPUP_UPDATE, ID_UPDATE);
\r
992 AppendMenu (popup, IDS_REVGRAPH_POPUP_SWITCHTOHEAD, ID_SWITCHTOHEAD);
\r
993 AppendMenu (popup, IDS_REVGRAPH_POPUP_SWITCH, ID_SWITCH);
\r
1000 if (!m_SelectedEntry2->GetClassification().Is (CNodeClassification::IS_MODIFIED_WC))
\r
1002 // TODO: TSVN currently can't compare URL -> WC, but only vice versa)
\r
1004 AppendMenu (popup, IDS_REVGRAPH_POPUP_COMPAREREVS, ID_COMPAREREVS);
\r
1006 AppendMenu (popup, IDS_REVGRAPH_POPUP_COMPAREHEADS, ID_COMPAREHEADS);
\r
1009 AppendMenu (popup, IDS_REVGRAPH_POPUP_UNIDIFFREVS, ID_UNIDIFFREVS);
\r
1011 AppendMenu (popup, IDS_REVGRAPH_POPUP_UNIDIFFHEADS, ID_UNIDIFFHEADS);
\r
1015 void CRevisionGraphWnd::AddGraphOps (CMenu& popup, const CVisibleGraphNode * node)
\r
1017 CSyncPointer<CGraphNodeStates> nodeStates (m_state.GetNodeStates());
\r
1021 DWORD state = nodeStates->GetCombinedFlags();
\r
1024 if (state & CGraphNodeStates::COLLAPSED_ALL)
\r
1025 AppendMenu (popup, IDS_REVGRAPH_POPUP_EXPAND_ALL, ID_EXPAND_ALL);
\r
1027 if (state & CGraphNodeStates::SPLIT_ALL)
\r
1028 AppendMenu (popup, IDS_REVGRAPH_POPUP_JOIN_ALL, ID_JOIN_ALL);
\r
1033 DWORD state = nodeStates->GetFlags (node);
\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
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
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
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
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
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
1079 CString CRevisionGraphWnd::GetSelectedURL() const
\r
1081 if (m_SelectedEntry1 == NULL)
\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
1091 CString CRevisionGraphWnd::GetWCURL() const
\r
1093 CTSVNPath path (m_sPath);
\r
1098 const SVNInfoData * status
\r
1099 = info.GetFirstFileInfo (path, SVNRev(), SVNRev());
\r
1101 return status == NULL ? CString() : status->url;
\r
1104 void CRevisionGraphWnd::DoShowLog()
\r
1106 CString URL = GetSelectedURL();
\r
1109 sCmd.Format(_T("\"%s\" /command:log /path:\"%s\" /startrev:%ld"),
\r
1110 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")),
\r
1112 m_SelectedEntry1->GetRevision());
\r
1114 if (!SVN::PathIsURL(CTSVNPath(m_sPath)))
\r
1116 sCmd += _T(" /propspath:\"");
\r
1121 CAppUtils::LaunchApplication(sCmd, NULL, false);
\r
1124 void CRevisionGraphWnd::DoCheckForModification()
\r
1127 dlg.m_pathList = CTSVNPathList (CTSVNPath (m_sPath));
\r
1131 void CRevisionGraphWnd::DoMergeTo()
\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
1139 CSVNProgressDlg dlg;
\r
1140 dlg.SetCommand(CSVNProgressDlg::SVNProgress_Merge);
\r
1141 dlg.SetPathList(CTSVNPathList(CTSVNPath(path)));
\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
1151 void CRevisionGraphWnd::DoUpdate()
\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
1161 if (m_state.GetFetchedWCState())
\r
1162 m_parent->UpdateFullHistory();
\r
1165 void CRevisionGraphWnd::DoSwitch()
\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
1174 if (m_state.GetFetchedWCState())
\r
1175 m_parent->UpdateFullHistory();
\r
1178 void CRevisionGraphWnd::DoSwitchToHead()
\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
1188 if (m_state.GetFetchedWCState())
\r
1189 m_parent->UpdateFullHistory();
\r
1192 void CRevisionGraphWnd::DoBrowseRepo()
\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
1199 CAppUtils::LaunchApplication(sCmd, NULL, false);
\r
1202 void CRevisionGraphWnd::ResetNodeFlags (DWORD flags)
\r
1204 m_state.GetNodeStates()->ResetFlags (flags);
\r
1205 m_parent->StartWorkerThread();
\r
1208 void CRevisionGraphWnd::ToggleNodeFlag (const CVisibleGraphNode *node, DWORD flag)
\r
1210 CSyncPointer<CGraphNodeStates> nodeStates (m_state.GetNodeStates());
\r
1212 if (nodeStates->GetFlags (node) & flag)
\r
1213 nodeStates->ResetFlags (node, flag);
\r
1215 nodeStates->SetFlags (node, flag);
\r
1217 m_parent->StartWorkerThread();
\r
1220 void CRevisionGraphWnd::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
\r
1222 if (m_bThreadRunning)
\r
1225 CSyncPointer<const ILayoutNodeList> nodeList (m_state.GetNodes());
\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
1231 index_t nodeIndex = GetHitNode (clientpoint);
\r
1232 const CVisibleGraphNode * clickedentry = NULL;
\r
1233 if (nodeIndex != NO_INDEX)
\r
1235 clickedentry = nodeList->GetNode (nodeIndex).node;
\r
1238 if ( !UpdateSelectedEntry (clickedentry)
\r
1239 && !m_state.GetNodeStates()->GetCombinedFlags())
\r
1243 if (popup.CreatePopupMenu())
\r
1245 AddSVNOps (popup);
\r
1246 AddGraphOps (popup, clickedentry);
\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
1252 CRect rect = GetWindowRect();
\r
1253 point = rect.CenterPoint();
\r
1256 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
\r
1259 case ID_COMPAREREVS:
\r
1260 if (m_SelectedEntry1 != NULL)
\r
1261 CompareRevs(false);
\r
1263 case ID_COMPAREHEADS:
\r
1264 if (m_SelectedEntry1 != NULL)
\r
1265 CompareRevs(true);
\r
1267 case ID_UNIDIFFREVS:
\r
1268 if (m_SelectedEntry1 != NULL)
\r
1269 UnifiedDiffRevs(false);
\r
1271 case ID_UNIDIFFHEADS:
\r
1272 if (m_SelectedEntry1 != NULL)
\r
1273 UnifiedDiffRevs(true);
\r
1279 DoCheckForModification();
\r
1287 case ID_SWITCHTOHEAD:
\r
1293 case ID_BROWSEREPO:
\r
1296 case ID_EXPAND_ALL:
\r
1297 ResetNodeFlags (CGraphNodeStates::COLLAPSED_ALL);
\r
1300 ResetNodeFlags (CGraphNodeStates::SPLIT_ALL);
\r
1302 case ID_GRAPH_EXPANDCOLLAPSE_ABOVE:
\r
1303 ToggleNodeFlag (clickedentry, CGraphNodeStates::COLLAPSED_ABOVE);
\r
1305 case ID_GRAPH_EXPANDCOLLAPSE_RIGHT:
\r
1306 ToggleNodeFlag (clickedentry, CGraphNodeStates::COLLAPSED_RIGHT);
\r
1308 case ID_GRAPH_EXPANDCOLLAPSE_BELOW:
\r
1309 ToggleNodeFlag (clickedentry, CGraphNodeStates::COLLAPSED_BELOW);
\r
1311 case ID_GRAPH_SPLITJOIN_ABOVE:
\r
1312 ToggleNodeFlag (clickedentry, CGraphNodeStates::SPLIT_ABOVE);
\r
1314 case ID_GRAPH_SPLITJOIN_RIGHT:
\r
1315 ToggleNodeFlag (clickedentry, CGraphNodeStates::SPLIT_RIGHT);
\r
1317 case ID_GRAPH_SPLITJOIN_BELOW:
\r
1318 ToggleNodeFlag (clickedentry, CGraphNodeStates::SPLIT_BELOW);
\r
1324 void CRevisionGraphWnd::OnMouseMove(UINT nFlags, CPoint point)
\r
1326 if (m_bThreadRunning)
\r
1328 return __super::OnMouseMove(nFlags, point);
\r
1330 if (!m_bIsRubberBand)
\r
1332 if (m_bShowOverview && (m_OverviewRect.PtInRect(point))&&(nFlags & MK_LBUTTON))
\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
1344 // update screen if we hover over a different
\r
1345 // node than during the last redraw
\r
1347 CPoint clientPoint = point;
\r
1348 GetCursorPos (&clientPoint);
\r
1349 ScreenToClient (&clientPoint);
\r
1351 const CRevisionGraphState::SVisibleGlyph* hitGlyph
\r
1352 = GetHitGlyph (clientPoint);
\r
1353 const CFullGraphNode* glyphNode
\r
1354 = hitGlyph ? hitGlyph->node->GetBase() : NULL;
\r
1356 const CFullGraphNode* hoverNode = NULL;
\r
1357 if (m_hoverIndex != NO_INDEX)
\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
1364 bool onHoverNodeGlyph = (hoverNode != NULL) && (glyphNode == hoverNode);
\r
1365 if ( !onHoverNodeGlyph
\r
1366 && ( (m_hoverIndex != GetHitNode (clientPoint))
\r
1367 || (m_hoverGlyphs != GetHoverGlyphs (clientPoint))))
\r
1369 m_showHoverGlyphs = false;
\r
1371 KillTimer (GLYPH_HOVER_EVENT);
\r
1372 SetTimer (GLYPH_HOVER_EVENT, GLYPH_HOVER_DELAY, NULL);
\r
1374 Invalidate(FALSE);
\r
1377 return __super::OnMouseMove(nFlags, point);
\r
1381 if ((abs(m_ptRubberStart.x - point.x) < 2)&&(abs(m_ptRubberStart.y - point.y) < 2))
\r
1383 return __super::OnMouseMove(nFlags, point);
\r
1388 if ((m_ptRubberEnd.x != 0)||(m_ptRubberEnd.y != 0))
\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
1398 __super::OnMouseMove(nFlags, point);
\r
1401 BOOL CRevisionGraphWnd::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
\r
1403 CRect viewRect = GetViewRect();
\r
1405 LPTSTR cursorID = IDC_ARROW;
\r
1406 HINSTANCE resourceHandle = NULL;
\r
1408 if ((nHitTest == HTCLIENT)&&(pWnd == this)&&(viewRect.Width())&&(viewRect.Height())&&(message))
\r
1411 if (GetCursorPos(&pt))
\r
1413 ScreenToClient(&pt);
\r
1414 if (m_OverviewPosRect.PtInRect(pt))
\r
1416 resourceHandle = AfxGetResourceHandle();
\r
1417 cursorID = GetKeyState(VK_LBUTTON) & 0x8000
\r
1418 ? MAKEINTRESOURCE(IDC_PANCURDOWN)
\r
1419 : MAKEINTRESOURCE(IDC_PANCUR);
\r
1424 HCURSOR hCur = LoadCursor(resourceHandle, MAKEINTRESOURCE(cursorID));
\r
1425 if (GetCursor() != hCur)
\r
1431 void CRevisionGraphWnd::OnTimer (UINT_PTR nIDEvent)
\r
1433 if (nIDEvent == GLYPH_HOVER_EVENT)
\r
1435 KillTimer (GLYPH_HOVER_EVENT);
\r
1437 m_showHoverGlyphs = true;
\r
1438 Invalidate (FALSE);
\r
1442 __super::OnTimer (nIDEvent);
\r
1446 LRESULT CRevisionGraphWnd::OnWorkerThreadDone(WPARAM, LPARAM)
\r
1450 Invalidate(FALSE);
\r
1453 LogCache::CRepositoryInfo& cachedProperties
\r
1454 = svn.GetLogCachePool()->GetRepositoryInfo();
\r
1456 CSyncPointer<const CFullHistory> fullHistoy (m_state.GetFullHistory());
\r
1457 if (fullHistoy.get() != NULL)
\r
1459 SetDlgTitle (cachedProperties.IsOffline
\r
1460 ( fullHistoy->GetRepositoryUUID()
\r
1461 , fullHistoy->GetRepositoryRoot()
\r
1468 void CRevisionGraphWnd::SetDlgTitle (bool offline)
\r
1470 if (m_sTitle.IsEmpty())
\r
1471 GetParent()->GetWindowText(m_sTitle);
\r
1475 newTitle.Format (IDS_REVGRAPH_DLGTITLEOFFLINE, (LPCTSTR)m_sTitle);
\r
1477 newTitle = m_sTitle;
\r
1479 GetParent()->SetWindowText (newTitle);
\r