OSDN Git Service

Progress Bar Show Animate
[tortoisegit/TortoiseGitJp.git] / src / TortoiseProc / RevisionGraph / RevisionGraphWnd.cpp
1 // TortoiseSVN - a Windows shell extension for easy version control\r
2 \r
3 // Copyright (C) 2003-2008 - TortoiseSVN\r
4 \r
5 // This program is free software; you can redistribute it and/or\r
6 // modify it under the terms of the GNU General Public License\r
7 // as published by the Free Software Foundation; either version 2\r
8 // of the License, or (at your option) any later version.\r
9 \r
10 // This program is distributed in the hope that it will be useful,\r
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of\r
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
13 // GNU General Public License for more details.\r
14 \r
15 // You should have received a copy of the GNU General Public License\r
16 // along with this program; if not, write to the Free Software Foundation,\r
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\r
18 //\r
19 #include "stdafx.h"\r
20 #include "TortoiseProc.h"\r
21 #include "Revisiongraphwnd.h"\r
22 #include "MessageBox.h"\r
23 #include "SVN.h"\r
24 #include "AppUtils.h"\r
25 #include "PathUtils.h"\r
26 #include "TempFile.h"\r
27 #include "UnicodeUtils.h"\r
28 #include "TSVNPath.h"\r
29 #include "SVNInfo.h"\r
30 #include "SVNDiff.h"\r
31 #include "RevisionGraphDlg.h"\r
32 #include "CachedLogInfo.h"\r
33 #include "RevisionIndex.h"\r
34 #include "RepositoryInfo.h"\r
35 #include "BrowseFolder.h"\r
36 #include "SVNProgressDlg.h"\r
37 #include "RevisionGraph/StandardLayout.h"\r
38 \r
39 #ifdef _DEBUG\r
40 #define new DEBUG_NEW\r
41 #undef THIS_FILE\r
42 static char THIS_FILE[] = __FILE__;\r
43 #endif\r
44 \r
45 using namespace Gdiplus;\r
46 \r
47 enum RevisionGraphContextMenuCommands\r
48 {\r
49         // needs to start with 1, since 0 is the return value if *nothing* is clicked on in the context menu\r
50         ID_SHOWLOG = 1,\r
51         ID_COMPAREREVS,\r
52         ID_COMPAREHEADS,\r
53         ID_UNIDIFFREVS,\r
54         ID_UNIDIFFHEADS,\r
55         ID_MERGETO\r
56 };\r
57 \r
58 CRevisionGraphWnd::CRevisionGraphWnd()\r
59         : CWnd()\r
60         , m_SelectedEntry1(NULL)\r
61         , m_SelectedEntry2(NULL)\r
62         , m_bThreadRunning(FALSE)\r
63         , m_pDlgTip(NULL)\r
64         , m_nFontSize(12)\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
71 {\r
72         memset(&m_lfBaseFont, 0, sizeof(LOGFONT));      \r
73         for (int i=0; i<MAXFONTS; i++)\r
74         {\r
75                 m_apFonts[i] = NULL;\r
76         }\r
77 \r
78         WNDCLASS wndcls;\r
79         HINSTANCE hInst = AfxGetInstanceHandle();\r
80 #define REVGRAPH_CLASSNAME _T("Revgraph_windowclass")\r
81         if (!(::GetClassInfo(hInst, REVGRAPH_CLASSNAME, &wndcls)))\r
82         {\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
93 \r
94                 RegisterClass(&wndcls);\r
95         }\r
96 \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
100 }\r
101 \r
102 CRevisionGraphWnd::~CRevisionGraphWnd()\r
103 {\r
104         for (int i=0; i<MAXFONTS; i++)\r
105         {\r
106                 if (m_apFonts[i] != NULL)\r
107                 {\r
108                         m_apFonts[i]->DeleteObject();\r
109                         delete m_apFonts[i];\r
110                 }\r
111                 m_apFonts[i] = NULL;\r
112         }\r
113         if (m_pDlgTip)\r
114                 delete m_pDlgTip;\r
115 }\r
116 \r
117 void CRevisionGraphWnd::DoDataExchange(CDataExchange* pDX)\r
118 {\r
119         CWnd::DoDataExchange(pDX);\r
120 }\r
121 \r
122 \r
123 BEGIN_MESSAGE_MAP(CRevisionGraphWnd, CWnd)\r
124         ON_WM_PAINT()\r
125         ON_WM_ERASEBKGND()\r
126         ON_WM_HSCROLL()\r
127         ON_WM_VSCROLL()\r
128         ON_WM_SIZE()\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
132         ON_WM_MOUSEWHEEL()\r
133         ON_WM_CONTEXTMENU()\r
134         ON_WM_MOUSEMOVE()\r
135         ON_WM_LBUTTONUP()\r
136         ON_WM_SETCURSOR()\r
137     ON_MESSAGE(WM_WORKERTHREADDONE,OnWorkerThreadDone)\r
138 END_MESSAGE_MAP()\r
139 \r
140 void CRevisionGraphWnd::Init(CWnd * pParent, LPRECT rect)\r
141 {\r
142         WNDCLASS wndcls;\r
143         HINSTANCE hInst = AfxGetInstanceHandle();\r
144 #define REVGRAPH_CLASSNAME _T("Revgraph_windowclass")\r
145         if (!(::GetClassInfo(hInst, REVGRAPH_CLASSNAME, &wndcls)))\r
146         {\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
157 \r
158                 RegisterClass(&wndcls);\r
159         }\r
160 \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
165         {\r
166                 TRACE("Unable to add tooltip!\n");\r
167         }\r
168         EnableToolTips();\r
169 \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
179 \r
180         m_dwTicks = GetTickCount();\r
181 }\r
182 \r
183 index_t CRevisionGraphWnd::GetHitNode (CPoint point) const\r
184 {\r
185     // any nodes at all?\r
186 \r
187     if (m_layout.get() == NULL)\r
188         return index_t(NO_INDEX);\r
189 \r
190     // translate point into logical coordinates\r
191 \r
192     int nVScrollPos = GetScrollPos(SB_VERT);\r
193     int nHScrollPos = GetScrollPos(SB_HORZ);\r
194 \r
195     CSize logCoordinates ( (int)((point.x + nHScrollPos) / m_fZoomFactor)\r
196                          , (int)((point.y + nVScrollPos) / m_fZoomFactor));\r
197 \r
198     // search the nodes for one at that grid position\r
199 \r
200     std::auto_ptr<const ILayoutNodeList> nodeList (m_layout->GetNodes());\r
201     return nodeList->GetAt (logCoordinates, 0);\r
202 }\r
203 \r
204 void CRevisionGraphWnd::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)\r
205 {\r
206         SCROLLINFO sinfo = {0};\r
207         sinfo.cbSize = sizeof(SCROLLINFO);\r
208         GetScrollInfo(SB_HORZ, &sinfo);\r
209 \r
210         // Determine the new position of scroll box.\r
211         switch (nSBCode)\r
212         {\r
213         case SB_LEFT:      // Scroll to far left.\r
214                 sinfo.nPos = sinfo.nMin;\r
215                 break;\r
216         case SB_RIGHT:      // Scroll to far right.\r
217                 sinfo.nPos = sinfo.nMax;\r
218                 break;\r
219         case SB_ENDSCROLL:   // End scroll.\r
220                 break;\r
221         case SB_LINELEFT:      // Scroll left.\r
222                 if (sinfo.nPos > sinfo.nMin)\r
223                         sinfo.nPos--;\r
224                 break;\r
225         case SB_LINERIGHT:   // Scroll right.\r
226                 if (sinfo.nPos < sinfo.nMax)\r
227                         sinfo.nPos++;\r
228                 break;\r
229         case SB_PAGELEFT:    // Scroll one page left.\r
230                 {\r
231                         if (sinfo.nPos > sinfo.nMin)\r
232                                 sinfo.nPos = max(sinfo.nMin, sinfo.nPos - (int) sinfo.nPage);\r
233                 }\r
234                 break;\r
235         case SB_PAGERIGHT:      // Scroll one page right.\r
236                 {\r
237                         if (sinfo.nPos < sinfo.nMax)\r
238                                 sinfo.nPos = min(sinfo.nMax, sinfo.nPos + (int) sinfo.nPage);\r
239                 }\r
240                 break;\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
243                 break;\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
246                 break;\r
247         }\r
248         SetScrollInfo(SB_HORZ, &sinfo);\r
249         Invalidate();\r
250         __super::OnHScroll(nSBCode, nPos, pScrollBar);\r
251 }\r
252 \r
253 void CRevisionGraphWnd::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)\r
254 {\r
255         SCROLLINFO sinfo = {0};\r
256         sinfo.cbSize = sizeof(SCROLLINFO);\r
257         GetScrollInfo(SB_VERT, &sinfo);\r
258 \r
259         // Determine the new position of scroll box.\r
260         switch (nSBCode)\r
261         {\r
262         case SB_LEFT:      // Scroll to far left.\r
263                 sinfo.nPos = sinfo.nMin;\r
264                 break;\r
265         case SB_RIGHT:      // Scroll to far right.\r
266                 sinfo.nPos = sinfo.nMax;\r
267                 break;\r
268         case SB_ENDSCROLL:   // End scroll.\r
269                 break;\r
270         case SB_LINELEFT:      // Scroll left.\r
271                 if (sinfo.nPos > sinfo.nMin)\r
272                         sinfo.nPos--;\r
273                 break;\r
274         case SB_LINERIGHT:   // Scroll right.\r
275                 if (sinfo.nPos < sinfo.nMax)\r
276                         sinfo.nPos++;\r
277                 break;\r
278         case SB_PAGELEFT:    // Scroll one page left.\r
279                 {\r
280                         if (sinfo.nPos > sinfo.nMin)\r
281                                 sinfo.nPos = max(sinfo.nMin, sinfo.nPos - (int) sinfo.nPage);\r
282                 }\r
283                 break;\r
284         case SB_PAGERIGHT:      // Scroll one page right.\r
285                 {\r
286                         if (sinfo.nPos < sinfo.nMax)\r
287                                 sinfo.nPos = min(sinfo.nMax, sinfo.nPos + (int) sinfo.nPage);\r
288                 }\r
289                 break;\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
292                 break;\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
295                 break;\r
296         }\r
297         SetScrollInfo(SB_VERT, &sinfo);\r
298         Invalidate();\r
299         __super::OnVScroll(nSBCode, nPos, pScrollBar);\r
300 }\r
301 \r
302 void CRevisionGraphWnd::OnSize(UINT nType, int cx, int cy)\r
303 {\r
304         __super::OnSize(nType, cx, cy);\r
305         SetScrollbars(GetScrollPos(SB_VERT), GetScrollPos(SB_HORZ));\r
306         Invalidate(FALSE);\r
307 }\r
308 \r
309 void CRevisionGraphWnd::OnLButtonDown(UINT nFlags, CPoint point)\r
310 {\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
314         SetFocus();\r
315         bool bHit = false;\r
316         bool bControl = !!(GetKeyState(VK_CONTROL)&0x8000);\r
317         if (!m_OverviewRect.PtInRect(point))\r
318         {\r
319         index_t nodeIndex = GetHitNode (point);\r
320             if (nodeIndex != NO_INDEX)\r
321             {\r
322             std::auto_ptr<const ILayoutNodeList> nodeList (m_layout->GetNodes());\r
323             const CVisibleGraphNode* reventry = nodeList->GetNode (nodeIndex).node;\r
324                     if (bControl)\r
325                     {\r
326                             if (m_SelectedEntry1 == reventry)\r
327                             {\r
328                                     if (m_SelectedEntry2)\r
329                                     {\r
330                                             m_SelectedEntry1 = m_SelectedEntry2;\r
331                                             m_SelectedEntry2 = NULL;\r
332                                     }\r
333                                     else\r
334                                             m_SelectedEntry1 = NULL;\r
335                             }\r
336                             else if (m_SelectedEntry2 == reventry)\r
337                                     m_SelectedEntry2 = NULL;\r
338                             else if (m_SelectedEntry1)\r
339                                     m_SelectedEntry2 = reventry;\r
340                             else\r
341                                     m_SelectedEntry1 = reventry;\r
342                     }\r
343                     else\r
344                     {\r
345                             if (m_SelectedEntry1 == reventry)\r
346                                     m_SelectedEntry1 = NULL;\r
347                             else\r
348                                     m_SelectedEntry1 = reventry;\r
349                             m_SelectedEntry2 = NULL;\r
350                     }\r
351                     bHit = true;\r
352                     Invalidate();\r
353             }\r
354     }\r
355 \r
356     if ((!bHit)&&(!bControl))\r
357         {\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
362                 Invalidate();\r
363                 if (m_OverviewRect.PtInRect(point))\r
364                         m_bIsRubberBand = false;\r
365         }\r
366         m_ptRubberStart = point;\r
367         \r
368         UINT uEnable = MF_BYCOMMAND;\r
369         if ((m_SelectedEntry1 != NULL)&&(m_SelectedEntry2 != NULL))\r
370                 uEnable |= MF_ENABLED;\r
371         else\r
372                 uEnable |= MF_GRAYED;\r
373 \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
378         \r
379         __super::OnLButtonDown(nFlags, point);\r
380 }\r
381 \r
382 void CRevisionGraphWnd::OnLButtonUp(UINT nFlags, CPoint point)\r
383 {\r
384         if (!m_bIsRubberBand)\r
385                 return;         // we don't have a rubberband, so no zooming necessary\r
386 \r
387         m_bIsRubberBand = false;\r
388         ReleaseCapture();\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
393         CRect rect;\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
397 \r
398         if ((x < 20)&&(y < 20))\r
399         {\r
400                 // too small zoom rectangle\r
401                 // assume zooming by accident\r
402                 Invalidate();\r
403                 __super::OnLButtonUp(nFlags, point);\r
404                 return;\r
405         }\r
406 \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
410 \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
414 \r
415         float fZoomfactor = m_fZoomFactor*fact;\r
416         if (fZoomfactor > 20.0)\r
417         {\r
418                 // with such a big zoomfactor, the user\r
419                 // most likely zoomed by accident\r
420                 Invalidate();\r
421                 __super::OnLButtonUp(nFlags, point);\r
422                 return;\r
423         }\r
424         if (fZoomfactor > 2.0)\r
425         {\r
426                 fZoomfactor = 2.0;\r
427                 fact = fZoomfactor/m_fZoomFactor;\r
428         }\r
429 \r
430         CRevisionGraphDlg * pDlg = (CRevisionGraphDlg*)GetParent();\r
431         if (pDlg)\r
432         {\r
433                 m_fZoomFactor = fZoomfactor;\r
434                 pDlg->DoZoom (m_fZoomFactor);\r
435                 SetScrollbars(int(float(y)*fact), int(float(x)*fact));\r
436         }\r
437         __super::OnLButtonUp(nFlags, point);\r
438 }\r
439 \r
440 INT_PTR CRevisionGraphWnd::OnToolHitTest(CPoint point, TOOLINFO* pTI) const\r
441 {\r
442         if (m_bThreadRunning)\r
443                 return -1;\r
444 \r
445     if (GetHitNode (point) == NO_INDEX)\r
446         return -1;\r
447 \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
453 \r
454     return 1;\r
455 }\r
456 \r
457 BOOL CRevisionGraphWnd::OnToolTipNotify(UINT /*id*/, NMHDR *pNMHDR, LRESULT *pResult)\r
458 {\r
459     if (pNMHDR->idFrom != (UINT)m_hWnd)\r
460                 return FALSE;\r
461 \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
465 \r
466         POINT point;\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
471 \r
472     CString strTipText = TooltipText (GetHitNode (point));\r
473 \r
474         *pResult = 0;\r
475         if (strTipText.IsEmpty())\r
476                 return TRUE;\r
477                 \r
478     CSize tooltipSize = UsableTooltipRect();\r
479     strTipText = DisplayableText (strTipText, tooltipSize);\r
480 \r
481         if (pNMHDR->code == TTN_NEEDTEXTA)\r
482         {\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
486         }\r
487         else\r
488         {\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
492         }\r
493 \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
498 }\r
499 \r
500 CSize CRevisionGraphWnd::UsableTooltipRect()\r
501 {\r
502     // get screen size\r
503 \r
504     int screenWidth = GetSystemMetrics(SM_CXSCREEN);\r
505     int screenHeight = GetSystemMetrics(SM_CYSCREEN);\r
506 \r
507     // get current mouse position\r
508 \r
509     CPoint cursorPos;\r
510     if (GetCursorPos (&cursorPos) == FALSE)\r
511     {\r
512         // we could not determine the mouse position \r
513         // use screen / 2 minus some safety margin\r
514 \r
515         return CSize (screenWidth / 2 - 10, screenHeight / 2 - 10);\r
516     }\r
517 \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
520 \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
524 \r
525     return biggestSector;\r
526 }\r
527 \r
528 CString CRevisionGraphWnd::DisplayableText ( const CString& wholeText\r
529                                            , const CSize& tooltipSize)\r
530 {\r
531     CDC* dc = GetDC();\r
532     if (dc == NULL)\r
533     {\r
534         // no access to the device context -> truncate hard at 1000 chars\r
535 \r
536         return wholeText.GetLength() >= MAX_TT_LENGTH_DEFAULT\r
537             ? wholeText.Left (MAX_TT_LENGTH_DEFAULT-4) + _T(" ...")\r
538             : wholeText;\r
539     }\r
540 \r
541     // select the tooltip font\r
542 \r
543     NONCLIENTMETRICS metrics;\r
544     metrics.cbSize = sizeof (metrics);\r
545     SystemParametersInfo (SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &metrics, 0);\r
546 \r
547     CFont font;\r
548     font.CreateFontIndirect(&metrics.lfStatusFont);\r
549     CFont* pOldFont = dc->SelectObject (&font); \r
550 \r
551     // split into lines and fill the tooltip rect\r
552 \r
553     CString result;\r
554 \r
555     int remainingHeight = tooltipSize.cy;\r
556     int pos = 0;\r
557     while (pos < wholeText.GetLength())\r
558     {\r
559         // extract a whole line\r
560 \r
561         int nextPos = wholeText.Find ('\n', pos);\r
562         if (nextPos < 0)\r
563             nextPos = wholeText.GetLength();\r
564 \r
565         CString line = wholeText.Mid (pos, nextPos-pos+1);\r
566 \r
567         // find a way to make it fit\r
568 \r
569         CSize size = dc->GetTextExtent (line);\r
570         while (size.cx > tooltipSize.cx)\r
571         {\r
572             line.Delete (line.GetLength()-1);\r
573             int nextPos = line.ReverseFind (' ');\r
574             if (nextPos < 0)\r
575                 break;\r
576 \r
577             line.Delete (pos+1, line.GetLength() - pos-1);\r
578             size = dc->GetTextExtent (line);\r
579         }\r
580 \r
581         // enough room for the new line?\r
582 \r
583         remainingHeight -= size.cy;\r
584         if (remainingHeight <= size.cy)\r
585         {\r
586             result += _T("...");\r
587             break;\r
588         }\r
589 \r
590         // add the line\r
591 \r
592         result += line;\r
593         pos += line.GetLength();\r
594     }\r
595         \r
596     // relase temp. resources\r
597 \r
598     dc->SelectObject (pOldFont);\r
599     ReleaseDC(dc);\r
600 \r
601     // ready\r
602 \r
603     return result;\r
604 }\r
605 \r
606 CString CRevisionGraphWnd::TooltipText (index_t index)\r
607 {\r
608     if (index != NO_INDEX)\r
609     {\r
610         std::auto_ptr<const ILayoutNodeList> nodeList (m_layout->GetNodes());\r
611         return nodeList->GetToolTip (index);\r
612     }\r
613 \r
614     return CString();\r
615 }\r
616 \r
617 void CRevisionGraphWnd::SaveGraphAs(CString sSavePath)\r
618 {\r
619         CString extension = CPathUtils::GetFileExtFromPath(sSavePath);\r
620         if (extension.CompareNoCase(_T(".wmf"))==0)\r
621         {\r
622                 // save the graph as an enhanced metafile\r
623                 CMetaFileDC wmfDC;\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
628                 CRect rect;\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
635         }\r
636         else\r
637         {\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
644                 {\r
645                         ATLTRACE("gdi plus found!");\r
646                 }\r
647                 else\r
648                 {\r
649                         ATLTRACE("gdi plus not found!");\r
650                         CMessageBox::Show(m_hWnd, IDS_ERR_GDIPLUS_MISSING, IDS_APPNAME, MB_ICONERROR);\r
651                         return;\r
652                 }\r
653 \r
654                 // save the graph as a pixel picture instead of a vector picture\r
655                 // create dc to paint on\r
656                 try\r
657                 {\r
658                         CWindowDC ddc(this);\r
659                         CDC dc;\r
660                         if (!dc.CreateCompatibleDC(&ddc))\r
661                         {\r
662                                 LPVOID lpMsgBuf;\r
663                                 if (!FormatMessage( \r
664                                         FORMAT_MESSAGE_ALLOCATE_BUFFER | \r
665                                         FORMAT_MESSAGE_FROM_SYSTEM | \r
666                                         FORMAT_MESSAGE_IGNORE_INSERTS,\r
667                                         NULL,\r
668                                         GetLastError(),\r
669                                         MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language\r
670                                         (LPTSTR) &lpMsgBuf,\r
671                                         0,\r
672                                         NULL ))\r
673                                 {\r
674                                         return;\r
675                                 }\r
676                                 MessageBox( (LPCTSTR)lpMsgBuf, _T("Error"), MB_OK | MB_ICONINFORMATION );\r
677                                 LocalFree( lpMsgBuf );\r
678                                 return;\r
679                         }\r
680                         CRect rect;\r
681                         rect = GetViewRect();\r
682                         HBITMAP hbm = ::CreateCompatibleBitmap(ddc.m_hDC, rect.Width(), rect.Height());\r
683                         if (hbm==0)\r
684                         {\r
685                                 LPVOID lpMsgBuf;\r
686                                 if (!FormatMessage( \r
687                                         FORMAT_MESSAGE_ALLOCATE_BUFFER | \r
688                                         FORMAT_MESSAGE_FROM_SYSTEM | \r
689                                         FORMAT_MESSAGE_IGNORE_INSERTS,\r
690                                         NULL,\r
691                                         GetLastError(),\r
692                                         MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language\r
693                                         (LPTSTR) &lpMsgBuf,\r
694                                         0,\r
695                                         NULL ))\r
696                                 {\r
697                                         return;\r
698                                 }\r
699                                 MessageBox( (LPCTSTR)lpMsgBuf, _T("Error"), MB_OK | MB_ICONINFORMATION );\r
700                                 LocalFree( lpMsgBuf );\r
701                                 return;\r
702                         }\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
712                         {   \r
713                                 {\r
714                                         Bitmap bitmap(hbm, NULL);\r
715                                         if (bitmap.GetLastStatus()==Ok)\r
716                                         {\r
717                                                 // Get the CLSID of the encoder.\r
718                                                 int ret = 0;\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
729                                                 else\r
730                                                 {\r
731                                                         sSavePath += _T(".jpg");\r
732                                                         ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);\r
733                                                 }\r
734                                                 if (ret >= 0)\r
735                                                 {\r
736                                                         CStringW tfile = CStringW(sSavePath);\r
737                                                         bitmap.Save(tfile, &encoderClsid, NULL);\r
738                                                 }\r
739                                                 else\r
740                                                 {\r
741                                                         sErrormessage.Format(IDS_REVGRAPH_ERR_NOENCODER, (LPCTSTR)CPathUtils::GetFileExtFromPath(sSavePath));\r
742                                                 }\r
743                                         }\r
744                                         else\r
745                                         {\r
746                                                 sErrormessage.LoadString(IDS_REVGRAPH_ERR_NOBITMAP);\r
747                                         }\r
748                                 }\r
749                                 GdiplusShutdown(gdiplusToken);\r
750                         }\r
751                         else\r
752                         {\r
753                                 sErrormessage.LoadString(IDS_REVGRAPH_ERR_GDIINIT);\r
754                         }\r
755                         dc.SelectObject(oldbm);\r
756                         dc.DeleteDC();\r
757                         if (!sErrormessage.IsEmpty())\r
758                         {\r
759                                 CMessageBox::Show(m_hWnd, sErrormessage, _T("TortoiseSVN"), MB_ICONERROR);\r
760                         }\r
761                 }\r
762                 catch (CException * pE)\r
763                 {\r
764                         TCHAR szErrorMsg[2048];\r
765                         pE->GetErrorMessage(szErrorMsg, 2048);\r
766                         CMessageBox::Show(m_hWnd, szErrorMsg, _T("TortoiseSVN"), MB_ICONERROR);\r
767                 }\r
768         }\r
769 }\r
770 \r
771 BOOL CRevisionGraphWnd::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)\r
772 {\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
777         pos -= (zDelta);\r
778         SetScrollPos(orientation, pos);\r
779         Invalidate();\r
780         return __super::OnMouseWheel(nFlags, zDelta, pt);\r
781 }\r
782 \r
783 void CRevisionGraphWnd::OnContextMenu(CWnd* /*pWnd*/, CPoint point)\r
784 {\r
785         if (m_bThreadRunning)\r
786                 return;\r
787 \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
791 \r
792     index_t nodeIndex = GetHitNode (clientpoint);\r
793         const CVisibleGraphNode * clickedentry = NULL;\r
794     if (nodeIndex != NO_INDEX)\r
795     {\r
796         std::auto_ptr<const ILayoutNodeList> nodeList (m_layout->GetNodes());\r
797         clickedentry = nodeList->GetNode (nodeIndex).node;\r
798     }\r
799 \r
800         if ((m_SelectedEntry1 == NULL)&&(clickedentry == NULL))\r
801                 return;\r
802 \r
803         if (m_SelectedEntry1 == NULL)\r
804         {\r
805                 m_SelectedEntry1 = clickedentry;\r
806                 Invalidate();\r
807         }\r
808         if ((m_SelectedEntry2 == NULL)&&(clickedentry != m_SelectedEntry1))\r
809         {\r
810                 m_SelectedEntry1 = clickedentry;\r
811                 Invalidate();\r
812         }\r
813         if (m_SelectedEntry1 && m_SelectedEntry2)\r
814         {\r
815                 if ((m_SelectedEntry2 != clickedentry)&&(m_SelectedEntry1 != clickedentry))\r
816                         return;\r
817         }\r
818         if (m_SelectedEntry1 == NULL)\r
819                 return;\r
820         CMenu popup;\r
821         if (popup.CreatePopupMenu())\r
822         {\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
827 \r
828         bool bSameURL = (m_SelectedEntry2 && (m_SelectedEntry1->GetPath() == m_SelectedEntry2->GetPath()));\r
829                 CString temp;\r
830                 if (m_SelectedEntry1 && (m_SelectedEntry2 == NULL))\r
831                 {\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
837                 }\r
838                 if (bothPresent)\r
839                 {\r
840                         temp.LoadString(IDS_REVGRAPH_POPUP_COMPAREREVS);\r
841                 popup.AppendMenu(MF_STRING | MF_ENABLED, ID_COMPAREREVS, temp);\r
842                     if (!bSameURL)\r
843                     {\r
844                             temp.LoadString(IDS_REVGRAPH_POPUP_COMPAREHEADS);\r
845                             popup.AppendMenu(MF_STRING | MF_ENABLED, ID_COMPAREHEADS, temp);\r
846                     }\r
847 \r
848                         temp.LoadString(IDS_REVGRAPH_POPUP_UNIDIFFREVS);\r
849                         popup.AppendMenu(MF_STRING | MF_ENABLED, ID_UNIDIFFREVS, temp);\r
850                         if (!bSameURL)\r
851                         {\r
852                                 temp.LoadString(IDS_REVGRAPH_POPUP_UNIDIFFHEADS);\r
853                                 popup.AppendMenu(MF_STRING | MF_ENABLED, ID_UNIDIFFHEADS, temp);\r
854                         }\r
855                 }\r
856 \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
860                 {\r
861                         CRect rect;\r
862                         GetWindowRect(&rect);\r
863                         point = rect.CenterPoint();\r
864                 }\r
865 \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
868                         return;\r
869                 switch (cmd)\r
870                 {\r
871                 case ID_COMPAREREVS:\r
872                         CompareRevs(false);\r
873                         break;\r
874                 case ID_COMPAREHEADS:\r
875                         CompareRevs(true);\r
876                         break;\r
877                 case ID_UNIDIFFREVS:\r
878                         UnifiedDiffRevs(false);\r
879                         break;\r
880                 case ID_UNIDIFFHEADS:\r
881                         UnifiedDiffRevs(true);\r
882                         break;\r
883                 case ID_SHOWLOG:\r
884                         {\r
885                                 CString sCmd;\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
891                                         (LPCTSTR)URL,\r
892                     m_SelectedEntry1->GetRevision());\r
893 \r
894                                 if (!SVN::PathIsURL(CTSVNPath(m_sPath)))\r
895                                 {\r
896                                         sCmd += _T(" /propspath:\"");\r
897                                         sCmd += m_sPath;\r
898                                         sCmd += _T("\"");\r
899                                 }       \r
900 \r
901                                 CAppUtils::LaunchApplication(sCmd, NULL, false);\r
902                         }\r
903                         break;\r
904                 case ID_MERGETO:\r
905                         {\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
909 \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
914                                 {\r
915                                         CSVNProgressDlg dlg;\r
916                                         dlg.SetCommand(CSVNProgressDlg::SVNProgress_Merge);\r
917                                         dlg.SetPathList(CTSVNPathList(CTSVNPath(path)));\r
918                                         dlg.SetUrl(URL);\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
923                                         dlg.DoModal();\r
924                                 }\r
925                         }\r
926                         break;\r
927                 }\r
928         }\r
929 }\r
930 \r
931 void CRevisionGraphWnd::OnMouseMove(UINT nFlags, CPoint point)\r
932 {\r
933         if (m_bThreadRunning)\r
934         {\r
935                 return __super::OnMouseMove(nFlags, point);\r
936         }\r
937         if (!m_bIsRubberBand)\r
938         {\r
939                 if ((!m_OverviewRect.IsRectEmpty())&&(m_OverviewRect.PtInRect(point))&&(nFlags & MK_LBUTTON))\r
940                 {\r
941                         // scrolling\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
946                         Invalidate(FALSE);\r
947                         return __super::OnMouseMove(nFlags, point);\r
948                 }\r
949                 else\r
950                         return __super::OnMouseMove(nFlags, point);\r
951         }\r
952 \r
953         if ((abs(m_ptRubberStart.x - point.x) < 2)&&(abs(m_ptRubberStart.y - point.y) < 2))\r
954         {\r
955                 return __super::OnMouseMove(nFlags, point);\r
956         }\r
957 \r
958         SetCapture();\r
959 \r
960         if ((m_ptRubberEnd.x != 0)||(m_ptRubberEnd.y != 0))\r
961                 DrawRubberBand();\r
962         m_ptRubberEnd = point;\r
963         CRect rect;\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
969         DrawRubberBand();\r
970 \r
971         __super::OnMouseMove(nFlags, point);\r
972 }\r
973 \r
974 BOOL CRevisionGraphWnd::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)\r
975 {\r
976     CRect viewRect = GetViewRect();\r
977 \r
978         if ((nHitTest == HTCLIENT)&&(pWnd == this)&&(viewRect.Width())&&(viewRect.Height())&&(message))\r
979         {\r
980                 POINT pt;\r
981                 if (GetCursorPos(&pt))\r
982                 {\r
983                         ScreenToClient(&pt);\r
984                         if (m_OverviewPosRect.PtInRect(pt))\r
985                         {\r
986                                 HCURSOR hCur = NULL;\r
987                                 if (GetKeyState(VK_LBUTTON)&0x8000)\r
988                                         hCur = LoadCursor(AfxGetResourceHandle(), MAKEINTRESOURCE(IDC_PANCURDOWN));\r
989                                 else\r
990                                         hCur = LoadCursor(AfxGetResourceHandle(), MAKEINTRESOURCE(IDC_PANCUR));\r
991                                 SetCursor(hCur);\r
992                                 return TRUE;\r
993                         }\r
994                 }\r
995         }\r
996         HCURSOR hCur = LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW));\r
997         SetCursor(hCur);\r
998         return TRUE;\r
999 }\r
1000 \r
1001 LRESULT CRevisionGraphWnd::OnWorkerThreadDone(WPARAM, LPARAM)\r
1002 {\r
1003         InitView();\r
1004         BuildPreview();\r
1005     Invalidate(FALSE);\r
1006 \r
1007     SVN svn;\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
1013         , false));\r
1014 \r
1015     return 0;\r
1016 }\r
1017 \r
1018 void CRevisionGraphWnd::SetDlgTitle (bool offline)\r
1019 {\r
1020         if (m_sTitle.IsEmpty())\r
1021                 GetParent()->GetWindowText(m_sTitle);\r
1022 \r
1023         CString newTitle;\r
1024         if (offline)\r
1025         newTitle.Format (IDS_REVGRAPH_DLGTITLEOFFLINE, (LPCTSTR)m_sTitle);\r
1026     else\r
1027         newTitle = m_sTitle;\r
1028 \r
1029         GetParent()->SetWindowText (newTitle);\r
1030 }\r
1031 \r