BrowseRefs: Context menu enhancements
[TortoiseGit.git] / src / TortoiseProc / RepositoryBrowser.cpp
blob454057ad5ee21687588f2168e7e1c58f76a14125
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"
22 #include "MessageBox.h"
23 #include "InputLogDlg.h"
24 #include "LogDlg.h"
25 #include "PropDlg.h"
26 #include "EditPropertiesDlg.h"
27 #include "Blame.h"
28 #include "BlameDlg.h"
29 #include "WaitCursorEx.h"
30 #include "Repositorybrowser.h"
31 #include "BrowseFolder.h"
32 #include "RenameDlg.h"
33 #include "RevisionGraph\RevisionGraphDlg.h"
34 #include "CheckoutDlg.h"
35 #include "ExportDlg.h"
36 #include "SVNProgressDlg.h"
37 #include "AppUtils.h"
38 #include "PathUtils.h"
39 #include "StringUtils.h"
40 #include "TempFile.h"
41 #include "UnicodeUtils.h"
42 #include "BrowseFolder.h"
43 #include "SVNDiff.h"
44 #include "SysImageList.h"
45 #include "RepoDrags.h"
46 #include "SVNInfo.h"
47 #include "SVNDataObject.h"
48 #include "SVNLogHelper.h"
49 #include "XPTheme.h"
50 #include "IconMenu.h"
53 enum RepoBrowserContextMenuCommands
55 // needs to start with 1, since 0 is the return value if *nothing* is clicked on in the context menu
56 ID_OPEN = 1,
57 ID_OPENWITH,
58 ID_SHOWLOG,
59 ID_REVGRAPH,
60 ID_BLAME,
61 ID_VIEWREV,
62 ID_VIEWPATHREV,
63 ID_EXPORT,
64 ID_CHECKOUT,
65 ID_REFRESH,
66 ID_SAVEAS,
67 ID_MKDIR,
68 ID_IMPORT,
69 ID_IMPORTFOLDER,
70 ID_BREAKLOCK,
71 ID_DELETE,
72 ID_RENAME,
73 ID_COPYTOWC,
74 ID_COPYTO,
75 ID_URLTOCLIPBOARD,
76 ID_PROPS,
77 ID_REVPROPS,
78 ID_GNUDIFF,
79 ID_DIFF,
80 ID_PREPAREDIFF,
81 ID_UPDATE,
85 IMPLEMENT_DYNAMIC(CRepositoryBrowser, CResizableStandAloneDialog)
87 CRepositoryBrowser::CRepositoryBrowser(const CString& url, const SVNRev& rev)
88 : CResizableStandAloneDialog(CRepositoryBrowser::IDD, NULL)
89 , m_cnrRepositoryBar(&m_barRepository)
90 , m_bStandAlone(true)
91 , m_InitialUrl(url)
92 , m_initialRev(rev)
93 , m_bInitDone(false)
94 , m_blockEvents(false)
95 , m_bSortAscending(true)
96 , m_nSortedColumn(0)
97 , m_pTreeDropTarget(NULL)
98 , m_pListDropTarget(NULL)
99 , m_bCancelled(false)
100 , m_diffKind(svn_node_none)
101 , m_hAccel(NULL)
102 , bDragMode(FALSE)
106 CRepositoryBrowser::CRepositoryBrowser(const CString& url, const SVNRev& rev, CWnd* pParent)
107 : CResizableStandAloneDialog(CRepositoryBrowser::IDD, pParent)
108 , m_cnrRepositoryBar(&m_barRepository)
109 , m_InitialUrl(url)
110 , m_initialRev(rev)
111 , m_bStandAlone(false)
112 , m_bInitDone(false)
113 , m_blockEvents(false)
114 , m_bSortAscending(true)
115 , m_nSortedColumn(0)
116 , m_pTreeDropTarget(NULL)
117 , m_pListDropTarget(NULL)
118 , m_bCancelled(false)
119 , m_diffKind(svn_node_none)
123 CRepositoryBrowser::~CRepositoryBrowser()
127 void CRepositoryBrowser::RecursiveRemove(HTREEITEM hItem, bool bChildrenOnly /* = false */)
129 HTREEITEM childItem;
130 if (m_RepoTree.ItemHasChildren(hItem))
132 for (childItem = m_RepoTree.GetChildItem(hItem);childItem != NULL; childItem = m_RepoTree.GetNextItem(childItem, TVGN_NEXT))
134 RecursiveRemove(childItem);
135 if (bChildrenOnly)
137 CTreeItem * pTreeItem = (CTreeItem*)m_RepoTree.GetItemData(childItem);
138 delete pTreeItem;
139 m_RepoTree.SetItemData(childItem, 0);
140 m_RepoTree.DeleteItem(childItem);
145 if ((hItem)&&(!bChildrenOnly))
147 CTreeItem * pTreeItem = (CTreeItem*)m_RepoTree.GetItemData(hItem);
148 delete pTreeItem;
149 m_RepoTree.SetItemData(hItem, 0);
153 void CRepositoryBrowser::DoDataExchange(CDataExchange* pDX)
155 CResizableStandAloneDialog::DoDataExchange(pDX);
156 DDX_Control(pDX, IDC_REPOTREE, m_RepoTree);
157 DDX_Control(pDX, IDC_REPOLIST, m_RepoList);
160 BEGIN_MESSAGE_MAP(CRepositoryBrowser, CResizableStandAloneDialog)
161 ON_BN_CLICKED(IDHELP, OnBnClickedHelp)
162 ON_WM_SETCURSOR()
163 ON_REGISTERED_MESSAGE(WM_AFTERINIT, OnAfterInitDialog)
164 ON_WM_MOUSEMOVE()
165 ON_WM_LBUTTONDOWN()
166 ON_WM_LBUTTONUP()
167 ON_NOTIFY(TVN_SELCHANGED, IDC_REPOTREE, &CRepositoryBrowser::OnTvnSelchangedRepotree)
168 ON_NOTIFY(TVN_ITEMEXPANDING, IDC_REPOTREE, &CRepositoryBrowser::OnTvnItemexpandingRepotree)
169 ON_NOTIFY(NM_DBLCLK, IDC_REPOLIST, &CRepositoryBrowser::OnNMDblclkRepolist)
170 ON_NOTIFY(HDN_ITEMCLICK, 0, &CRepositoryBrowser::OnHdnItemclickRepolist)
171 ON_NOTIFY(LVN_ITEMCHANGED, IDC_REPOLIST, &CRepositoryBrowser::OnLvnItemchangedRepolist)
172 ON_NOTIFY(LVN_BEGINDRAG, IDC_REPOLIST, &CRepositoryBrowser::OnLvnBegindragRepolist)
173 ON_NOTIFY(LVN_BEGINRDRAG, IDC_REPOLIST, &CRepositoryBrowser::OnLvnBeginrdragRepolist)
174 ON_WM_CONTEXTMENU()
175 ON_NOTIFY(LVN_ENDLABELEDIT, IDC_REPOLIST, &CRepositoryBrowser::OnLvnEndlabeleditRepolist)
176 ON_NOTIFY(TVN_ENDLABELEDIT, IDC_REPOTREE, &CRepositoryBrowser::OnTvnEndlabeleditRepotree)
177 ON_WM_TIMER()
178 ON_COMMAND(ID_URL_FOCUS, &CRepositoryBrowser::OnUrlFocus)
179 ON_COMMAND(ID_EDIT_COPY, &CRepositoryBrowser::OnCopy)
180 ON_COMMAND(ID_INLINEEDIT, &CRepositoryBrowser::OnInlineedit)
181 ON_COMMAND(ID_REFRESHBROWSER, &CRepositoryBrowser::OnRefresh)
182 ON_COMMAND(ID_DELETEBROWSERITEM, &CRepositoryBrowser::OnDelete)
183 ON_COMMAND(ID_URL_UP, &CRepositoryBrowser::OnGoUp)
184 ON_NOTIFY(TVN_BEGINDRAG, IDC_REPOTREE, &CRepositoryBrowser::OnTvnBegindragRepotree)
185 ON_NOTIFY(TVN_BEGINRDRAG, IDC_REPOTREE, &CRepositoryBrowser::OnTvnBeginrdragRepotree)
186 END_MESSAGE_MAP()
188 SVNRev CRepositoryBrowser::GetRevision() const
190 return m_barRepository.GetCurrentRev();
193 CString CRepositoryBrowser::GetPath() const
195 return m_barRepository.GetCurrentUrl();
198 BOOL CRepositoryBrowser::OnInitDialog()
200 CResizableStandAloneDialog::OnInitDialog();
202 GetWindowText(m_origDlgTitle);
204 m_hAccel = LoadAccelerators(AfxGetResourceHandle(),MAKEINTRESOURCE(IDR_ACC_REPOBROWSER));
206 m_cnrRepositoryBar.SubclassDlgItem(IDC_REPOS_BAR_CNR, this);
207 m_barRepository.Create(&m_cnrRepositoryBar, 12345);
208 m_barRepository.SetIRepo(this);
210 m_pTreeDropTarget = new CTreeDropTarget(this);
211 RegisterDragDrop(m_RepoTree.GetSafeHwnd(), m_pTreeDropTarget);
212 // create the supported formats:
213 FORMATETC ftetc={0};
214 ftetc.cfFormat = CF_SVNURL;
215 ftetc.dwAspect = DVASPECT_CONTENT;
216 ftetc.lindex = -1;
217 ftetc.tymed = TYMED_HGLOBAL;
218 m_pTreeDropTarget->AddSuportedFormat(ftetc);
219 ftetc.cfFormat=CF_HDROP;
220 m_pTreeDropTarget->AddSuportedFormat(ftetc);
222 m_pListDropTarget = new CListDropTarget(this);
223 RegisterDragDrop(m_RepoList.GetSafeHwnd(), m_pListDropTarget);
224 // create the supported formats:
225 ftetc.cfFormat = CF_SVNURL;
226 m_pListDropTarget->AddSuportedFormat(ftetc);
227 ftetc.cfFormat=CF_HDROP;
228 m_pListDropTarget->AddSuportedFormat(ftetc);
230 if (m_bStandAlone)
232 GetDlgItem(IDCANCEL)->ShowWindow(FALSE);
234 // reposition the buttons
235 CRect rect_cancel;
236 GetDlgItem(IDCANCEL)->GetWindowRect(rect_cancel);
237 ScreenToClient(rect_cancel);
238 GetDlgItem(IDOK)->MoveWindow(rect_cancel);
241 m_nIconFolder = SYS_IMAGE_LIST().GetDirIconIndex();
242 m_nOpenIconFolder = SYS_IMAGE_LIST().GetDirOpenIconIndex();
243 // set up the list control
244 // set the extended style of the list control
245 // the style LVS_EX_FULLROWSELECT interferes with the background watermark image but it's more important to be able to select in the whole row.
246 CRegDWORD regFullRowSelect(_T("Software\\TortoiseGit\\FullRowSelect"), TRUE);
247 DWORD exStyle = LVS_EX_HEADERDRAGDROP | LVS_EX_DOUBLEBUFFER | LVS_EX_INFOTIP | LVS_EX_SUBITEMIMAGES;
248 if (DWORD(regFullRowSelect))
249 exStyle |= LVS_EX_FULLROWSELECT;
250 m_RepoList.SetExtendedStyle(exStyle);
251 m_RepoList.SetImageList(&SYS_IMAGE_LIST(), LVSIL_SMALL);
252 m_RepoList.ShowText(CString(MAKEINTRESOURCE(IDS_REPOBROWSE_INITWAIT)));
254 m_RepoTree.SetImageList(&SYS_IMAGE_LIST(), TVSIL_NORMAL);
256 CXPTheme theme;
257 theme.SetWindowTheme(m_RepoList.GetSafeHwnd(), L"Explorer", NULL);
258 theme.SetWindowTheme(m_RepoTree.GetSafeHwnd(), L"Explorer", NULL);
261 AddAnchor(IDC_REPOS_BAR_CNR, TOP_LEFT, TOP_RIGHT);
262 AddAnchor(IDC_F5HINT, BOTTOM_LEFT, BOTTOM_RIGHT);
263 AddAnchor(IDC_REPOTREE, TOP_LEFT, BOTTOM_LEFT);
264 AddAnchor(IDC_REPOLIST, TOP_LEFT, BOTTOM_RIGHT);
265 AddAnchor(IDCANCEL, BOTTOM_RIGHT);
266 AddAnchor(IDOK, BOTTOM_RIGHT);
267 AddAnchor(IDHELP, BOTTOM_RIGHT);
268 EnableSaveRestore(_T("RepositoryBrowser"));
269 if (hWndExplorer)
270 CenterWindow(CWnd::FromHandle(hWndExplorer));
271 m_bThreadRunning = true;
272 if (AfxBeginThread(InitThreadEntry, this)==NULL)
274 m_bThreadRunning = false;
275 CMessageBox::Show(NULL, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
277 return TRUE;
280 void CRepositoryBrowser::InitRepo()
282 CWaitCursorEx wait;
284 m_InitialUrl = CPathUtils::PathUnescape(m_InitialUrl);
285 if (m_InitialUrl.Find('?')>=0)
287 m_initialRev = SVNRev(m_InitialUrl.Mid(m_InitialUrl.Find('?')+1));
288 m_InitialUrl = m_InitialUrl.Left(m_InitialUrl.Find('?'));
291 // We don't know if the url passed to us points to a file or a folder,
292 // let's find out:
293 SVNInfo info;
294 const SVNInfoData * data = NULL;
295 CString error; // contains the first error of GetFirstFileInfo()
298 data = info.GetFirstFileInfo(CTSVNPath(m_InitialUrl),m_initialRev, m_initialRev);
299 if ((data == NULL)||(data->kind != svn_node_dir))
301 // in case the url is not a valid directory, try the parent dir
302 // until there's no more parent dir
303 m_InitialUrl = m_InitialUrl.Left(m_InitialUrl.ReverseFind('/'));
304 if ((m_InitialUrl.Compare(_T("http://")) == 0) ||
305 (m_InitialUrl.Compare(_T("https://")) == 0)||
306 (m_InitialUrl.Compare(_T("svn://")) == 0)||
307 (m_InitialUrl.Compare(_T("svn+ssh://")) == 0)||
308 (m_InitialUrl.Compare(_T("file:///")) == 0)||
309 (m_InitialUrl.Compare(_T("file://")) == 0))
311 m_InitialUrl.Empty();
313 if (error.IsEmpty())
315 if (((data)&&(data->kind == svn_node_dir))||(data == NULL))
316 error = info.GetLastErrorMsg();
319 } while(!m_InitialUrl.IsEmpty() && ((data == NULL) || (data->kind != svn_node_dir)));
321 if (data == NULL)
323 m_InitialUrl.Empty();
324 m_RepoList.ShowText(error, true);
325 return;
327 else if (m_initialRev.IsHead())
329 m_barRepository.SetHeadRevision(data->rev);
331 m_InitialUrl.TrimRight('/');
333 m_bCancelled = false;
334 m_strReposRoot = data->reposRoot;
335 m_sUUID = data->reposUUID;
336 m_strReposRoot = CPathUtils::PathUnescape(m_strReposRoot);
337 // the initial url can be in the format file:///\, but the
338 // repository root returned would still be file://
339 // to avoid string length comparison faults, we adjust
340 // the repository root here to match the initial url
341 if ((m_InitialUrl.Left(9).CompareNoCase(_T("file:///\\")) == 0) &&
342 (m_strReposRoot.Left(9).CompareNoCase(_T("file:///\\")) != 0))
343 m_strReposRoot.Replace(_T("file://"), _T("file:///\\"));
344 SetWindowText(m_strReposRoot + _T(" - ") + m_origDlgTitle);
345 // now check the repository root for the url type, then
346 // set the corresponding background image
347 if (!m_strReposRoot.IsEmpty())
349 UINT nID = IDI_REPO_UNKNOWN;
350 if (m_strReposRoot.Left(7).CompareNoCase(_T("http://"))==0)
351 nID = IDI_REPO_HTTP;
352 if (m_strReposRoot.Left(8).CompareNoCase(_T("https://"))==0)
353 nID = IDI_REPO_HTTPS;
354 if (m_strReposRoot.Left(6).CompareNoCase(_T("svn://"))==0)
355 nID = IDI_REPO_SVN;
356 if (m_strReposRoot.Left(10).CompareNoCase(_T("svn+ssh://"))==0)
357 nID = IDI_REPO_SVNSSH;
358 if (m_strReposRoot.Left(7).CompareNoCase(_T("file://"))==0)
359 nID = IDI_REPO_FILE;
360 CXPTheme theme;
361 if (theme.IsAppThemed())
362 CAppUtils::SetListCtrlBackgroundImage(m_RepoList.GetSafeHwnd(), nID);
366 UINT CRepositoryBrowser::InitThreadEntry(LPVOID pVoid)
368 return ((CRepositoryBrowser*)pVoid)->InitThread();
371 //this is the thread function which calls the subversion function
372 UINT CRepositoryBrowser::InitThread()
374 // In this thread, we try to find out the repository root.
375 // Since this is a remote operation, it can take a while, that's
376 // Why we do this inside a thread.
378 // force the cursor to change
379 RefreshCursor();
381 DialogEnableWindow(IDOK, FALSE);
382 DialogEnableWindow(IDCANCEL, FALSE);
384 InitRepo();
386 PostMessage(WM_AFTERINIT);
387 DialogEnableWindow(IDOK, TRUE);
388 DialogEnableWindow(IDCANCEL, TRUE);
390 m_bThreadRunning = false;
392 RefreshCursor();
393 return 0;
396 LRESULT CRepositoryBrowser::OnAfterInitDialog(WPARAM /*wParam*/, LPARAM /*lParam*/)
398 if ((m_InitialUrl.IsEmpty())||(m_strReposRoot.IsEmpty()))
400 return 0;
403 m_barRepository.GotoUrl(m_InitialUrl, m_initialRev, true);
404 m_RepoList.ClearText();
405 m_bInitDone = TRUE;
406 return 0;
409 void CRepositoryBrowser::OnOK()
411 RevokeDragDrop(m_RepoList.GetSafeHwnd());
412 RevokeDragDrop(m_RepoTree.GetSafeHwnd());
414 SaveColumnWidths(true);
416 HTREEITEM hItem = m_RepoTree.GetRootItem();
417 RecursiveRemove(hItem);
419 m_barRepository.SaveHistory();
420 CResizableStandAloneDialog::OnOK();
423 void CRepositoryBrowser::OnCancel()
425 RevokeDragDrop(m_RepoList.GetSafeHwnd());
426 RevokeDragDrop(m_RepoTree.GetSafeHwnd());
428 SaveColumnWidths(true);
430 HTREEITEM hItem = m_RepoTree.GetRootItem();
431 RecursiveRemove(hItem);
433 __super::OnCancel();
436 void CRepositoryBrowser::OnBnClickedHelp()
438 OnHelp();
441 /******************************************************************************/
442 /* tree and list view resizing */
443 /******************************************************************************/
445 BOOL CRepositoryBrowser::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
447 if (m_bThreadRunning)
449 HCURSOR hCur = LoadCursor(NULL, MAKEINTRESOURCE(IDC_WAIT));
450 SetCursor(hCur);
451 return TRUE;
453 if (pWnd == this)
455 RECT rect;
456 POINT pt;
457 GetClientRect(&rect);
458 GetCursorPos(&pt);
459 ScreenToClient(&pt);
460 if (PtInRect(&rect, pt))
462 ClientToScreen(&pt);
463 // are we right of the tree control?
464 GetDlgItem(IDC_REPOTREE)->GetWindowRect(&rect);
465 if ((pt.x > rect.right)&&
466 (pt.y >= rect.top)&&
467 (pt.y <= rect.bottom))
469 // but left of the list control?
470 GetDlgItem(IDC_REPOLIST)->GetWindowRect(&rect);
471 if (pt.x < rect.left)
473 HCURSOR hCur = LoadCursor(NULL, MAKEINTRESOURCE(IDC_SIZEWE));
474 SetCursor(hCur);
475 return TRUE;
480 return CStandAloneDialogTmpl<CResizableDialog>::OnSetCursor(pWnd, nHitTest, message);
483 void CRepositoryBrowser::OnMouseMove(UINT nFlags, CPoint point)
485 CDC * pDC;
486 RECT rect, tree, list, treelist, treelistclient;
488 if (bDragMode == FALSE)
489 return;
491 // create an union of the tree and list control rectangle
492 GetDlgItem(IDC_REPOLIST)->GetWindowRect(&list);
493 GetDlgItem(IDC_REPOTREE)->GetWindowRect(&tree);
494 UnionRect(&treelist, &tree, &list);
495 treelistclient = treelist;
496 ScreenToClient(&treelistclient);
498 //convert the mouse coordinates relative to the top-left of
499 //the window
500 ClientToScreen(&point);
501 GetClientRect(&rect);
502 ClientToScreen(&rect);
503 point.x -= rect.left;
504 point.y -= treelist.top;
506 //same for the window coordinates - make them relative to 0,0
507 OffsetRect(&treelist, -treelist.left, -treelist.top);
509 if (point.x < treelist.left+REPOBROWSER_CTRL_MIN_WIDTH)
510 point.x = treelist.left+REPOBROWSER_CTRL_MIN_WIDTH;
511 if (point.x > treelist.right-REPOBROWSER_CTRL_MIN_WIDTH)
512 point.x = treelist.right-REPOBROWSER_CTRL_MIN_WIDTH;
514 if ((nFlags & MK_LBUTTON) && (point.x != oldx))
516 pDC = GetDC();
518 if (pDC)
520 DrawXorBar(pDC, oldx+2, treelistclient.top, 4, treelistclient.bottom-treelistclient.top-2);
521 DrawXorBar(pDC, point.x+2, treelistclient.top, 4, treelistclient.bottom-treelistclient.top-2);
523 ReleaseDC(pDC);
526 oldx = point.x;
527 oldy = point.y;
530 CStandAloneDialogTmpl<CResizableDialog>::OnMouseMove(nFlags, point);
533 void CRepositoryBrowser::OnLButtonDown(UINT nFlags, CPoint point)
535 CDC * pDC;
536 RECT rect, tree, list, treelist, treelistclient;
538 // create an union of the tree and list control rectangle
539 GetDlgItem(IDC_REPOLIST)->GetWindowRect(&list);
540 GetDlgItem(IDC_REPOTREE)->GetWindowRect(&tree);
541 UnionRect(&treelist, &tree, &list);
542 treelistclient = treelist;
543 ScreenToClient(&treelistclient);
545 //convert the mouse coordinates relative to the top-left of
546 //the window
547 ClientToScreen(&point);
548 GetClientRect(&rect);
549 ClientToScreen(&rect);
550 point.x -= rect.left;
551 point.y -= treelist.top;
553 //same for the window coordinates - make them relative to 0,0
554 OffsetRect(&treelist, -treelist.left, -treelist.top);
556 if (point.x < treelist.left+REPOBROWSER_CTRL_MIN_WIDTH)
557 point.x = treelist.left+REPOBROWSER_CTRL_MIN_WIDTH;
558 if (point.x > treelist.right-3)
559 return CStandAloneDialogTmpl<CResizableDialog>::OnLButtonDown(nFlags, point);
560 if (point.x > treelist.right-REPOBROWSER_CTRL_MIN_WIDTH)
561 point.x = treelist.right-REPOBROWSER_CTRL_MIN_WIDTH;
563 if ((point.y < treelist.top) ||
564 (point.y > treelist.bottom))
565 return CStandAloneDialogTmpl<CResizableDialog>::OnLButtonDown(nFlags, point);
567 bDragMode = true;
569 SetCapture();
571 pDC = GetDC();
572 DrawXorBar(pDC, point.x+2, treelistclient.top, 4, treelistclient.bottom-treelistclient.top-2);
573 ReleaseDC(pDC);
575 oldx = point.x;
576 oldy = point.y;
578 CStandAloneDialogTmpl<CResizableDialog>::OnLButtonDown(nFlags, point);
581 void CRepositoryBrowser::OnLButtonUp(UINT nFlags, CPoint point)
583 CDC * pDC;
584 RECT rect, tree, list, treelist, treelistclient;
586 if (bDragMode == FALSE)
587 return;
589 // create an union of the tree and list control rectangle
590 GetDlgItem(IDC_REPOLIST)->GetWindowRect(&list);
591 GetDlgItem(IDC_REPOTREE)->GetWindowRect(&tree);
592 UnionRect(&treelist, &tree, &list);
593 treelistclient = treelist;
594 ScreenToClient(&treelistclient);
596 ClientToScreen(&point);
597 GetClientRect(&rect);
598 ClientToScreen(&rect);
600 CPoint point2 = point;
601 if (point2.x < treelist.left+REPOBROWSER_CTRL_MIN_WIDTH)
602 point2.x = treelist.left+REPOBROWSER_CTRL_MIN_WIDTH;
603 if (point2.x > treelist.right-REPOBROWSER_CTRL_MIN_WIDTH)
604 point2.x = treelist.right-REPOBROWSER_CTRL_MIN_WIDTH;
606 point.x -= rect.left;
607 point.y -= treelist.top;
609 OffsetRect(&treelist, -treelist.left, -treelist.top);
611 if (point.x < treelist.left+REPOBROWSER_CTRL_MIN_WIDTH)
612 point.x = treelist.left+REPOBROWSER_CTRL_MIN_WIDTH;
613 if (point.x > treelist.right-REPOBROWSER_CTRL_MIN_WIDTH)
614 point.x = treelist.right-REPOBROWSER_CTRL_MIN_WIDTH;
616 pDC = GetDC();
617 DrawXorBar(pDC, oldx+2, treelistclient.top, 4, treelistclient.bottom-treelistclient.top-2);
618 ReleaseDC(pDC);
620 oldx = point.x;
621 oldy = point.y;
623 bDragMode = false;
624 ReleaseCapture();
626 //position the child controls
627 GetDlgItem(IDC_REPOTREE)->GetWindowRect(&treelist);
628 treelist.right = point2.x - 2;
629 ScreenToClient(&treelist);
630 RemoveAnchor(IDC_REPOTREE);
631 GetDlgItem(IDC_REPOTREE)->MoveWindow(&treelist);
632 GetDlgItem(IDC_REPOLIST)->GetWindowRect(&treelist);
633 treelist.left = point2.x + 2;
634 ScreenToClient(&treelist);
635 RemoveAnchor(IDC_REPOLIST);
636 GetDlgItem(IDC_REPOLIST)->MoveWindow(&treelist);
638 AddAnchor(IDC_REPOTREE, TOP_LEFT, BOTTOM_LEFT);
639 AddAnchor(IDC_REPOLIST, TOP_LEFT, BOTTOM_RIGHT);
641 CStandAloneDialogTmpl<CResizableDialog>::OnLButtonUp(nFlags, point);
644 void CRepositoryBrowser::DrawXorBar(CDC * pDC, int x1, int y1, int width, int height)
646 static WORD _dotPatternBmp[8] =
648 0x0055, 0x00aa, 0x0055, 0x00aa,
649 0x0055, 0x00aa, 0x0055, 0x00aa
652 HBITMAP hbm;
653 HBRUSH hbr, hbrushOld;
655 hbm = CreateBitmap(8, 8, 1, 1, _dotPatternBmp);
656 hbr = CreatePatternBrush(hbm);
658 pDC->SetBrushOrg(x1, y1);
659 hbrushOld = (HBRUSH)pDC->SelectObject(hbr);
661 PatBlt(pDC->GetSafeHdc(), x1, y1, width, height, PATINVERT);
663 pDC->SelectObject(hbrushOld);
665 DeleteObject(hbr);
666 DeleteObject(hbm);
669 /******************************************************************************/
670 /* repository information gathering */
671 /******************************************************************************/
673 BOOL CRepositoryBrowser::ReportList(const CString& path, svn_node_kind_t kind,
674 svn_filesize_t size, bool has_props,
675 svn_revnum_t created_rev, apr_time_t time,
676 const CString& author, const CString& locktoken,
677 const CString& lockowner, const CString& lockcomment,
678 bool is_dav_comment, apr_time_t lock_creationdate,
679 apr_time_t lock_expirationdate,
680 const CString& absolutepath)
682 static deque<CItem> * pDirList = NULL;
683 static CTreeItem * pTreeItem = NULL;
684 static CString dirPath;
686 CString sParent = absolutepath;
687 int slashpos = path.ReverseFind('/');
688 bool abspath_has_slash = (absolutepath.GetAt(absolutepath.GetLength()-1) == '/');
689 if ((slashpos > 0) && (!abspath_has_slash))
690 sParent += _T("/");
691 sParent += path.Left(slashpos);
692 if (sParent.Compare(_T("/"))==0)
693 sParent.Empty();
694 if ((path.IsEmpty())||
695 (pDirList == NULL)||
696 (sParent.Compare(dirPath)))
698 HTREEITEM hItem = FindUrl(m_strReposRoot + sParent);
699 pTreeItem = (CTreeItem*)m_RepoTree.GetItemData(hItem);
700 pDirList = &(pTreeItem->children);
702 dirPath = sParent;
704 if (path.IsEmpty())
705 return TRUE;
707 if (kind == svn_node_dir)
709 FindUrl(m_strReposRoot + absolutepath + (abspath_has_slash ? _T("") : _T("/")) + path);
710 if (pTreeItem)
711 pTreeItem->has_child_folders = true;
713 pDirList->push_back(CItem(path.Mid(slashpos+1), kind, size, has_props,
714 created_rev, time, author, locktoken,
715 lockowner, lockcomment, is_dav_comment,
716 lock_creationdate, lock_expirationdate,
717 m_strReposRoot+absolutepath+(abspath_has_slash ? _T("") : _T("/"))+path));
718 if (pTreeItem)
719 pTreeItem->children_fetched = true;
720 return TRUE;
723 bool CRepositoryBrowser::ChangeToUrl(CString& url, SVNRev& rev, bool bAlreadyChecked)
725 CWaitCursorEx wait;
726 if (!bAlreadyChecked)
728 // check if the entered url is valid
729 SVNInfo info;
730 const SVNInfoData * data = NULL;
731 CString orig_url = url;
732 m_bCancelled = false;
735 data = info.GetFirstFileInfo(CTSVNPath(url), rev, rev);
736 if (data && rev.IsHead())
738 rev = data->rev;
740 if ((data == NULL)||(data->kind != svn_node_dir))
742 // in case the url is not a valid directory, try the parent dir
743 // until there's no more parent dir
744 url = url.Left(url.ReverseFind('/'));
746 } while(!m_bCancelled && !url.IsEmpty() && ((data == NULL) || (data->kind != svn_node_dir)));
747 if (url.IsEmpty())
748 url = orig_url;
750 CString partUrl = url;
751 HTREEITEM hItem = m_RepoTree.GetRootItem();
752 if ((LONG(rev) != LONG(m_initialRev))||
753 (m_strReposRoot.IsEmpty())||
754 (m_strReposRoot.Compare(url.Left(m_strReposRoot.GetLength())))||
755 (url.GetAt(m_strReposRoot.GetLength()) != '/'))
757 // if the revision changed, then invalidate everything
758 RecursiveRemove(hItem);
759 m_RepoTree.DeleteAllItems();
760 m_RepoList.DeleteAllItems();
761 m_RepoList.ShowText(CString(MAKEINTRESOURCE(IDS_REPOBROWSE_WAIT)), true);
762 hItem = m_RepoTree.GetRootItem();
763 if ((m_strReposRoot.IsEmpty())||(m_strReposRoot.Compare(url.Left(m_strReposRoot.GetLength())))||
764 (url.GetAt(m_strReposRoot.GetLength()) != '/'))
766 // if the repository root has changed, initialize all data from scratch
767 // and clear the project properties we might have loaded previously
768 m_ProjectProperties = ProjectProperties();
769 m_InitialUrl = url;
770 InitRepo();
771 if ((m_InitialUrl.IsEmpty())||(m_strReposRoot.IsEmpty()))
772 return false;
775 if (hItem == NULL)
777 // the tree view is empty, just fill in the repository root
778 CTreeItem * pTreeItem = new CTreeItem();
779 pTreeItem->unescapedname = m_strReposRoot;
780 pTreeItem->url = m_strReposRoot;
782 TVINSERTSTRUCT tvinsert = {0};
783 tvinsert.hParent = TVI_ROOT;
784 tvinsert.hInsertAfter = TVI_ROOT;
785 tvinsert.itemex.mask = TVIF_CHILDREN | TVIF_DI_SETITEM | TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
786 tvinsert.itemex.pszText = m_strReposRoot.GetBuffer(m_strReposRoot.GetLength());
787 tvinsert.itemex.cChildren = 1;
788 tvinsert.itemex.lParam = (LPARAM)pTreeItem;
789 tvinsert.itemex.iImage = m_nIconFolder;
790 tvinsert.itemex.iSelectedImage = m_nOpenIconFolder;
792 hItem = m_RepoTree.InsertItem(&tvinsert);
793 m_strReposRoot.ReleaseBuffer();
795 if (hItem == NULL)
797 // something terrible happened!
798 return false;
800 hItem = FindUrl(url);
801 if (hItem == NULL)
802 return false;
804 CTreeItem * pTreeItem = (CTreeItem *)m_RepoTree.GetItemData(hItem);
805 if (pTreeItem == NULL)
806 return FALSE;
808 if (!m_RepoList.HasText())
809 m_RepoList.ShowText(_T(" "), true);
811 RefreshNode(hItem);
812 m_RepoTree.Expand(hItem, TVE_EXPAND);
813 FillList(&pTreeItem->children);
815 m_blockEvents = true;
816 m_RepoTree.EnsureVisible(hItem);
817 m_RepoTree.SelectItem(hItem);
818 m_blockEvents = false;
820 m_RepoList.ClearText();
822 return true;
825 void CRepositoryBrowser::FillList(deque<CItem> * pItems)
827 if (pItems == NULL)
828 return;
829 CWaitCursorEx wait;
830 m_RepoList.SetRedraw(false);
831 m_RepoList.DeleteAllItems();
833 int c = ((CHeaderCtrl*)(m_RepoList.GetDlgItem(0)))->GetItemCount()-1;
834 while (c>=0)
835 m_RepoList.DeleteColumn(c--);
837 c = 0;
838 CString temp;
840 // column 0: contains tree
841 temp.LoadString(IDS_LOG_FILE);
842 m_RepoList.InsertColumn(c++, temp);
844 // column 1: file extension
845 temp.LoadString(IDS_STATUSLIST_COLEXT);
846 m_RepoList.InsertColumn(c++, temp);
848 // column 2: revision number
849 temp.LoadString(IDS_LOG_REVISION);
850 m_RepoList.InsertColumn(c++, temp, LVCFMT_RIGHT);
852 // column 3: author
853 temp.LoadString(IDS_LOG_AUTHOR);
854 m_RepoList.InsertColumn(c++, temp);
856 // column 4: size
857 temp.LoadString(IDS_LOG_SIZE);
858 m_RepoList.InsertColumn(c++, temp, LVCFMT_RIGHT);
860 // column 5: date
861 temp.LoadString(IDS_LOG_DATE);
862 m_RepoList.InsertColumn(c++, temp);
864 // column 6: lock owner
865 temp.LoadString(IDS_STATUSLIST_COLLOCK);
866 m_RepoList.InsertColumn(c++, temp);
868 // now fill in the data
870 TCHAR date_native[SVN_DATE_BUFFER];
871 int nCount = 0;
872 for (deque<CItem>::const_iterator it = pItems->begin(); it != pItems->end(); ++it)
874 int icon_idx;
875 if (it->kind == svn_node_dir)
876 icon_idx = m_nIconFolder;
877 else
878 icon_idx = SYS_IMAGE_LIST().GetFileIconIndex(it->path);
879 int index = m_RepoList.InsertItem(nCount, it->path, icon_idx);
880 // extension
881 temp = CPathUtils::GetFileExtFromPath(it->path);
882 if (it->kind == svn_node_file)
883 m_RepoList.SetItemText(index, 1, temp);
884 // revision
885 temp.Format(_T("%ld"), it->created_rev);
886 m_RepoList.SetItemText(index, 2, temp);
887 // author
888 m_RepoList.SetItemText(index, 3, it->author);
889 // size
890 if (it->kind == svn_node_file)
892 StrFormatByteSize(it->size, temp.GetBuffer(20), 20);
893 temp.ReleaseBuffer();
894 m_RepoList.SetItemText(index, 4, temp);
896 // date
897 SVN::formatDate(date_native, (apr_time_t&)it->time, true);
898 m_RepoList.SetItemText(index, 5, date_native);
899 // lock owner
900 m_RepoList.SetItemText(index, 6, it->lockowner);
901 m_RepoList.SetItemData(index, (DWORD_PTR)&(*it));
904 ListView_SortItemsEx(m_RepoList, ListSort, this);
905 SetSortArrow();
907 for (int col = 0; col <= (((CHeaderCtrl*)(m_RepoList.GetDlgItem(0)))->GetItemCount()-1); col++)
909 m_RepoList.SetColumnWidth(col, LVSCW_AUTOSIZE_USEHEADER);
911 for (int col = 0; col <= (((CHeaderCtrl*)(m_RepoList.GetDlgItem(0)))->GetItemCount()-1); col++)
913 m_arColumnAutoWidths[col] = m_RepoList.GetColumnWidth(col);
916 CRegString regColWidths(_T("Software\\TortoiseGit\\RepoBrowserColumnWidth"));
917 if (!CString(regColWidths).IsEmpty())
919 StringToWidthArray(regColWidths, m_arColumnWidths);
921 int maxcol = ((CHeaderCtrl*)(m_RepoList.GetDlgItem(0)))->GetItemCount()-1;
922 for (int col = 1; col <= maxcol; col++)
924 if (m_arColumnWidths[col] == 0)
925 m_RepoList.SetColumnWidth(col, LVSCW_AUTOSIZE_USEHEADER);
926 else
927 m_RepoList.SetColumnWidth(col, m_arColumnWidths[col]);
931 m_RepoList.SetRedraw(true);
934 HTREEITEM CRepositoryBrowser::FindUrl(const CString& fullurl, bool create /* = true */)
936 return FindUrl(fullurl, fullurl, create, TVI_ROOT);
939 HTREEITEM CRepositoryBrowser::FindUrl(const CString& fullurl, const CString& url, bool create /* true */, HTREEITEM hItem /* = TVI_ROOT */)
941 if (hItem == TVI_ROOT)
943 hItem = m_RepoTree.GetRootItem();
944 if (fullurl.Compare(m_strReposRoot)==0)
945 return hItem;
946 return FindUrl(fullurl, url.Mid(m_strReposRoot.GetLength()+1), create, hItem);
948 HTREEITEM hSibling = hItem;
949 if (m_RepoTree.GetNextItem(hItem, TVGN_CHILD))
951 hSibling = m_RepoTree.GetNextItem(hItem, TVGN_CHILD);
954 CTreeItem * pTItem = ((CTreeItem*)m_RepoTree.GetItemData(hSibling));
955 if (pTItem)
957 CString sSibling = pTItem->unescapedname;
958 if (sSibling.Compare(url.Left(sSibling.GetLength()))==0)
960 if (sSibling.GetLength() == url.GetLength())
961 return hSibling;
962 if (url.GetAt(sSibling.GetLength()) == '/')
963 return FindUrl(fullurl, url.Mid(sSibling.GetLength()+1), create, hSibling);
966 } while ((hSibling = m_RepoTree.GetNextItem(hSibling, TVGN_NEXT)) != NULL);
968 if (!create)
969 return NULL;
970 // create tree items for every path part in the url
971 CString sUrl = url;
972 int slash = -1;
973 HTREEITEM hNewItem = hItem;
974 CString sTemp;
975 while ((slash=sUrl.Find('/')) >= 0)
977 CTreeItem * pTreeItem = new CTreeItem();
978 sTemp = sUrl.Left(slash);
979 pTreeItem->unescapedname = sTemp;
980 pTreeItem->url = fullurl.Left(fullurl.GetLength()-sUrl.GetLength()+slash);
981 UINT state = pTreeItem->url.CompareNoCase(m_diffURL.GetSVNPathString()) ? 0 : TVIS_BOLD;
982 TVINSERTSTRUCT tvinsert = {0};
983 tvinsert.hParent = hNewItem;
984 tvinsert.hInsertAfter = TVI_SORT;
985 tvinsert.itemex.mask = TVIF_CHILDREN | TVIF_DI_SETITEM | TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_STATE;
986 tvinsert.itemex.state = state;
987 tvinsert.itemex.stateMask = state;
988 tvinsert.itemex.pszText = sTemp.GetBuffer(sTemp.GetLength());
989 tvinsert.itemex.cChildren = 1;
990 tvinsert.itemex.lParam = (LPARAM)pTreeItem;
991 tvinsert.itemex.iImage = m_nIconFolder;
992 tvinsert.itemex.iSelectedImage = m_nOpenIconFolder;
994 hNewItem = m_RepoTree.InsertItem(&tvinsert);
995 sTemp.ReleaseBuffer();
996 sUrl = sUrl.Mid(slash+1);
997 ATLTRACE(_T("created tree entry %s, url %s\n"), sTemp, pTreeItem->url);
999 if (!sUrl.IsEmpty())
1001 CTreeItem * pTreeItem = new CTreeItem();
1002 sTemp = sUrl;
1003 pTreeItem->unescapedname = sTemp;
1004 pTreeItem->url = fullurl;
1005 UINT state = pTreeItem->url.CompareNoCase(m_diffURL.GetSVNPathString()) ? 0 : TVIS_BOLD;
1006 TVINSERTSTRUCT tvinsert = {0};
1007 tvinsert.hParent = hNewItem;
1008 tvinsert.hInsertAfter = TVI_SORT;
1009 tvinsert.itemex.mask = TVIF_CHILDREN | TVIF_DI_SETITEM | TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_STATE;
1010 tvinsert.itemex.state = state;
1011 tvinsert.itemex.stateMask = state;
1012 tvinsert.itemex.pszText = sTemp.GetBuffer(sTemp.GetLength());
1013 tvinsert.itemex.cChildren = 1;
1014 tvinsert.itemex.lParam = (LPARAM)pTreeItem;
1015 tvinsert.itemex.iImage = m_nIconFolder;
1016 tvinsert.itemex.iSelectedImage = m_nOpenIconFolder;
1018 hNewItem = m_RepoTree.InsertItem(&tvinsert);
1019 sTemp.ReleaseBuffer();
1020 m_RepoTree.SortChildren(hNewItem);
1021 return hNewItem;
1023 return NULL;
1026 bool CRepositoryBrowser::RefreshNode(const CString& url, bool force /* = false*/, bool recursive /* = false*/)
1028 HTREEITEM hNode = FindUrl(url);
1029 return RefreshNode(hNode, force, recursive);
1032 bool CRepositoryBrowser::RefreshNode(HTREEITEM hNode, bool force /* = false*/, bool recursive /* = false*/)
1034 if (hNode == NULL)
1035 return false;
1036 SaveColumnWidths();
1037 CWaitCursorEx wait;
1038 CTreeItem * pTreeItem = (CTreeItem *)m_RepoTree.GetItemData(hNode);
1039 HTREEITEM hSel1 = m_RepoTree.GetSelectedItem();
1040 if (m_RepoTree.ItemHasChildren(hNode))
1042 HTREEITEM hChild = m_RepoTree.GetChildItem(hNode);
1043 HTREEITEM hNext;
1044 m_blockEvents = true;
1045 while (hChild)
1047 hNext = m_RepoTree.GetNextItem(hChild, TVGN_NEXT);
1048 RecursiveRemove(hChild);
1049 m_RepoTree.DeleteItem(hChild);
1050 hChild = hNext;
1052 m_blockEvents = false;
1054 if (pTreeItem == NULL)
1055 return false;
1056 pTreeItem->children.clear();
1057 pTreeItem->has_child_folders = false;
1058 m_bCancelled = false;
1059 if (!List(CTSVNPath(pTreeItem->url), GetRevision(), GetRevision(), recursive ? svn_depth_infinity : svn_depth_immediates, true))
1061 // error during list()
1062 m_RepoList.ShowText(GetLastErrorMessage());
1063 return false;
1065 pTreeItem->children_fetched = true;
1066 // if there are no child folders, remove the '+' in front of the node
1068 TVITEM tvitem = {0};
1069 tvitem.hItem = hNode;
1070 tvitem.mask = TVIF_CHILDREN;
1071 tvitem.cChildren = pTreeItem->has_child_folders ? 1 : 0;
1072 m_RepoTree.SetItem(&tvitem);
1074 if ((force)||(hSel1 == hNode)||(hSel1 != m_RepoTree.GetSelectedItem()))
1076 FillList(&pTreeItem->children);
1078 return true;
1081 BOOL CRepositoryBrowser::PreTranslateMessage(MSG* pMsg)
1083 if (pMsg->message>=WM_KEYFIRST && pMsg->message<=WM_KEYLAST)
1085 // Check if there is an in place Edit active:
1086 // in place edits are done with an edit control, where the parent
1087 // is the control with the editable item (tree or list control here)
1088 HWND hWndFocus = ::GetFocus();
1089 if (hWndFocus)
1090 hWndFocus = ::GetParent(hWndFocus);
1091 if (hWndFocus && ((hWndFocus == m_RepoTree.GetSafeHwnd())||(hWndFocus == m_RepoList.GetSafeHwnd())))
1093 // Do a direct translation.
1094 ::TranslateMessage(pMsg);
1095 ::DispatchMessage(pMsg);
1096 return TRUE;
1098 if (m_hAccel)
1100 if (pMsg->message == WM_KEYDOWN)
1102 switch (pMsg->wParam)
1104 case 'C':
1105 case VK_INSERT:
1106 case VK_DELETE:
1107 case VK_BACK:
1109 if ((pMsg->hwnd == m_barRepository.GetSafeHwnd())||(::IsChild(m_barRepository.GetSafeHwnd(), pMsg->hwnd)))
1110 return __super::PreTranslateMessage(pMsg);
1112 break;
1115 int ret = TranslateAccelerator(m_hWnd, m_hAccel, pMsg);
1116 if (ret)
1117 return TRUE;
1120 return __super::PreTranslateMessage(pMsg);
1123 void CRepositoryBrowser::OnDelete()
1125 CTSVNPathList urlList;
1126 bool bTreeItem = false;
1128 POSITION pos = m_RepoList.GetFirstSelectedItemPosition();
1129 int index = -1;
1130 while ((index = m_RepoList.GetNextSelectedItem(pos))>=0)
1132 CItem * pItem = (CItem *)m_RepoList.GetItemData(index);
1133 CString absPath = pItem->absolutepath;
1134 absPath.Replace(_T("\\"), _T("%5C"));
1135 urlList.AddPath(CTSVNPath(absPath));
1137 if ((urlList.GetCount() == 0))
1139 HTREEITEM hItem = m_RepoTree.GetSelectedItem();
1140 CTreeItem * pTreeItem = (CTreeItem *)m_RepoTree.GetItemData(hItem);
1141 if (pTreeItem)
1143 urlList.AddPath(CTSVNPath(pTreeItem->url));
1144 bTreeItem = true;
1148 if (urlList.GetCount() == 0)
1149 return;
1152 CWaitCursorEx wait_cursor;
1153 CInputLogDlg input(this);
1154 input.SetUUID(m_sUUID);
1155 input.SetProjectProperties(&m_ProjectProperties);
1156 CString hint;
1157 if (urlList.GetCount() == 1)
1158 hint.Format(IDS_INPUT_REMOVEONE, (LPCTSTR)urlList[0].GetFileOrDirectoryName());
1159 else
1160 hint.Format(IDS_INPUT_REMOVEMORE, urlList.GetCount());
1161 input.SetActionText(hint);
1162 if (input.DoModal() == IDOK)
1164 if (!Remove(urlList, true, false, input.GetLogMessage()))
1166 wait_cursor.Hide();
1167 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseSVN"), MB_ICONERROR);
1168 return;
1170 if (bTreeItem)
1171 RefreshNode(m_RepoTree.GetParentItem(m_RepoTree.GetSelectedItem()), true);
1172 else
1173 RefreshNode(m_RepoTree.GetSelectedItem(), true);
1177 void CRepositoryBrowser::OnGoUp()
1179 m_barRepository.OnGoUp();
1182 void CRepositoryBrowser::OnUrlFocus()
1184 m_barRepository.SetFocusToURL();
1187 void CRepositoryBrowser::OnCopy()
1189 // Ctrl-C : copy the selected item urls to the clipboard
1190 CString url;
1191 POSITION pos = m_RepoList.GetFirstSelectedItemPosition();
1192 int index = -1;
1193 while ((index = m_RepoList.GetNextSelectedItem(pos))>=0)
1195 CItem * pItem = (CItem *)m_RepoList.GetItemData(index);
1196 url += CUnicodeUtils::GetUnicode(CPathUtils::PathEscape(CUnicodeUtils::GetUTF8(pItem->absolutepath))) + _T("\r\n");
1198 if (!url.IsEmpty())
1200 url.TrimRight(_T("\r\n"));
1201 CStringUtils::WriteAsciiStringToClipboard(url);
1205 void CRepositoryBrowser::OnInlineedit()
1207 POSITION pos = m_RepoList.GetFirstSelectedItemPosition();
1208 int selIndex = m_RepoList.GetNextSelectedItem(pos);
1209 m_blockEvents = true;
1210 if (selIndex >= 0)
1212 m_RepoList.SetFocus();
1213 m_RepoList.EditLabel(selIndex);
1215 else
1217 m_RepoTree.SetFocus();
1218 HTREEITEM hTreeItem = m_RepoTree.GetSelectedItem();
1219 if (hTreeItem != m_RepoTree.GetRootItem())
1220 m_RepoTree.EditLabel(hTreeItem);
1222 m_blockEvents = false;
1225 void CRepositoryBrowser::OnRefresh()
1227 m_blockEvents = true;
1228 RefreshNode(m_RepoTree.GetSelectedItem(), true, !!(GetKeyState(VK_CONTROL)&0x8000));
1229 m_blockEvents = false;
1232 void CRepositoryBrowser::OnTvnSelchangedRepotree(NMHDR *pNMHDR, LRESULT *pResult)
1234 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
1235 *pResult = 0;
1237 if (m_blockEvents)
1238 return;
1240 if (pNMTreeView->action == TVC_BYKEYBOARD)
1241 SetTimer(REPOBROWSER_FETCHTIMER, 300, NULL);
1242 else
1243 OnTimer(REPOBROWSER_FETCHTIMER);
1246 void CRepositoryBrowser::OnTimer(UINT_PTR nIDEvent)
1248 if (nIDEvent == REPOBROWSER_FETCHTIMER)
1250 KillTimer(REPOBROWSER_FETCHTIMER);
1251 // find the currently selected item
1252 HTREEITEM hSelItem = m_RepoTree.GetSelectedItem();
1253 if (hSelItem)
1255 CTreeItem * pTreeItem = (CTreeItem *)m_RepoTree.GetItemData(hSelItem);
1256 if (pTreeItem)
1258 if (!pTreeItem->children_fetched)
1260 m_RepoList.ShowText(_T(" "), true);
1261 RefreshNode(hSelItem);
1262 m_RepoList.ClearText();
1265 FillList(&pTreeItem->children);
1266 m_barRepository.ShowUrl(pTreeItem->url, GetRevision());
1271 __super::OnTimer(nIDEvent);
1274 void CRepositoryBrowser::OnTvnItemexpandingRepotree(NMHDR *pNMHDR, LRESULT *pResult)
1276 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
1277 *pResult = 0;
1279 if (m_blockEvents)
1280 return;
1282 CTreeItem * pTreeItem = (CTreeItem *)pNMTreeView->itemNew.lParam;
1284 if (pTreeItem == NULL)
1285 return;
1287 if (pNMTreeView->action == TVE_COLLAPSE)
1289 // user wants to collapse a tree node.
1290 // if we don't know anything about the children
1291 // of the node, we suppress the collapsing but fetch the info instead
1292 if (!pTreeItem->children_fetched)
1294 RefreshNode(pNMTreeView->itemNew.hItem);
1295 *pResult = 1;
1296 return;
1298 return;
1301 // user wants to expand a tree node.
1302 // check if we already know its children - if not we have to ask the repository!
1304 if (!pTreeItem->children_fetched)
1306 RefreshNode(pNMTreeView->itemNew.hItem);
1308 else
1310 // if there are no child folders, remove the '+' in front of the node
1311 if (!pTreeItem->has_child_folders)
1313 TVITEM tvitem = {0};
1314 tvitem.hItem = pNMTreeView->itemNew.hItem;
1315 tvitem.mask = TVIF_CHILDREN;
1316 tvitem.cChildren = 0;
1317 m_RepoTree.SetItem(&tvitem);
1322 void CRepositoryBrowser::OnNMDblclkRepolist(NMHDR *pNMHDR, LRESULT *pResult)
1324 LPNMITEMACTIVATE pNmItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
1325 *pResult = 0;
1327 if (m_blockEvents)
1328 return;
1330 if (pNmItemActivate->iItem < 0)
1331 return;
1332 CItem * pItem = (CItem*)m_RepoList.GetItemData(pNmItemActivate->iItem);
1333 if ((pItem)&&(pItem->kind == svn_node_dir))
1335 // a double click on a folder results in selecting that folder
1336 ChangeToUrl(pItem->absolutepath, m_initialRev, true);
1340 void CRepositoryBrowser::OnHdnItemclickRepolist(NMHDR *pNMHDR, LRESULT *pResult)
1342 LPNMHEADER phdr = reinterpret_cast<LPNMHEADER>(pNMHDR);
1343 // a click on a header means sorting the items
1344 if (m_nSortedColumn != phdr->iItem)
1345 m_bSortAscending = true;
1346 else
1347 m_bSortAscending = !m_bSortAscending;
1348 m_nSortedColumn = phdr->iItem;
1350 m_blockEvents = true;
1351 ListView_SortItemsEx(m_RepoList, ListSort, this);
1352 SetSortArrow();
1353 m_blockEvents = false;
1354 *pResult = 0;
1357 int CRepositoryBrowser::ListSort(LPARAM lParam1, LPARAM lParam2, LPARAM lParam3)
1359 CRepositoryBrowser * pThis = (CRepositoryBrowser*)lParam3;
1360 CItem * pItem1 = (CItem*)pThis->m_RepoList.GetItemData(static_cast<int>(lParam1));
1361 CItem * pItem2 = (CItem*)pThis->m_RepoList.GetItemData(static_cast<int>(lParam2));
1362 int nRet = 0;
1363 switch (pThis->m_nSortedColumn)
1365 case 1: // extension
1366 nRet = pThis->m_RepoList.GetItemText(static_cast<int>(lParam1), 1)
1367 .CompareNoCase(pThis->m_RepoList.GetItemText(static_cast<int>(lParam2), 1));
1368 if (nRet != 0)
1369 break;
1370 // fall through
1371 case 2: // revision number
1372 nRet = pItem1->created_rev - pItem2->created_rev;
1373 if (nRet != 0)
1374 break;
1375 // fall through
1376 case 3: // author
1377 nRet = pItem1->author.CompareNoCase(pItem2->author);
1378 if (nRet != 0)
1379 break;
1380 // fall through
1381 case 4: // size
1382 nRet = int(pItem1->size - pItem2->size);
1383 if (nRet != 0)
1384 break;
1385 // fall through
1386 case 5: // date
1387 nRet = (pItem1->time - pItem2->time) > 0 ? 1 : -1;
1388 if (nRet != 0)
1389 break;
1390 // fall through
1391 case 6: // lock owner
1392 nRet = pItem1->lockowner.CompareNoCase(pItem2->lockowner);
1393 if (nRet != 0)
1394 break;
1395 // fall through
1396 case 0: // filename
1397 nRet = CStringUtils::CompareNumerical(pItem1->path, pItem2->path);
1398 break;
1401 if (!pThis->m_bSortAscending)
1402 nRet = -nRet;
1404 // we want folders on top, then the files
1405 if (pItem1->kind != pItem2->kind)
1407 if (pItem1->kind == svn_node_dir)
1408 nRet = -1;
1409 else
1410 nRet = 1;
1413 return nRet;
1416 void CRepositoryBrowser::SetSortArrow()
1418 CHeaderCtrl * pHeader = m_RepoList.GetHeaderCtrl();
1419 HDITEM HeaderItem = {0};
1420 HeaderItem.mask = HDI_FORMAT;
1421 for (int i=0; i<pHeader->GetItemCount(); ++i)
1423 pHeader->GetItem(i, &HeaderItem);
1424 HeaderItem.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
1425 pHeader->SetItem(i, &HeaderItem);
1428 pHeader->GetItem(m_nSortedColumn, &HeaderItem);
1429 HeaderItem.fmt |= (m_bSortAscending ? HDF_SORTUP : HDF_SORTDOWN);
1430 pHeader->SetItem(m_nSortedColumn, &HeaderItem);
1433 void CRepositoryBrowser::OnLvnItemchangedRepolist(NMHDR *pNMHDR, LRESULT *pResult)
1435 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1436 *pResult = 0;
1437 if (m_blockEvents)
1438 return;
1439 if (m_RepoList.HasText())
1440 return;
1441 if (pNMLV->uChanged & LVIF_STATE)
1443 if (pNMLV->uNewState & LVIS_SELECTED)
1445 CItem * pItem = (CItem*)m_RepoList.GetItemData(pNMLV->iItem);
1446 if (pItem)
1447 m_barRepository.ShowUrl(pItem->absolutepath, GetRevision());
1452 void CRepositoryBrowser::OnLvnEndlabeleditRepolist(NMHDR *pNMHDR, LRESULT *pResult)
1454 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1455 *pResult = 0;
1456 if (pDispInfo->item.pszText == NULL)
1457 return;
1458 // rename the item in the repository
1459 CItem * pItem = (CItem *)m_RepoList.GetItemData(pDispInfo->item.iItem);
1461 CWaitCursorEx wait_cursor;
1462 CInputLogDlg input(this);
1463 input.SetUUID(m_sUUID);
1464 input.SetProjectProperties(&m_ProjectProperties);
1465 CTSVNPath targetUrl = CTSVNPath(EscapeUrl(CTSVNPath(pItem->absolutepath.Left(pItem->absolutepath.ReverseFind('/')+1)+pDispInfo->item.pszText)));
1466 if (!targetUrl.IsValidOnWindows())
1468 if (CMessageBox::Show(GetSafeHwnd(), IDS_WARN_NOVALIDPATH, IDS_APPNAME, MB_ICONINFORMATION|MB_YESNO) != IDYES)
1469 return;
1471 CString sHint;
1472 sHint.Format(IDS_INPUT_RENAME, (LPCTSTR)(pItem->absolutepath), (LPCTSTR)targetUrl.GetSVNPathString());
1473 input.SetActionText(sHint);
1474 if (input.DoModal() == IDOK)
1476 m_bCancelled = false;
1477 if (!Move(CTSVNPathList(CTSVNPath(EscapeUrl(CTSVNPath(pItem->absolutepath)))),
1478 targetUrl,
1479 true, input.GetLogMessage()))
1481 wait_cursor.Hide();
1482 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseSVN"), MB_ICONERROR);
1483 return;
1485 *pResult = TRUE;
1486 RefreshNode(m_RepoTree.GetSelectedItem(), true);
1490 void CRepositoryBrowser::OnTvnEndlabeleditRepotree(NMHDR *pNMHDR, LRESULT *pResult)
1492 LPNMTVDISPINFO pTVDispInfo = reinterpret_cast<LPNMTVDISPINFO>(pNMHDR);
1493 *pResult = 0;
1494 if (pTVDispInfo->item.pszText == NULL)
1495 return;
1497 // rename the item in the repository
1498 HTREEITEM hSelectedItem = pTVDispInfo->item.hItem;
1499 CTreeItem * pItem = (CTreeItem *)m_RepoTree.GetItemData(hSelectedItem);
1500 if (pItem == NULL)
1501 return;
1503 CWaitCursorEx wait_cursor;
1504 CInputLogDlg input(this);
1505 input.SetUUID(m_sUUID);
1506 input.SetProjectProperties(&m_ProjectProperties);
1507 CTSVNPath targetUrl = CTSVNPath(EscapeUrl(CTSVNPath(pItem->url.Left(pItem->url.ReverseFind('/')+1)+pTVDispInfo->item.pszText)));
1508 if (!targetUrl.IsValidOnWindows())
1510 if (CMessageBox::Show(GetSafeHwnd(), IDS_WARN_NOVALIDPATH, IDS_APPNAME, MB_ICONINFORMATION|MB_YESNO) != IDYES)
1511 return;
1513 CString sHint;
1514 sHint.Format(IDS_INPUT_RENAME, (LPCTSTR)(pItem->url), (LPCTSTR)targetUrl.GetSVNPathString());
1515 input.SetActionText(sHint);
1516 if (input.DoModal() == IDOK)
1518 m_bCancelled = false;
1519 if (!Move(CTSVNPathList(CTSVNPath(EscapeUrl(CTSVNPath(pItem->url)))),
1520 targetUrl,
1521 true, input.GetLogMessage()))
1523 wait_cursor.Hide();
1524 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseSVN"), MB_ICONERROR);
1525 return;
1527 *pResult = TRUE;
1528 pItem->url = targetUrl.GetSVNPathString();
1529 pItem->unescapedname = pTVDispInfo->item.pszText;
1530 m_RepoTree.SetItemData(hSelectedItem, (DWORD_PTR)pItem);
1531 if (hSelectedItem == m_RepoTree.GetSelectedItem())
1532 RefreshNode(hSelectedItem, true);
1536 void CRepositoryBrowser::OnLvnBeginrdragRepolist(NMHDR *pNMHDR, LRESULT *pResult)
1538 m_bRightDrag = true;
1539 *pResult = 0;
1540 OnBeginDrag(pNMHDR);
1543 void CRepositoryBrowser::OnLvnBegindragRepolist(NMHDR *pNMHDR, LRESULT *pResult)
1545 m_bRightDrag = false;
1546 *pResult = 0;
1547 OnBeginDrag(pNMHDR);
1550 void CRepositoryBrowser::OnBeginDrag(NMHDR *pNMHDR)
1552 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1554 if (m_RepoList.HasText())
1555 return;
1556 CIDropSource* pdsrc = new CIDropSource;
1557 if (pdsrc == NULL)
1558 return;
1559 pdsrc->AddRef();
1561 CTSVNPathList sourceURLs;
1562 POSITION pos = m_RepoList.GetFirstSelectedItemPosition();
1563 int index = -1;
1564 while ((index = m_RepoList.GetNextSelectedItem(pos))>=0)
1566 CItem * pItem = (CItem *)m_RepoList.GetItemData(index);
1567 if (pItem)
1568 sourceURLs.AddPath(CTSVNPath(EscapeUrl(CTSVNPath(pItem->absolutepath))));
1571 SVNDataObject* pdobj = new SVNDataObject(sourceURLs, GetRevision(), GetRevision());
1572 if (pdobj == NULL)
1574 delete pdsrc;
1575 return;
1577 pdobj->AddRef();
1578 pdobj->SetAsyncMode(TRUE);
1580 CDragSourceHelper dragsrchelper;
1581 dragsrchelper.InitializeFromWindow(m_RepoList.GetSafeHwnd(), pNMLV->ptAction, pdobj);
1582 // Initiate the Drag & Drop
1583 DWORD dwEffect;
1584 ::DoDragDrop(pdobj, pdsrc, DROPEFFECT_MOVE|DROPEFFECT_COPY, &dwEffect);
1585 pdsrc->Release();
1586 pdobj->Release();
1589 void CRepositoryBrowser::OnTvnBegindragRepotree(NMHDR *pNMHDR, LRESULT *pResult)
1591 m_bRightDrag = false;
1592 *pResult = 0;
1593 OnBeginDragTree(pNMHDR);
1596 void CRepositoryBrowser::OnTvnBeginrdragRepotree(NMHDR *pNMHDR, LRESULT *pResult)
1598 m_bRightDrag = true;
1599 *pResult = 0;
1600 OnBeginDragTree(pNMHDR);
1603 void CRepositoryBrowser::OnBeginDragTree(NMHDR *pNMHDR)
1605 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
1607 if (m_blockEvents)
1608 return;
1610 CTreeItem * pTreeItem = (CTreeItem *)pNMTreeView->itemNew.lParam;
1612 if (pTreeItem == NULL)
1613 return;
1615 CIDropSource* pdsrc = new CIDropSource;
1616 if (pdsrc == NULL)
1617 return;
1618 pdsrc->AddRef();
1620 CTSVNPathList sourceURLs;
1621 sourceURLs.AddPath(CTSVNPath(EscapeUrl(CTSVNPath(pTreeItem->url))));
1623 SVNDataObject* pdobj = new SVNDataObject(sourceURLs, GetRevision(), GetRevision());
1624 if (pdobj == NULL)
1626 delete pdsrc;
1627 return;
1629 pdobj->AddRef();
1631 CDragSourceHelper dragsrchelper;
1632 dragsrchelper.InitializeFromWindow(m_RepoTree.GetSafeHwnd(), pNMTreeView->ptDrag, pdobj);
1633 // Initiate the Drag & Drop
1634 DWORD dwEffect;
1635 ::DoDragDrop(pdobj, pdsrc, DROPEFFECT_MOVE|DROPEFFECT_COPY, &dwEffect);
1636 pdsrc->Release();
1637 pdobj->Release();
1641 bool CRepositoryBrowser::OnDrop(const CTSVNPath& target, const CTSVNPathList& pathlist, const SVNRev& srcRev, DWORD dwEffect, POINTL /*pt*/)
1643 ATLTRACE(_T("dropped %ld items on %s, source revision is %s, dwEffect is %ld\n"), pathlist.GetCount(), (LPCTSTR)target.GetSVNPathString(), srcRev.ToString(), dwEffect);
1644 if (pathlist.GetCount() == 0)
1645 return false;
1647 CString targetName = pathlist[0].GetFileOrDirectoryName();
1648 if (m_bRightDrag)
1650 // right dragging means we have to show a context menu
1651 POINT pt;
1652 DWORD ptW = GetMessagePos();
1653 pt.x = GET_X_LPARAM(ptW);
1654 pt.y = GET_Y_LPARAM(ptW);
1655 CMenu popup;
1656 if (popup.CreatePopupMenu())
1658 CString temp(MAKEINTRESOURCE(IDS_REPOBROWSE_COPYDROP));
1659 popup.AppendMenu(MF_STRING | MF_ENABLED, 1, temp);
1660 temp.LoadString(IDS_REPOBROWSE_MOVEDROP);
1661 popup.AppendMenu(MF_STRING | MF_ENABLED, 2, temp);
1662 if ((pathlist.GetCount() == 1)&&(PathIsURL(pathlist[0])))
1664 // these entries are only shown if *one* item was dragged, and if the
1665 // item is not one dropped from e.g. the explorer but from the repository
1666 // browser itself.
1667 popup.AppendMenu(MF_SEPARATOR, 3);
1668 temp.LoadString(IDS_REPOBROWSE_COPYRENAMEDROP);
1669 popup.AppendMenu(MF_STRING | MF_ENABLED, 4, temp);
1670 temp.LoadString(IDS_REPOBROWSE_MOVERENAMEDROP);
1671 popup.AppendMenu(MF_STRING | MF_ENABLED, 5, temp);
1673 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, pt.x, pt.y, this, 0);
1674 switch (cmd)
1676 default:// nothing clicked
1677 return false;
1678 case 1: // copy drop
1679 dwEffect = DROPEFFECT_COPY;
1680 break;
1681 case 2: // move drop
1682 dwEffect = DROPEFFECT_MOVE;
1683 break;
1684 case 4: // copy rename drop
1686 dwEffect = DROPEFFECT_COPY;
1687 CRenameDlg dlg;
1688 dlg.m_name = targetName;
1689 dlg.m_windowtitle.LoadString(IDS_REPOBROWSE_RENAME);
1690 CStringUtils::RemoveAccelerators(dlg.m_windowtitle);
1691 if (dlg.DoModal() != IDOK)
1692 return false;
1693 targetName = dlg.m_name;
1694 if (!CTSVNPath(targetName).IsValidOnWindows())
1696 if (CMessageBox::Show(GetSafeHwnd(), IDS_WARN_NOVALIDPATH, IDS_APPNAME, MB_ICONINFORMATION|MB_YESNO) != IDYES)
1697 return false;
1700 break;
1701 case 5: // move rename drop
1703 dwEffect = DROPEFFECT_MOVE;
1704 CRenameDlg dlg;
1705 dlg.m_name = targetName;
1706 dlg.m_windowtitle.LoadString(IDS_REPOBROWSE_RENAME);
1707 CStringUtils::RemoveAccelerators(dlg.m_windowtitle);
1708 if (dlg.DoModal() != IDOK)
1709 return false;
1710 targetName = dlg.m_name;
1711 if (!CTSVNPath(targetName).IsValidOnWindows())
1713 if (CMessageBox::Show(GetSafeHwnd(), IDS_WARN_NOVALIDPATH, IDS_APPNAME, MB_ICONINFORMATION|MB_YESNO) != IDYES)
1714 return false;
1717 break;
1722 // check the first item in the path list:
1723 // if it's an url, we do a copy or move operation
1724 // if it's a local path, we do an import
1725 if (PathIsURL(pathlist[0]))
1727 // If any of the paths are 'special' (branches, tags, or trunk) and we are
1728 // about to perform a move, we should warn the user and get them to confirm
1729 // that this is what they intended. Yes, I *have* accidentally moved the
1730 // trunk when I was trying to create a tag! :)
1731 if (DROPEFFECT_COPY != dwEffect)
1733 bool pathListIsSpecial = false;
1734 int pathCount = pathlist.GetCount();
1735 for (int i=0 ; i<pathCount ; ++i)
1737 const CTSVNPath& path = pathlist[i];
1738 if (path.IsSpecialDirectory())
1740 pathListIsSpecial = true;
1741 break;
1744 if (pathListIsSpecial)
1746 UINT msgResult = CMessageBox::Show(GetSafeHwnd(), IDS_WARN_CONFIRM_MOVE_SPECIAL_DIRECTORY, IDS_APPNAME, MB_ICONQUESTION | MB_YESNO);
1747 if (IDYES != msgResult)
1749 return false;
1754 // drag-n-drop inside the repobrowser
1755 CInputLogDlg input(this);
1756 input.SetUUID(m_sUUID);
1757 input.SetProjectProperties(&m_ProjectProperties);
1758 CString sHint;
1759 if (pathlist.GetCount() == 1)
1761 if (dwEffect == DROPEFFECT_COPY)
1762 sHint.Format(IDS_INPUT_COPY, (LPCTSTR)pathlist[0].GetSVNPathString(), (LPCTSTR)(target.GetSVNPathString()+_T("/")+targetName));
1763 else
1764 sHint.Format(IDS_INPUT_MOVE, (LPCTSTR)pathlist[0].GetSVNPathString(), (LPCTSTR)(target.GetSVNPathString()+_T("/")+targetName));
1766 else
1768 if (dwEffect == DROPEFFECT_COPY)
1769 sHint.Format(IDS_INPUT_COPYMORE, pathlist.GetCount(), (LPCTSTR)target.GetSVNPathString());
1770 else
1771 sHint.Format(IDS_INPUT_MOVEMORE, pathlist.GetCount(), (LPCTSTR)target.GetSVNPathString());
1773 input.SetActionText(sHint);
1774 if (input.DoModal() == IDOK)
1776 m_bCancelled = false;
1777 CWaitCursorEx wait_cursor;
1778 BOOL bRet = FALSE;
1779 if (dwEffect == DROPEFFECT_COPY)
1780 if (pathlist.GetCount() == 1)
1781 bRet = Copy(pathlist, CTSVNPath(target.GetSVNPathString() + _T("/") + targetName), srcRev, srcRev, input.GetLogMessage(), false);
1782 else
1783 bRet = Copy(pathlist, target, srcRev, srcRev, input.GetLogMessage(), true);
1784 else
1785 if (pathlist.GetCount() == 1)
1786 bRet = Move(pathlist, CTSVNPath(target.GetSVNPathString() + _T("/") + targetName), TRUE, input.GetLogMessage(), false);
1787 else
1788 bRet = Move(pathlist, target, TRUE, input.GetLogMessage(), true);
1789 if (!bRet)
1791 wait_cursor.Hide();
1792 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseSVN"), MB_ICONERROR);
1794 else if (GetRevision().IsHead())
1796 // mark the target as dirty
1797 HTREEITEM hTarget = FindUrl(target.GetSVNPathString(), false);
1798 if (hTarget)
1800 CTreeItem * pItem = (CTreeItem*)m_RepoTree.GetItemData(hTarget);
1801 if (pItem)
1803 // mark the target as 'dirty'
1804 pItem->children_fetched = false;
1805 RecursiveRemove(hTarget, true);
1806 TVITEM tvitem = {0};
1807 tvitem.hItem = hTarget;
1808 tvitem.mask = TVIF_CHILDREN;
1809 tvitem.cChildren = 1;
1810 m_RepoTree.SetItem(&tvitem);
1813 if (dwEffect == DROPEFFECT_MOVE)
1815 // if items were moved, we have to
1816 // invalidate all sources too
1817 for (int i=0; i<pathlist.GetCount(); ++i)
1819 HTREEITEM hSource = FindUrl(pathlist[i].GetSVNPathString(), false);
1820 if (hSource)
1822 CTreeItem * pItem = (CTreeItem*)m_RepoTree.GetItemData(hSource);
1823 if (pItem)
1825 // the source has moved, so remove it!
1826 RecursiveRemove(hSource);
1827 m_RepoTree.DeleteItem(hSource);
1833 // if the copy/move operation was to the currently shown url,
1834 // update the current view. Otherwise mark the target URL as 'not fetched'.
1835 HTREEITEM hSelected = m_RepoTree.GetSelectedItem();
1836 if (hSelected)
1838 CTreeItem * pItem = (CTreeItem*)m_RepoTree.GetItemData(hSelected);
1839 if (pItem)
1841 // mark the target as 'dirty'
1842 pItem->children_fetched = false;
1843 if ((dwEffect == DROPEFFECT_MOVE)||(pItem->url.Compare(target.GetSVNPathString())==0))
1845 // Refresh the current view
1846 RefreshNode(hSelected, true);
1853 else
1855 // import files dragged onto us
1856 if (pathlist.GetCount() > 1)
1858 if (CMessageBox::Show(m_hWnd, IDS_REPOBROWSE_MULTIIMPORT, IDS_APPNAME, MB_YESNO | MB_ICONQUESTION)!=IDYES)
1859 return false;
1862 CInputLogDlg input(this);
1863 input.SetProjectProperties(&m_ProjectProperties);
1864 input.SetUUID(m_sUUID);
1865 CString sHint;
1866 if (pathlist.GetCount() == 1)
1867 sHint.Format(IDS_INPUT_IMPORTFILEFULL, pathlist[0].GetWinPath(), (LPCTSTR)(target.GetSVNPathString() + _T("/") + pathlist[0].GetFileOrDirectoryName()));
1868 else
1869 sHint.Format(IDS_INPUT_IMPORTFILES, pathlist.GetCount());
1870 input.SetActionText(sHint);
1872 if (input.DoModal() == IDOK)
1874 m_bCancelled = false;
1875 for (int importindex = 0; importindex<pathlist.GetCount(); ++importindex)
1877 CString filename = pathlist[importindex].GetFileOrDirectoryName();
1878 if (!Import(pathlist[importindex],
1879 CTSVNPath(target.GetSVNPathString()+_T("/")+filename),
1880 input.GetLogMessage(), &m_ProjectProperties, svn_depth_infinity, TRUE, FALSE))
1882 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseSVN"), MB_ICONERROR);
1883 return false;
1886 if (GetRevision().IsHead())
1888 // if the import operation was to the currently shown url,
1889 // update the current view. Otherwise mark the target URL as 'not fetched'.
1890 HTREEITEM hSelected = m_RepoTree.GetSelectedItem();
1891 if (hSelected)
1893 CTreeItem * pItem = (CTreeItem*)m_RepoTree.GetItemData(hSelected);
1894 if (pItem)
1896 if (pItem->url.Compare(target.GetSVNPathString())==0)
1898 // Refresh the current view
1899 RefreshNode(hSelected, true);
1901 else
1903 // only mark the target as 'dirty'
1904 pItem->children_fetched = false;
1912 return true;
1915 CString CRepositoryBrowser::EscapeUrl(const CTSVNPath& url)
1917 return CUnicodeUtils::GetUnicode(CPathUtils::PathEscape(CUnicodeUtils::GetUTF8(url.GetSVNPathString())));
1920 void CRepositoryBrowser::OnContextMenu(CWnd* pWnd, CPoint point)
1922 HTREEITEM hSelectedTreeItem = NULL;
1923 HTREEITEM hChosenTreeItem = NULL;
1924 if ((point.x == -1) && (point.y == -1))
1926 if (pWnd == &m_RepoTree)
1928 CRect rect;
1929 m_RepoTree.GetItemRect(m_RepoTree.GetSelectedItem(), &rect, TRUE);
1930 m_RepoTree.ClientToScreen(&rect);
1931 point = rect.CenterPoint();
1933 else
1935 CRect rect;
1936 POSITION pos = m_RepoList.GetFirstSelectedItemPosition();
1937 m_RepoList.GetItemRect(m_RepoList.GetNextSelectedItem(pos), &rect, LVIR_LABEL);
1938 m_RepoList.ClientToScreen(&rect);
1939 point = rect.CenterPoint();
1942 m_bCancelled = false;
1943 CTSVNPathList urlList;
1944 CTSVNPathList urlListEscaped;
1945 int nFolders = 0;
1946 int nLocked = 0;
1947 if (pWnd == &m_RepoList)
1949 CString urls;
1951 POSITION pos = m_RepoList.GetFirstSelectedItemPosition();
1952 int index = -1;
1953 while ((index = m_RepoList.GetNextSelectedItem(pos))>=0)
1955 CItem * pItem = (CItem *)m_RepoList.GetItemData(index);
1956 CString absPath = pItem->absolutepath;
1957 absPath.Replace(_T("\\"), _T("%5C"));
1958 urlList.AddPath(CTSVNPath(absPath));
1959 urlListEscaped.AddPath(CTSVNPath(EscapeUrl(CTSVNPath(absPath))));
1960 if (pItem->kind == svn_node_dir)
1961 nFolders++;
1962 if (!pItem->locktoken.IsEmpty())
1963 nLocked++;
1965 if (urlList.GetCount() == 0)
1967 // Right-click outside any list control items. It may be the background,
1968 // but it also could be the list control headers.
1969 CRect hr;
1970 m_RepoList.GetHeaderCtrl()->GetWindowRect(&hr);
1971 if (!hr.PtInRect(point))
1973 // Seems to be a right-click on the list view background.
1974 // Use the currently selected item in the tree view as the source.
1975 m_blockEvents = true;
1976 hSelectedTreeItem = m_RepoTree.GetSelectedItem();
1977 if (hSelectedTreeItem)
1979 m_RepoTree.SetItemState(hSelectedTreeItem, 0, TVIS_SELECTED);
1980 m_blockEvents = false;
1981 m_RepoTree.SetItemState(hSelectedTreeItem, TVIS_DROPHILITED, TVIS_DROPHILITED);
1982 CTreeItem * pTreeItem = (CTreeItem *)m_RepoTree.GetItemData(hSelectedTreeItem);
1983 if (pTreeItem)
1985 urlList.AddPath(CTSVNPath(pTreeItem->url));
1986 urlListEscaped.AddPath(CTSVNPath(EscapeUrl(CTSVNPath(pTreeItem->url))));
1987 nFolders++;
1993 if ((pWnd == &m_RepoTree)||(urlList.GetCount() == 0))
1995 UINT uFlags;
1996 CPoint ptTree = point;
1997 m_RepoTree.ScreenToClient(&ptTree);
1998 HTREEITEM hItem = m_RepoTree.HitTest(ptTree, &uFlags);
1999 // in case the right-clicked item is not the selected one,
2000 // use the TVIS_DROPHILITED style to indicate on which item
2001 // the context menu will work on
2002 if ((hItem) && (uFlags & TVHT_ONITEM) && (hItem != m_RepoTree.GetSelectedItem()))
2004 m_blockEvents = true;
2005 hSelectedTreeItem = m_RepoTree.GetSelectedItem();
2006 m_RepoTree.SetItemState(hSelectedTreeItem, 0, TVIS_SELECTED);
2007 m_blockEvents = false;
2008 m_RepoTree.SetItemState(hItem, TVIS_DROPHILITED, TVIS_DROPHILITED);
2010 if (hItem)
2012 hChosenTreeItem = hItem;
2013 CTreeItem * pTreeItem = (CTreeItem *)m_RepoTree.GetItemData(hItem);
2014 if (pTreeItem)
2016 urlList.AddPath(CTSVNPath(pTreeItem->url));
2017 urlListEscaped.AddPath(CTSVNPath(EscapeUrl(CTSVNPath(pTreeItem->url))));
2018 nFolders++;
2023 if (urlList.GetCount() == 0)
2024 return;
2026 CIconMenu popup;
2027 if (popup.CreatePopupMenu())
2029 if (urlList.GetCount() == 1)
2031 if (nFolders == 0)
2033 // Let "Open" be the very first entry, like in Explorer
2034 popup.AppendMenuIcon(ID_OPEN, IDS_REPOBROWSE_OPEN, IDI_OPEN); // "open"
2035 popup.AppendMenuIcon(ID_OPENWITH, IDS_LOG_POPUP_OPENWITH, IDI_OPEN); // "open with..."
2036 popup.AppendMenu(MF_SEPARATOR, NULL);
2038 popup.AppendMenuIcon(ID_SHOWLOG, IDS_REPOBROWSE_SHOWLOG, IDI_LOG); // "Show Log..."
2039 // the revision graph on the repository root would be empty. We
2040 // don't show the context menu entry there.
2041 if (urlList[0].GetSVNPathString().Compare(m_strReposRoot)!=0)
2043 popup.AppendMenuIcon(ID_REVGRAPH, IDS_MENUREVISIONGRAPH, IDI_REVISIONGRAPH); // "Revision graph"
2045 if (nFolders == 0)
2047 popup.AppendMenuIcon(ID_BLAME, IDS_MENUBLAME, IDI_BLAME); // "Blame..."
2049 if (!m_ProjectProperties.sWebViewerRev.IsEmpty())
2051 popup.AppendMenuIcon(ID_VIEWREV, IDS_LOG_POPUP_VIEWREV); // "View revision in webviewer"
2053 if (!m_ProjectProperties.sWebViewerPathRev.IsEmpty())
2055 popup.AppendMenuIcon(ID_VIEWPATHREV, IDS_LOG_POPUP_VIEWPATHREV); // "View revision for path in webviewer"
2057 if ((!m_ProjectProperties.sWebViewerPathRev.IsEmpty())||
2058 (!m_ProjectProperties.sWebViewerRev.IsEmpty()))
2060 popup.AppendMenu(MF_SEPARATOR, NULL);
2062 if (nFolders)
2064 popup.AppendMenuIcon(ID_EXPORT, IDS_MENUEXPORT, IDI_EXPORT); // "Export"
2067 // We allow checkout of multiple folders at once (we do that one by one)
2068 if (nFolders == urlList.GetCount())
2070 popup.AppendMenuIcon(ID_CHECKOUT, IDS_MENUCHECKOUT, IDI_CHECKOUT); // "Checkout.."
2072 if (urlList.GetCount() == 1)
2074 if (nFolders)
2076 popup.AppendMenuIcon(ID_REFRESH, IDS_REPOBROWSE_REFRESH, IDI_REFRESH); // "Refresh"
2078 popup.AppendMenu(MF_SEPARATOR, NULL);
2080 if (GetRevision().IsHead())
2082 if (nFolders)
2084 popup.AppendMenuIcon(ID_MKDIR, IDS_REPOBROWSE_MKDIR, IDI_MKDIR); // "create directory"
2085 popup.AppendMenuIcon(ID_IMPORT, IDS_REPOBROWSE_IMPORT, IDI_IMPORT); // "Add/Import File"
2086 popup.AppendMenuIcon(ID_IMPORTFOLDER, IDS_REPOBROWSE_IMPORTFOLDER, IDI_IMPORT); // "Add/Import Folder"
2087 popup.AppendMenu(MF_SEPARATOR, NULL);
2090 popup.AppendMenuIcon(ID_RENAME, IDS_REPOBROWSE_RENAME, IDI_RENAME); // "Rename"
2092 if (nLocked)
2094 popup.AppendMenuIcon(ID_BREAKLOCK, IDS_MENU_UNLOCKFORCE, IDI_UNLOCK); // "Break Lock"
2097 if (urlList.GetCount() > 0)
2099 if (GetRevision().IsHead())
2101 popup.AppendMenuIcon(ID_DELETE, IDS_REPOBROWSE_DELETE, IDI_DELETE); // "Remove"
2103 if (nFolders == 0)
2105 popup.AppendMenuIcon(ID_SAVEAS, IDS_REPOBROWSE_SAVEAS, IDI_SAVEAS); // "Save as..."
2107 if ((urlList.GetCount() == nFolders)||(nFolders == 0))
2109 popup.AppendMenuIcon(ID_COPYTOWC, IDS_REPOBROWSE_COPYTOWC); // "Copy To Working Copy..."
2112 if (urlList.GetCount() == 1)
2114 popup.AppendMenuIcon(ID_COPYTO, IDS_REPOBROWSE_COPY, IDI_COPY); // "Copy To..."
2115 popup.AppendMenuIcon(ID_URLTOCLIPBOARD, IDS_REPOBROWSE_URLTOCLIPBOARD, IDI_COPYCLIP); // "Copy URL to clipboard"
2116 popup.AppendMenu(MF_SEPARATOR, NULL);
2117 popup.AppendMenuIcon(ID_PROPS, IDS_REPOBROWSE_SHOWPROP, IDI_PROPERTIES); // "Show Properties"
2118 // Revision properties are not associated to paths
2119 // so we only show that context menu on the repository root
2120 if (urlList[0].GetSVNPathString().Compare(m_strReposRoot)==0)
2122 popup.AppendMenuIcon(ID_REVPROPS, IDS_REPOBROWSE_SHOWREVPROP, IDI_PROPERTIES); // "Show Revision Properties"
2124 if (nFolders == 1)
2126 popup.AppendMenu(MF_SEPARATOR, NULL);
2127 popup.AppendMenuIcon(ID_PREPAREDIFF, IDS_REPOBROWSE_PREPAREDIFF); // "Mark for comparison"
2129 if ((m_diffKind == svn_node_dir)&&(!m_diffURL.IsEquivalentTo(urlList[0])))
2131 popup.AppendMenuIcon(ID_GNUDIFF, IDS_LOG_POPUP_GNUDIFF, IDI_DIFF); // "Show differences as unified diff"
2132 popup.AppendMenuIcon(ID_DIFF, IDS_REPOBROWSE_SHOWDIFF, IDI_DIFF); // "Compare URLs"
2136 if (urlList.GetCount() == 2)
2138 if ((nFolders == 2)||(nFolders == 0))
2140 popup.AppendMenu(MF_SEPARATOR, NULL);
2141 popup.AppendMenuIcon(ID_GNUDIFF, IDS_LOG_POPUP_GNUDIFF, IDI_DIFF); // "Show differences as unified diff"
2142 popup.AppendMenuIcon(ID_DIFF, IDS_REPOBROWSE_SHOWDIFF, ID_DIFF); // "Compare URLs"
2143 popup.AppendMenu(MF_SEPARATOR, NULL);
2145 popup.AppendMenuIcon(ID_SHOWLOG, IDS_MENULOG, IDI_LOG); // "Show Log..."
2147 if ((urlList.GetCount() == 1) &&
2148 m_path.Exists() &&
2149 CTSVNPath(m_InitialUrl).IsAncestorOf(urlList[0]))
2151 CTSVNPath wcPath = m_path;
2152 wcPath.AppendPathString(urlList[0].GetWinPathString().Mid(m_InitialUrl.GetLength()));
2153 if (!wcPath.Exists())
2155 bool bWCPresent = false;
2156 while (!bWCPresent && m_path.IsAncestorOf(wcPath))
2158 bWCPresent = wcPath.GetContainingDirectory().Exists();
2159 wcPath = wcPath.GetContainingDirectory();
2161 if (bWCPresent)
2163 popup.AppendMenu(MF_SEPARATOR, NULL);
2164 popup.AppendMenuIcon(ID_UPDATE, IDS_LOG_POPUP_UPDATE, IDI_UPDATE); // "Update item to revision"
2168 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
2170 if (pWnd == &m_RepoTree)
2172 UINT uFlags;
2173 CPoint ptTree = point;
2174 m_RepoTree.ScreenToClient(&ptTree);
2175 HTREEITEM hItem = m_RepoTree.HitTest(ptTree, &uFlags);
2176 // restore the previously selected item state
2177 if ((hItem) && (uFlags & TVHT_ONITEM) && (hItem != m_RepoTree.GetSelectedItem()))
2179 m_blockEvents = true;
2180 m_RepoTree.SetItemState(hSelectedTreeItem, TVIS_SELECTED, TVIS_SELECTED);
2181 m_blockEvents = false;
2182 m_RepoTree.SetItemState(hItem, 0, TVIS_DROPHILITED);
2185 if (hSelectedTreeItem)
2187 m_blockEvents = true;
2188 m_RepoTree.SetItemState(hSelectedTreeItem, 0, TVIS_DROPHILITED);
2189 m_RepoTree.SetItemState(hSelectedTreeItem, TVIS_SELECTED, TVIS_SELECTED);
2190 m_blockEvents = false;
2192 DialogEnableWindow(IDOK, FALSE);
2193 bool bOpenWith = false;
2194 switch (cmd)
2196 case ID_UPDATE:
2198 CTSVNPath wcPath = m_path;
2199 wcPath.AppendPathString(urlList[0].GetWinPathString().Mid(m_InitialUrl.GetLength()));
2200 CString sCmd;
2201 sCmd.Format(_T("\"%s\" /command:update /path:\"%s\" /rev"),
2202 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")), wcPath.GetWinPath());
2204 CAppUtils::LaunchApplication(sCmd, NULL, false);
2206 break;
2207 case ID_PREPAREDIFF:
2209 m_RepoTree.SetItemState(FindUrl(m_diffURL.GetSVNPathString(), false), 0, TVIS_BOLD);
2210 if (urlList.GetCount() == 1)
2212 m_diffURL = urlList[0];
2213 m_diffKind = nFolders ? svn_node_dir : svn_node_file;
2214 // make the marked tree item bold
2215 if (m_diffKind == svn_node_dir)
2217 m_RepoTree.SetItemState(FindUrl(m_diffURL.GetSVNPathString(), false), TVIS_BOLD, TVIS_BOLD);
2220 else
2222 m_diffURL.Reset();
2223 m_diffKind = svn_node_none;
2226 break;
2227 case ID_URLTOCLIPBOARD:
2229 CString url;
2230 for (int i=0; i<urlList.GetCount(); ++i)
2231 url += CUnicodeUtils::GetUnicode(CPathUtils::PathEscape(CUnicodeUtils::GetUTF8(urlList[i].GetSVNPathString()))) + _T("\r\n");
2232 url.TrimRight(_T("\r\n"));
2233 CStringUtils::WriteAsciiStringToClipboard(url);
2235 break;
2236 case ID_SAVEAS:
2238 CTSVNPath tempfile;
2239 bool bSavePathOK = AskForSavePath(urlList, tempfile, nFolders > 0);
2240 if (bSavePathOK)
2242 CWaitCursorEx wait_cursor;
2244 CString saveurl;
2245 CProgressDlg progDlg;
2246 int counter = 0; // the file counter
2247 progDlg.SetTitle(IDS_REPOBROWSE_SAVEASPROGTITLE);
2248 progDlg.SetAnimation(IDR_DOWNLOAD);
2249 progDlg.ShowModeless(GetSafeHwnd());
2250 progDlg.SetProgress((DWORD)0, (DWORD)urlList.GetCount());
2251 SetAndClearProgressInfo(&progDlg);
2252 for (int i=0; i<urlList.GetCount(); ++i)
2254 saveurl = EscapeUrl(urlList[i]);
2255 CTSVNPath savepath = tempfile;
2256 if (tempfile.IsDirectory())
2257 savepath.AppendPathString(urlList[i].GetFileOrDirectoryName());
2258 CString sInfoLine;
2259 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, (LPCTSTR)saveurl, (LPCTSTR)GetRevision().ToString());
2260 progDlg.SetLine(1, sInfoLine, true);
2261 if (!Cat(CTSVNPath(saveurl), GetRevision(), GetRevision(), savepath)||(progDlg.HasUserCancelled()))
2263 wait_cursor.Hide();
2264 progDlg.Stop();
2265 SetAndClearProgressInfo((HWND)NULL);
2266 if (!progDlg.HasUserCancelled())
2267 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseSVN"), MB_ICONERROR);
2268 return;
2270 counter++;
2271 progDlg.SetProgress((DWORD)counter, (DWORD)urlList.GetCount());
2273 progDlg.Stop();
2274 SetAndClearProgressInfo((HWND)NULL);
2277 break;
2278 case ID_SHOWLOG:
2280 if (urlList.GetCount() == 2)
2282 // get log of first URL
2283 CString sCopyFrom1, sCopyFrom2;
2284 SVNLogHelper helper;
2285 helper.SetRepositoryRoot(m_strReposRoot);
2286 SVNRev rev1 = helper.GetCopyFromRev(CTSVNPath(EscapeUrl(urlList[0])), GetRevision(), sCopyFrom1);
2287 if (!rev1.IsValid())
2289 CMessageBox::Show(this->m_hWnd, helper.GetLastErrorMessage(), _T("TortoiseSVN"), MB_ICONERROR);
2290 break;
2292 SVNRev rev2 = helper.GetCopyFromRev(CTSVNPath(EscapeUrl(urlList[1])), GetRevision(), sCopyFrom2);
2293 if (!rev2.IsValid())
2295 CMessageBox::Show(this->m_hWnd, helper.GetLastErrorMessage(), _T("TortoiseSVN"), MB_ICONERROR);
2296 break;
2298 if ((sCopyFrom1.IsEmpty())||(sCopyFrom1.Compare(sCopyFrom2)!=0))
2300 // no common copy from URL, so showing a log between
2301 // the two urls is not possible.
2302 CMessageBox::Show(m_hWnd, IDS_ERR_NOCOMMONCOPYFROM, IDS_APPNAME, MB_ICONERROR);
2303 break;
2305 if ((LONG)rev1 < (LONG)rev2)
2307 SVNRev temp = rev1;
2308 rev1 = rev2;
2309 rev2 = temp;
2311 CString sCmd;
2312 sCmd.Format(_T("\"%s\" /command:log /path:\"%s\" /startrev:%s /endrev:%s"),
2313 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")), (LPCTSTR)sCopyFrom1, (LPCTSTR)rev1.ToString(), (LPCTSTR)rev2.ToString());
2315 ATLTRACE(sCmd);
2316 if (!m_path.IsUrl())
2318 sCmd += _T(" /propspath:\"");
2319 sCmd += m_path.GetWinPathString();
2320 sCmd += _T("\"");
2323 CAppUtils::LaunchApplication(sCmd, NULL, false);
2325 else
2327 CString sCmd;
2328 sCmd.Format(_T("\"%s\" /command:log /path:\"%s\" /startrev:%s"),
2329 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")), (LPCTSTR)EscapeUrl(urlList[0]), (LPCTSTR)GetRevision().ToString());
2331 if (!m_path.IsUrl())
2333 sCmd += _T(" /propspath:\"");
2334 sCmd += m_path.GetWinPathString();
2335 sCmd += _T("\"");
2338 CAppUtils::LaunchApplication(sCmd, NULL, false);
2341 break;
2342 case ID_VIEWREV:
2344 CString url = m_ProjectProperties.sWebViewerRev;
2345 url.Replace(_T("%REVISION%"), GetRevision().ToString());
2346 if (!url.IsEmpty())
2347 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
2349 break;
2350 case ID_VIEWPATHREV:
2352 CString relurl = EscapeUrl(urlList[0]);
2353 relurl = relurl.Mid(m_strReposRoot.GetLength());
2354 CString weburl = m_ProjectProperties.sWebViewerPathRev;
2355 weburl.Replace(_T("%REVISION%"), GetRevision().ToString());
2356 weburl.Replace(_T("%PATH%"), relurl);
2357 if (!weburl.IsEmpty())
2358 ShellExecute(this->m_hWnd, _T("open"), weburl, NULL, NULL, SW_SHOWDEFAULT);
2360 break;
2361 case ID_CHECKOUT:
2363 CString itemsToCheckout;
2364 for (int i=0; i<urlList.GetCount(); ++i)
2366 itemsToCheckout += EscapeUrl(urlList[i]) + _T("*");
2368 itemsToCheckout.TrimRight('*');
2369 CString sCmd;
2370 sCmd.Format(_T("\"%s\" /command:checkout /url:\"%s\" /revision:%s"),
2371 (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")), (LPCTSTR)itemsToCheckout, (LPCTSTR)GetRevision().ToString());
2373 CAppUtils::LaunchApplication(sCmd, NULL, false);
2375 break;
2376 case ID_EXPORT:
2378 CExportDlg dlg;
2379 dlg.m_URL = EscapeUrl(urlList[0]);
2380 dlg.Revision = GetRevision();
2381 if (dlg.DoModal()==IDOK)
2383 CTSVNPath exportDirectory;
2384 exportDirectory.SetFromWin(dlg.m_strExportDirectory, true);
2386 CSVNProgressDlg progDlg;
2387 int opts = 0;
2388 if (dlg.m_bNoExternals)
2389 opts |= ProgOptIgnoreExternals;
2390 if (dlg.m_eolStyle.CompareNoCase(_T("CRLF"))==0)
2391 opts |= ProgOptEolCRLF;
2392 if (dlg.m_eolStyle.CompareNoCase(_T("CR"))==0)
2393 opts |= ProgOptEolCR;
2394 if (dlg.m_eolStyle.CompareNoCase(_T("LF"))==0)
2395 opts |= ProgOptEolLF;
2396 progDlg.SetCommand(CSVNProgressDlg::SVNProgress_Export);
2397 progDlg.SetOptions(opts);
2398 progDlg.SetPathList(CTSVNPathList(exportDirectory));
2399 progDlg.SetUrl(dlg.m_URL);
2400 progDlg.SetRevision(dlg.Revision);
2401 progDlg.SetDepth(dlg.m_depth);
2402 progDlg.DoModal();
2405 break;
2406 case ID_REVGRAPH:
2408 CRevisionGraphDlg dlg;
2409 dlg.SetPath(EscapeUrl(urlList[0]));
2410 dlg.SetPegRevision(GetRevision());
2411 dlg.DoModal();
2413 break;
2414 case ID_OPENWITH:
2415 bOpenWith = true;
2416 case ID_OPEN:
2418 // if we're on HEAD and the repository is available via http or https,
2419 // we just open the browser with that url.
2420 if (GetRevision().IsHead() && (bOpenWith==false))
2422 if (urlList[0].GetSVNPathString().Left(4).CompareNoCase(_T("http")) == 0)
2424 CString sBrowserUrl = EscapeUrl(urlList[0]);
2426 ShellExecute(NULL, _T("open"), sBrowserUrl, NULL, NULL, SW_SHOWNORMAL);
2427 break;
2430 // in all other cases, we have to 'cat' the file and open it.
2431 CTSVNPath tempfile = CTempFiles::Instance().GetTempFilePath(false, urlList[0], GetRevision());
2432 CWaitCursorEx wait_cursor;
2433 CProgressDlg progDlg;
2434 progDlg.SetTitle(IDS_APPNAME);
2435 progDlg.SetAnimation(IDR_DOWNLOAD);
2436 CString sInfoLine;
2437 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, (LPCTSTR)urlList[0].GetFileOrDirectoryName(), (LPCTSTR)GetRevision().ToString());
2438 progDlg.SetLine(1, sInfoLine, true);
2439 SetAndClearProgressInfo(&progDlg);
2440 progDlg.ShowModeless(m_hWnd);
2441 if (!Cat(urlList[0], GetRevision(), GetRevision(), tempfile))
2443 progDlg.Stop();
2444 SetAndClearProgressInfo((HWND)NULL);
2445 wait_cursor.Hide();
2446 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseSVN"), MB_ICONERROR);
2447 break;;
2449 progDlg.Stop();
2450 SetAndClearProgressInfo((HWND)NULL);
2451 // set the file as read-only to tell the app which opens the file that it's only
2452 // a temporary file and must not be edited.
2453 SetFileAttributes(tempfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
2454 if (!bOpenWith)
2456 int ret = (int)ShellExecute(NULL, _T("open"), tempfile.GetWinPathString(), NULL, NULL, SW_SHOWNORMAL);
2457 if (ret <= HINSTANCE_ERROR)
2458 bOpenWith = true;
2460 else
2462 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
2463 cmd += tempfile.GetWinPathString() + _T(" ");
2464 CAppUtils::LaunchApplication(cmd, NULL, false);
2467 break;
2468 case ID_DELETE:
2470 CWaitCursorEx wait_cursor;
2471 CInputLogDlg input(this);
2472 input.SetUUID(m_sUUID);
2473 input.SetProjectProperties(&m_ProjectProperties);
2474 CString hint;
2475 if (urlList.GetCount() == 1)
2476 hint.Format(IDS_INPUT_REMOVEONE, (LPCTSTR)urlList[0].GetFileOrDirectoryName());
2477 else
2478 hint.Format(IDS_INPUT_REMOVEMORE, urlList.GetCount());
2479 input.SetActionText(hint);
2480 if (input.DoModal() == IDOK)
2482 if (!Remove(urlList, true, false, input.GetLogMessage()))
2484 wait_cursor.Hide();
2485 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseSVN"), MB_ICONERROR);
2486 return;
2488 if (hChosenTreeItem)
2490 HTREEITEM hParent = m_RepoTree.GetParentItem(hChosenTreeItem);
2491 RecursiveRemove(hChosenTreeItem);
2492 RefreshNode(hParent);
2494 else
2495 RefreshNode(m_RepoTree.GetSelectedItem(), true);
2498 break;
2499 case ID_BREAKLOCK:
2501 if (!Unlock(urlListEscaped, TRUE))
2503 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseSVN"), MB_ICONERROR);
2504 return;
2506 RefreshNode(m_RepoTree.GetSelectedItem(), true);
2508 break;
2509 case ID_IMPORTFOLDER:
2511 CString path;
2512 CBrowseFolder folderBrowser;
2513 folderBrowser.m_style = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS;
2514 if (folderBrowser.Show(GetSafeHwnd(), path)==CBrowseFolder::OK)
2516 CTSVNPath svnPath(path);
2517 CWaitCursorEx wait_cursor;
2518 CString filename = svnPath.GetFileOrDirectoryName();
2519 CInputLogDlg input(this);
2520 input.SetUUID(m_sUUID);
2521 input.SetProjectProperties(&m_ProjectProperties);
2522 CString sHint;
2523 sHint.Format(IDS_INPUT_IMPORTFOLDER, (LPCTSTR)svnPath.GetSVNPathString(), (LPCTSTR)(urlList[0].GetSVNPathString()+_T("/")+filename));
2524 input.SetActionText(sHint);
2525 if (input.DoModal() == IDOK)
2527 CProgressDlg progDlg;
2528 progDlg.SetTitle(IDS_APPNAME);
2529 CString sInfoLine;
2530 sInfoLine.Format(IDS_PROGRESSIMPORT, (LPCTSTR)filename);
2531 progDlg.SetLine(1, sInfoLine, true);
2532 SetAndClearProgressInfo(&progDlg);
2533 progDlg.ShowModeless(m_hWnd);
2534 if (!Import(svnPath,
2535 CTSVNPath(EscapeUrl(CTSVNPath(urlList[0].GetSVNPathString()+_T("/")+filename))),
2536 input.GetLogMessage(),
2537 &m_ProjectProperties,
2538 svn_depth_infinity,
2539 FALSE, FALSE))
2541 progDlg.Stop();
2542 SetAndClearProgressInfo((HWND)NULL);
2543 wait_cursor.Hide();
2544 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseSVN"), MB_ICONERROR);
2545 return;
2547 progDlg.Stop();
2548 SetAndClearProgressInfo((HWND)NULL);
2549 RefreshNode(m_RepoTree.GetSelectedItem(), true);
2553 break;
2554 case ID_IMPORT:
2556 // Display the Open dialog box.
2557 CString openPath;
2558 if (CAppUtils::FileOpenSave(openPath, NULL, IDS_REPOBROWSE_IMPORT, IDS_COMMONFILEFILTER, true, m_hWnd))
2560 CTSVNPath path(openPath);
2561 CWaitCursorEx wait_cursor;
2562 CString filename = path.GetFileOrDirectoryName();
2563 CInputLogDlg input(this);
2564 input.SetUUID(m_sUUID);
2565 input.SetProjectProperties(&m_ProjectProperties);
2566 CString sHint;
2567 sHint.Format(IDS_INPUT_IMPORTFILEFULL, path.GetWinPath(), (LPCTSTR)(urlList[0].GetSVNPathString()+_T("/")+filename));
2568 input.SetActionText(sHint);
2569 if (input.DoModal() == IDOK)
2571 CProgressDlg progDlg;
2572 progDlg.SetTitle(IDS_APPNAME);
2573 CString sInfoLine;
2574 sInfoLine.Format(IDS_PROGRESSIMPORT, (LPCTSTR)filename);
2575 progDlg.SetLine(1, sInfoLine, true);
2576 SetAndClearProgressInfo(&progDlg);
2577 progDlg.ShowModeless(m_hWnd);
2578 if (!Import(path,
2579 CTSVNPath(EscapeUrl(CTSVNPath(urlList[0].GetSVNPathString()+_T("/")+filename))),
2580 input.GetLogMessage(),
2581 &m_ProjectProperties,
2582 svn_depth_empty,
2583 TRUE, FALSE))
2585 progDlg.Stop();
2586 SetAndClearProgressInfo((HWND)NULL);
2587 wait_cursor.Hide();
2588 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseSVN"), MB_ICONERROR);
2589 return;
2591 progDlg.Stop();
2592 SetAndClearProgressInfo((HWND)NULL);
2593 RefreshNode(m_RepoTree.GetSelectedItem(), true);
2597 break;
2598 case ID_RENAME:
2600 if (pWnd == &m_RepoList)
2602 POSITION pos = m_RepoList.GetFirstSelectedItemPosition();
2603 int selIndex = m_RepoList.GetNextSelectedItem(pos);
2604 if (selIndex >= 0)
2606 m_RepoList.SetFocus();
2607 m_RepoList.EditLabel(selIndex);
2609 else
2611 m_RepoTree.SetFocus();
2612 HTREEITEM hTreeItem = m_RepoTree.GetSelectedItem();
2613 if (hTreeItem != m_RepoTree.GetRootItem())
2614 m_RepoTree.EditLabel(hTreeItem);
2617 else if (pWnd == &m_RepoTree)
2619 m_RepoTree.SetFocus();
2620 if (hChosenTreeItem != m_RepoTree.GetRootItem())
2621 m_RepoTree.EditLabel(hChosenTreeItem);
2624 break;
2625 case ID_COPYTO:
2627 CRenameDlg dlg;
2628 dlg.m_name = urlList[0].GetSVNPathString();
2629 dlg.m_windowtitle.LoadString(IDS_REPOBROWSE_COPY);
2630 CStringUtils::RemoveAccelerators(dlg.m_windowtitle);
2631 if (dlg.DoModal() == IDOK)
2633 CWaitCursorEx wait_cursor;
2634 CInputLogDlg input(this);
2635 input.SetUUID(m_sUUID);
2636 input.SetProjectProperties(&m_ProjectProperties);
2637 CString sHint;
2638 sHint.Format(IDS_INPUT_COPY, (LPCTSTR)urlList[0].GetSVNPathString(), (LPCTSTR)dlg.m_name);
2639 input.SetActionText(sHint);
2640 if (!CTSVNPath(dlg.m_name).IsValidOnWindows())
2642 if (CMessageBox::Show(GetSafeHwnd(), IDS_WARN_NOVALIDPATH, IDS_APPNAME, MB_ICONINFORMATION|MB_YESNO) != IDYES)
2643 break;
2645 if (input.DoModal() == IDOK)
2647 if (!Copy(urlList, CTSVNPath(dlg.m_name), GetRevision(), GetRevision(), input.GetLogMessage()))
2649 wait_cursor.Hide();
2650 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseSVN"), MB_ICONERROR);
2651 return;
2653 if (GetRevision().IsHead())
2655 RefreshNode(m_RepoTree.GetSelectedItem(), true);
2660 break;
2661 case ID_COPYTOWC:
2663 CTSVNPath tempfile;
2664 bool bSavePathOK = AskForSavePath(urlList, tempfile, nFolders > 0);
2665 if (bSavePathOK)
2667 CWaitCursorEx wait_cursor;
2669 CProgressDlg progDlg;
2670 progDlg.SetAnimation(IDR_DOWNLOAD);
2671 progDlg.SetTitle(IDS_APPNAME);
2672 SetAndClearProgressInfo(&progDlg);
2673 progDlg.ShowModeless(m_hWnd);
2675 bool bCopyAsChild = (urlList.GetCount() > 1);
2676 if (!Copy(urlList, tempfile, GetRevision(), GetRevision(), CString(), bCopyAsChild)||(progDlg.HasUserCancelled()))
2678 progDlg.Stop();
2679 SetAndClearProgressInfo((HWND)NULL);
2680 wait_cursor.Hide();
2681 progDlg.Stop();
2682 if (!progDlg.HasUserCancelled())
2683 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseSVN"), MB_ICONERROR);
2684 return;
2686 progDlg.Stop();
2687 SetAndClearProgressInfo((HWND)NULL);
2690 break;
2691 case ID_MKDIR:
2693 CRenameDlg dlg;
2694 dlg.m_name = _T("");
2695 dlg.m_windowtitle.LoadString(IDS_REPOBROWSE_MKDIR);
2696 CStringUtils::RemoveAccelerators(dlg.m_windowtitle);
2697 if (dlg.DoModal() == IDOK)
2699 CWaitCursorEx wait_cursor;
2700 CInputLogDlg input(this);
2701 input.SetUUID(m_sUUID);
2702 input.SetProjectProperties(&m_ProjectProperties);
2703 CString sHint;
2704 sHint.Format(IDS_INPUT_MKDIR, (LPCTSTR)(urlList[0].GetSVNPathString()+_T("/")+dlg.m_name.Trim()));
2705 input.SetActionText(sHint);
2706 if (input.DoModal() == IDOK)
2708 // when creating the new folder, also trim any whitespace chars from it
2709 if (!MakeDir(CTSVNPathList(CTSVNPath(EscapeUrl(CTSVNPath(urlList[0].GetSVNPathString()+_T("/")+dlg.m_name.Trim())))), input.GetLogMessage(), true))
2711 wait_cursor.Hide();
2712 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseSVN"), MB_ICONERROR);
2713 return;
2715 RefreshNode(m_RepoTree.GetSelectedItem(), true);
2719 break;
2720 case ID_REFRESH:
2722 RefreshNode(urlList[0].GetSVNPathString(), true);
2724 break;
2725 case ID_GNUDIFF:
2727 m_bCancelled = false;
2728 SVNDiff diff(this, this->m_hWnd, true);
2729 if (urlList.GetCount() == 1)
2731 if (PromptShown())
2732 diff.ShowUnifiedDiff(CTSVNPath(EscapeUrl(urlList[0])), GetRevision(),
2733 CTSVNPath(EscapeUrl(m_diffURL)), GetRevision());
2734 else
2735 CAppUtils::StartShowUnifiedDiff(m_hWnd, CTSVNPath(EscapeUrl(urlList[0])), GetRevision(),
2736 CTSVNPath(EscapeUrl(m_diffURL)), GetRevision());
2738 else
2740 if (PromptShown())
2741 diff.ShowUnifiedDiff(CTSVNPath(EscapeUrl(urlList[0])), GetRevision(),
2742 CTSVNPath(EscapeUrl(urlList[1])), GetRevision());
2743 else
2744 CAppUtils::StartShowUnifiedDiff(m_hWnd, CTSVNPath(EscapeUrl(urlList[0])), GetRevision(),
2745 CTSVNPath(EscapeUrl(urlList[1])), GetRevision());
2748 break;
2749 case ID_DIFF:
2751 m_bCancelled = false;
2752 SVNDiff diff(this, this->m_hWnd, true);
2753 diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
2754 if (urlList.GetCount() == 1)
2756 if (PromptShown())
2757 diff.ShowCompare(CTSVNPath(EscapeUrl(urlList[0])), GetRevision(),
2758 CTSVNPath(EscapeUrl(m_diffURL)), GetRevision(), SVNRev(), true);
2759 else
2760 CAppUtils::StartShowCompare(m_hWnd, CTSVNPath(EscapeUrl(urlList[0])), GetRevision(),
2761 CTSVNPath(EscapeUrl(m_diffURL)), GetRevision(), SVNRev(), SVNRev(),
2762 !!(GetAsyncKeyState(VK_SHIFT) & 0x8000), true);
2764 else
2766 if (PromptShown())
2767 diff.ShowCompare(CTSVNPath(EscapeUrl(urlList[0])), GetRevision(),
2768 CTSVNPath(EscapeUrl(urlList[1])), GetRevision(), SVNRev(), true);
2769 else
2770 CAppUtils::StartShowCompare(m_hWnd, CTSVNPath(EscapeUrl(urlList[0])), GetRevision(),
2771 CTSVNPath(EscapeUrl(urlList[1])), GetRevision(), SVNRev(), SVNRev(),
2772 !!(GetAsyncKeyState(VK_SHIFT) & 0x8000), true);
2775 break;
2776 case ID_PROPS:
2778 if (GetRevision().IsHead())
2780 CEditPropertiesDlg dlg;
2781 dlg.SetProjectProperties(&m_ProjectProperties);
2782 dlg.SetUUID(m_sUUID);
2783 CTSVNPathList escapedlist;
2784 for (int i=0; i<urlList.GetCount(); ++i)
2786 escapedlist.AddPath(CTSVNPath(EscapeUrl(urlList[i])));
2788 dlg.SetPathList(escapedlist);
2789 dlg.SetRevision(GetHEADRevision(urlList[0]));
2790 dlg.DoModal();
2792 else
2794 CPropDlg dlg;
2795 dlg.m_rev = GetRevision();
2796 dlg.m_Path = CTSVNPath(EscapeUrl(urlList[0]));
2797 dlg.DoModal();
2800 break;
2801 case ID_REVPROPS:
2803 CEditPropertiesDlg dlg;
2804 dlg.SetProjectProperties(&m_ProjectProperties);
2805 dlg.SetUUID(m_sUUID);
2806 CTSVNPathList escapedlist;
2807 for (int i=0; i<urlList.GetCount(); ++i)
2809 escapedlist.AddPath(CTSVNPath(EscapeUrl(urlList[i])));
2811 dlg.SetPathList(escapedlist);
2812 dlg.SetRevision(GetRevision());
2813 dlg.RevProps(true);
2814 dlg.DoModal();
2816 break;
2817 case ID_BLAME:
2819 CBlameDlg dlg;
2820 dlg.EndRev = GetRevision();
2821 if (dlg.DoModal() == IDOK)
2823 CBlame blame;
2824 CString tempfile;
2825 CString logfile;
2826 tempfile = blame.BlameToTempFile(CTSVNPath(EscapeUrl(urlList[0])), dlg.StartRev, dlg.EndRev, dlg.EndRev, logfile, SVN::GetOptionsString(dlg.m_bIgnoreEOL, dlg.m_IgnoreSpaces), dlg.m_bIncludeMerge, TRUE, TRUE);
2827 if (!tempfile.IsEmpty())
2829 if (dlg.m_bTextView)
2831 //open the default text editor for the result file
2832 CAppUtils::StartTextViewer(tempfile);
2834 else
2836 CString sParams = _T("/path:\"") + urlList[0].GetSVNPathString() + _T("\" ");
2837 if(!CAppUtils::LaunchTortoiseBlame(tempfile, logfile, CPathUtils::GetFileNameFromPath(urlList[0].GetFileOrDirectoryName()),sParams))
2839 break;
2843 else
2845 CMessageBox::Show(this->m_hWnd, blame.GetLastErrorMessage(), _T("TortoiseSVN"), MB_ICONERROR);
2850 default:
2851 break;
2853 DialogEnableWindow(IDOK, TRUE);
2858 bool CRepositoryBrowser::AskForSavePath(const CTSVNPathList& urlList, CTSVNPath &tempfile, bool bFolder)
2860 bool bSavePathOK = false;
2861 if ((!bFolder)&&(urlList.GetCount() == 1))
2863 CString savePath = urlList[0].GetFilename();
2864 bSavePathOK = CAppUtils::FileOpenSave(savePath, NULL, IDS_REPOBROWSE_SAVEAS, IDS_COMMONFILEFILTER, false, m_hWnd);
2865 if (bSavePathOK)
2866 tempfile.SetFromWin(savePath);
2868 else
2870 CBrowseFolder browser;
2871 CString sTempfile;
2872 browser.m_style = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS;
2873 browser.Show(GetSafeHwnd(), sTempfile);
2874 if (!sTempfile.IsEmpty())
2876 bSavePathOK = true;
2877 tempfile.SetFromWin(sTempfile);
2880 return bSavePathOK;
2883 bool CRepositoryBrowser::StringToWidthArray(const CString& WidthString, int WidthArray[])
2885 TCHAR * endchar;
2886 for (int i=0; i<7; ++i)
2888 CString hex = WidthString.Mid(i*8, 8);
2889 if ( hex.IsEmpty() )
2891 // This case only occurs when upgrading from an older
2892 // TSVN version in which there were fewer columns.
2893 WidthArray[i] = 0;
2895 else
2897 WidthArray[i] = _tcstol(hex, &endchar, 16);
2900 return true;
2903 CString CRepositoryBrowser::WidthArrayToString(int WidthArray[])
2905 CString sResult;
2906 TCHAR buf[10];
2907 for (int i=0; i<7; ++i)
2909 _stprintf_s(buf, 10, _T("%08X"), WidthArray[i]);
2910 sResult += buf;
2912 return sResult;
2915 void CRepositoryBrowser::SaveColumnWidths(bool bSaveToRegistry /* = false */)
2917 CRegString regColWidth(_T("Software\\TortoiseGit\\RepoBrowserColumnWidth"));
2918 int maxcol = ((CHeaderCtrl*)(m_RepoList.GetDlgItem(0)))->GetItemCount()-1;
2919 // first clear the width array
2920 for (int col = 0; col < 7; ++col)
2921 m_arColumnWidths[col] = 0;
2922 for (int col = 0; col <= maxcol; ++col)
2924 m_arColumnWidths[col] = m_RepoList.GetColumnWidth(col);
2925 if (m_arColumnWidths[col] == m_arColumnAutoWidths[col])
2926 m_arColumnWidths[col] = 0;
2928 if (bSaveToRegistry)
2930 CString sWidths = WidthArrayToString(m_arColumnWidths);
2931 regColWidth = sWidths;