Add Setting Dialog
[TortoiseGit.git] / src / TortoiseProc / RevisionGraph / RevisionGraphWnd.cpp
blob3f7d0d2955947cd29ed30a9774678f2b1d4447e8
1 // TortoiseSVN - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2008 - TortoiseSVN
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 #include "stdafx.h"
20 #include "TortoiseProc.h"
21 #include "Revisiongraphwnd.h"
22 #include "MessageBox.h"
23 #include "SVN.h"
24 #include "AppUtils.h"
25 #include "PathUtils.h"
26 #include "TempFile.h"
27 #include "UnicodeUtils.h"
28 #include "TSVNPath.h"
29 #include "SVNInfo.h"
30 #include "SVNDiff.h"
31 #include "RevisionGraphDlg.h"
32 #include "CachedLogInfo.h"
33 #include "RevisionIndex.h"
34 #include "RepositoryInfo.h"
35 #include "BrowseFolder.h"
36 #include "SVNProgressDlg.h"
37 #include "RevisionGraph/StandardLayout.h"
39 #ifdef _DEBUG
40 #define new DEBUG_NEW
41 #undef THIS_FILE
42 static char THIS_FILE[] = __FILE__;
43 #endif
45 using namespace Gdiplus;
47 enum RevisionGraphContextMenuCommands
49 // needs to start with 1, since 0 is the return value if *nothing* is clicked on in the context menu
50 ID_SHOWLOG = 1,
51 ID_COMPAREREVS,
52 ID_COMPAREHEADS,
53 ID_UNIDIFFREVS,
54 ID_UNIDIFFHEADS,
55 ID_MERGETO
58 CRevisionGraphWnd::CRevisionGraphWnd()
59 : CWnd()
60 , m_SelectedEntry1(NULL)
61 , m_SelectedEntry2(NULL)
62 , m_bThreadRunning(FALSE)
63 , m_pDlgTip(NULL)
64 , m_nFontSize(12)
65 , m_bTweakTrunkColors(true)
66 , m_bTweakTagsColors(true)
67 , m_fZoomFactor(1.0)
68 , m_ptRubberEnd(0,0)
69 , m_ptRubberStart(0,0)
70 , m_bShowOverview(false)
72 memset(&m_lfBaseFont, 0, sizeof(LOGFONT));
73 for (int i=0; i<MAXFONTS; i++)
75 m_apFonts[i] = NULL;
78 WNDCLASS wndcls;
79 HINSTANCE hInst = AfxGetInstanceHandle();
80 #define REVGRAPH_CLASSNAME _T("Revgraph_windowclass")
81 if (!(::GetClassInfo(hInst, REVGRAPH_CLASSNAME, &wndcls)))
83 // otherwise we need to register a new class
84 wndcls.style = CS_DBLCLKS | CS_OWNDC;
85 wndcls.lpfnWndProc = ::DefWindowProc;
86 wndcls.cbClsExtra = wndcls.cbWndExtra = 0;
87 wndcls.hInstance = hInst;
88 wndcls.hIcon = NULL;
89 wndcls.hCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
90 wndcls.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
91 wndcls.lpszMenuName = NULL;
92 wndcls.lpszClassName = REVGRAPH_CLASSNAME;
94 RegisterClass(&wndcls);
97 m_bShowOverview = CRegDWORD(_T("Software\\TortoiseGit\\RevisionGraph\\ShowRevGraphOverview"), TRUE);
98 m_bTweakTrunkColors = CRegDWORD(_T("Software\\TortoiseGit\\RevisionGraph\\TweakTrunkColors"), TRUE) != FALSE;
99 m_bTweakTagsColors = CRegDWORD(_T("Software\\TortoiseGit\\RevisionGraph\\TweakTagsColors"), TRUE) != FALSE;
102 CRevisionGraphWnd::~CRevisionGraphWnd()
104 for (int i=0; i<MAXFONTS; i++)
106 if (m_apFonts[i] != NULL)
108 m_apFonts[i]->DeleteObject();
109 delete m_apFonts[i];
111 m_apFonts[i] = NULL;
113 if (m_pDlgTip)
114 delete m_pDlgTip;
117 void CRevisionGraphWnd::DoDataExchange(CDataExchange* pDX)
119 CWnd::DoDataExchange(pDX);
123 BEGIN_MESSAGE_MAP(CRevisionGraphWnd, CWnd)
124 ON_WM_PAINT()
125 ON_WM_ERASEBKGND()
126 ON_WM_HSCROLL()
127 ON_WM_VSCROLL()
128 ON_WM_SIZE()
129 ON_WM_LBUTTONDOWN()
130 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipNotify)
131 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipNotify)
132 ON_WM_MOUSEWHEEL()
133 ON_WM_CONTEXTMENU()
134 ON_WM_MOUSEMOVE()
135 ON_WM_LBUTTONUP()
136 ON_WM_SETCURSOR()
137 ON_MESSAGE(WM_WORKERTHREADDONE,OnWorkerThreadDone)
138 END_MESSAGE_MAP()
140 void CRevisionGraphWnd::Init(CWnd * pParent, LPRECT rect)
142 WNDCLASS wndcls;
143 HINSTANCE hInst = AfxGetInstanceHandle();
144 #define REVGRAPH_CLASSNAME _T("Revgraph_windowclass")
145 if (!(::GetClassInfo(hInst, REVGRAPH_CLASSNAME, &wndcls)))
147 // otherwise we need to register a new class
148 wndcls.style = CS_DBLCLKS | CS_OWNDC;
149 wndcls.lpfnWndProc = ::DefWindowProc;
150 wndcls.cbClsExtra = wndcls.cbWndExtra = 0;
151 wndcls.hInstance = hInst;
152 wndcls.hIcon = NULL;
153 wndcls.hCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
154 wndcls.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
155 wndcls.lpszMenuName = NULL;
156 wndcls.lpszClassName = REVGRAPH_CLASSNAME;
158 RegisterClass(&wndcls);
161 if (!IsWindow(m_hWnd))
162 CreateEx(WS_EX_CLIENTEDGE, REVGRAPH_CLASSNAME, _T("RevGraph"), WS_CHILD|WS_VISIBLE|WS_TABSTOP, *rect, pParent, 0);
163 m_pDlgTip = new CToolTipCtrl;
164 if(!m_pDlgTip->Create(this))
166 TRACE("Unable to add tooltip!\n");
168 EnableToolTips();
170 memset(&m_lfBaseFont, 0, sizeof(m_lfBaseFont));
171 m_lfBaseFont.lfHeight = 0;
172 m_lfBaseFont.lfWeight = FW_NORMAL;
173 m_lfBaseFont.lfItalic = FALSE;
174 m_lfBaseFont.lfCharSet = DEFAULT_CHARSET;
175 m_lfBaseFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
176 m_lfBaseFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
177 m_lfBaseFont.lfQuality = DEFAULT_QUALITY;
178 m_lfBaseFont.lfPitchAndFamily = DEFAULT_PITCH;
180 m_dwTicks = GetTickCount();
183 index_t CRevisionGraphWnd::GetHitNode (CPoint point) const
185 // any nodes at all?
187 if (m_layout.get() == NULL)
188 return index_t(NO_INDEX);
190 // translate point into logical coordinates
192 int nVScrollPos = GetScrollPos(SB_VERT);
193 int nHScrollPos = GetScrollPos(SB_HORZ);
195 CSize logCoordinates ( (int)((point.x + nHScrollPos) / m_fZoomFactor)
196 , (int)((point.y + nVScrollPos) / m_fZoomFactor));
198 // search the nodes for one at that grid position
200 std::auto_ptr<const ILayoutNodeList> nodeList (m_layout->GetNodes());
201 return nodeList->GetAt (logCoordinates, 0);
204 void CRevisionGraphWnd::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
206 SCROLLINFO sinfo = {0};
207 sinfo.cbSize = sizeof(SCROLLINFO);
208 GetScrollInfo(SB_HORZ, &sinfo);
210 // Determine the new position of scroll box.
211 switch (nSBCode)
213 case SB_LEFT: // Scroll to far left.
214 sinfo.nPos = sinfo.nMin;
215 break;
216 case SB_RIGHT: // Scroll to far right.
217 sinfo.nPos = sinfo.nMax;
218 break;
219 case SB_ENDSCROLL: // End scroll.
220 break;
221 case SB_LINELEFT: // Scroll left.
222 if (sinfo.nPos > sinfo.nMin)
223 sinfo.nPos--;
224 break;
225 case SB_LINERIGHT: // Scroll right.
226 if (sinfo.nPos < sinfo.nMax)
227 sinfo.nPos++;
228 break;
229 case SB_PAGELEFT: // Scroll one page left.
231 if (sinfo.nPos > sinfo.nMin)
232 sinfo.nPos = max(sinfo.nMin, sinfo.nPos - (int) sinfo.nPage);
234 break;
235 case SB_PAGERIGHT: // Scroll one page right.
237 if (sinfo.nPos < sinfo.nMax)
238 sinfo.nPos = min(sinfo.nMax, sinfo.nPos + (int) sinfo.nPage);
240 break;
241 case SB_THUMBPOSITION: // Scroll to absolute position. nPos is the position
242 sinfo.nPos = sinfo.nTrackPos; // of the scroll box at the end of the drag operation.
243 break;
244 case SB_THUMBTRACK: // Drag scroll box to specified position. nPos is the
245 sinfo.nPos = sinfo.nTrackPos; // position that the scroll box has been dragged to.
246 break;
248 SetScrollInfo(SB_HORZ, &sinfo);
249 Invalidate();
250 __super::OnHScroll(nSBCode, nPos, pScrollBar);
253 void CRevisionGraphWnd::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
255 SCROLLINFO sinfo = {0};
256 sinfo.cbSize = sizeof(SCROLLINFO);
257 GetScrollInfo(SB_VERT, &sinfo);
259 // Determine the new position of scroll box.
260 switch (nSBCode)
262 case SB_LEFT: // Scroll to far left.
263 sinfo.nPos = sinfo.nMin;
264 break;
265 case SB_RIGHT: // Scroll to far right.
266 sinfo.nPos = sinfo.nMax;
267 break;
268 case SB_ENDSCROLL: // End scroll.
269 break;
270 case SB_LINELEFT: // Scroll left.
271 if (sinfo.nPos > sinfo.nMin)
272 sinfo.nPos--;
273 break;
274 case SB_LINERIGHT: // Scroll right.
275 if (sinfo.nPos < sinfo.nMax)
276 sinfo.nPos++;
277 break;
278 case SB_PAGELEFT: // Scroll one page left.
280 if (sinfo.nPos > sinfo.nMin)
281 sinfo.nPos = max(sinfo.nMin, sinfo.nPos - (int) sinfo.nPage);
283 break;
284 case SB_PAGERIGHT: // Scroll one page right.
286 if (sinfo.nPos < sinfo.nMax)
287 sinfo.nPos = min(sinfo.nMax, sinfo.nPos + (int) sinfo.nPage);
289 break;
290 case SB_THUMBPOSITION: // Scroll to absolute position. nPos is the position
291 sinfo.nPos = sinfo.nTrackPos; // of the scroll box at the end of the drag operation.
292 break;
293 case SB_THUMBTRACK: // Drag scroll box to specified position. nPos is the
294 sinfo.nPos = sinfo.nTrackPos; // position that the scroll box has been dragged to.
295 break;
297 SetScrollInfo(SB_VERT, &sinfo);
298 Invalidate();
299 __super::OnVScroll(nSBCode, nPos, pScrollBar);
302 void CRevisionGraphWnd::OnSize(UINT nType, int cx, int cy)
304 __super::OnSize(nType, cx, cy);
305 SetScrollbars(GetScrollPos(SB_VERT), GetScrollPos(SB_HORZ));
306 Invalidate(FALSE);
309 void CRevisionGraphWnd::OnLButtonDown(UINT nFlags, CPoint point)
311 if (m_bThreadRunning)
312 return __super::OnLButtonDown(nFlags, point);
313 ATLTRACE("right clicked on x=%d y=%d\n", point.x, point.y);
314 SetFocus();
315 bool bHit = false;
316 bool bControl = !!(GetKeyState(VK_CONTROL)&0x8000);
317 if (!m_OverviewRect.PtInRect(point))
319 index_t nodeIndex = GetHitNode (point);
320 if (nodeIndex != NO_INDEX)
322 std::auto_ptr<const ILayoutNodeList> nodeList (m_layout->GetNodes());
323 const CVisibleGraphNode* reventry = nodeList->GetNode (nodeIndex).node;
324 if (bControl)
326 if (m_SelectedEntry1 == reventry)
328 if (m_SelectedEntry2)
330 m_SelectedEntry1 = m_SelectedEntry2;
331 m_SelectedEntry2 = NULL;
333 else
334 m_SelectedEntry1 = NULL;
336 else if (m_SelectedEntry2 == reventry)
337 m_SelectedEntry2 = NULL;
338 else if (m_SelectedEntry1)
339 m_SelectedEntry2 = reventry;
340 else
341 m_SelectedEntry1 = reventry;
343 else
345 if (m_SelectedEntry1 == reventry)
346 m_SelectedEntry1 = NULL;
347 else
348 m_SelectedEntry1 = reventry;
349 m_SelectedEntry2 = NULL;
351 bHit = true;
352 Invalidate();
356 if ((!bHit)&&(!bControl))
358 m_SelectedEntry1 = NULL;
359 m_SelectedEntry2 = NULL;
360 m_bIsRubberBand = true;
361 ATLTRACE("LButtonDown: x = %ld, y = %ld\n", point.x, point.y);
362 Invalidate();
363 if (m_OverviewRect.PtInRect(point))
364 m_bIsRubberBand = false;
366 m_ptRubberStart = point;
368 UINT uEnable = MF_BYCOMMAND;
369 if ((m_SelectedEntry1 != NULL)&&(m_SelectedEntry2 != NULL))
370 uEnable |= MF_ENABLED;
371 else
372 uEnable |= MF_GRAYED;
374 EnableMenuItem(GetParent()->GetMenu()->m_hMenu, ID_VIEW_COMPAREREVISIONS, uEnable);
375 EnableMenuItem(GetParent()->GetMenu()->m_hMenu, ID_VIEW_COMPAREHEADREVISIONS, uEnable);
376 EnableMenuItem(GetParent()->GetMenu()->m_hMenu, ID_VIEW_UNIFIEDDIFF, uEnable);
377 EnableMenuItem(GetParent()->GetMenu()->m_hMenu, ID_VIEW_UNIFIEDDIFFOFHEADREVISIONS, uEnable);
379 __super::OnLButtonDown(nFlags, point);
382 void CRevisionGraphWnd::OnLButtonUp(UINT nFlags, CPoint point)
384 if (!m_bIsRubberBand)
385 return; // we don't have a rubberband, so no zooming necessary
387 m_bIsRubberBand = false;
388 ReleaseCapture();
389 if (m_bThreadRunning)
390 return __super::OnLButtonUp(nFlags, point);
391 // zooming is finished
392 m_ptRubberEnd = CPoint(0,0);
393 CRect rect;
394 GetClientRect(&rect);
395 int x = abs(m_ptRubberStart.x - point.x);
396 int y = abs(m_ptRubberStart.y - point.y);
398 if ((x < 20)&&(y < 20))
400 // too small zoom rectangle
401 // assume zooming by accident
402 Invalidate();
403 __super::OnLButtonUp(nFlags, point);
404 return;
407 float xfact = float(rect.Width())/float(x);
408 float yfact = float(rect.Height())/float(y);
409 float fact = max(yfact, xfact);
411 // find out where to scroll to
412 x = min(m_ptRubberStart.x, point.x) + GetScrollPos(SB_HORZ);
413 y = min(m_ptRubberStart.y, point.y) + GetScrollPos(SB_VERT);
415 float fZoomfactor = m_fZoomFactor*fact;
416 if (fZoomfactor > 20.0)
418 // with such a big zoomfactor, the user
419 // most likely zoomed by accident
420 Invalidate();
421 __super::OnLButtonUp(nFlags, point);
422 return;
424 if (fZoomfactor > 2.0)
426 fZoomfactor = 2.0;
427 fact = fZoomfactor/m_fZoomFactor;
430 CRevisionGraphDlg * pDlg = (CRevisionGraphDlg*)GetParent();
431 if (pDlg)
433 m_fZoomFactor = fZoomfactor;
434 pDlg->DoZoom (m_fZoomFactor);
435 SetScrollbars(int(float(y)*fact), int(float(x)*fact));
437 __super::OnLButtonUp(nFlags, point);
440 INT_PTR CRevisionGraphWnd::OnToolHitTest(CPoint point, TOOLINFO* pTI) const
442 if (m_bThreadRunning)
443 return -1;
445 if (GetHitNode (point) == NO_INDEX)
446 return -1;
448 pTI->hwnd = this->m_hWnd;
449 this->GetClientRect(&pTI->rect);
450 pTI->uFlags |= TTF_ALWAYSTIP | TTF_IDISHWND;
451 pTI->uId = (UINT)m_hWnd;
452 pTI->lpszText = LPSTR_TEXTCALLBACK;
454 return 1;
457 BOOL CRevisionGraphWnd::OnToolTipNotify(UINT /*id*/, NMHDR *pNMHDR, LRESULT *pResult)
459 if (pNMHDR->idFrom != (UINT)m_hWnd)
460 return FALSE;
462 // need to handle both ANSI and UNICODE versions of the message
463 TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;
464 TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;
466 POINT point;
467 DWORD ptW = GetMessagePos();
468 point.x = GET_X_LPARAM(ptW);
469 point.y = GET_Y_LPARAM(ptW);
470 ScreenToClient(&point);
472 CString strTipText = TooltipText (GetHitNode (point));
474 *pResult = 0;
475 if (strTipText.IsEmpty())
476 return TRUE;
478 CSize tooltipSize = UsableTooltipRect();
479 strTipText = DisplayableText (strTipText, tooltipSize);
481 if (pNMHDR->code == TTN_NEEDTEXTA)
483 ::SendMessage(pNMHDR->hwndFrom, TTM_SETMAXTIPWIDTH, 0, tooltipSize.cx);
484 pTTTA->lpszText = m_szTip;
485 WideCharToMultiByte(CP_ACP, 0, strTipText, -1, m_szTip, strTipText.GetLength()+1, 0, 0);
487 else
489 ::SendMessage(pNMHDR->hwndFrom, TTM_SETMAXTIPWIDTH, 0, tooltipSize.cx);
490 lstrcpyn(m_wszTip, strTipText, strTipText.GetLength()+1);
491 pTTTW->lpszText = m_wszTip;
494 // show the tooltip for 32 seconds. A higher value than 32767 won't work
495 // even though it's nowhere documented!
496 ::SendMessage(pNMHDR->hwndFrom, TTM_SETDELAYTIME, TTDT_AUTOPOP, 32767);
497 return TRUE; // message was handled
500 CSize CRevisionGraphWnd::UsableTooltipRect()
502 // get screen size
504 int screenWidth = GetSystemMetrics(SM_CXSCREEN);
505 int screenHeight = GetSystemMetrics(SM_CYSCREEN);
507 // get current mouse position
509 CPoint cursorPos;
510 if (GetCursorPos (&cursorPos) == FALSE)
512 // we could not determine the mouse position
513 // use screen / 2 minus some safety margin
515 return CSize (screenWidth / 2 - 10, screenHeight / 2 - 10);
518 // tool tip will display in the biggest sector beside the cursor
519 // deduct some safety margin (for the mouse cursor itself
521 CSize biggestSector
522 ( max (screenWidth - cursorPos.x - 20, cursorPos.x - 4)
523 , max (screenHeight - cursorPos.y - 20, cursorPos.y - 4));
525 return biggestSector;
528 CString CRevisionGraphWnd::DisplayableText ( const CString& wholeText
529 , const CSize& tooltipSize)
531 CDC* dc = GetDC();
532 if (dc == NULL)
534 // no access to the device context -> truncate hard at 1000 chars
536 return wholeText.GetLength() >= MAX_TT_LENGTH_DEFAULT
537 ? wholeText.Left (MAX_TT_LENGTH_DEFAULT-4) + _T(" ...")
538 : wholeText;
541 // select the tooltip font
543 NONCLIENTMETRICS metrics;
544 metrics.cbSize = sizeof (metrics);
545 SystemParametersInfo (SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &metrics, 0);
547 CFont font;
548 font.CreateFontIndirect(&metrics.lfStatusFont);
549 CFont* pOldFont = dc->SelectObject (&font);
551 // split into lines and fill the tooltip rect
553 CString result;
555 int remainingHeight = tooltipSize.cy;
556 int pos = 0;
557 while (pos < wholeText.GetLength())
559 // extract a whole line
561 int nextPos = wholeText.Find ('\n', pos);
562 if (nextPos < 0)
563 nextPos = wholeText.GetLength();
565 CString line = wholeText.Mid (pos, nextPos-pos+1);
567 // find a way to make it fit
569 CSize size = dc->GetTextExtent (line);
570 while (size.cx > tooltipSize.cx)
572 line.Delete (line.GetLength()-1);
573 int nextPos = line.ReverseFind (' ');
574 if (nextPos < 0)
575 break;
577 line.Delete (pos+1, line.GetLength() - pos-1);
578 size = dc->GetTextExtent (line);
581 // enough room for the new line?
583 remainingHeight -= size.cy;
584 if (remainingHeight <= size.cy)
586 result += _T("...");
587 break;
590 // add the line
592 result += line;
593 pos += line.GetLength();
596 // relase temp. resources
598 dc->SelectObject (pOldFont);
599 ReleaseDC(dc);
601 // ready
603 return result;
606 CString CRevisionGraphWnd::TooltipText (index_t index)
608 if (index != NO_INDEX)
610 std::auto_ptr<const ILayoutNodeList> nodeList (m_layout->GetNodes());
611 return nodeList->GetToolTip (index);
614 return CString();
617 void CRevisionGraphWnd::SaveGraphAs(CString sSavePath)
619 CString extension = CPathUtils::GetFileExtFromPath(sSavePath);
620 if (extension.CompareNoCase(_T(".wmf"))==0)
622 // save the graph as an enhanced metafile
623 CMetaFileDC wmfDC;
624 wmfDC.CreateEnhanced(NULL, sSavePath, NULL, _T("TortoiseSVN\0Revision Graph\0\0"));
625 float fZoom = m_fZoomFactor;
626 m_fZoomFactor = 1.0;
627 DoZoom(m_fZoomFactor);
628 CRect rect;
629 rect = GetViewRect();
630 DrawGraph(&wmfDC, rect, 0, 0, true);
631 HENHMETAFILE hemf = wmfDC.CloseEnhanced();
632 DeleteEnhMetaFile(hemf);
633 m_fZoomFactor = fZoom;
634 DoZoom(m_fZoomFactor);
636 else
638 // to save the graph as a pixel picture (e.g. gif, png, jpeg, ...)
639 // the user needs to have GDI+ installed. So check if GDI+ is
640 // available before we start using it.
641 TCHAR gdifindbuf[MAX_PATH];
642 _tcscpy_s(gdifindbuf, MAX_PATH, _T("gdiplus.dll"));
643 if (PathFindOnPath(gdifindbuf, NULL))
645 ATLTRACE("gdi plus found!");
647 else
649 ATLTRACE("gdi plus not found!");
650 CMessageBox::Show(m_hWnd, IDS_ERR_GDIPLUS_MISSING, IDS_APPNAME, MB_ICONERROR);
651 return;
654 // save the graph as a pixel picture instead of a vector picture
655 // create dc to paint on
658 CWindowDC ddc(this);
659 CDC dc;
660 if (!dc.CreateCompatibleDC(&ddc))
662 LPVOID lpMsgBuf;
663 if (!FormatMessage(
664 FORMAT_MESSAGE_ALLOCATE_BUFFER |
665 FORMAT_MESSAGE_FROM_SYSTEM |
666 FORMAT_MESSAGE_IGNORE_INSERTS,
667 NULL,
668 GetLastError(),
669 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
670 (LPTSTR) &lpMsgBuf,
672 NULL ))
674 return;
676 MessageBox( (LPCTSTR)lpMsgBuf, _T("Error"), MB_OK | MB_ICONINFORMATION );
677 LocalFree( lpMsgBuf );
678 return;
680 CRect rect;
681 rect = GetViewRect();
682 HBITMAP hbm = ::CreateCompatibleBitmap(ddc.m_hDC, rect.Width(), rect.Height());
683 if (hbm==0)
685 LPVOID lpMsgBuf;
686 if (!FormatMessage(
687 FORMAT_MESSAGE_ALLOCATE_BUFFER |
688 FORMAT_MESSAGE_FROM_SYSTEM |
689 FORMAT_MESSAGE_IGNORE_INSERTS,
690 NULL,
691 GetLastError(),
692 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
693 (LPTSTR) &lpMsgBuf,
695 NULL ))
697 return;
699 MessageBox( (LPCTSTR)lpMsgBuf, _T("Error"), MB_OK | MB_ICONINFORMATION );
700 LocalFree( lpMsgBuf );
701 return;
703 HBITMAP oldbm = (HBITMAP)dc.SelectObject(hbm);
704 // paint the whole graph
705 DrawGraph(&dc, rect, 0, 0, false);
706 // now use GDI+ to save the picture
707 CLSID encoderClsid;
708 GdiplusStartupInput gdiplusStartupInput;
709 ULONG_PTR gdiplusToken;
710 CString sErrormessage;
711 if (GdiplusStartup( &gdiplusToken, &gdiplusStartupInput, NULL )==Ok)
714 Bitmap bitmap(hbm, NULL);
715 if (bitmap.GetLastStatus()==Ok)
717 // Get the CLSID of the encoder.
718 int ret = 0;
719 if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(_T(".png"))==0)
720 ret = GetEncoderClsid(L"image/png", &encoderClsid);
721 else if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(_T(".jpg"))==0)
722 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
723 else if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(_T(".jpeg"))==0)
724 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
725 else if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(_T(".bmp"))==0)
726 ret = GetEncoderClsid(L"image/bmp", &encoderClsid);
727 else if (CPathUtils::GetFileExtFromPath(sSavePath).CompareNoCase(_T(".gif"))==0)
728 ret = GetEncoderClsid(L"image/gif", &encoderClsid);
729 else
731 sSavePath += _T(".jpg");
732 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
734 if (ret >= 0)
736 CStringW tfile = CStringW(sSavePath);
737 bitmap.Save(tfile, &encoderClsid, NULL);
739 else
741 sErrormessage.Format(IDS_REVGRAPH_ERR_NOENCODER, (LPCTSTR)CPathUtils::GetFileExtFromPath(sSavePath));
744 else
746 sErrormessage.LoadString(IDS_REVGRAPH_ERR_NOBITMAP);
749 GdiplusShutdown(gdiplusToken);
751 else
753 sErrormessage.LoadString(IDS_REVGRAPH_ERR_GDIINIT);
755 dc.SelectObject(oldbm);
756 dc.DeleteDC();
757 if (!sErrormessage.IsEmpty())
759 CMessageBox::Show(m_hWnd, sErrormessage, _T("TortoiseSVN"), MB_ICONERROR);
762 catch (CException * pE)
764 TCHAR szErrorMsg[2048];
765 pE->GetErrorMessage(szErrorMsg, 2048);
766 CMessageBox::Show(m_hWnd, szErrorMsg, _T("TortoiseSVN"), MB_ICONERROR);
771 BOOL CRevisionGraphWnd::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
773 if (m_bThreadRunning)
774 return __super::OnMouseWheel(nFlags, zDelta, pt);
775 int orientation = GetKeyState(VK_CONTROL)&0x8000 ? SB_HORZ : SB_VERT;
776 int pos = GetScrollPos(orientation);
777 pos -= (zDelta);
778 SetScrollPos(orientation, pos);
779 Invalidate();
780 return __super::OnMouseWheel(nFlags, zDelta, pt);
783 void CRevisionGraphWnd::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
785 if (m_bThreadRunning)
786 return;
788 CPoint clientpoint = point;
789 this->ScreenToClient(&clientpoint);
790 ATLTRACE("right clicked on x=%d y=%d\n", clientpoint.x, clientpoint.y);
792 index_t nodeIndex = GetHitNode (clientpoint);
793 const CVisibleGraphNode * clickedentry = NULL;
794 if (nodeIndex != NO_INDEX)
796 std::auto_ptr<const ILayoutNodeList> nodeList (m_layout->GetNodes());
797 clickedentry = nodeList->GetNode (nodeIndex).node;
800 if ((m_SelectedEntry1 == NULL)&&(clickedentry == NULL))
801 return;
803 if (m_SelectedEntry1 == NULL)
805 m_SelectedEntry1 = clickedentry;
806 Invalidate();
808 if ((m_SelectedEntry2 == NULL)&&(clickedentry != m_SelectedEntry1))
810 m_SelectedEntry1 = clickedentry;
811 Invalidate();
813 if (m_SelectedEntry1 && m_SelectedEntry2)
815 if ((m_SelectedEntry2 != clickedentry)&&(m_SelectedEntry1 != clickedentry))
816 return;
818 if (m_SelectedEntry1 == NULL)
819 return;
820 CMenu popup;
821 if (popup.CreatePopupMenu())
823 bool bothPresent = (m_SelectedEntry1 != NULL)
824 && !m_SelectedEntry1->GetClassification().Is (CNodeClassification::IS_DELETED)
825 && (m_SelectedEntry2 != NULL)
826 && !m_SelectedEntry2->GetClassification().Is (CNodeClassification::IS_DELETED);
828 bool bSameURL = (m_SelectedEntry2 && (m_SelectedEntry1->GetPath() == m_SelectedEntry2->GetPath()));
829 CString temp;
830 if (m_SelectedEntry1 && (m_SelectedEntry2 == NULL))
832 temp.LoadString(IDS_REPOBROWSE_SHOWLOG);
833 popup.AppendMenu(MF_STRING | MF_ENABLED, ID_SHOWLOG, temp);
834 popup.AppendMenu(MF_SEPARATOR, NULL);
835 temp.LoadString(IDS_LOG_POPUP_MERGEREV);
836 popup.AppendMenu(MF_STRING | MF_ENABLED, ID_MERGETO, temp);
838 if (bothPresent)
840 temp.LoadString(IDS_REVGRAPH_POPUP_COMPAREREVS);
841 popup.AppendMenu(MF_STRING | MF_ENABLED, ID_COMPAREREVS, temp);
842 if (!bSameURL)
844 temp.LoadString(IDS_REVGRAPH_POPUP_COMPAREHEADS);
845 popup.AppendMenu(MF_STRING | MF_ENABLED, ID_COMPAREHEADS, temp);
848 temp.LoadString(IDS_REVGRAPH_POPUP_UNIDIFFREVS);
849 popup.AppendMenu(MF_STRING | MF_ENABLED, ID_UNIDIFFREVS, temp);
850 if (!bSameURL)
852 temp.LoadString(IDS_REVGRAPH_POPUP_UNIDIFFHEADS);
853 popup.AppendMenu(MF_STRING | MF_ENABLED, ID_UNIDIFFHEADS, temp);
857 // if the context menu is invoked through the keyboard, we have to use
858 // a calculated position on where to anchor the menu on
859 if ((point.x == -1) && (point.y == -1))
861 CRect rect;
862 GetWindowRect(&rect);
863 point = rect.CenterPoint();
866 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
867 if (m_SelectedEntry1 == NULL)
868 return;
869 switch (cmd)
871 case ID_COMPAREREVS:
872 CompareRevs(false);
873 break;
874 case ID_COMPAREHEADS:
875 CompareRevs(true);
876 break;
877 case ID_UNIDIFFREVS:
878 UnifiedDiffRevs(false);
879 break;
880 case ID_UNIDIFFHEADS:
881 UnifiedDiffRevs(true);
882 break;
883 case ID_SHOWLOG:
885 CString sCmd;
886 CString URL = m_fullHistory->GetRepositoryRoot()
887 + CUnicodeUtils::GetUnicode (m_SelectedEntry1->GetPath().GetPath().c_str());
888 URL = CUnicodeUtils::GetUnicode(CPathUtils::PathEscape(CUnicodeUtils::GetUTF8(URL)));
889 sCmd.Format(_T("\"%s\" /command:log /path:\"%s\" /startrev:%ld"),
890 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")),
891 (LPCTSTR)URL,
892 m_SelectedEntry1->GetRevision());
894 if (!SVN::PathIsURL(CTSVNPath(m_sPath)))
896 sCmd += _T(" /propspath:\"");
897 sCmd += m_sPath;
898 sCmd += _T("\"");
901 CAppUtils::LaunchApplication(sCmd, NULL, false);
903 break;
904 case ID_MERGETO:
906 CString URL = m_fullHistory->GetRepositoryRoot()
907 + CUnicodeUtils::GetUnicode (m_SelectedEntry1->GetPath().GetPath().c_str());
908 URL = CUnicodeUtils::GetUnicode(CPathUtils::PathEscape(CUnicodeUtils::GetUTF8(URL)));
910 CString path = m_sPath;
911 CBrowseFolder folderBrowser;
912 folderBrowser.SetInfo(CString(MAKEINTRESOURCE(IDS_LOG_MERGETO)));
913 if (folderBrowser.Show(GetSafeHwnd(), path, path) == CBrowseFolder::OK)
915 CSVNProgressDlg dlg;
916 dlg.SetCommand(CSVNProgressDlg::SVNProgress_Merge);
917 dlg.SetPathList(CTSVNPathList(CTSVNPath(path)));
918 dlg.SetUrl(URL);
919 dlg.SetSecondUrl(URL);
920 SVNRevRangeArray revarray;
921 revarray.AddRevRange(m_SelectedEntry1->GetRevision(), svn_revnum_t(m_SelectedEntry1->GetRevision())-1);
922 dlg.SetRevisionRanges(revarray);
923 dlg.DoModal();
926 break;
931 void CRevisionGraphWnd::OnMouseMove(UINT nFlags, CPoint point)
933 if (m_bThreadRunning)
935 return __super::OnMouseMove(nFlags, point);
937 if (!m_bIsRubberBand)
939 if ((!m_OverviewRect.IsRectEmpty())&&(m_OverviewRect.PtInRect(point))&&(nFlags & MK_LBUTTON))
941 // scrolling
942 CRect viewRect = GetViewRect();
943 int x = (int)((point.x-m_OverviewRect.left - (m_OverviewPosRect.Width()/2)) / m_previewZoom * m_fZoomFactor);
944 int y = (int)((point.y - (m_OverviewPosRect.Height()/2)) / m_previewZoom * m_fZoomFactor);
945 SetScrollbars(y, x);
946 Invalidate(FALSE);
947 return __super::OnMouseMove(nFlags, point);
949 else
950 return __super::OnMouseMove(nFlags, point);
953 if ((abs(m_ptRubberStart.x - point.x) < 2)&&(abs(m_ptRubberStart.y - point.y) < 2))
955 return __super::OnMouseMove(nFlags, point);
958 SetCapture();
960 if ((m_ptRubberEnd.x != 0)||(m_ptRubberEnd.y != 0))
961 DrawRubberBand();
962 m_ptRubberEnd = point;
963 CRect rect;
964 GetClientRect(&rect);
965 m_ptRubberEnd.x = max(m_ptRubberEnd.x, rect.left);
966 m_ptRubberEnd.x = min(m_ptRubberEnd.x, rect.right);
967 m_ptRubberEnd.y = max(m_ptRubberEnd.y, rect.top);
968 m_ptRubberEnd.y = min(m_ptRubberEnd.y, rect.bottom);
969 DrawRubberBand();
971 __super::OnMouseMove(nFlags, point);
974 BOOL CRevisionGraphWnd::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
976 CRect viewRect = GetViewRect();
978 if ((nHitTest == HTCLIENT)&&(pWnd == this)&&(viewRect.Width())&&(viewRect.Height())&&(message))
980 POINT pt;
981 if (GetCursorPos(&pt))
983 ScreenToClient(&pt);
984 if (m_OverviewPosRect.PtInRect(pt))
986 HCURSOR hCur = NULL;
987 if (GetKeyState(VK_LBUTTON)&0x8000)
988 hCur = LoadCursor(AfxGetResourceHandle(), MAKEINTRESOURCE(IDC_PANCURDOWN));
989 else
990 hCur = LoadCursor(AfxGetResourceHandle(), MAKEINTRESOURCE(IDC_PANCUR));
991 SetCursor(hCur);
992 return TRUE;
996 HCURSOR hCur = LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW));
997 SetCursor(hCur);
998 return TRUE;
1001 LRESULT CRevisionGraphWnd::OnWorkerThreadDone(WPARAM, LPARAM)
1003 InitView();
1004 BuildPreview();
1005 Invalidate(FALSE);
1007 SVN svn;
1008 LogCache::CRepositoryInfo& cachedProperties
1009 = svn.GetLogCachePool()->GetRepositoryInfo();
1010 SetDlgTitle (cachedProperties.IsOffline
1011 ( m_fullHistory->GetRepositoryUUID()
1012 , m_fullHistory->GetRepositoryRoot()
1013 , false));
1015 return 0;
1018 void CRevisionGraphWnd::SetDlgTitle (bool offline)
1020 if (m_sTitle.IsEmpty())
1021 GetParent()->GetWindowText(m_sTitle);
1023 CString newTitle;
1024 if (offline)
1025 newTitle.Format (IDS_REVGRAPH_DLGTITLEOFFLINE, (LPCTSTR)m_sTitle);
1026 else
1027 newTitle = m_sTitle;
1029 GetParent()->SetWindowText (newTitle);