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.
20 #include "TortoiseProc.h"
21 #include "Revisiongraphwnd.h"
22 #include "MessageBox.h"
25 #include "PathUtils.h"
27 #include "UnicodeUtils.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"
42 static char THIS_FILE
[] = __FILE__
;
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
58 CRevisionGraphWnd::CRevisionGraphWnd()
60 , m_SelectedEntry1(NULL
)
61 , m_SelectedEntry2(NULL
)
62 , m_bThreadRunning(FALSE
)
65 , m_bTweakTrunkColors(true)
66 , m_bTweakTagsColors(true)
69 , m_ptRubberStart(0,0)
70 , m_bShowOverview(false)
72 memset(&m_lfBaseFont
, 0, sizeof(LOGFONT
));
73 for (int i
=0; i
<MAXFONTS
; i
++)
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
;
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();
117 void CRevisionGraphWnd::DoDataExchange(CDataExchange
* pDX
)
119 CWnd::DoDataExchange(pDX
);
123 BEGIN_MESSAGE_MAP(CRevisionGraphWnd
, CWnd
)
130 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW
, 0, 0xFFFF, OnToolTipNotify
)
131 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA
, 0, 0xFFFF, OnToolTipNotify
)
137 ON_MESSAGE(WM_WORKERTHREADDONE
,OnWorkerThreadDone
)
140 void CRevisionGraphWnd::Init(CWnd
* pParent
, LPRECT rect
)
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
;
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");
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
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.
213 case SB_LEFT
: // Scroll to far left.
214 sinfo
.nPos
= sinfo
.nMin
;
216 case SB_RIGHT
: // Scroll to far right.
217 sinfo
.nPos
= sinfo
.nMax
;
219 case SB_ENDSCROLL
: // End scroll.
221 case SB_LINELEFT
: // Scroll left.
222 if (sinfo
.nPos
> sinfo
.nMin
)
225 case SB_LINERIGHT
: // Scroll right.
226 if (sinfo
.nPos
< sinfo
.nMax
)
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
);
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
);
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.
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.
248 SetScrollInfo(SB_HORZ
, &sinfo
);
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.
262 case SB_LEFT
: // Scroll to far left.
263 sinfo
.nPos
= sinfo
.nMin
;
265 case SB_RIGHT
: // Scroll to far right.
266 sinfo
.nPos
= sinfo
.nMax
;
268 case SB_ENDSCROLL
: // End scroll.
270 case SB_LINELEFT
: // Scroll left.
271 if (sinfo
.nPos
> sinfo
.nMin
)
274 case SB_LINERIGHT
: // Scroll right.
275 if (sinfo
.nPos
< sinfo
.nMax
)
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
);
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
);
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.
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.
297 SetScrollInfo(SB_VERT
, &sinfo
);
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
));
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
);
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
;
326 if (m_SelectedEntry1
== reventry
)
328 if (m_SelectedEntry2
)
330 m_SelectedEntry1
= m_SelectedEntry2
;
331 m_SelectedEntry2
= NULL
;
334 m_SelectedEntry1
= NULL
;
336 else if (m_SelectedEntry2
== reventry
)
337 m_SelectedEntry2
= NULL
;
338 else if (m_SelectedEntry1
)
339 m_SelectedEntry2
= reventry
;
341 m_SelectedEntry1
= reventry
;
345 if (m_SelectedEntry1
== reventry
)
346 m_SelectedEntry1
= NULL
;
348 m_SelectedEntry1
= reventry
;
349 m_SelectedEntry2
= NULL
;
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
);
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
;
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;
389 if (m_bThreadRunning
)
390 return __super::OnLButtonUp(nFlags
, point
);
391 // zooming is finished
392 m_ptRubberEnd
= CPoint(0,0);
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
403 __super::OnLButtonUp(nFlags
, point
);
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
421 __super::OnLButtonUp(nFlags
, point
);
424 if (fZoomfactor
> 2.0)
427 fact
= fZoomfactor
/m_fZoomFactor
;
430 CRevisionGraphDlg
* pDlg
= (CRevisionGraphDlg
*)GetParent();
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
)
445 if (GetHitNode (point
) == NO_INDEX
)
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
;
457 BOOL
CRevisionGraphWnd::OnToolTipNotify(UINT
/*id*/, NMHDR
*pNMHDR
, LRESULT
*pResult
)
459 if (pNMHDR
->idFrom
!= (UINT
)m_hWnd
)
462 // need to handle both ANSI and UNICODE versions of the message
463 TOOLTIPTEXTA
* pTTTA
= (TOOLTIPTEXTA
*)pNMHDR
;
464 TOOLTIPTEXTW
* pTTTW
= (TOOLTIPTEXTW
*)pNMHDR
;
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
));
475 if (strTipText
.IsEmpty())
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);
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()
504 int screenWidth
= GetSystemMetrics(SM_CXSCREEN
);
505 int screenHeight
= GetSystemMetrics(SM_CYSCREEN
);
507 // get current mouse position
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
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
)
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(" ...")
541 // select the tooltip font
543 NONCLIENTMETRICS metrics
;
544 metrics
.cbSize
= sizeof (metrics
);
545 SystemParametersInfo (SPI_GETNONCLIENTMETRICS
, sizeof(NONCLIENTMETRICS
), &metrics
, 0);
548 font
.CreateFontIndirect(&metrics
.lfStatusFont
);
549 CFont
* pOldFont
= dc
->SelectObject (&font
);
551 // split into lines and fill the tooltip rect
555 int remainingHeight
= tooltipSize
.cy
;
557 while (pos
< wholeText
.GetLength())
559 // extract a whole line
561 int nextPos
= wholeText
.Find ('\n', pos
);
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 (' ');
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
)
593 pos
+= line
.GetLength();
596 // relase temp. resources
598 dc
->SelectObject (pOldFont
);
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
);
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
624 wmfDC
.CreateEnhanced(NULL
, sSavePath
, NULL
, _T("TortoiseSVN\0Revision Graph\0\0"));
625 float fZoom
= m_fZoomFactor
;
627 DoZoom(m_fZoomFactor
);
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
);
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!");
649 ATLTRACE("gdi plus not found!");
650 CMessageBox::Show(m_hWnd
, IDS_ERR_GDIPLUS_MISSING
, IDS_APPNAME
, MB_ICONERROR
);
654 // save the graph as a pixel picture instead of a vector picture
655 // create dc to paint on
660 if (!dc
.CreateCompatibleDC(&ddc
))
664 FORMAT_MESSAGE_ALLOCATE_BUFFER
|
665 FORMAT_MESSAGE_FROM_SYSTEM
|
666 FORMAT_MESSAGE_IGNORE_INSERTS
,
669 MAKELANGID(LANG_NEUTRAL
, SUBLANG_DEFAULT
), // Default language
676 MessageBox( (LPCTSTR
)lpMsgBuf
, _T("Error"), MB_OK
| MB_ICONINFORMATION
);
677 LocalFree( lpMsgBuf
);
681 rect
= GetViewRect();
682 HBITMAP hbm
= ::CreateCompatibleBitmap(ddc
.m_hDC
, rect
.Width(), rect
.Height());
687 FORMAT_MESSAGE_ALLOCATE_BUFFER
|
688 FORMAT_MESSAGE_FROM_SYSTEM
|
689 FORMAT_MESSAGE_IGNORE_INSERTS
,
692 MAKELANGID(LANG_NEUTRAL
, SUBLANG_DEFAULT
), // Default language
699 MessageBox( (LPCTSTR
)lpMsgBuf
, _T("Error"), MB_OK
| MB_ICONINFORMATION
);
700 LocalFree( lpMsgBuf
);
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
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.
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
);
731 sSavePath
+= _T(".jpg");
732 ret
= GetEncoderClsid(L
"image/jpeg", &encoderClsid
);
736 CStringW tfile
= CStringW(sSavePath
);
737 bitmap
.Save(tfile
, &encoderClsid
, NULL
);
741 sErrormessage
.Format(IDS_REVGRAPH_ERR_NOENCODER
, (LPCTSTR
)CPathUtils::GetFileExtFromPath(sSavePath
));
746 sErrormessage
.LoadString(IDS_REVGRAPH_ERR_NOBITMAP
);
749 GdiplusShutdown(gdiplusToken
);
753 sErrormessage
.LoadString(IDS_REVGRAPH_ERR_GDIINIT
);
755 dc
.SelectObject(oldbm
);
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
);
778 SetScrollPos(orientation
, pos
);
780 return __super::OnMouseWheel(nFlags
, zDelta
, pt
);
783 void CRevisionGraphWnd::OnContextMenu(CWnd
* /*pWnd*/, CPoint point
)
785 if (m_bThreadRunning
)
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
))
803 if (m_SelectedEntry1
== NULL
)
805 m_SelectedEntry1
= clickedentry
;
808 if ((m_SelectedEntry2
== NULL
)&&(clickedentry
!= m_SelectedEntry1
))
810 m_SelectedEntry1
= clickedentry
;
813 if (m_SelectedEntry1
&& m_SelectedEntry2
)
815 if ((m_SelectedEntry2
!= clickedentry
)&&(m_SelectedEntry1
!= clickedentry
))
818 if (m_SelectedEntry1
== NULL
)
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()));
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
);
840 temp
.LoadString(IDS_REVGRAPH_POPUP_COMPAREREVS
);
841 popup
.AppendMenu(MF_STRING
| MF_ENABLED
, ID_COMPAREREVS
, temp
);
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
);
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))
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
)
874 case ID_COMPAREHEADS
:
878 UnifiedDiffRevs(false);
880 case ID_UNIDIFFHEADS
:
881 UnifiedDiffRevs(true);
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")),
892 m_SelectedEntry1
->GetRevision());
894 if (!SVN::PathIsURL(CTSVNPath(m_sPath
)))
896 sCmd
+= _T(" /propspath:\"");
901 CAppUtils::LaunchApplication(sCmd
, NULL
, false);
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
)
916 dlg
.SetCommand(CSVNProgressDlg::SVNProgress_Merge
);
917 dlg
.SetPathList(CTSVNPathList(CTSVNPath(path
)));
919 dlg
.SetSecondUrl(URL
);
920 SVNRevRangeArray revarray
;
921 revarray
.AddRevRange(m_SelectedEntry1
->GetRevision(), svn_revnum_t(m_SelectedEntry1
->GetRevision())-1);
922 dlg
.SetRevisionRanges(revarray
);
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
))
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
);
947 return __super::OnMouseMove(nFlags
, point
);
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
);
960 if ((m_ptRubberEnd
.x
!= 0)||(m_ptRubberEnd
.y
!= 0))
962 m_ptRubberEnd
= point
;
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
);
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
))
981 if (GetCursorPos(&pt
))
984 if (m_OverviewPosRect
.PtInRect(pt
))
987 if (GetKeyState(VK_LBUTTON
)&0x8000)
988 hCur
= LoadCursor(AfxGetResourceHandle(), MAKEINTRESOURCE(IDC_PANCURDOWN
));
990 hCur
= LoadCursor(AfxGetResourceHandle(), MAKEINTRESOURCE(IDC_PANCUR
));
996 HCURSOR hCur
= LoadCursor(NULL
, MAKEINTRESOURCE(IDC_ARROW
));
1001 LRESULT
CRevisionGraphWnd::OnWorkerThreadDone(WPARAM
, LPARAM
)
1008 LogCache::CRepositoryInfo
& cachedProperties
1009 = svn
.GetLogCachePool()->GetRepositoryInfo();
1010 SetDlgTitle (cachedProperties
.IsOffline
1011 ( m_fullHistory
->GetRepositoryUUID()
1012 , m_fullHistory
->GetRepositoryRoot()
1018 void CRevisionGraphWnd::SetDlgTitle (bool offline
)
1020 if (m_sTitle
.IsEmpty())
1021 GetParent()->GetWindowText(m_sTitle
);
1025 newTitle
.Format (IDS_REVGRAPH_DLGTITLEOFFLINE
, (LPCTSTR
)m_sTitle
);
1027 newTitle
= m_sTitle
;
1029 GetParent()->SetWindowText (newTitle
);