Randomize Sync Dialog startup position
[TortoiseGit.git] / src / TortoiseProc / RepositoryBrowser.cpp
blob1edfcda8f2f795e9242c95b1b98df81334373fdc
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2013 - TortoiseGit
4 // Copyright (C) 2012-2013 Sven Strickroth <email@cs-ware.de>
5 // Copyright (C) 2003-2013 - TortoiseSVN
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License
9 // as published by the Free Software Foundation; either version 2
10 // of the License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software Foundation,
19 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 // RepositoryBrowser.cpp : implementation file
24 #include "stdafx.h"
25 #include "TortoiseProc.h"
26 #include "RepositoryBrowser.h"
27 #include "LogDlg.h"
28 #include "AppUtils.h"
29 #include "IconMenu.h"
30 #include "UnicodeUtils.h"
31 #include "SysImageList.h"
32 #include <sys/stat.h>
33 #include "SysInfo.h"
34 #include "registry.h"
35 #include "PathUtils.h"
36 #include "StringUtils.h"
37 #include "GitDiff.h"
39 #define OVERLAY_EXTERNAL 1
41 void SetSortArrowA(CListCtrl * control, int nColumn, bool bAscending)
43 if (control == NULL)
44 return;
46 // set the sort arrow
47 CHeaderCtrl * pHeader = control->GetHeaderCtrl();
48 HDITEM HeaderItem = {0};
49 HeaderItem.mask = HDI_FORMAT;
50 for (int i = 0; i < pHeader->GetItemCount(); ++i)
52 pHeader->GetItem(i, &HeaderItem);
53 HeaderItem.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
54 pHeader->SetItem(i, &HeaderItem);
56 if (nColumn >= 0)
58 pHeader->GetItem(nColumn, &HeaderItem);
59 HeaderItem.fmt |= (bAscending ? HDF_SORTUP : HDF_SORTDOWN);
60 pHeader->SetItem(nColumn, &HeaderItem);
64 class CRepoListCompareFunc
66 public:
67 CRepoListCompareFunc(CListCtrl* pList, int col, bool desc)
68 : m_col(col)
69 , m_desc(desc)
70 , m_pList(pList)
73 static int CALLBACK StaticCompare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
75 return ((CRepoListCompareFunc *) lParamSort)->Compare(lParam1, lParam2);
78 int Compare(LPARAM lParam1, LPARAM lParam2)
80 CShadowFilesTree * pLeft = (CShadowFilesTree *)m_pList->GetItemData((int)lParam1);
81 CShadowFilesTree * pRight = (CShadowFilesTree *)m_pList->GetItemData((int)lParam2);
83 int result = 0;
84 switch(m_col)
86 case CRepositoryBrowser::eCol_Name:
87 result = SortStrCmp(pLeft->m_sName, pRight->m_sName);
88 if (result != 0)
89 break;
90 case CRepositoryBrowser::eCol_Extension:
91 result = m_pList->GetItemText(static_cast<int>(lParam1), 1).CompareNoCase(m_pList->GetItemText(static_cast<int>(lParam2), 1));
92 if (result == 0) // if extensions are the same, use the filename to sort
93 result = SortStrCmp(pRight->m_sName, pRight->m_sName);
94 if (result != 0)
95 break;
96 case CRepositoryBrowser::eCol_FileSize:
97 if (pLeft->m_iSize > pRight->m_iSize)
98 result = 1;
99 else if (pLeft->m_iSize < pRight->m_iSize)
100 result = -1;
101 else // fallback
102 result = SortStrCmp(pLeft->m_sName, pRight->m_sName);
105 if (m_desc)
106 result = -result;
108 if (pLeft->m_bFolder != pRight->m_bFolder)
110 if (pRight->m_bFolder)
111 result = 1;
112 else
113 result = -1;
116 return result;
118 int SortStrCmp(CString &left, CString &right)
120 if (CRepositoryBrowser::s_bSortLogical)
121 return StrCmpLogicalW(left, right);
122 return StrCmpI(left, right);
125 int m_col;
126 bool m_desc;
127 CListCtrl* m_pList;
130 // CRepositoryBrowser dialog
132 bool CRepositoryBrowser::s_bSortLogical = true;
134 IMPLEMENT_DYNAMIC(CRepositoryBrowser, CResizableStandAloneDialog)
136 CRepositoryBrowser::CRepositoryBrowser(CString rev, CWnd* pParent /*=NULL*/)
137 : CResizableStandAloneDialog(CRepositoryBrowser::IDD, pParent)
138 , m_currSortCol(0)
139 , m_currSortDesc(false)
140 , m_sRevision(rev)
141 , m_bHasWC(true)
142 , m_ColumnManager(&m_RepoList)
143 , m_nIconFolder(0)
144 , m_nOpenIconFolder(0)
145 , m_nExternalOvl(0)
146 , bDragMode(false)
147 , oldy(0)
148 , oldx(0)
152 CRepositoryBrowser::~CRepositoryBrowser()
156 void CRepositoryBrowser::DoDataExchange(CDataExchange* pDX)
158 CDialog::DoDataExchange(pDX);
159 DDX_Control(pDX, IDC_REPOTREE, m_RepoTree);
160 DDX_Control(pDX, IDC_REPOLIST, m_RepoList);
164 BEGIN_MESSAGE_MAP(CRepositoryBrowser, CResizableStandAloneDialog)
165 ON_NOTIFY(TVN_SELCHANGED, IDC_REPOTREE, &CRepositoryBrowser::OnTvnSelchangedRepoTree)
166 ON_WM_CONTEXTMENU()
167 ON_NOTIFY(LVN_COLUMNCLICK, IDC_REPOLIST, &CRepositoryBrowser::OnLvnColumnclickRepoList)
168 ON_NOTIFY(LVN_ITEMCHANGED, IDC_REPOLIST, &CRepositoryBrowser::OnLvnItemchangedRepolist)
169 ON_NOTIFY(NM_DBLCLK, IDC_REPOLIST, &CRepositoryBrowser::OnNMDblclk_RepoList)
170 ON_BN_CLICKED(IDC_BUTTON_REVISION, &CRepositoryBrowser::OnBnClickedButtonRevision)
171 ON_WM_SETCURSOR()
172 ON_WM_DESTROY()
173 ON_WM_MOUSEMOVE()
174 ON_WM_LBUTTONDOWN()
175 ON_WM_LBUTTONUP()
176 END_MESSAGE_MAP()
179 // CRepositoryBrowser message handlers
181 BOOL CRepositoryBrowser::OnInitDialog()
183 CResizableStandAloneDialog::OnInitDialog();
184 CAppUtils::MarkWindowAsUnpinnable(m_hWnd);
186 AddAnchor(IDC_STATIC_REPOURL, TOP_LEFT);
187 AddAnchor(IDC_REPOBROWSER_URL, TOP_LEFT, TOP_RIGHT);
188 AddAnchor(IDC_STATIC_REF, TOP_RIGHT);
189 AddAnchor(IDC_BUTTON_REVISION, TOP_RIGHT);
190 AddAnchor(IDC_REPOTREE, TOP_LEFT, BOTTOM_LEFT);
191 AddAnchor(IDC_REPOLIST, TOP_LEFT, BOTTOM_RIGHT);
192 AddAnchor(IDHELP, BOTTOM_RIGHT);
193 AddAnchor(IDC_INFOLABEL, BOTTOM_LEFT, BOTTOM_RIGHT);
194 AddAnchor(IDOK, BOTTOM_RIGHT);
195 AddAnchor(IDCANCEL, BOTTOM_RIGHT);
197 CRepositoryBrowser::s_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER);
198 if (CRepositoryBrowser::s_bSortLogical)
199 CRepositoryBrowser::s_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_LOCAL_MACHINE);
201 static UINT columnNames[] = { IDS_STATUSLIST_COLFILENAME, IDS_STATUSLIST_COLEXT, IDS_LOG_SIZE };
202 static int columnWidths[] = { 150, 100, 100 };
203 DWORD dwDefaultColumns = (1 << eCol_Name) | (1 << eCol_Extension) | (1 << eCol_FileSize);
204 m_ColumnManager.SetNames(columnNames, _countof(columnNames));
205 m_ColumnManager.ReadSettings(dwDefaultColumns, 0, _T("RepoBrowser"), _countof(columnNames), columnWidths);
207 // set up the list control
208 // set the extended style of the list control
209 // 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.
210 CRegDWORD regFullRowSelect(_T("Software\\TortoiseGit\\FullRowSelect"), TRUE);
211 DWORD exStyle = LVS_EX_HEADERDRAGDROP | LVS_EX_DOUBLEBUFFER | LVS_EX_INFOTIP | LVS_EX_SUBITEMIMAGES;
212 if (DWORD(regFullRowSelect))
213 exStyle |= LVS_EX_FULLROWSELECT;
214 m_RepoList.SetExtendedStyle(exStyle);
215 m_RepoList.SetImageList(&SYS_IMAGE_LIST(), LVSIL_SMALL);
216 CAppUtils::SetListCtrlBackgroundImage(m_RepoList.GetSafeHwnd(), IDI_REPOBROWSER_BKG);
218 m_RepoTree.SetImageList(&SYS_IMAGE_LIST(), TVSIL_NORMAL);
219 if (SysInfo::Instance().IsVistaOrLater())
221 DWORD exStyle = TVS_EX_FADEINOUTEXPANDOS | TVS_EX_AUTOHSCROLL | TVS_EX_DOUBLEBUFFER;
222 m_RepoTree.SetExtendedStyle(exStyle, exStyle);
225 m_nExternalOvl = SYS_IMAGE_LIST().AddIcon((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_EXTERNALOVL), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE));
226 // set externaloverlay in SYS_IMAGE_LIST() in Refresh method, so that it is updated after every launch of the logdialog
228 SetWindowTheme(m_RepoTree.GetSafeHwnd(), L"Explorer", NULL);
229 SetWindowTheme(m_RepoList.GetSafeHwnd(), L"Explorer", NULL);
231 m_nIconFolder = SYS_IMAGE_LIST().GetDirIconIndex();
232 m_nOpenIconFolder = SYS_IMAGE_LIST().GetDirOpenIconIndex();
234 EnableSaveRestore(L"Reposbrowser");
236 DWORD xPos = CRegDWORD(_T("Software\\TortoiseGit\\TortoiseProc\\ResizableState\\RepobrowserDivider"), 0);
237 if (xPos == 0)
239 RECT rc;
240 GetDlgItem(IDC_REPOTREE)->GetClientRect(&rc);
241 xPos = rc.right - rc.left;
243 HandleDividerMove(CPoint(xPos + 20, 10), false);
245 CString sWindowTitle;
246 GetWindowText(sWindowTitle);
247 CAppUtils::SetWindowTitle(m_hWnd, g_Git.m_CurrentDir, sWindowTitle);
249 m_bHasWC = !g_GitAdminDir.IsBareRepo(g_Git.m_CurrentDir);
251 Refresh();
253 m_RepoList.SetFocus();
255 return FALSE;
258 void CRepositoryBrowser::OnDestroy()
260 int maxcol = m_ColumnManager.GetColumnCount();
261 for (int col = 0; col < maxcol; ++col)
262 if (m_ColumnManager.IsVisible(col))
263 m_ColumnManager.ColumnResized(col);
264 m_ColumnManager.WriteSettings();
266 CResizableStandAloneDialog::OnDestroy();
269 void CRepositoryBrowser::OnOK()
271 if (GetFocus() == &m_RepoList && (GetKeyState(VK_MENU) & 0x8000) == 0)
273 // list control has focus: 'enter' the folder
274 if (m_RepoList.GetSelectedCount() != 1)
275 return;
277 POSITION pos = m_RepoList.GetFirstSelectedItemPosition();
278 if (pos)
280 CShadowFilesTree *item = (CShadowFilesTree *)m_RepoList.GetItemData(m_RepoList.GetNextSelectedItem(pos));
281 if (item->m_bFolder)
283 FillListCtrlForShadowTree(item);
284 m_RepoTree.SelectItem(item->m_hTree);
286 else
287 OpenFile(item->GetFullName(), OPEN, item->m_bSubmodule, item->m_hash);
289 return;
292 SaveDividerPosition();
293 CResizableStandAloneDialog::OnOK();
296 void CRepositoryBrowser::OnCancel()
298 SaveDividerPosition();
299 CResizableStandAloneDialog::OnCancel();
302 void CRepositoryBrowser::OnNMDblclk_RepoList(NMHDR *pNMHDR, LRESULT *pResult)
304 *pResult = 0;
306 LPNMITEMACTIVATE pNmItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
307 if (pNmItemActivate->iItem < 0)
308 return;
310 CShadowFilesTree * pItem = (CShadowFilesTree *)m_RepoList.GetItemData(pNmItemActivate->iItem);
311 if (pItem == NULL)
312 return;
314 if (!pItem->m_bFolder)
316 OpenFile(pItem->GetFullName(), OPEN, pItem->m_bSubmodule, pItem->m_hash);
317 return;
319 else
321 FillListCtrlForShadowTree(pItem);
322 m_RepoTree.SelectItem(pItem->m_hTree);
326 void CRepositoryBrowser::Refresh()
328 BeginWaitCursor();
329 if (m_nExternalOvl >= 0)
330 SYS_IMAGE_LIST().SetOverlayImage(m_nExternalOvl, OVERLAY_EXTERNAL);
332 m_RepoTree.DeleteAllItems();
333 m_RepoList.DeleteAllItems();
334 m_TreeRoot.m_ShadowTree.clear();
335 m_TreeRoot.m_sName = "";
336 m_TreeRoot.m_bFolder = true;
338 TVINSERTSTRUCT tvinsert = {0};
339 tvinsert.hParent = TVI_ROOT;
340 tvinsert.hInsertAfter = TVI_ROOT;
341 tvinsert.itemex.mask = TVIF_DI_SETITEM | TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_STATE;
342 tvinsert.itemex.pszText = L"/";
343 tvinsert.itemex.lParam = (LPARAM)&m_TreeRoot;
344 tvinsert.itemex.iImage = m_nIconFolder;
345 tvinsert.itemex.iSelectedImage = m_nOpenIconFolder;
346 m_TreeRoot.m_hTree= m_RepoTree.InsertItem(&tvinsert);
348 ReadTree(&m_TreeRoot);
349 m_RepoTree.Expand(m_TreeRoot.m_hTree, TVE_EXPAND);
350 FillListCtrlForShadowTree(&m_TreeRoot);
351 m_RepoTree.SelectItem(m_TreeRoot.m_hTree);
352 EndWaitCursor();
355 int CRepositoryBrowser::ReadTreeRecursive(git_repository &repo, git_tree * tree, CShadowFilesTree * treeroot)
357 size_t count = git_tree_entrycount(tree);
359 for (int i = 0; i < count; ++i)
361 const git_tree_entry *entry = git_tree_entry_byindex(tree, i);
362 if (entry == NULL)
363 continue;
365 const int mode = git_tree_entry_filemode(entry);
367 CString base = CUnicodeUtils::GetUnicode(git_tree_entry_name(entry), CP_UTF8);
369 const git_oid *oid = git_tree_entry_id(entry);
370 CShadowFilesTree * pNextTree = &treeroot->m_ShadowTree[base];
371 pNextTree->m_sName = base;
372 pNextTree->m_pParent = treeroot;
373 pNextTree->m_hash = CGitHash((char *)oid->id);
375 if (mode == GIT_FILEMODE_COMMIT)
376 pNextTree->m_bSubmodule = true;
377 else if (mode & S_IFDIR)
379 pNextTree->m_bFolder = true;
381 TVINSERTSTRUCT tvinsert = {0};
382 tvinsert.hParent = treeroot->m_hTree;
383 tvinsert.hInsertAfter = TVI_SORT;
384 tvinsert.itemex.mask = TVIF_DI_SETITEM | TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_STATE;
385 tvinsert.itemex.pszText = base.GetBuffer(base.GetLength());
386 tvinsert.itemex.lParam = (LPARAM)pNextTree;
387 tvinsert.itemex.iImage = m_nIconFolder;
388 tvinsert.itemex.iSelectedImage = m_nOpenIconFolder;
389 pNextTree->m_hTree = m_RepoTree.InsertItem(&tvinsert);
390 base.ReleaseBuffer();
392 git_object *object = nullptr;
393 git_tree_entry_to_object(&object, &repo, entry);
394 if (object == nullptr)
395 continue;
397 ReadTreeRecursive(repo, (git_tree*)object, pNextTree);
399 git_object_free(object);
401 else
403 git_blob * blob = nullptr;
404 git_blob_lookup(&blob, &repo, oid);
405 if (blob == NULL)
406 continue;
408 pNextTree->m_iSize = git_blob_rawsize(blob);
409 git_blob_free(blob);
413 return 0;
416 int CRepositoryBrowser::ReadTree(CShadowFilesTree * treeroot)
418 CStringA gitdir = CUnicodeUtils::GetMulti(g_Git.m_CurrentDir, CP_UTF8);
419 git_repository *repository = NULL;
420 git_commit *commit = NULL;
421 git_tree * tree = NULL;
422 int ret = 0;
425 ret = git_repository_open(&repository, gitdir.GetBuffer());
426 if (ret)
428 MessageBox(CGit::GetLibGit2LastErr(_T("Could not open repository.")), _T("TortoiseGit"), MB_ICONERROR);
429 break;
432 if (m_sRevision == _T("HEAD"))
434 ret = git_repository_head_unborn(repository);
435 if (ret == 1) // is orphan
436 break;
437 else if (ret != 0)
439 MessageBox(g_Git.GetLibGit2LastErr(_T("Could not check HEAD.")), _T("TortoiseGit"), MB_ICONERROR);
440 break;
444 CGitHash hash;
445 if (g_Git.GetHash(hash, m_sRevision))
447 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of ") + m_sRevision + _T(".")), _T("TortoiseGit"), MB_ICONERROR);
448 break;
450 ret = git_commit_lookup(&commit, repository, (git_oid *) hash.m_hash);
451 if (ret)
453 MessageBox(CGit::GetLibGit2LastErr(_T("Could not lookup commit.")), _T("TortoiseGit"), MB_ICONERROR);
454 break;
457 ret = git_commit_tree(&tree, commit);
458 if (ret)
460 MessageBox(CGit::GetLibGit2LastErr(_T("Could not get tree of commit.")), _T("TortoiseGit"), MB_ICONERROR);
461 break;
464 treeroot->m_hash = CGitHash((char *)git_tree_id(tree)->id);
465 ReadTreeRecursive(*repository, tree, treeroot);
467 // try to resolve hash to a branch name
468 if (m_sRevision == hash.ToString())
470 MAP_HASH_NAME map;
471 if (g_Git.GetMapHashToFriendName(map))
472 MessageBox(g_Git.GetGitLastErr(_T("Could not get all refs.")), _T("TortoiseGit"), MB_ICONERROR);
473 if (!map[hash].empty())
474 m_sRevision = map[hash].at(0);
476 this->GetDlgItem(IDC_BUTTON_REVISION)->SetWindowText(m_sRevision);
477 } while(0);
479 if (tree)
480 git_tree_free(tree);
482 if (commit)
483 git_commit_free(commit);
485 if (repository)
486 git_repository_free(repository);
488 return ret;
491 void CRepositoryBrowser::OnTvnSelchangedRepoTree(NMHDR *pNMHDR, LRESULT *pResult)
493 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
494 *pResult = 0;
496 FillListCtrlForTreeNode(pNMTreeView->itemNew.hItem);
499 void CRepositoryBrowser::FillListCtrlForTreeNode(HTREEITEM treeNode)
501 m_RepoList.DeleteAllItems();
503 CShadowFilesTree* pTree = (CShadowFilesTree*)(m_RepoTree.GetItemData(treeNode));
504 if (pTree == NULL)
506 ASSERT(FALSE);
507 return;
510 CString url = _T("/") + pTree->GetFullName();
511 GetDlgItem(IDC_REPOBROWSER_URL)->SetWindowText(url);
513 FillListCtrlForShadowTree(pTree);
516 void CRepositoryBrowser::FillListCtrlForShadowTree(CShadowFilesTree* pTree)
518 for (TShadowFilesTreeMap::iterator itShadowTree = pTree->m_ShadowTree.begin(); itShadowTree != pTree->m_ShadowTree.end(); ++itShadowTree)
520 int icon = m_nIconFolder;
521 if (!(*itShadowTree).second.m_bFolder && !(*itShadowTree).second.m_bSubmodule)
523 icon = SYS_IMAGE_LIST().GetFileIconIndex((*itShadowTree).second.m_sName);
526 int indexItem = m_RepoList.InsertItem(m_RepoList.GetItemCount(), (*itShadowTree).second.m_sName, icon);
528 if ((*itShadowTree).second.m_bSubmodule)
530 m_RepoList.SetItemState(indexItem, INDEXTOOVERLAYMASK(OVERLAY_EXTERNAL), LVIS_OVERLAYMASK);
532 m_RepoList.SetItemData(indexItem, (DWORD_PTR)&(*itShadowTree).second);
533 if (!(*itShadowTree).second.m_bFolder && !(*itShadowTree).second.m_bSubmodule)
535 CString temp;
537 temp = CPathUtils::GetFileExtFromPath((*itShadowTree).second.m_sName);
538 m_RepoList.SetItemText(indexItem, eCol_Extension, temp);
540 StrFormatByteSize((*itShadowTree).second.m_iSize, temp.GetBuffer(20), 20);
541 temp.ReleaseBuffer();
542 m_RepoList.SetItemText(indexItem, eCol_FileSize, temp);
546 CRepoListCompareFunc compareFunc(&m_RepoList, m_currSortCol, m_currSortDesc);
547 m_RepoList.SortItemsEx(&CRepoListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
549 SetSortArrowA(&m_RepoList, m_currSortCol, !m_currSortDesc);
551 UpdateInfoLabel();
554 void CRepositoryBrowser::UpdateInfoLabel()
556 CString temp;
557 POSITION pos = m_RepoList.GetFirstSelectedItemPosition();
558 if (pos)
560 if (m_RepoList.GetSelectedCount() > 1)
562 temp.FormatMessage(IDS_REPOBROWSE_INFOMULTI, m_RepoList.GetSelectedCount());
564 else
566 int index = m_RepoList.GetNextSelectedItem(pos);
567 CShadowFilesTree *item = (CShadowFilesTree *)m_RepoList.GetItemData(index);
568 if (item->m_bSubmodule)
569 temp.FormatMessage(IDS_REPOBROWSE_INFOEXT, (LPCTSTR)m_RepoList.GetItemText(index, eCol_Name), item->m_hash.ToString());
570 else if (item->m_bFolder)
571 temp.FormatMessage(IDS_REPOBROWSE_INFODIR, (LPCTSTR)m_RepoList.GetItemText(index, eCol_Name));
572 else
573 temp.FormatMessage(IDS_REPOBROWSE_INFOFILE, (LPCTSTR)m_RepoList.GetItemText(index, eCol_Name), (LPCTSTR)m_RepoList.GetItemText(index, eCol_FileSize));
576 else
578 HTREEITEM hTreeItem = m_RepoTree.GetSelectedItem();
579 if (hTreeItem != nullptr)
581 CShadowFilesTree* pTree = (CShadowFilesTree*)m_RepoTree.GetItemData(hTreeItem);
582 if (pTree != nullptr)
584 size_t files = 0, submodules = 0;
585 for (TShadowFilesTreeMap::iterator itShadowTree = pTree->m_ShadowTree.begin(); itShadowTree != pTree->m_ShadowTree.end(); ++itShadowTree)
587 if (!(*itShadowTree).second.m_bFolder && !(*itShadowTree).second.m_bSubmodule)
588 ++files;
589 if ((*itShadowTree).second.m_bSubmodule)
590 ++submodules;
592 temp.FormatMessage(IDS_REPOBROWSE_INFO, (LPCTSTR)pTree->m_sName, files, submodules, pTree->m_ShadowTree.size() - files - submodules, pTree->m_ShadowTree.size());
596 SetDlgItemText(IDC_INFOLABEL, temp);
599 void CRepositoryBrowser::OnLvnItemchangedRepolist(NMHDR * /* pNMHDR */, LRESULT *pResult)
601 *pResult = 0;
602 UpdateInfoLabel();
605 void CRepositoryBrowser::OnContextMenu(CWnd* pWndFrom, CPoint point)
607 if (pWndFrom == &m_RepoList)
608 OnContextMenu_RepoList(point);
609 else if (pWndFrom == &m_RepoTree)
610 OnContextMenu_RepoTree(point);
613 void CRepositoryBrowser::OnContextMenu_RepoTree(CPoint point)
615 CPoint clientPoint = point;
616 m_RepoTree.ScreenToClient(&clientPoint);
618 HTREEITEM hTreeItem = m_RepoTree.HitTest(clientPoint);
619 if (hTreeItem == NULL)
620 return;
622 TShadowFilesTreeList selectedLeafs;
623 selectedLeafs.push_back((CShadowFilesTree *)m_RepoTree.GetItemData(hTreeItem));
625 ShowContextMenu(point, selectedLeafs, ONLY_FOLDERS);
628 void CRepositoryBrowser::OnContextMenu_RepoList(CPoint point)
630 TShadowFilesTreeList selectedLeafs;
631 selectedLeafs.reserve(m_RepoList.GetSelectedCount());
633 bool folderSelected = false;
634 bool filesSelected = false;
635 bool submodulesSelected = false;
637 POSITION pos = m_RepoList.GetFirstSelectedItemPosition();
638 while (pos)
640 CShadowFilesTree * item = (CShadowFilesTree *)m_RepoList.GetItemData(m_RepoList.GetNextSelectedItem(pos));
641 if (item->m_bSubmodule)
642 submodulesSelected = true;
643 if (item->m_bFolder)
644 folderSelected = true;
645 else
646 filesSelected = true;
647 selectedLeafs.push_back(item);
650 eSelectionType selType = ONLY_FILES;
651 if (folderSelected && filesSelected)
652 selType = MIXED_FOLDERS_FILES;
653 else if (folderSelected)
654 selType = ONLY_FOLDERS;
655 else if (submodulesSelected)
656 selType = ONLY_FILESSUBMODULES;
657 ShowContextMenu(point, selectedLeafs, selType);
660 void CRepositoryBrowser::ShowContextMenu(CPoint point, TShadowFilesTreeList &selectedLeafs, eSelectionType selType)
662 CIconMenu popupMenu;
663 popupMenu.CreatePopupMenu();
665 bool bAddSeparator = false;
667 if (selectedLeafs.size() == 1)
669 popupMenu.AppendMenuIcon(eCmd_Open, IDS_REPOBROWSE_OPEN, IDI_OPEN);
670 popupMenu.SetDefaultItem(eCmd_Open, FALSE);
671 if (selType == ONLY_FILESSUBMODULES)
673 popupMenu.AppendMenuIcon(eCmd_OpenWith, IDS_LOG_POPUP_OPENWITH, IDI_OPEN);
674 popupMenu.AppendMenuIcon(eCmd_OpenWithAlternativeEditor, IDS_LOG_POPUP_VIEWREV);
677 popupMenu.AppendMenu(MF_SEPARATOR);
679 if (m_bHasWC && selType == ONLY_FILESSUBMODULES)
681 popupMenu.AppendMenuIcon(eCmd_CompareWC, IDS_LOG_POPUP_COMPARE, IDI_DIFF);
682 bAddSeparator = true;
685 if (bAddSeparator)
686 popupMenu.AppendMenu(MF_SEPARATOR);
687 bAddSeparator = false;
689 CString temp;
690 temp.LoadString(IDS_MENULOG);
691 popupMenu.AppendMenuIcon(eCmd_ViewLog, temp, IDI_LOG);
693 if (selType == ONLY_FILES)
695 if (m_bHasWC)
696 popupMenu.AppendMenuIcon(eCmd_Blame, IDS_LOG_POPUP_BLAME, IDI_BLAME);
698 popupMenu.AppendMenu(MF_SEPARATOR);
699 temp.LoadString(IDS_LOG_POPUP_SAVE);
700 popupMenu.AppendMenuIcon(eCmd_SaveAs, temp, IDI_SAVEAS);
703 bAddSeparator = true;
706 if (!selectedLeafs.empty() && selType == ONLY_FILES && m_bHasWC)
708 popupMenu.AppendMenuIcon(eCmd_Revert, IDS_LOG_POPUP_REVERTTOREV, IDI_REVERT);
709 bAddSeparator = true;
712 if (bAddSeparator)
713 popupMenu.AppendMenu(MF_SEPARATOR);
714 bAddSeparator = false;
716 if (!selectedLeafs.empty())
718 popupMenu.AppendMenuIcon(eCmd_CopyPath, IDS_STATUSLIST_CONTEXT_COPY, IDI_COPYCLIP);
719 popupMenu.AppendMenuIcon(eCmd_CopyHash, IDS_COPY_COMMIT_HASH, IDI_COPYCLIP);
722 eCmd cmd = (eCmd)popupMenu.TrackPopupMenuEx(TPM_LEFTALIGN|TPM_RETURNCMD, point.x, point.y, this, 0);
723 switch(cmd)
725 case eCmd_ViewLog:
727 CString sCmd;
728 sCmd.Format(_T("/command:log /path:\"%s\\%s\""), g_Git.m_CurrentDir, selectedLeafs.at(0)->GetFullName());
729 if (selectedLeafs.at(0)->m_bSubmodule)
730 sCmd += _T(" /submodule");
731 CAppUtils::RunTortoiseGitProc(sCmd);
733 break;
734 case eCmd_Blame:
736 CAppUtils::LaunchTortoiseBlame(g_Git.m_CurrentDir + _T("\\") + selectedLeafs.at(0)->GetFullName(), m_sRevision);
738 break;
739 case eCmd_Open:
740 if (!selectedLeafs.at(0)->m_bSubmodule && selectedLeafs.at(0)->m_bFolder)
742 FillListCtrlForTreeNode(selectedLeafs.at(0)->m_hTree);
743 m_RepoTree.SelectItem(selectedLeafs.at(0)->m_hTree);
744 return;
746 OpenFile(selectedLeafs.at(0)->GetFullName(), OPEN, selectedLeafs.at(0)->m_bSubmodule, selectedLeafs.at(0)->m_hash);
747 break;
748 case eCmd_OpenWith:
749 OpenFile(selectedLeafs.at(0)->GetFullName(), OPEN_WITH, selectedLeafs.at(0)->m_bSubmodule, selectedLeafs.at(0)->m_hash);
750 break;
751 case eCmd_OpenWithAlternativeEditor:
752 OpenFile(selectedLeafs.at(0)->GetFullName(), ALTERNATIVEEDITOR, selectedLeafs.at(0)->m_bSubmodule, selectedLeafs.at(0)->m_hash);
753 break;
754 case eCmd_CompareWC:
756 CTGitPath file(selectedLeafs.at(0)->GetFullName());
757 CGitDiff::Diff(&file, &file, GIT_REV_ZERO, m_sRevision);
759 break;
760 case eCmd_Revert:
762 int count = 0;
763 for (TShadowFilesTreeList::iterator itShadowTree = selectedLeafs.begin(); itShadowTree != selectedLeafs.end(); ++itShadowTree)
765 if (RevertItemToVersion((*itShadowTree)->GetFullName()))
766 ++count;
767 else
768 break;
770 CString msg;
771 msg.Format(IDS_STATUSLIST_FILESREVERTED, count, m_sRevision);
772 MessageBox(msg, _T("TortoiseGit"), MB_OK);
774 break;
775 case eCmd_SaveAs:
776 FileSaveAs(selectedLeafs.at(0)->GetFullName());
777 break;
778 case eCmd_CopyPath:
780 CString sClipboard;
781 for (TShadowFilesTreeList::iterator itShadowTree = selectedLeafs.begin(); itShadowTree != selectedLeafs.end(); ++itShadowTree)
783 sClipboard += (*itShadowTree)->m_sName + _T("\r\n");
785 CStringUtils::WriteAsciiStringToClipboard(sClipboard);
787 break;
788 case eCmd_CopyHash:
790 CopyHashToClipboard(selectedLeafs);
792 break;
796 BOOL CRepositoryBrowser::PreTranslateMessage(MSG* pMsg)
798 if (pMsg->message == WM_KEYDOWN)
800 switch (pMsg->wParam)
802 case VK_F5:
804 Refresh();
806 break;
810 return CResizableStandAloneDialog::PreTranslateMessage(pMsg);
813 void CRepositoryBrowser::OnLvnColumnclickRepoList(NMHDR *pNMHDR, LRESULT *pResult)
815 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
816 if (pResult)
817 *pResult = 0;
819 if (m_currSortCol == pNMLV->iSubItem)
820 m_currSortDesc = !m_currSortDesc;
821 else
823 m_currSortCol = pNMLV->iSubItem;
824 m_currSortDesc = false;
827 CRepoListCompareFunc compareFunc(&m_RepoList, m_currSortCol, m_currSortDesc);
828 m_RepoList.SortItemsEx(&CRepoListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
830 SetSortArrowA(&m_RepoList, m_currSortCol, !m_currSortDesc);
833 void CRepositoryBrowser::OnBnClickedButtonRevision()
835 // use the git log to allow selection of a version
836 CLogDlg dlg;
837 // tell the dialog to use mode for selecting revisions
838 dlg.SetSelect(true);
839 // only one revision must be selected however
840 dlg.SingleSelection(true);
841 if (dlg.DoModal() == IDOK)
843 // get selected hash if any
844 m_sRevision = dlg.GetSelectedHash();
845 Refresh();
849 void CRepositoryBrowser::SaveDividerPosition()
851 RECT rc;
852 GetDlgItem(IDC_REPOTREE)->GetClientRect(&rc);
853 CRegDWORD xPos = CRegDWORD(_T("Software\\TortoiseGit\\TortoiseProc\\ResizableState\\RepobrowserDivider"));
854 xPos = rc.right - rc.left;
857 void CRepositoryBrowser::HandleDividerMove(CPoint point, bool bDraw)
859 RECT rect, tree, list, treelist, treelistclient;
861 // create an union of the tree and list control rectangle
862 GetDlgItem(IDC_REPOLIST)->GetWindowRect(&list);
863 GetDlgItem(IDC_REPOTREE)->GetWindowRect(&tree);
864 UnionRect(&treelist, &tree, &list);
865 treelistclient = treelist;
866 ScreenToClient(&treelistclient);
868 ClientToScreen(&point);
869 GetClientRect(&rect);
870 ClientToScreen(&rect);
872 CPoint point2 = point;
873 if (point2.x < treelist.left + REPOBROWSER_CTRL_MIN_WIDTH)
874 point2.x = treelist.left + REPOBROWSER_CTRL_MIN_WIDTH;
875 if (point2.x > treelist.right - REPOBROWSER_CTRL_MIN_WIDTH)
876 point2.x = treelist.right - REPOBROWSER_CTRL_MIN_WIDTH;
878 point.x -= rect.left;
879 point.y -= treelist.top;
881 OffsetRect(&treelist, -treelist.left, -treelist.top);
883 if (point.x < treelist.left+REPOBROWSER_CTRL_MIN_WIDTH)
884 point.x = treelist.left+REPOBROWSER_CTRL_MIN_WIDTH;
885 if (point.x > treelist.right-REPOBROWSER_CTRL_MIN_WIDTH)
886 point.x = treelist.right-REPOBROWSER_CTRL_MIN_WIDTH;
888 if (bDraw)
890 CDC * pDC = GetDC();
891 DrawXorBar(pDC, oldx + 2, treelistclient.top, 4, treelistclient.bottom - treelistclient.top - 2);
892 ReleaseDC(pDC);
895 oldx = point.x;
896 oldy = point.y;
898 //position the child controls
899 GetDlgItem(IDC_REPOTREE)->GetWindowRect(&treelist);
900 treelist.right = point2.x - 2;
901 ScreenToClient(&treelist);
902 RemoveAnchor(IDC_REPOTREE);
903 GetDlgItem(IDC_REPOTREE)->MoveWindow(&treelist);
904 GetDlgItem(IDC_REPOLIST)->GetWindowRect(&treelist);
905 treelist.left = point2.x + 2;
906 ScreenToClient(&treelist);
907 RemoveAnchor(IDC_REPOLIST);
908 GetDlgItem(IDC_REPOLIST)->MoveWindow(&treelist);
910 AddAnchor(IDC_REPOTREE, TOP_LEFT, BOTTOM_LEFT);
911 AddAnchor(IDC_REPOLIST, TOP_LEFT, BOTTOM_RIGHT);
914 void CRepositoryBrowser::OnMouseMove(UINT nFlags, CPoint point)
916 if (bDragMode == FALSE)
917 return;
919 RECT rect, tree, list, treelist, treelistclient;
920 // create an union of the tree and list control rectangle
921 GetDlgItem(IDC_REPOLIST)->GetWindowRect(&list);
922 GetDlgItem(IDC_REPOTREE)->GetWindowRect(&tree);
923 UnionRect(&treelist, &tree, &list);
924 treelistclient = treelist;
925 ScreenToClient(&treelistclient);
927 //convert the mouse coordinates relative to the top-left of
928 //the window
929 ClientToScreen(&point);
930 GetClientRect(&rect);
931 ClientToScreen(&rect);
932 point.x -= rect.left;
933 point.y -= treelist.top;
935 //same for the window coordinates - make them relative to 0,0
936 OffsetRect(&treelist, -treelist.left, -treelist.top);
938 if (point.x < treelist.left + REPOBROWSER_CTRL_MIN_WIDTH)
939 point.x = treelist.left + REPOBROWSER_CTRL_MIN_WIDTH;
940 if (point.x > treelist.right - REPOBROWSER_CTRL_MIN_WIDTH)
941 point.x = treelist.right - REPOBROWSER_CTRL_MIN_WIDTH;
943 if ((nFlags & MK_LBUTTON) && (point.x != oldx))
945 CDC * pDC = GetDC();
947 if (pDC)
949 DrawXorBar(pDC, oldx + 2, treelistclient.top, 4, treelistclient.bottom - treelistclient.top - 2);
950 DrawXorBar(pDC, point.x + 2, treelistclient.top, 4, treelistclient.bottom - treelistclient.top - 2);
952 ReleaseDC(pDC);
955 oldx = point.x;
956 oldy = point.y;
959 CStandAloneDialogTmpl<CResizableDialog>::OnMouseMove(nFlags, point);
962 void CRepositoryBrowser::OnLButtonDown(UINT nFlags, CPoint point)
964 RECT rect, tree, list, treelist, treelistclient;
966 // create an union of the tree and list control rectangle
967 GetDlgItem(IDC_REPOLIST)->GetWindowRect(&list);
968 GetDlgItem(IDC_REPOTREE)->GetWindowRect(&tree);
969 UnionRect(&treelist, &tree, &list);
970 treelistclient = treelist;
971 ScreenToClient(&treelistclient);
973 //convert the mouse coordinates relative to the top-left of
974 //the window
975 ClientToScreen(&point);
976 GetClientRect(&rect);
977 ClientToScreen(&rect);
978 point.x -= rect.left;
979 point.y -= treelist.top;
981 //same for the window coordinates - make them relative to 0,0
982 OffsetRect(&treelist, -treelist.left, -treelist.top);
984 if (point.x < treelist.left + REPOBROWSER_CTRL_MIN_WIDTH)
985 return CStandAloneDialogTmpl < CResizableDialog>::OnLButtonDown(nFlags, point);
986 if (point.x > treelist.right - 3)
987 return CStandAloneDialogTmpl < CResizableDialog>::OnLButtonDown(nFlags, point);
988 if (point.x > treelist.right - REPOBROWSER_CTRL_MIN_WIDTH)
989 point.x = treelist.right - REPOBROWSER_CTRL_MIN_WIDTH;
991 if ((point.y < treelist.top + 3) || (point.y > treelist.bottom - 3))
992 return CStandAloneDialogTmpl<CResizableDialog>::OnLButtonDown(nFlags, point);
994 bDragMode = true;
996 SetCapture();
998 CDC * pDC = GetDC();
999 DrawXorBar(pDC, point.x + 2, treelistclient.top, 4, treelistclient.bottom - treelistclient.top - 2);
1000 ReleaseDC(pDC);
1002 oldx = point.x;
1003 oldy = point.y;
1005 CStandAloneDialogTmpl<CResizableDialog>::OnLButtonDown(nFlags, point);
1008 void CRepositoryBrowser::OnLButtonUp(UINT nFlags, CPoint point)
1010 if (bDragMode == FALSE)
1011 return;
1013 HandleDividerMove(point, true);
1015 bDragMode = false;
1016 ReleaseCapture();
1018 CStandAloneDialogTmpl<CResizableDialog>::OnLButtonUp(nFlags, point);
1021 void CRepositoryBrowser::OnCaptureChanged(CWnd *pWnd)
1023 bDragMode = false;
1025 __super::OnCaptureChanged(pWnd);
1028 void CRepositoryBrowser::DrawXorBar(CDC * pDC, int x1, int y1, int width, int height)
1030 static WORD _dotPatternBmp[8] =
1032 0x0055, 0x00aa, 0x0055, 0x00aa,
1033 0x0055, 0x00aa, 0x0055, 0x00aa
1036 HBITMAP hbm;
1037 HBRUSH hbr, hbrushOld;
1039 hbm = CreateBitmap(8, 8, 1, 1, _dotPatternBmp);
1040 hbr = CreatePatternBrush(hbm);
1042 pDC->SetBrushOrg(x1, y1);
1043 hbrushOld = (HBRUSH)pDC->SelectObject(hbr);
1045 PatBlt(pDC->GetSafeHdc(), x1, y1, width, height, PATINVERT);
1047 pDC->SelectObject(hbrushOld);
1049 DeleteObject(hbr);
1050 DeleteObject(hbm);
1053 BOOL CRepositoryBrowser::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
1055 if (pWnd == this)
1057 RECT rect;
1058 POINT pt;
1059 GetClientRect(&rect);
1060 GetCursorPos(&pt);
1061 ScreenToClient(&pt);
1062 if (PtInRect(&rect, pt))
1064 ClientToScreen(&pt);
1065 // are we right of the tree control?
1066 GetDlgItem(IDC_REPOTREE)->GetWindowRect(&rect);
1067 if ((pt.x > rect.right) && (pt.y >= rect.top + 3) && (pt.y <= rect.bottom - 3))
1069 // but left of the list control?
1070 GetDlgItem(IDC_REPOLIST)->GetWindowRect(&rect);
1071 if (pt.x < rect.left)
1073 HCURSOR hCur = LoadCursor(NULL, MAKEINTRESOURCE(IDC_SIZEWE));
1074 SetCursor(hCur);
1075 return TRUE;
1080 return CStandAloneDialogTmpl<CResizableDialog>::OnSetCursor(pWnd, nHitTest, message);
1083 void CRepositoryBrowser::FileSaveAs(const CString path)
1085 CTGitPath gitPath(path);
1087 CGitHash hash;
1088 if (g_Git.GetHash(hash, m_sRevision))
1090 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of ") + m_sRevision + _T(".")), _T("TortoiseGit"), MB_ICONERROR);
1091 return;
1094 CString filename;
1095 filename.Format(_T("%s-%s%s"), gitPath.GetBaseFilename(), hash.ToString().Left(g_Git.GetShortHASHLength()), gitPath.GetFileExtension());
1096 CFileDialog dlg(FALSE, NULL, filename, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, NULL);
1098 CString cmd, out;
1099 INT_PTR ret = dlg.DoModal();
1100 SetCurrentDirectory(g_Git.m_CurrentDir);
1101 if (ret == IDOK)
1103 filename = dlg.GetPathName();
1104 if (g_Git.GetOneFile(m_sRevision, gitPath, filename))
1106 out.Format(IDS_STATUSLIST_CHECKOUTFILEFAILED, gitPath.GetGitPathString(), m_sRevision, filename);
1107 MessageBox(out, _T("TortoiseGit"), MB_OK);
1108 return;
1113 void CRepositoryBrowser::OpenFile(const CString path, eOpenType mode, bool isSubmodule, CGitHash itemHash)
1115 CTGitPath gitPath(path);
1117 CString temppath;
1118 CString file;
1119 GetTempPath(temppath);
1120 CGitHash hash;
1121 if (g_Git.GetHash(hash, m_sRevision))
1123 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of ") + m_sRevision + _T(".")), _T("TortoiseGit"), MB_ICONERROR);
1124 return;
1127 file.Format(_T("%s%s_%s%s"), temppath, gitPath.GetBaseFilename(), hash.ToString().Left(g_Git.GetShortHASHLength()), gitPath.GetFileExtension());
1129 if (isSubmodule)
1131 file += _T(".txt");
1132 CFile submoduleCommit(file, CFile::modeCreate | CFile::modeWrite);
1133 CStringA commitInfo = "Subproject commit " + CStringA(itemHash.ToString());
1134 submoduleCommit.Write(commitInfo, commitInfo.GetLength());
1136 else if (g_Git.GetOneFile(m_sRevision, gitPath, file))
1138 CString out;
1139 out.Format(IDS_STATUSLIST_CHECKOUTFILEFAILED, gitPath.GetGitPathString(), m_sRevision, file);
1140 MessageBox(out, _T("TortoiseGit"), MB_OK);
1141 return;
1144 if (mode == ALTERNATIVEEDITOR)
1146 CAppUtils::LaunchAlternativeEditor(file);
1147 return;
1149 else if (mode == OPEN)
1151 int ret = HINSTANCE_ERROR;
1152 ret = (int)ShellExecute(this->m_hWnd, NULL, file, NULL, NULL, SW_SHOW);
1154 if (ret > HINSTANCE_ERROR)
1155 return;
1158 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ") + file;
1159 CAppUtils::LaunchApplication(cmd, NULL, false);
1161 bool CRepositoryBrowser::RevertItemToVersion(const CString &path)
1163 CString cmd, out;
1164 cmd.Format(_T("git.exe checkout %s -- \"%s\""), m_sRevision, path);
1165 if (g_Git.Run(cmd, &out, CP_UTF8))
1167 if (MessageBox(out, _T("TortoiseGit"), MB_ICONEXCLAMATION | MB_OKCANCEL) == IDCANCEL)
1168 return false;
1171 return true;
1174 void CRepositoryBrowser::CopyHashToClipboard(TShadowFilesTreeList &selectedLeafs)
1176 if (!selectedLeafs.empty())
1178 CString sClipdata;
1179 bool first = true;
1180 for (int i = 0; i < selectedLeafs.size(); i++)
1182 if (!first)
1183 sClipdata += _T("\r\n");
1184 sClipdata += selectedLeafs[i]->m_hash;
1185 first = false;
1187 CStringUtils::WriteAsciiStringToClipboard(sClipdata, GetSafeHwnd());