1 // TortoiseSVN - a Windows shell extension for easy version control
\r
3 // Copyright (C) 2003-2008 - 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 "RevisionGraph/StandardLayout.h"
\r
40 #define new DEBUG_NEW
\r
42 static char THIS_FILE[] = __FILE__;
\r
45 using namespace Gdiplus;
\r
47 enum RevisionGraphContextMenuCommands
\r
49 // needs to start with 1, since 0 is the return value if *nothing* is clicked on in the context menu
\r
58 CRevisionGraphWnd::CRevisionGraphWnd()
\r
60 , m_SelectedEntry1(NULL)
\r
61 , m_SelectedEntry2(NULL)
\r
62 , m_bThreadRunning(FALSE)
\r
65 , m_bTweakTrunkColors(true)
\r
66 , m_bTweakTagsColors(true)
\r
67 , m_fZoomFactor(1.0)
\r
68 , m_ptRubberEnd(0,0)
\r
69 , m_ptRubberStart(0,0)
\r
70 , m_bShowOverview(false)
\r
72 memset(&m_lfBaseFont, 0, sizeof(LOGFONT));
\r
73 for (int i=0; i<MAXFONTS; i++)
\r
75 m_apFonts[i] = NULL;
\r
79 HINSTANCE hInst = AfxGetInstanceHandle();
\r
80 #define REVGRAPH_CLASSNAME _T("Revgraph_windowclass")
\r
81 if (!(::GetClassInfo(hInst, REVGRAPH_CLASSNAME, &wndcls)))
\r
83 // otherwise we need to register a new class
\r
84 wndcls.style = CS_DBLCLKS | CS_OWNDC;
\r
85 wndcls.lpfnWndProc = ::DefWindowProc;
\r
86 wndcls.cbClsExtra = wndcls.cbWndExtra = 0;
\r
87 wndcls.hInstance = hInst;
\r
88 wndcls.hIcon = NULL;
\r
89 wndcls.hCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
\r
90 wndcls.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
\r
91 wndcls.lpszMenuName = NULL;
\r
92 wndcls.lpszClassName = REVGRAPH_CLASSNAME;
\r
94 RegisterClass(&wndcls);
\r
97 m_bShowOverview = CRegDWORD(_T("Software\\TortoiseSVN\\RevisionGraph\\ShowRevGraphOverview"), TRUE);
\r
98 m_bTweakTrunkColors = CRegDWORD(_T("Software\\TortoiseSVN\\RevisionGraph\\TweakTrunkColors"), TRUE) != FALSE;
\r
99 m_bTweakTagsColors = CRegDWORD(_T("Software\\TortoiseSVN\\RevisionGraph\\TweakTagsColors"), TRUE) != FALSE;
\r
102 CRevisionGraphWnd::~CRevisionGraphWnd()
\r
104 for (int i=0; i<MAXFONTS; i++)
\r
106 if (m_apFonts[i] != NULL)
\r
108 m_apFonts[i]->DeleteObject();
\r
109 delete m_apFonts[i];
\r
111 m_apFonts[i] = NULL;
\r
117 void CRevisionGraphWnd::DoDataExchange(CDataExchange* pDX)
\r
119 CWnd::DoDataExchange(pDX);
\r
123 BEGIN_MESSAGE_MAP(CRevisionGraphWnd, CWnd)
\r
129 ON_WM_LBUTTONDOWN()
\r
130 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipNotify)
\r
131 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipNotify)
\r
133 ON_WM_CONTEXTMENU()
\r
137 ON_MESSAGE(WM_WORKERTHREADDONE,OnWorkerThreadDone)
\r
140 void CRevisionGraphWnd::Init(CWnd * pParent, LPRECT rect)
\r
143 HINSTANCE hInst = AfxGetInstanceHandle();
\r
144 #define REVGRAPH_CLASSNAME _T("Revgraph_windowclass")
\r
145 if (!(::GetClassInfo(hInst, REVGRAPH_CLASSNAME, &wndcls)))
\r
147 // otherwise we need to register a new class
\r
148 wndcls.style = CS_DBLCLKS | CS_OWNDC;
\r
149 wndcls.lpfnWndProc = ::DefWindowProc;
\r
150 wndcls.cbClsExtra = wndcls.cbWndExtra = 0;
\r
151 wndcls.hInstance = hInst;
\r
152 wndcls.hIcon = NULL;
\r
153 wndcls.hCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
\r
154 wndcls.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
\r
155 wndcls.lpszMenuName = NULL;
\r
156 wndcls.lpszClassName = REVGRAPH_CLASSNAME;
\r
158 RegisterClass(&wndcls);
\r
161 if (!IsWindow(m_hWnd))
\r
162 CreateEx(WS_EX_CLIENTEDGE, REVGRAPH_CLASSNAME, _T("RevGraph"), WS_CHILD|WS_VISIBLE|WS_TABSTOP, *rect, pParent, 0);
\r
163 m_pDlgTip = new CToolTipCtrl;
\r
164 if(!m_pDlgTip->Create(this))
\r
166 TRACE("Unable to add tooltip!\n");
\r
170 memset(&m_lfBaseFont, 0, sizeof(m_lfBaseFont));
\r
171 m_lfBaseFont.lfHeight = 0;
\r
172 m_lfBaseFont.lfWeight = FW_NORMAL;
\r
173 m_lfBaseFont.lfItalic = FALSE;
\r
174 m_lfBaseFont.lfCharSet = DEFAULT_CHARSET;
\r
175 m_lfBaseFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
\r
176 m_lfBaseFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
\r
177 m_lfBaseFont.lfQuality = DEFAULT_QUALITY;
\r
178 m_lfBaseFont.lfPitchAndFamily = DEFAULT_PITCH;
\r
180 m_dwTicks = GetTickCount();
\r
183 index_t CRevisionGraphWnd::GetHitNode (CPoint point) const
\r
185 // any nodes at all?
\r
187 if (m_layout.get() == NULL)
\r
188 return index_t(NO_INDEX);
\r
190 // translate point into logical coordinates
\r
192 int nVScrollPos = GetScrollPos(SB_VERT);
\r
193 int nHScrollPos = GetScrollPos(SB_HORZ);
\r
195 CSize logCoordinates ( (int)((point.x + nHScrollPos) / m_fZoomFactor)
\r
196 , (int)((point.y + nVScrollPos) / m_fZoomFactor));
\r
198 // search the nodes for one at that grid position
\r
200 std::auto_ptr<const ILayoutNodeList> nodeList (m_layout->GetNodes());
\r
201 return nodeList->GetAt (logCoordinates, 0);
\r
204 void CRevisionGraphWnd::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
\r
206 SCROLLINFO sinfo = {0};
\r
207 sinfo.cbSize = sizeof(SCROLLINFO);
\r
208 GetScrollInfo(SB_HORZ, &sinfo);
\r
210 // Determine the new position of scroll box.
\r
213 case SB_LEFT: // Scroll to far left.
\r
214 sinfo.nPos = sinfo.nMin;
\r
216 case SB_RIGHT: // Scroll to far right.
\r
217 sinfo.nPos = sinfo.nMax;
\r
219 case SB_ENDSCROLL: // End scroll.
\r
221 case SB_LINELEFT: // Scroll left.
\r
222 if (sinfo.nPos > sinfo.nMin)
\r
225 case SB_LINERIGHT: // Scroll right.
\r
226 if (sinfo.nPos < sinfo.nMax)
\r
229 case SB_PAGELEFT: // Scroll one page left.
\r
231 if (sinfo.nPos > sinfo.nMin)
\r
232 sinfo.nPos = max(sinfo.nMin, sinfo.nPos - (int) sinfo.nPage);
\r
235 case SB_PAGERIGHT: // Scroll one page right.
\r
237 if (sinfo.nPos < sinfo.nMax)
\r
238 sinfo.nPos = min(sinfo.nMax, sinfo.nPos + (int) sinfo.nPage);
\r
241 case SB_THUMBPOSITION: // Scroll to absolute position. nPos is the position
\r
242 sinfo.nPos = sinfo.nTrackPos; // of the scroll box at the end of the drag operation.
\r
244 case SB_THUMBTRACK: // Drag scroll box to specified position. nPos is the
\r
245 sinfo.nPos = sinfo.nTrackPos; // position that the scroll box has been dragged to.
\r
248 SetScrollInfo(SB_HORZ, &sinfo);
\r
250 __super::OnHScroll(nSBCode, nPos, pScrollBar);
\r
253 void CRevisionGraphWnd::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
\r
255 SCROLLINFO sinfo = {0};
\r
256 sinfo.cbSize = sizeof(SCROLLINFO);
\r
257 GetScrollInfo(SB_VERT, &sinfo);
\r
259 // Determine the new position of scroll box.
\r
262 case SB_LEFT: // Scroll to far left.
\r
263 sinfo.nPos = sinfo.nMin;
\r
265 case SB_RIGHT: // Scroll to far right.
\r
266 sinfo.nPos = sinfo.nMax;
\r
268 case SB_ENDSCROLL: // End scroll.
\r
270 case SB_LINELEFT: // Scroll left.
\r
271 if (sinfo.nPos > sinfo.nMin)
\r
274 case SB_LINERIGHT: // Scroll right.
\r
275 if (sinfo.nPos < sinfo.nMax)
\r
278 case SB_PAGELEFT: // Scroll one page left.
\r
280 if (sinfo.nPos > sinfo.nMin)
\r
281 sinfo.nPos = max(sinfo.nMin, sinfo.nPos - (int) sinfo.nPage);
\r
284 case SB_PAGERIGHT: // Scroll one page right.
\r
286 if (sinfo.nPos < sinfo.nMax)
\r
287 sinfo.nPos = min(sinfo.nMax, sinfo.nPos + (int) sinfo.nPage);
\r
290 case SB_THUMBPOSITION: // Scroll to absolute position. nPos is the position
\r
291 sinfo.nPos = sinfo.nTrackPos; // of the scroll box at the end of the drag operation.
\r
293 case SB_THUMBTRACK: // Drag scroll box to specified position. nPos is the
\r
294 sinfo.nPos = sinfo.nTrackPos; // position that the scroll box has been dragged to.
\r
297 SetScrollInfo(SB_VERT, &sinfo);
\r
299 __super::OnVScroll(nSBCode, nPos, pScrollBar);
\r
302 void CRevisionGraphWnd::OnSize(UINT nType, int cx, int cy)
\r
304 __super::OnSize(nType, cx, cy);
\r
305 SetScrollbars(GetScrollPos(SB_VERT), GetScrollPos(SB_HORZ));
\r
309 void CRevisionGraphWnd::OnLButtonDown(UINT nFlags, CPoint point)
\r
311 if (m_bThreadRunning)
\r
312 return __super::OnLButtonDown(nFlags, point);
\r
313 ATLTRACE("right clicked on x=%d y=%d\n", point.x, point.y);
\r
316 bool bControl = !!(GetKeyState(VK_CONTROL)&0x8000);
\r
317 if (!m_OverviewRect.PtInRect(point))
\r
319 index_t nodeIndex = GetHitNode (point);
\r
320 if (nodeIndex != NO_INDEX)
\r
322 std::auto_ptr<const ILayoutNodeList> nodeList (m_layout->GetNodes());
\r
323 const CVisibleGraphNode* reventry = nodeList->GetNode (nodeIndex).node;
\r
326 if (m_SelectedEntry1 == reventry)
\r
328 if (m_SelectedEntry2)
\r
330 m_SelectedEntry1 = m_SelectedEntry2;
\r
331 m_SelectedEntry2 = NULL;
\r
334 m_SelectedEntry1 = NULL;
\r
336 else if (m_SelectedEntry2 == reventry)
\r
337 m_SelectedEntry2 = NULL;
\r
338 else if (m_SelectedEntry1)
\r
339 m_SelectedEntry2 = reventry;
\r
341 m_SelectedEntry1 = reventry;
\r
345 if (m_SelectedEntry1 == reventry)
\r
346 m_SelectedEntry1 = NULL;
\r
348 m_SelectedEntry1 = reventry;
\r
349 m_SelectedEntry2 = NULL;
\r
356 if ((!bHit)&&(!bControl))
\r
358 m_SelectedEntry1 = NULL;
\r
359 m_SelectedEntry2 = NULL;
\r
360 m_bIsRubberBand = true;
\r
361 ATLTRACE("LButtonDown: x = %ld, y = %ld\n", point.x, point.y);
\r
363 if (m_OverviewRect.PtInRect(point))
\r
364 m_bIsRubberBand = false;
\r
366 m_ptRubberStart = point;
\r
368 UINT uEnable = MF_BYCOMMAND;
\r
369 if ((m_SelectedEntry1 != NULL)&&(m_SelectedEntry2 != NULL))
\r
370 uEnable |= MF_ENABLED;
\r
372 uEnable |= MF_GRAYED;
\r
374 EnableMenuItem(GetParent()->GetMenu()->m_hMenu, ID_VIEW_COMPAREREVISIONS, uEnable);
\r
375 EnableMenuItem(GetParent()->GetMenu()->m_hMenu, ID_VIEW_COMPAREHEADREVISIONS, uEnable);
\r
376 EnableMenuItem(GetParent()->GetMenu()->m_hMenu, ID_VIEW_UNIFIEDDIFF, uEnable);
\r
377 EnableMenuItem(GetParent()->GetMenu()->m_hMenu, ID_VIEW_UNIFIEDDIFFOFHEADREVISIONS, uEnable);
\r
379 __super::OnLButtonDown(nFlags, point);
\r
382 void CRevisionGraphWnd::OnLButtonUp(UINT nFlags, CPoint point)
\r
384 if (!m_bIsRubberBand)
\r
385 return; // we don't have a rubberband, so no zooming necessary
\r
387 m_bIsRubberBand = false;
\r
389 if (m_bThreadRunning)
\r
390 return __super::OnLButtonUp(nFlags, point);
\r
391 // zooming is finished
\r
392 m_ptRubberEnd = CPoint(0,0);
\r
394 GetClientRect(&rect);
\r
395 int x = abs(m_ptRubberStart.x - point.x);
\r
396 int y = abs(m_ptRubberStart.y - point.y);
\r
398 if ((x < 20)&&(y < 20))
\r
400 // too small zoom rectangle
\r
401 // assume zooming by accident
\r
403 __super::OnLButtonUp(nFlags, point);
\r
407 float xfact = float(rect.Width())/float(x);
\r
408 float yfact = float(rect.Height())/float(y);
\r
409 float fact = max(yfact, xfact);
\r
411 // find out where to scroll to
\r
412 x = min(m_ptRubberStart.x, point.x) + GetScrollPos(SB_HORZ);
\r
413 y = min(m_ptRubberStart.y, point.y) + GetScrollPos(SB_VERT);
\r
415 float fZoomfactor = m_fZoomFactor*fact;
\r
416 if (fZoomfactor > 20.0)
\r
418 // with such a big zoomfactor, the user
\r
419 // most likely zoomed by accident
\r
421 __super::OnLButtonUp(nFlags, point);
\r
424 if (fZoomfactor > 2.0)
\r
427 fact = fZoomfactor/m_fZoomFactor;
\r
430 CRevisionGraphDlg * pDlg = (CRevisionGraphDlg*)GetParent();
\r
433 m_fZoomFactor = fZoomfactor;
\r
434 pDlg->DoZoom (m_fZoomFactor);
\r
435 SetScrollbars(int(float(y)*fact), int(float(x)*fact));
\r
437 __super::OnLButtonUp(nFlags, point);
\r
440 INT_PTR CRevisionGraphWnd::OnToolHitTest(CPoint point, TOOLINFO* pTI) const
\r
442 if (m_bThreadRunning)
\r
445 if (GetHitNode (point) == NO_INDEX)
\r
448 pTI->hwnd = this->m_hWnd;
\r
449 this->GetClientRect(&pTI->rect);
\r
450 pTI->uFlags |= TTF_ALWAYSTIP | TTF_IDISHWND;
\r
451 pTI->uId = (UINT)m_hWnd;
\r
452 pTI->lpszText = LPSTR_TEXTCALLBACK;
\r
457 BOOL CRevisionGraphWnd::OnToolTipNotify(UINT /*id*/, NMHDR *pNMHDR, LRESULT *pResult)
\r
459 if (pNMHDR->idFrom != (UINT)m_hWnd)
\r
462 // need to handle both ANSI and UNICODE versions of the message
\r
463 TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;
\r
464 TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;
\r
467 DWORD ptW = GetMessagePos();
\r
468 point.x = GET_X_LPARAM(ptW);
\r
469 point.y = GET_Y_LPARAM(ptW);
\r
470 ScreenToClient(&point);
\r
472 CString strTipText = TooltipText (GetHitNode (point));
\r
475 if (strTipText.IsEmpty())
\r
478 CSize tooltipSize = UsableTooltipRect();
\r
479 strTipText = DisplayableText (strTipText, tooltipSize);
\r
481 if (pNMHDR->code == TTN_NEEDTEXTA)
\r
483 ::SendMessage(pNMHDR->hwndFrom, TTM_SETMAXTIPWIDTH, 0, tooltipSize.cx);
\r
484 pTTTA->lpszText = m_szTip;
\r
485 WideCharToMultiByte(CP_ACP, 0, strTipText, -1, m_szTip, strTipText.GetLength()+1, 0, 0);
\r
489 ::SendMessage(pNMHDR->hwndFrom, TTM_SETMAXTIPWIDTH, 0, tooltipSize.cx);
\r
490 lstrcpyn(m_wszTip, strTipText, strTipText.GetLength()+1);
\r
491 pTTTW->lpszText = m_wszTip;
\r
494 // show the tooltip for 32 seconds. A higher value than 32767 won't work
\r
495 // even though it's nowhere documented!
\r
496 ::SendMessage(pNMHDR->hwndFrom, TTM_SETDELAYTIME, TTDT_AUTOPOP, 32767);
\r
497 return TRUE; // message was handled
\r
500 CSize CRevisionGraphWnd::UsableTooltipRect()
\r
504 int screenWidth = GetSystemMetrics(SM_CXSCREEN);
\r
505 int screenHeight = GetSystemMetrics(SM_CYSCREEN);
\r
507 // get current mouse position
\r
510 if (GetCursorPos (&cursorPos) == FALSE)
\r
512 // we could not determine the mouse position
\r
513 // use screen / 2 minus some safety margin
\r
515 return CSize (screenWidth / 2 - 10, screenHeight / 2 - 10);
\r
518 // tool tip will display in the biggest sector beside the cursor
\r
519 // deduct some safety margin (for the mouse cursor itself
\r
521 CSize biggestSector
\r
522 ( max (screenWidth - cursorPos.x - 20, cursorPos.x - 4)
\r
523 , max (screenHeight - cursorPos.y - 20, cursorPos.y - 4));
\r
525 return biggestSector;
\r
528 CString CRevisionGraphWnd::DisplayableText ( const CString& wholeText
\r
529 , const CSize& tooltipSize)
\r
534 // no access to the device context -> truncate hard at 1000 chars
\r
536 return wholeText.GetLength() >= MAX_TT_LENGTH_DEFAULT
\r
537 ? wholeText.Left (MAX_TT_LENGTH_DEFAULT-4) + _T(" ...")
\r
541 // select the tooltip font
\r
543 NONCLIENTMETRICS metrics;
\r
544 metrics.cbSize = sizeof (metrics);
\r
545 SystemParametersInfo (SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &metrics, 0);
\r
548 font.CreateFontIndirect(&metrics.lfStatusFont);
\r
549 CFont* pOldFont = dc->SelectObject (&font);
\r
551 // split into lines and fill the tooltip rect
\r
555 int remainingHeight = tooltipSize.cy;
\r
557 while (pos < wholeText.GetLength())
\r
559 // extract a whole line
\r
561 int nextPos = wholeText.Find ('\n', pos);
\r
563 nextPos = wholeText.GetLength();
\r
565 CString line = wholeText.Mid (pos, nextPos-pos+1);
\r
567 // find a way to make it fit
\r
569 CSize size = dc->GetTextExtent (line);
\r
570 while (size.cx > tooltipSize.cx)
\r
572 line.Delete (line.GetLength()-1);
\r
573 int nextPos = line.ReverseFind (' ');
\r
577 line.Delete (pos+1, line.GetLength() - pos-1);
\r
578 size = dc->GetTextExtent (line);
\r
581 // enough room for the new line?
\r
583 remainingHeight -= size.cy;
\r
584 if (remainingHeight <= size.cy)
\r
586 result += _T("...");
\r
593 pos += line.GetLength();
\r
596 // relase temp. resources
\r
598 dc->SelectObject (pOldFont);
\r
606 CString CRevisionGraphWnd::TooltipText (index_t index)
\r
608 if (index != NO_INDEX)
\r
610 std::auto_ptr<const ILayoutNodeList> nodeList (m_layout->GetNodes());
\r
611 return nodeList->GetToolTip (index);
\r
617 void CRevisionGraphWnd::SaveGraphAs(CString sSavePath)
\r
619 CString extension = CPathUtils::GetFileExtFromPath(sSavePath);
\r
620 if (extension.CompareNoCase(_T(".wmf"))==0)
\r
622 // save the graph as an enhanced metafile
\r
624 wmfDC.CreateEnhanced(NULL, sSavePath, NULL, _T("TortoiseSVN\0Revision Graph\0\0"));
\r
625 float fZoom = m_fZoomFactor;
\r
626 m_fZoomFactor = 1.0;
\r
627 DoZoom(m_fZoomFactor);
\r
629 rect = GetViewRect();
\r
630 DrawGraph(&wmfDC, rect, 0, 0, true);
\r
631 HENHMETAFILE hemf = wmfDC.CloseEnhanced();
\r
632 DeleteEnhMetaFile(hemf);
\r
633 m_fZoomFactor = fZoom;
\r
634 DoZoom(m_fZoomFactor);
\r
638 // to save the graph as a pixel picture (e.g. gif, png, jpeg, ...)
\r
639 // the user needs to have GDI+ installed. So check if GDI+ is
\r
640 // available before we start using it.
\r
641 TCHAR gdifindbuf[MAX_PATH];
\r
642 _tcscpy_s(gdifindbuf, MAX_PATH, _T("gdiplus.dll"));
\r
643 if (PathFindOnPath(gdifindbuf, NULL))
\r
645 ATLTRACE("gdi plus found!");
\r
649 ATLTRACE("gdi plus not found!");
\r
650 CMessageBox::Show(m_hWnd, IDS_ERR_GDIPLUS_MISSING, IDS_APPNAME, MB_ICONERROR);
\r
654 // save the graph as a pixel picture instead of a vector picture
\r
655 // create dc to paint on
\r
658 CWindowDC ddc(this);
\r
660 if (!dc.CreateCompatibleDC(&ddc))
\r
663 if (!FormatMessage(
\r
664 FORMAT_MESSAGE_ALLOCATE_BUFFER |
\r
665 FORMAT_MESSAGE_FROM_SYSTEM |
\r
666 FORMAT_MESSAGE_IGNORE_INSERTS,
\r
669 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
\r
670 (LPTSTR) &lpMsgBuf,
\r
676 MessageBox( (LPCTSTR)lpMsgBuf, _T("Error"), MB_OK | MB_ICONINFORMATION );
\r
677 LocalFree( lpMsgBuf );
\r
681 rect = GetViewRect();
\r
682 HBITMAP hbm = ::CreateCompatibleBitmap(ddc.m_hDC, rect.Width(), rect.Height());
\r
686 if (!FormatMessage(
\r
687 FORMAT_MESSAGE_ALLOCATE_BUFFER |
\r
688 FORMAT_MESSAGE_FROM_SYSTEM |
\r
689 FORMAT_MESSAGE_IGNORE_INSERTS,
\r
692 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
\r
693 (LPTSTR) &lpMsgBuf,
\r
699 MessageBox( (LPCTSTR)lpMsgBuf, _T("Error"), MB_OK | MB_ICONINFORMATION );
\r
700 LocalFree( lpMsgBuf );
\r
703 HBITMAP oldbm = (HBITMAP)dc.SelectObject(hbm);
\r
704 // paint the whole graph
\r
705 DrawGraph(&dc, rect, 0, 0, false);
\r
706 // now use GDI+ to save the picture
\r
707 CLSID encoderClsid;
\r
708 GdiplusStartupInput gdiplusStartupInput;
\r
709 ULONG_PTR gdiplusToken;
\r
710 CString sErrormessage;
\r
711 if (GdiplusStartup( &gdiplusToken, &gdiplusStartupInput, NULL )==Ok)
\r
714 Bitmap bitmap(hbm, NULL);
\r
715 if (bitmap.GetLastStatus()==Ok)
\r
717 // Get the CLSID of the encoder.
\r
719 if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(_T(".png"))==0)
\r
720 ret = GetEncoderClsid(L"image/png", &encoderClsid);
\r
721 else if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(_T(".jpg"))==0)
\r
722 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
\r
723 else if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(_T(".jpeg"))==0)
\r
724 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
\r
725 else if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(_T(".bmp"))==0)
\r
726 ret = GetEncoderClsid(L"image/bmp", &encoderClsid);
\r
727 else if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(_T(".gif"))==0)
\r
728 ret = GetEncoderClsid(L"image/gif", &encoderClsid);
\r
731 sSavePath += _T(".jpg");
\r
732 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
\r
736 CStringW tfile = CStringW(sSavePath);
\r
737 bitmap.Save(tfile, &encoderClsid, NULL);
\r
741 sErrormessage.Format(IDS_REVGRAPH_ERR_NOENCODER, (LPCTSTR)CPathUtils::GetFileExtFromPath(sSavePath));
\r
746 sErrormessage.LoadString(IDS_REVGRAPH_ERR_NOBITMAP);
\r
749 GdiplusShutdown(gdiplusToken);
\r
753 sErrormessage.LoadString(IDS_REVGRAPH_ERR_GDIINIT);
\r
755 dc.SelectObject(oldbm);
\r
757 if (!sErrormessage.IsEmpty())
\r
759 CMessageBox::Show(m_hWnd, sErrormessage, _T("TortoiseSVN"), MB_ICONERROR);
\r
762 catch (CException * pE)
\r
764 TCHAR szErrorMsg[2048];
\r
765 pE->GetErrorMessage(szErrorMsg, 2048);
\r
766 CMessageBox::Show(m_hWnd, szErrorMsg, _T("TortoiseSVN"), MB_ICONERROR);
\r
771 BOOL CRevisionGraphWnd::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
\r
773 if (m_bThreadRunning)
\r
774 return __super::OnMouseWheel(nFlags, zDelta, pt);
\r
775 int orientation = GetKeyState(VK_CONTROL)&0x8000 ? SB_HORZ : SB_VERT;
\r
776 int pos = GetScrollPos(orientation);
\r
778 SetScrollPos(orientation, pos);
\r
780 return __super::OnMouseWheel(nFlags, zDelta, pt);
\r
783 void CRevisionGraphWnd::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
\r
785 if (m_bThreadRunning)
\r
788 CPoint clientpoint = point;
\r
789 this->ScreenToClient(&clientpoint);
\r
790 ATLTRACE("right clicked on x=%d y=%d\n", clientpoint.x, clientpoint.y);
\r
792 index_t nodeIndex = GetHitNode (clientpoint);
\r
793 const CVisibleGraphNode * clickedentry = NULL;
\r
794 if (nodeIndex != NO_INDEX)
\r
796 std::auto_ptr<const ILayoutNodeList> nodeList (m_layout->GetNodes());
\r
797 clickedentry = nodeList->GetNode (nodeIndex).node;
\r
800 if ((m_SelectedEntry1 == NULL)&&(clickedentry == NULL))
\r
803 if (m_SelectedEntry1 == NULL)
\r
805 m_SelectedEntry1 = clickedentry;
\r
808 if ((m_SelectedEntry2 == NULL)&&(clickedentry != m_SelectedEntry1))
\r
810 m_SelectedEntry1 = clickedentry;
\r
813 if (m_SelectedEntry1 && m_SelectedEntry2)
\r
815 if ((m_SelectedEntry2 != clickedentry)&&(m_SelectedEntry1 != clickedentry))
\r
818 if (m_SelectedEntry1 == NULL)
\r
821 if (popup.CreatePopupMenu())
\r
823 bool bothPresent = (m_SelectedEntry1 != NULL)
\r
824 && !m_SelectedEntry1->GetClassification().Is (CNodeClassification::IS_DELETED)
\r
825 && (m_SelectedEntry2 != NULL)
\r
826 && !m_SelectedEntry2->GetClassification().Is (CNodeClassification::IS_DELETED);
\r
828 bool bSameURL = (m_SelectedEntry2 && (m_SelectedEntry1->GetPath() == m_SelectedEntry2->GetPath()));
\r
830 if (m_SelectedEntry1 && (m_SelectedEntry2 == NULL))
\r
832 temp.LoadString(IDS_REPOBROWSE_SHOWLOG);
\r
833 popup.AppendMenu(MF_STRING | MF_ENABLED, ID_SHOWLOG, temp);
\r
834 popup.AppendMenu(MF_SEPARATOR, NULL);
\r
835 temp.LoadString(IDS_LOG_POPUP_MERGEREV);
\r
836 popup.AppendMenu(MF_STRING | MF_ENABLED, ID_MERGETO, temp);
\r
840 temp.LoadString(IDS_REVGRAPH_POPUP_COMPAREREVS);
\r
841 popup.AppendMenu(MF_STRING | MF_ENABLED, ID_COMPAREREVS, temp);
\r
844 temp.LoadString(IDS_REVGRAPH_POPUP_COMPAREHEADS);
\r
845 popup.AppendMenu(MF_STRING | MF_ENABLED, ID_COMPAREHEADS, temp);
\r
848 temp.LoadString(IDS_REVGRAPH_POPUP_UNIDIFFREVS);
\r
849 popup.AppendMenu(MF_STRING | MF_ENABLED, ID_UNIDIFFREVS, temp);
\r
852 temp.LoadString(IDS_REVGRAPH_POPUP_UNIDIFFHEADS);
\r
853 popup.AppendMenu(MF_STRING | MF_ENABLED, ID_UNIDIFFHEADS, temp);
\r
857 // if the context menu is invoked through the keyboard, we have to use
\r
858 // a calculated position on where to anchor the menu on
\r
859 if ((point.x == -1) && (point.y == -1))
\r
862 GetWindowRect(&rect);
\r
863 point = rect.CenterPoint();
\r
866 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
\r
867 if (m_SelectedEntry1 == NULL)
\r
871 case ID_COMPAREREVS:
\r
872 CompareRevs(false);
\r
874 case ID_COMPAREHEADS:
\r
877 case ID_UNIDIFFREVS:
\r
878 UnifiedDiffRevs(false);
\r
880 case ID_UNIDIFFHEADS:
\r
881 UnifiedDiffRevs(true);
\r
886 CString URL = m_fullHistory->GetRepositoryRoot()
\r
887 + CUnicodeUtils::GetUnicode (m_SelectedEntry1->GetPath().GetPath().c_str());
\r
888 URL = CUnicodeUtils::GetUnicode(CPathUtils::PathEscape(CUnicodeUtils::GetUTF8(URL)));
\r
889 sCmd.Format(_T("\"%s\" /command:log /path:\"%s\" /startrev:%ld"),
\r
890 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")),
\r
892 m_SelectedEntry1->GetRevision());
\r
894 if (!SVN::PathIsURL(CTSVNPath(m_sPath)))
\r
896 sCmd += _T(" /propspath:\"");
\r
901 CAppUtils::LaunchApplication(sCmd, NULL, false);
\r
906 CString URL = m_fullHistory->GetRepositoryRoot()
\r
907 + CUnicodeUtils::GetUnicode (m_SelectedEntry1->GetPath().GetPath().c_str());
\r
908 URL = CUnicodeUtils::GetUnicode(CPathUtils::PathEscape(CUnicodeUtils::GetUTF8(URL)));
\r
910 CString path = m_sPath;
\r
911 CBrowseFolder folderBrowser;
\r
912 folderBrowser.SetInfo(CString(MAKEINTRESOURCE(IDS_LOG_MERGETO)));
\r
913 if (folderBrowser.Show(GetSafeHwnd(), path, path) == CBrowseFolder::OK)
\r
915 CSVNProgressDlg dlg;
\r
916 dlg.SetCommand(CSVNProgressDlg::SVNProgress_Merge);
\r
917 dlg.SetPathList(CTSVNPathList(CTSVNPath(path)));
\r
919 dlg.SetSecondUrl(URL);
\r
920 SVNRevRangeArray revarray;
\r
921 revarray.AddRevRange(m_SelectedEntry1->GetRevision(), svn_revnum_t(m_SelectedEntry1->GetRevision())-1);
\r
922 dlg.SetRevisionRanges(revarray);
\r
931 void CRevisionGraphWnd::OnMouseMove(UINT nFlags, CPoint point)
\r
933 if (m_bThreadRunning)
\r
935 return __super::OnMouseMove(nFlags, point);
\r
937 if (!m_bIsRubberBand)
\r
939 if ((!m_OverviewRect.IsRectEmpty())&&(m_OverviewRect.PtInRect(point))&&(nFlags & MK_LBUTTON))
\r
942 CRect viewRect = GetViewRect();
\r
943 int x = (int)((point.x-m_OverviewRect.left - (m_OverviewPosRect.Width()/2)) / m_previewZoom * m_fZoomFactor);
\r
944 int y = (int)((point.y - (m_OverviewPosRect.Height()/2)) / m_previewZoom * m_fZoomFactor);
\r
945 SetScrollbars(y, x);
\r
947 return __super::OnMouseMove(nFlags, point);
\r
950 return __super::OnMouseMove(nFlags, point);
\r
953 if ((abs(m_ptRubberStart.x - point.x) < 2)&&(abs(m_ptRubberStart.y - point.y) < 2))
\r
955 return __super::OnMouseMove(nFlags, point);
\r
960 if ((m_ptRubberEnd.x != 0)||(m_ptRubberEnd.y != 0))
\r
962 m_ptRubberEnd = point;
\r
964 GetClientRect(&rect);
\r
965 m_ptRubberEnd.x = max(m_ptRubberEnd.x, rect.left);
\r
966 m_ptRubberEnd.x = min(m_ptRubberEnd.x, rect.right);
\r
967 m_ptRubberEnd.y = max(m_ptRubberEnd.y, rect.top);
\r
968 m_ptRubberEnd.y = min(m_ptRubberEnd.y, rect.bottom);
\r
971 __super::OnMouseMove(nFlags, point);
\r
974 BOOL CRevisionGraphWnd::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
\r
976 CRect viewRect = GetViewRect();
\r
978 if ((nHitTest == HTCLIENT)&&(pWnd == this)&&(viewRect.Width())&&(viewRect.Height())&&(message))
\r
981 if (GetCursorPos(&pt))
\r
983 ScreenToClient(&pt);
\r
984 if (m_OverviewPosRect.PtInRect(pt))
\r
986 HCURSOR hCur = NULL;
\r
987 if (GetKeyState(VK_LBUTTON)&0x8000)
\r
988 hCur = LoadCursor(AfxGetResourceHandle(), MAKEINTRESOURCE(IDC_PANCURDOWN));
\r
990 hCur = LoadCursor(AfxGetResourceHandle(), MAKEINTRESOURCE(IDC_PANCUR));
\r
996 HCURSOR hCur = LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW));
\r
1001 LRESULT CRevisionGraphWnd::OnWorkerThreadDone(WPARAM, LPARAM)
\r
1005 Invalidate(FALSE);
\r
1008 LogCache::CRepositoryInfo& cachedProperties
\r
1009 = svn.GetLogCachePool()->GetRepositoryInfo();
\r
1010 SetDlgTitle (cachedProperties.IsOffline
\r
1011 ( m_fullHistory->GetRepositoryUUID()
\r
1012 , m_fullHistory->GetRepositoryRoot()
\r
1018 void CRevisionGraphWnd::SetDlgTitle (bool offline)
\r
1020 if (m_sTitle.IsEmpty())
\r
1021 GetParent()->GetWindowText(m_sTitle);
\r
1025 newTitle.Format (IDS_REVGRAPH_DLGTITLEOFFLINE, (LPCTSTR)m_sTitle);
\r
1027 newTitle = m_sTitle;
\r
1029 GetParent()->SetWindowText (newTitle);
\r