Check that list is not empty before accessing it
[TortoiseGit.git] / src / TortoiseProc / RepositoryBrowser.cpp
blobd5247eeef04ebde9145825f312c121610afbb474
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2017 - TortoiseGit
4 // Copyright (C) 2003-2013 - TortoiseSVN
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 // RepositoryBrowser.cpp : implementation file
23 #include "stdafx.h"
24 #include "TortoiseProc.h"
25 #include "RepositoryBrowser.h"
26 #include "LogDlg.h"
27 #include "AppUtils.h"
28 #include "IconMenu.h"
29 #include "UnicodeUtils.h"
30 #include "SysImageList.h"
31 #include <sys/stat.h>
32 #include "registry.h"
33 #include "PathUtils.h"
34 #include "StringUtils.h"
35 #include "GitDiff.h"
36 #include "DragDropImpl.h"
37 #include "GitDataObject.h"
38 #include "TempFile.h"
40 #define OVERLAY_EXTERNAL 1
41 #define OVERLAY_EXECUTABLE 2
42 #define OVERLAY_SYMLINK 3
44 void SetSortArrowA(CListCtrl * control, int nColumn, bool bAscending)
46 if (!control)
47 return;
49 // set the sort arrow
50 CHeaderCtrl * pHeader = control->GetHeaderCtrl();
51 HDITEM HeaderItem = {0};
52 HeaderItem.mask = HDI_FORMAT;
53 for (int i = 0; i < pHeader->GetItemCount(); ++i)
55 pHeader->GetItem(i, &HeaderItem);
56 HeaderItem.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
57 pHeader->SetItem(i, &HeaderItem);
59 if (nColumn >= 0)
61 pHeader->GetItem(nColumn, &HeaderItem);
62 HeaderItem.fmt |= (bAscending ? HDF_SORTUP : HDF_SORTDOWN);
63 pHeader->SetItem(nColumn, &HeaderItem);
67 class CRepoListCompareFunc
69 public:
70 CRepoListCompareFunc(CListCtrl* pList, int col, bool desc)
71 : m_col(col)
72 , m_desc(desc)
73 , m_pList(pList)
76 static int CALLBACK StaticCompare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
78 return reinterpret_cast<CRepoListCompareFunc*>(lParamSort)->Compare(lParam1, lParam2);
81 int Compare(LPARAM lParam1, LPARAM lParam2)
83 auto pLeft = reinterpret_cast<CShadowFilesTree*>(m_pList->GetItemData((int)lParam1));
84 auto pRight = reinterpret_cast<CShadowFilesTree*>(m_pList->GetItemData((int)lParam2));
86 int result = 0;
87 switch(m_col)
89 case CRepositoryBrowser::eCol_Name:
90 result = SortStrCmp(pLeft->m_sName, pRight->m_sName);
91 if (result != 0)
92 break;
93 case CRepositoryBrowser::eCol_Extension:
94 result = m_pList->GetItemText(static_cast<int>(lParam1), 1).CompareNoCase(m_pList->GetItemText(static_cast<int>(lParam2), 1));
95 if (result == 0) // if extensions are the same, use the filename to sort
96 result = SortStrCmp(pRight->m_sName, pRight->m_sName);
97 if (result != 0)
98 break;
99 case CRepositoryBrowser::eCol_FileSize:
100 if (pLeft->m_iSize > pRight->m_iSize)
101 result = 1;
102 else if (pLeft->m_iSize < pRight->m_iSize)
103 result = -1;
104 else // fallback
105 result = SortStrCmp(pLeft->m_sName, pRight->m_sName);
108 if (m_desc)
109 result = -result;
111 if (pLeft->m_bFolder != pRight->m_bFolder)
113 if (pRight->m_bFolder)
114 result = 1;
115 else
116 result = -1;
119 return result;
121 int SortStrCmp(const CString &left, const CString &right)
123 if (CRepositoryBrowser::s_bSortLogical)
124 return StrCmpLogicalW(left, right);
125 return StrCmpI(left, right);
128 int m_col;
129 bool m_desc;
130 CListCtrl* m_pList;
133 // CRepositoryBrowser dialog
135 bool CRepositoryBrowser::s_bSortLogical = true;
137 IMPLEMENT_DYNAMIC(CRepositoryBrowser, CResizableStandAloneDialog)
139 CRepositoryBrowser::CRepositoryBrowser(CString rev, CWnd* pParent /*=nullptr*/)
140 : CResizableStandAloneDialog(CRepositoryBrowser::IDD, pParent)
141 , m_currSortCol(0)
142 , m_currSortDesc(false)
143 , m_sRevision(rev)
144 , m_bHasWC(true)
145 , m_ColumnManager(&m_RepoList)
146 , m_nIconFolder(0)
147 , m_nOpenIconFolder(0)
148 , m_nExternalOvl(0)
149 , m_nExecutableOvl(0)
150 , m_nSymlinkOvl(0)
151 , bDragMode(false)
152 , oldy(0)
153 , oldx(0)
157 CRepositoryBrowser::~CRepositoryBrowser()
161 void CRepositoryBrowser::DoDataExchange(CDataExchange* pDX)
163 CDialog::DoDataExchange(pDX);
164 DDX_Control(pDX, IDC_REPOTREE, m_RepoTree);
165 DDX_Control(pDX, IDC_REPOLIST, m_RepoList);
169 BEGIN_MESSAGE_MAP(CRepositoryBrowser, CResizableStandAloneDialog)
170 ON_NOTIFY(TVN_SELCHANGED, IDC_REPOTREE, &CRepositoryBrowser::OnTvnSelchangedRepoTree)
171 ON_NOTIFY(TVN_ITEMEXPANDING, IDC_REPOTREE, &CRepositoryBrowser::OnTvnItemExpandingRepoTree)
172 ON_WM_CONTEXTMENU()
173 ON_NOTIFY(LVN_COLUMNCLICK, IDC_REPOLIST, &CRepositoryBrowser::OnLvnColumnclickRepoList)
174 ON_NOTIFY(LVN_ITEMCHANGED, IDC_REPOLIST, &CRepositoryBrowser::OnLvnItemchangedRepolist)
175 ON_NOTIFY(NM_DBLCLK, IDC_REPOLIST, &CRepositoryBrowser::OnNMDblclk_RepoList)
176 ON_BN_CLICKED(IDC_BUTTON_REVISION, &CRepositoryBrowser::OnBnClickedButtonRevision)
177 ON_WM_SETCURSOR()
178 ON_WM_DESTROY()
179 ON_WM_MOUSEMOVE()
180 ON_WM_LBUTTONDOWN()
181 ON_WM_LBUTTONUP()
182 ON_WM_SYSCOLORCHANGE()
183 ON_NOTIFY(LVN_BEGINDRAG, IDC_REPOLIST, &CRepositoryBrowser::OnLvnBegindragRepolist)
184 ON_NOTIFY(TVN_BEGINDRAG, IDC_REPOTREE, &CRepositoryBrowser::OnTvnBegindragRepotree)
185 END_MESSAGE_MAP()
188 // CRepositoryBrowser message handlers
190 BOOL CRepositoryBrowser::OnInitDialog()
192 CResizableStandAloneDialog::OnInitDialog();
193 CAppUtils::MarkWindowAsUnpinnable(m_hWnd);
195 AddAnchor(IDC_STATIC_REPOURL, TOP_LEFT);
196 AddAnchor(IDC_REPOBROWSER_URL, TOP_LEFT, TOP_RIGHT);
197 AddAnchor(IDC_STATIC_REF, TOP_RIGHT);
198 AddAnchor(IDC_BUTTON_REVISION, TOP_RIGHT);
199 AddAnchor(IDC_REPOTREE, TOP_LEFT, BOTTOM_LEFT);
200 AddAnchor(IDC_REPOLIST, TOP_LEFT, BOTTOM_RIGHT);
201 AddAnchor(IDHELP, BOTTOM_RIGHT);
202 AddAnchor(IDC_INFOLABEL, BOTTOM_LEFT, BOTTOM_RIGHT);
203 AddAnchor(IDOK, BOTTOM_RIGHT);
204 AddAnchor(IDCANCEL, BOTTOM_RIGHT);
206 CRepositoryBrowser::s_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER);
207 if (CRepositoryBrowser::s_bSortLogical)
208 CRepositoryBrowser::s_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_LOCAL_MACHINE);
210 static UINT columnNames[] = { IDS_STATUSLIST_COLFILENAME, IDS_STATUSLIST_COLEXT, IDS_LOG_SIZE };
211 static int columnWidths[] = { 150, 100, 100 };
212 DWORD dwDefaultColumns = (1 << eCol_Name) | (1 << eCol_Extension) | (1 << eCol_FileSize);
213 m_ColumnManager.SetNames(columnNames, _countof(columnNames));
214 m_ColumnManager.ReadSettings(dwDefaultColumns, 0, L"RepoBrowser", _countof(columnNames), columnWidths);
215 m_ColumnManager.SetRightAlign(2);
217 // set up the list control
218 // set the extended style of the list control
219 // 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.
220 CRegDWORD regFullRowSelect(L"Software\\TortoiseGit\\FullRowSelect", TRUE);
221 DWORD exStyle = LVS_EX_HEADERDRAGDROP | LVS_EX_DOUBLEBUFFER | LVS_EX_INFOTIP | LVS_EX_SUBITEMIMAGES;
222 if (DWORD(regFullRowSelect))
223 exStyle |= LVS_EX_FULLROWSELECT;
224 m_RepoList.SetExtendedStyle(exStyle);
225 m_RepoList.SetImageList(&SYS_IMAGE_LIST(), LVSIL_SMALL);
226 CAppUtils::SetListCtrlBackgroundImage(m_RepoList.GetSafeHwnd(), IDI_REPOBROWSER_BKG);
228 m_RepoTree.SetImageList(&SYS_IMAGE_LIST(), TVSIL_NORMAL);
229 exStyle = TVS_EX_FADEINOUTEXPANDOS | TVS_EX_AUTOHSCROLL | TVS_EX_DOUBLEBUFFER;
230 m_RepoTree.SetExtendedStyle(exStyle, exStyle);
232 m_nExternalOvl = SYS_IMAGE_LIST().AddIcon((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_EXTERNALOVL), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE));
233 m_nExecutableOvl = SYS_IMAGE_LIST().AddIcon((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_EXECUTABLEOVL), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE));
234 m_nSymlinkOvl = SYS_IMAGE_LIST().AddIcon((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_SYMLINKOVL), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE));
235 // set externaloverlay in SYS_IMAGE_LIST() in Refresh method, so that it is updated after every launch of the logdialog
237 SetWindowTheme(m_RepoTree.GetSafeHwnd(), L"Explorer", nullptr);
238 SetWindowTheme(m_RepoList.GetSafeHwnd(), L"Explorer", nullptr);
240 int borderWidth = 0;
241 if (IsAppThemed())
243 HTHEME hTheme = OpenThemeData(m_RepoTree, L"TREEVIEW");
244 GetThemeMetric(hTheme, NULL, TVP_TREEITEM, TREIS_NORMAL, TMT_BORDERSIZE, &borderWidth);
245 CloseThemeData(hTheme);
247 else
248 borderWidth = GetSystemMetrics(SM_CYBORDER);
249 m_RepoTree.SetItemHeight((SHORT)(m_RepoTree.GetItemHeight() + 2 * borderWidth));
251 m_nIconFolder = SYS_IMAGE_LIST().GetDirIconIndex();
252 m_nOpenIconFolder = SYS_IMAGE_LIST().GetDirOpenIconIndex();
254 EnableSaveRestore(L"Reposbrowser");
256 DWORD xPos = CRegDWORD(L"Software\\TortoiseGit\\TortoiseProc\\ResizableState\\RepobrowserDivider", 0);
257 if (xPos == 0)
259 RECT rc;
260 GetDlgItem(IDC_REPOTREE)->GetClientRect(&rc);
261 xPos = rc.right - rc.left;
263 HandleDividerMove(CPoint(xPos + 20, 10), false);
265 CString sWindowTitle;
266 GetWindowText(sWindowTitle);
267 CAppUtils::SetWindowTitle(m_hWnd, g_Git.m_CurrentDir, sWindowTitle);
269 m_bHasWC = !GitAdminDir::IsBareRepo(g_Git.m_CurrentDir);
271 Refresh();
273 m_RepoList.SetFocus();
275 return FALSE;
278 void CRepositoryBrowser::OnDestroy()
280 int maxcol = m_ColumnManager.GetColumnCount();
281 for (int col = 0; col < maxcol; ++col)
282 if (m_ColumnManager.IsVisible(col))
283 m_ColumnManager.ColumnResized(col);
284 m_ColumnManager.WriteSettings();
286 CResizableStandAloneDialog::OnDestroy();
289 void CRepositoryBrowser::OnOK()
291 if (GetFocus() == &m_RepoList && (GetKeyState(VK_MENU) & 0x8000) == 0)
293 // list control has focus: 'enter' the folder
294 if (m_RepoList.GetSelectedCount() != 1)
295 return;
297 POSITION pos = m_RepoList.GetFirstSelectedItemPosition();
298 if (pos)
300 auto item = GetListEntry(m_RepoList.GetNextSelectedItem(pos));
301 if (item->m_bFolder)
303 FillListCtrlForShadowTree(item);
304 m_RepoTree.SelectItem(item->m_hTree);
306 else
307 OpenFile(item->GetFullName(), OPEN, item->m_bSubmodule, item->m_hash);
309 return;
312 SaveDividerPosition();
313 CResizableStandAloneDialog::OnOK();
316 void CRepositoryBrowser::OnCancel()
318 SaveDividerPosition();
319 CResizableStandAloneDialog::OnCancel();
322 void CRepositoryBrowser::OnNMDblclk_RepoList(NMHDR *pNMHDR, LRESULT *pResult)
324 *pResult = 0;
326 LPNMITEMACTIVATE pNmItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
327 if (pNmItemActivate->iItem < 0)
328 return;
330 auto pItem = GetListEntry(pNmItemActivate->iItem);
331 if (!pItem )
332 return;
334 if (!pItem->m_bFolder)
336 OpenFile(pItem->GetFullName(), OPEN, pItem->m_bSubmodule, pItem->m_hash);
337 return;
339 else
341 FillListCtrlForShadowTree(pItem);
342 m_RepoTree.SelectItem(pItem->m_hTree);
346 void CRepositoryBrowser::Refresh()
348 BeginWaitCursor();
349 if (m_nExternalOvl >= 0)
350 SYS_IMAGE_LIST().SetOverlayImage(m_nExternalOvl, OVERLAY_EXTERNAL);
351 if (m_nExecutableOvl >= 0)
352 SYS_IMAGE_LIST().SetOverlayImage(m_nExecutableOvl, OVERLAY_EXECUTABLE);
353 if (m_nSymlinkOvl >= 0)
354 SYS_IMAGE_LIST().SetOverlayImage(m_nSymlinkOvl, OVERLAY_SYMLINK);
356 m_RepoTree.DeleteAllItems();
357 m_RepoList.DeleteAllItems();
358 m_TreeRoot.m_ShadowTree.clear();
359 m_TreeRoot.m_sName.Empty();
360 m_TreeRoot.m_bFolder = true;
362 TVINSERTSTRUCT tvinsert = {0};
363 tvinsert.hParent = TVI_ROOT;
364 tvinsert.hInsertAfter = TVI_ROOT;
365 tvinsert.itemex.mask = TVIF_DI_SETITEM | TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_STATE;
366 tvinsert.itemex.pszText = L"/";
367 tvinsert.itemex.lParam = (LPARAM)&m_TreeRoot;
368 tvinsert.itemex.iImage = m_nIconFolder;
369 tvinsert.itemex.iSelectedImage = m_nOpenIconFolder;
370 m_TreeRoot.m_hTree= m_RepoTree.InsertItem(&tvinsert);
372 ReadTree(&m_TreeRoot);
373 m_RepoTree.Expand(m_TreeRoot.m_hTree, TVE_EXPAND);
374 FillListCtrlForShadowTree(&m_TreeRoot);
375 m_RepoTree.SelectItem(m_TreeRoot.m_hTree);
376 EndWaitCursor();
379 int CRepositoryBrowser::ReadTreeRecursive(git_repository& repo, const git_tree* tree, CShadowFilesTree* treeroot, bool recursive)
381 size_t count = git_tree_entrycount(tree);
382 bool hasSubfolders = false;
383 treeroot->m_bLoaded = true;
385 for (size_t i = 0; i < count; ++i)
387 const git_tree_entry *entry = git_tree_entry_byindex(tree, i);
388 if (!entry)
389 continue;
391 const int mode = git_tree_entry_filemode(entry);
393 CString base = CUnicodeUtils::GetUnicode(git_tree_entry_name(entry), CP_UTF8);
395 const git_oid *oid = git_tree_entry_id(entry);
396 CShadowFilesTree * pNextTree = &treeroot->m_ShadowTree[base];
397 pNextTree->m_sName = base;
398 pNextTree->m_pParent = treeroot;
399 pNextTree->m_hash = oid->id;
401 if (mode == GIT_FILEMODE_COMMIT)
402 pNextTree->m_bSubmodule = true;
403 else if (mode & S_IFDIR)
405 hasSubfolders = true;
406 pNextTree->m_bFolder = true;
407 pNextTree->m_bLoaded = false;
409 TVINSERTSTRUCT tvinsert = {0};
410 tvinsert.hParent = treeroot->m_hTree;
411 tvinsert.hInsertAfter = TVI_SORT;
412 tvinsert.itemex.mask = TVIF_DI_SETITEM | TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_STATE | TVIF_CHILDREN;
413 tvinsert.itemex.pszText = base.GetBuffer(base.GetLength());
414 tvinsert.itemex.cChildren = 1;
415 tvinsert.itemex.lParam = (LPARAM)pNextTree;
416 tvinsert.itemex.iImage = m_nIconFolder;
417 tvinsert.itemex.iSelectedImage = m_nOpenIconFolder;
418 pNextTree->m_hTree = m_RepoTree.InsertItem(&tvinsert);
419 base.ReleaseBuffer();
420 if (recursive)
422 CAutoTree subtree;
423 if (git_tree_lookup(subtree.GetPointer(), &repo, oid))
425 MessageBox(CGit::GetLibGit2LastErr(L"Could not lookup path."), L"TortoiseGit", MB_ICONERROR);
426 return -1;
429 ReadTreeRecursive(repo, subtree, pNextTree, recursive);
432 else
434 if (mode == GIT_FILEMODE_BLOB_EXECUTABLE)
435 pNextTree->m_bExecutable = true;
436 if (mode == GIT_FILEMODE_LINK)
437 pNextTree->m_bSymlink = true;
438 CAutoBlob blob;
439 git_blob_lookup(blob.GetPointer(), &repo, oid);
440 if (!blob)
441 continue;
443 pNextTree->m_iSize = git_blob_rawsize(blob);
447 if (!hasSubfolders)
449 TVITEM tvitem = { 0 };
450 tvitem.hItem = treeroot->m_hTree;
451 tvitem.mask = TVIF_CHILDREN;
452 tvitem.cChildren = 0;
453 m_RepoTree.SetItem(&tvitem);
456 return 0;
459 int CRepositoryBrowser::ReadTree(CShadowFilesTree* treeroot, const CString& root, bool recursive)
461 CWaitCursor wait;
462 CAutoRepository repository(g_Git.GetGitRepository());
463 if (!repository)
465 MessageBox(CGit::GetLibGit2LastErr(L"Could not open repository."), L"TortoiseGit", MB_ICONERROR);
466 return -1;
469 if (m_sRevision == L"HEAD")
471 int ret = git_repository_head_unborn(repository);
472 if (ret == 1) // is orphan
473 return ret;
474 else if (ret != 0)
476 MessageBox(g_Git.GetLibGit2LastErr(L"Could not check HEAD."), L"TortoiseGit", MB_ICONERROR);
477 return ret;
481 CGitHash hash;
482 if (CGit::GetHash(repository, hash, m_sRevision))
484 MessageBox(CGit::GetLibGit2LastErr(L"Could not get hash of " + m_sRevision + L'.'), L"TortoiseGit", MB_ICONERROR);
485 return -1;
488 CAutoCommit commit;
489 if (git_commit_lookup(commit.GetPointer(), repository, (git_oid *)hash.m_hash))
491 MessageBox(CGit::GetLibGit2LastErr(L"Could not lookup commit."), L"TortoiseGit", MB_ICONERROR);
492 return -1;
495 CAutoTree tree;
496 if (git_commit_tree(tree.GetPointer(), commit))
498 MessageBox(CGit::GetLibGit2LastErr(L"Could not get tree of commit."), L"TortoiseGit", MB_ICONERROR);
499 return -1;
502 if (!root.IsEmpty())
504 CAutoTreeEntry treeEntry;
505 if (git_tree_entry_bypath(treeEntry.GetPointer(), tree, CUnicodeUtils::GetUTF8(root)))
507 MessageBox(CGit::GetLibGit2LastErr(L"Could not lookup path."), L"TortoiseGit", MB_ICONERROR);
508 return -1;
510 if (git_tree_entry_type(treeEntry) != GIT_OBJ_TREE)
512 MessageBox(CGit::GetLibGit2LastErr(L"Could not lookup path."), L"TortoiseGit", MB_ICONERROR);
513 return -1;
516 CAutoObject object;
517 if (git_tree_entry_to_object(object.GetPointer(), repository, treeEntry))
519 MessageBox(CGit::GetLibGit2LastErr(L"Could not lookup path."), L"TortoiseGit", MB_ICONERROR);
520 return -1;
523 tree.ConvertFrom(std::move(object));
526 treeroot->m_hash = git_tree_id(tree)->id;
527 ReadTreeRecursive(*repository, tree, treeroot, recursive);
529 // try to resolve hash to a branch name
530 if (m_sRevision == hash.ToString())
532 MAP_HASH_NAME map;
533 if (CGit::GetMapHashToFriendName(repository, map))
534 MessageBox(g_Git.GetLibGit2LastErr(L"Could not get all refs."), L"TortoiseGit", MB_ICONERROR);
535 if (!map[hash].empty())
536 m_sRevision = map[hash].at(0);
538 this->GetDlgItem(IDC_BUTTON_REVISION)->SetWindowText(m_sRevision);
540 return 0;
543 void CRepositoryBrowser::OnTvnSelchangedRepoTree(NMHDR *pNMHDR, LRESULT *pResult)
545 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
546 *pResult = 0;
548 FillListCtrlForTreeNode(pNMTreeView->itemNew.hItem);
551 void CRepositoryBrowser::OnTvnItemExpandingRepoTree(NMHDR *pNMHDR, LRESULT *pResult)
553 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
554 *pResult = 0;
556 auto pTree = GetTreeEntry(pNMTreeView->itemNew.hItem);
557 if (!pTree)
558 return;
560 if (!pTree->m_bLoaded)
561 ReadTree(pTree, pTree->GetFullName());
564 void CRepositoryBrowser::FillListCtrlForTreeNode(HTREEITEM treeNode)
566 m_RepoList.DeleteAllItems();
568 auto pTree = GetTreeEntry(treeNode);
569 if (!pTree)
570 return;
572 CString url = L'/' + pTree->GetFullName();
573 GetDlgItem(IDC_REPOBROWSER_URL)->SetWindowText(url);
575 if (!pTree->m_bLoaded)
576 ReadTree(pTree, pTree->GetFullName());
578 FillListCtrlForShadowTree(pTree);
581 void CRepositoryBrowser::FillListCtrlForShadowTree(CShadowFilesTree* pTree)
583 for (auto itShadowTree = pTree->m_ShadowTree.cbegin(); itShadowTree != pTree->m_ShadowTree.cend(); ++itShadowTree)
585 int icon = m_nIconFolder;
586 if (!(*itShadowTree).second.m_bFolder && !(*itShadowTree).second.m_bSubmodule)
588 icon = SYS_IMAGE_LIST().GetFileIconIndex((*itShadowTree).second.m_sName);
591 int indexItem = m_RepoList.InsertItem(m_RepoList.GetItemCount(), (*itShadowTree).second.m_sName, icon);
593 if ((*itShadowTree).second.m_bSubmodule)
595 m_RepoList.SetItemState(indexItem, INDEXTOOVERLAYMASK(OVERLAY_EXTERNAL), LVIS_OVERLAYMASK);
597 if ((*itShadowTree).second.m_bExecutable)
598 m_RepoList.SetItemState(indexItem, INDEXTOOVERLAYMASK(OVERLAY_EXECUTABLE), LVIS_OVERLAYMASK);
599 if ((*itShadowTree).second.m_bSymlink)
600 m_RepoList.SetItemState(indexItem, INDEXTOOVERLAYMASK(OVERLAY_SYMLINK), LVIS_OVERLAYMASK);
601 m_RepoList.SetItemData(indexItem, (DWORD_PTR)&(*itShadowTree).second);
602 if (!(*itShadowTree).second.m_bFolder && !(*itShadowTree).second.m_bSubmodule)
604 CString temp;
606 temp = CPathUtils::GetFileExtFromPath((*itShadowTree).second.m_sName);
607 m_RepoList.SetItemText(indexItem, eCol_Extension, temp);
609 StrFormatByteSize64((*itShadowTree).second.m_iSize, CStrBuf(temp, 20), 20);
610 m_RepoList.SetItemText(indexItem, eCol_FileSize, temp);
614 CRepoListCompareFunc compareFunc(&m_RepoList, m_currSortCol, m_currSortDesc);
615 m_RepoList.SortItemsEx(&CRepoListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
617 SetSortArrowA(&m_RepoList, m_currSortCol, !m_currSortDesc);
619 UpdateInfoLabel();
622 void CRepositoryBrowser::UpdateInfoLabel()
624 CString temp;
625 POSITION pos = m_RepoList.GetFirstSelectedItemPosition();
626 if (pos)
628 if (m_RepoList.GetSelectedCount() > 1)
630 temp.FormatMessage(IDS_REPOBROWSE_INFOMULTI, m_RepoList.GetSelectedCount());
632 else
634 int index = m_RepoList.GetNextSelectedItem(pos);
635 auto item = GetListEntry(index);
636 if (item->m_bSubmodule)
637 temp.FormatMessage(IDS_REPOBROWSE_INFOEXT, (LPCTSTR)m_RepoList.GetItemText(index, eCol_Name), (LPCTSTR)item->m_hash.ToString());
638 else if (item->m_bFolder)
639 temp.FormatMessage(IDS_REPOBROWSE_INFODIR, (LPCTSTR)m_RepoList.GetItemText(index, eCol_Name));
640 else
641 temp.FormatMessage(IDS_REPOBROWSE_INFOFILE, (LPCTSTR)m_RepoList.GetItemText(index, eCol_Name), (LPCTSTR)m_RepoList.GetItemText(index, eCol_FileSize));
644 else
646 HTREEITEM hTreeItem = m_RepoTree.GetSelectedItem();
647 if (hTreeItem != nullptr)
649 auto pTree = GetTreeEntry(hTreeItem);
650 if (pTree != nullptr)
652 size_t files = 0, submodules = 0;
653 for (auto itShadowTree = pTree->m_ShadowTree.cbegin(); itShadowTree != pTree->m_ShadowTree.cend(); ++itShadowTree)
655 if (!(*itShadowTree).second.m_bFolder && !(*itShadowTree).second.m_bSubmodule)
656 ++files;
657 if ((*itShadowTree).second.m_bSubmodule)
658 ++submodules;
660 temp.FormatMessage(IDS_REPOBROWSE_INFO, (LPCTSTR)pTree->m_sName, files, submodules, pTree->m_ShadowTree.size() - files - submodules, pTree->m_ShadowTree.size());
664 SetDlgItemText(IDC_INFOLABEL, temp);
667 void CRepositoryBrowser::OnLvnItemchangedRepolist(NMHDR * /* pNMHDR */, LRESULT *pResult)
669 *pResult = 0;
670 UpdateInfoLabel();
673 void CRepositoryBrowser::OnContextMenu(CWnd* pWndFrom, CPoint point)
675 if (pWndFrom == &m_RepoList)
677 CRect headerPosition;
678 m_RepoList.GetHeaderCtrl()->GetWindowRect(headerPosition);
679 if (!headerPosition.PtInRect(point))
680 OnContextMenu_RepoList(point);
682 else if (pWndFrom == &m_RepoTree)
683 OnContextMenu_RepoTree(point);
686 void CRepositoryBrowser::OnContextMenu_RepoTree(CPoint point)
688 CPoint clientPoint = point;
689 m_RepoTree.ScreenToClient(&clientPoint);
691 HTREEITEM hTreeItem = m_RepoTree.HitTest(clientPoint);
692 if (!hTreeItem)
693 return;
695 TShadowFilesTreeList selectedLeafs;
696 selectedLeafs.push_back(GetTreeEntry(hTreeItem));
698 ShowContextMenu(point, selectedLeafs, ONLY_FOLDERS);
701 void CRepositoryBrowser::OnContextMenu_RepoList(CPoint point)
703 TShadowFilesTreeList selectedLeafs;
704 selectedLeafs.reserve(m_RepoList.GetSelectedCount());
706 bool folderSelected = false;
707 bool filesSelected = false;
708 bool submodulesSelected = false;
710 POSITION pos = m_RepoList.GetFirstSelectedItemPosition();
711 while (pos)
713 auto item = GetListEntry(m_RepoList.GetNextSelectedItem(pos));
714 if (item->m_bSubmodule)
715 submodulesSelected = true;
716 if (item->m_bFolder)
717 folderSelected = true;
718 else
719 filesSelected = true;
720 selectedLeafs.push_back(item);
723 eSelectionType selType = ONLY_FILES;
724 if (folderSelected && filesSelected)
725 selType = MIXED_FOLDERS_FILES;
726 else if (folderSelected)
727 selType = ONLY_FOLDERS;
728 else if (submodulesSelected)
729 selType = ONLY_FILESSUBMODULES;
730 ShowContextMenu(point, selectedLeafs, selType);
733 void CRepositoryBrowser::ShowContextMenu(CPoint point, TShadowFilesTreeList &selectedLeafs, eSelectionType selType)
735 CIconMenu popupMenu;
736 popupMenu.CreatePopupMenu();
738 bool bAddSeparator = false;
740 if (selectedLeafs.size() == 1)
742 popupMenu.AppendMenuIcon(eCmd_Open, IDS_REPOBROWSE_OPEN, IDI_OPEN);
743 popupMenu.SetDefaultItem(eCmd_Open, FALSE);
744 if (selType == ONLY_FILES || selType == ONLY_FILESSUBMODULES)
746 popupMenu.AppendMenuIcon(eCmd_OpenWith, IDS_LOG_POPUP_OPENWITH, IDI_OPEN);
747 popupMenu.AppendMenuIcon(eCmd_OpenWithAlternativeEditor, IDS_LOG_POPUP_VIEWREV);
750 popupMenu.AppendMenu(MF_SEPARATOR);
752 if (m_bHasWC && (selType == ONLY_FILES || selType == ONLY_FILESSUBMODULES))
754 popupMenu.AppendMenuIcon(eCmd_CompareWC, IDS_LOG_POPUP_COMPARE, IDI_DIFF);
755 bAddSeparator = true;
758 if (bAddSeparator)
759 popupMenu.AppendMenu(MF_SEPARATOR);
760 bAddSeparator = false;
762 CString temp;
763 temp.LoadString(IDS_MENULOG);
764 popupMenu.AppendMenuIcon(eCmd_ViewLog, temp, IDI_LOG);
765 if (selectedLeafs[0]->m_bSubmodule)
767 temp.LoadString(IDS_MENULOGSUBMODULE);
768 popupMenu.AppendMenuIcon(eCmd_ViewLogSubmodule, temp, IDI_LOG);
771 if (selType == ONLY_FILES)
773 if (m_bHasWC)
774 popupMenu.AppendMenuIcon(eCmd_Blame, IDS_LOG_POPUP_BLAME, IDI_BLAME);
776 popupMenu.AppendMenu(MF_SEPARATOR);
777 temp.LoadString(IDS_LOG_POPUP_SAVE);
778 popupMenu.AppendMenuIcon(eCmd_SaveAs, temp, IDI_SAVEAS);
781 bAddSeparator = true;
784 if (!selectedLeafs.empty() && selType == ONLY_FILES && m_bHasWC)
786 popupMenu.AppendMenuIcon(eCmd_Revert, IDS_LOG_POPUP_REVERTTOREV, IDI_REVERT);
787 bAddSeparator = true;
790 if (bAddSeparator)
791 popupMenu.AppendMenu(MF_SEPARATOR);
793 if (selectedLeafs.size() == 1 && selType == ONLY_FILES)
795 popupMenu.AppendMenuIcon(eCmd_PrepareDiff, IDS_PREPAREDIFF, IDI_DIFF);
796 if (!m_sMarkForDiffFilename.IsEmpty())
798 CString diffWith;
799 if (selectedLeafs.at(0)->GetFullName() == m_sMarkForDiffFilename)
800 diffWith = m_sMarkForDiffVersion;
801 else
803 PathCompactPathEx(CStrBuf(diffWith, 2 * GIT_HASH_SIZE), m_sMarkForDiffFilename, 2 * GIT_HASH_SIZE, 0);
804 diffWith += L':' + m_sMarkForDiffVersion.ToString().Left(g_Git.GetShortHASHLength());
806 CString menuEntry;
807 menuEntry.Format(IDS_MENUDIFFNOW, (LPCTSTR)diffWith);
808 popupMenu.AppendMenuIcon(eCmd_PrepareDiff_Compare, menuEntry, IDI_DIFF);
810 popupMenu.AppendMenu(MF_SEPARATOR);
813 if (!selectedLeafs.empty())
815 popupMenu.AppendMenuIcon(eCmd_CopyPath, IDS_STATUSLIST_CONTEXT_COPY, IDI_COPYCLIP);
816 popupMenu.AppendMenuIcon(eCmd_CopyHash, IDS_COPY_COMMIT_HASH, IDI_COPYCLIP);
819 eCmd cmd = (eCmd)popupMenu.TrackPopupMenuEx(TPM_LEFTALIGN | TPM_RETURNCMD, point.x, point.y, this, nullptr);
820 switch(cmd)
822 case eCmd_ViewLog:
823 case eCmd_ViewLogSubmodule:
825 CString sCmd;
826 sCmd.Format(L"/command:log /path:\"%s\"", (LPCTSTR)g_Git.CombinePath(selectedLeafs.at(0)->GetFullName()));
827 if (cmd == eCmd_ViewLog && selectedLeafs.at(0)->m_bSubmodule)
828 sCmd += L" /submodule";
829 if (cmd == eCmd_ViewLog)
830 sCmd += L" /endrev:" + m_sRevision;
831 CAppUtils::RunTortoiseGitProc(sCmd);
833 break;
834 case eCmd_Blame:
836 CAppUtils::LaunchTortoiseBlame(g_Git.CombinePath(selectedLeafs.at(0)->GetFullName()), m_sRevision);
838 break;
839 case eCmd_Open:
840 if (!selectedLeafs.at(0)->m_bSubmodule && selectedLeafs.at(0)->m_bFolder)
842 FillListCtrlForTreeNode(selectedLeafs.at(0)->m_hTree);
843 m_RepoTree.SelectItem(selectedLeafs.at(0)->m_hTree);
844 return;
846 OpenFile(selectedLeafs.at(0)->GetFullName(), OPEN, selectedLeafs.at(0)->m_bSubmodule, selectedLeafs.at(0)->m_hash);
847 break;
848 case eCmd_OpenWith:
849 OpenFile(selectedLeafs.at(0)->GetFullName(), OPEN_WITH, selectedLeafs.at(0)->m_bSubmodule, selectedLeafs.at(0)->m_hash);
850 break;
851 case eCmd_OpenWithAlternativeEditor:
852 OpenFile(selectedLeafs.at(0)->GetFullName(), ALTERNATIVEEDITOR, selectedLeafs.at(0)->m_bSubmodule, selectedLeafs.at(0)->m_hash);
853 break;
854 case eCmd_CompareWC:
856 CTGitPath file(selectedLeafs.at(0)->GetFullName());
857 CGitDiff::Diff(&file, &file, GIT_REV_ZERO, m_sRevision);
859 break;
860 case eCmd_Revert:
862 int count = 0;
863 for (auto itShadowTree = selectedLeafs.cbegin(); itShadowTree != selectedLeafs.cend(); ++itShadowTree)
865 if (RevertItemToVersion((*itShadowTree)->GetFullName()))
866 ++count;
867 else
868 break;
870 CString msg;
871 msg.Format(IDS_STATUSLIST_FILESREVERTED, count, (LPCTSTR)m_sRevision);
872 MessageBox(msg, L"TortoiseGit", MB_OK);
874 break;
875 case eCmd_SaveAs:
876 FileSaveAs(selectedLeafs.at(0)->GetFullName());
877 break;
878 case eCmd_CopyPath:
880 CString sClipboard;
881 for (auto itShadowTree = selectedLeafs.cbegin(); itShadowTree != selectedLeafs.cend(); ++itShadowTree)
883 sClipboard += (*itShadowTree)->m_sName + L"\r\n";
885 CStringUtils::WriteAsciiStringToClipboard(sClipboard);
887 break;
888 case eCmd_CopyHash:
890 CopyHashToClipboard(selectedLeafs);
892 break;
893 case eCmd_PrepareDiff:
894 m_sMarkForDiffFilename = selectedLeafs.at(0)->GetFullName();
895 if (g_Git.GetHash(m_sMarkForDiffVersion, m_sRevision))
897 m_sMarkForDiffFilename.Empty();
898 MessageBox(g_Git.GetGitLastErr(L"Could not get SHA-1 for " + m_sRevision), L"TortoiseGit", MB_ICONERROR);
900 break;
901 case eCmd_PrepareDiff_Compare:
903 CTGitPath savedFile(m_sMarkForDiffFilename);
904 CTGitPath selectedFile(selectedLeafs.at(0)->GetFullName());
905 CGitHash currentHash;
906 if (g_Git.GetHash(currentHash, m_sRevision))
908 MessageBox(g_Git.GetGitLastErr(L"Could not get SHA-1 for " + m_sRevision), L"TortoiseGit", MB_ICONERROR);
909 return;
911 CGitDiff::Diff(&selectedFile, &savedFile, currentHash, m_sMarkForDiffVersion);
913 break;
917 BOOL CRepositoryBrowser::PreTranslateMessage(MSG* pMsg)
919 if (pMsg->message == WM_KEYDOWN)
921 switch (pMsg->wParam)
923 case VK_F5:
925 Refresh();
927 break;
931 return CResizableStandAloneDialog::PreTranslateMessage(pMsg);
934 void CRepositoryBrowser::OnLvnColumnclickRepoList(NMHDR *pNMHDR, LRESULT *pResult)
936 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
937 if (pResult)
938 *pResult = 0;
940 if (m_currSortCol == pNMLV->iSubItem)
941 m_currSortDesc = !m_currSortDesc;
942 else
944 m_currSortCol = pNMLV->iSubItem;
945 m_currSortDesc = false;
948 CRepoListCompareFunc compareFunc(&m_RepoList, m_currSortCol, m_currSortDesc);
949 m_RepoList.SortItemsEx(&CRepoListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
951 SetSortArrowA(&m_RepoList, m_currSortCol, !m_currSortDesc);
954 void CRepositoryBrowser::OnBnClickedButtonRevision()
956 // use the git log to allow selection of a version
957 CLogDlg dlg;
958 dlg.SetParams(CTGitPath(), CTGitPath(), m_sRevision, m_sRevision, 0);
959 // tell the dialog to use mode for selecting revisions
960 dlg.SetSelect(true);
961 dlg.ShowWorkingTreeChanges(false);
962 // only one revision must be selected however
963 dlg.SingleSelection(true);
964 if (dlg.DoModal() == IDOK && !dlg.GetSelectedHash().empty())
966 m_sRevision = dlg.GetSelectedHash().at(0).ToString();
967 Refresh();
971 void CRepositoryBrowser::SaveDividerPosition()
973 RECT rc;
974 GetDlgItem(IDC_REPOTREE)->GetClientRect(&rc);
975 CRegDWORD xPos(L"Software\\TortoiseGit\\TortoiseProc\\ResizableState\\RepobrowserDivider");
976 xPos = rc.right - rc.left;
979 void CRepositoryBrowser::HandleDividerMove(CPoint point, bool bDraw)
981 RECT rect, tree, list, treelist, treelistclient;
983 // create an union of the tree and list control rectangle
984 GetDlgItem(IDC_REPOLIST)->GetWindowRect(&list);
985 GetDlgItem(IDC_REPOTREE)->GetWindowRect(&tree);
986 UnionRect(&treelist, &tree, &list);
987 treelistclient = treelist;
988 ScreenToClient(&treelistclient);
990 ClientToScreen(&point);
991 GetClientRect(&rect);
992 ClientToScreen(&rect);
994 CPoint point2 = point;
995 if (point2.x < treelist.left + REPOBROWSER_CTRL_MIN_WIDTH)
996 point2.x = treelist.left + REPOBROWSER_CTRL_MIN_WIDTH;
997 if (point2.x > treelist.right - REPOBROWSER_CTRL_MIN_WIDTH)
998 point2.x = treelist.right - REPOBROWSER_CTRL_MIN_WIDTH;
1000 point.x -= rect.left;
1001 point.y -= treelist.top;
1003 OffsetRect(&treelist, -treelist.left, -treelist.top);
1005 if (point.x < treelist.left+REPOBROWSER_CTRL_MIN_WIDTH)
1006 point.x = treelist.left+REPOBROWSER_CTRL_MIN_WIDTH;
1007 if (point.x > treelist.right-REPOBROWSER_CTRL_MIN_WIDTH)
1008 point.x = treelist.right-REPOBROWSER_CTRL_MIN_WIDTH;
1010 if (bDraw)
1012 CDC * pDC = GetDC();
1013 DrawXorBar(pDC, oldx + 2, treelistclient.top, 4, treelistclient.bottom - treelistclient.top - 2);
1014 ReleaseDC(pDC);
1017 oldx = point.x;
1018 oldy = point.y;
1020 //position the child controls
1021 GetDlgItem(IDC_REPOTREE)->GetWindowRect(&treelist);
1022 treelist.right = point2.x - 2;
1023 ScreenToClient(&treelist);
1024 RemoveAnchor(IDC_REPOTREE);
1025 GetDlgItem(IDC_REPOTREE)->MoveWindow(&treelist);
1026 GetDlgItem(IDC_REPOLIST)->GetWindowRect(&treelist);
1027 treelist.left = point2.x + 2;
1028 ScreenToClient(&treelist);
1029 RemoveAnchor(IDC_REPOLIST);
1030 GetDlgItem(IDC_REPOLIST)->MoveWindow(&treelist);
1032 AddAnchor(IDC_REPOTREE, TOP_LEFT, BOTTOM_LEFT);
1033 AddAnchor(IDC_REPOLIST, TOP_LEFT, BOTTOM_RIGHT);
1036 void CRepositoryBrowser::OnMouseMove(UINT nFlags, CPoint point)
1038 if (bDragMode == FALSE)
1039 return;
1041 RECT rect, tree, list, treelist, treelistclient;
1042 // create an union of the tree and list control rectangle
1043 GetDlgItem(IDC_REPOLIST)->GetWindowRect(&list);
1044 GetDlgItem(IDC_REPOTREE)->GetWindowRect(&tree);
1045 UnionRect(&treelist, &tree, &list);
1046 treelistclient = treelist;
1047 ScreenToClient(&treelistclient);
1049 //convert the mouse coordinates relative to the top-left of
1050 //the window
1051 ClientToScreen(&point);
1052 GetClientRect(&rect);
1053 ClientToScreen(&rect);
1054 point.x -= rect.left;
1055 point.y -= treelist.top;
1057 //same for the window coordinates - make them relative to 0,0
1058 OffsetRect(&treelist, -treelist.left, -treelist.top);
1060 if (point.x < treelist.left + REPOBROWSER_CTRL_MIN_WIDTH)
1061 point.x = treelist.left + REPOBROWSER_CTRL_MIN_WIDTH;
1062 if (point.x > treelist.right - REPOBROWSER_CTRL_MIN_WIDTH)
1063 point.x = treelist.right - REPOBROWSER_CTRL_MIN_WIDTH;
1065 if ((nFlags & MK_LBUTTON) && (point.x != oldx))
1067 CDC * pDC = GetDC();
1069 if (pDC)
1071 DrawXorBar(pDC, oldx + 2, treelistclient.top, 4, treelistclient.bottom - treelistclient.top - 2);
1072 DrawXorBar(pDC, point.x + 2, treelistclient.top, 4, treelistclient.bottom - treelistclient.top - 2);
1074 ReleaseDC(pDC);
1077 oldx = point.x;
1078 oldy = point.y;
1081 CStandAloneDialogTmpl<CResizableDialog>::OnMouseMove(nFlags, point);
1084 void CRepositoryBrowser::OnLButtonDown(UINT nFlags, CPoint point)
1086 RECT rect, tree, list, treelist, treelistclient;
1088 // create an union of the tree and list control rectangle
1089 GetDlgItem(IDC_REPOLIST)->GetWindowRect(&list);
1090 GetDlgItem(IDC_REPOTREE)->GetWindowRect(&tree);
1091 UnionRect(&treelist, &tree, &list);
1092 treelistclient = treelist;
1093 ScreenToClient(&treelistclient);
1095 //convert the mouse coordinates relative to the top-left of
1096 //the window
1097 ClientToScreen(&point);
1098 GetClientRect(&rect);
1099 ClientToScreen(&rect);
1100 point.x -= rect.left;
1101 point.y -= treelist.top;
1103 //same for the window coordinates - make them relative to 0,0
1104 OffsetRect(&treelist, -treelist.left, -treelist.top);
1106 if (point.x < treelist.left + REPOBROWSER_CTRL_MIN_WIDTH)
1107 return CStandAloneDialogTmpl < CResizableDialog>::OnLButtonDown(nFlags, point);
1108 if (point.x > treelist.right - 3)
1109 return CStandAloneDialogTmpl < CResizableDialog>::OnLButtonDown(nFlags, point);
1110 if (point.x > treelist.right - REPOBROWSER_CTRL_MIN_WIDTH)
1111 point.x = treelist.right - REPOBROWSER_CTRL_MIN_WIDTH;
1113 if ((point.y < treelist.top + 3) || (point.y > treelist.bottom - 3))
1114 return CStandAloneDialogTmpl<CResizableDialog>::OnLButtonDown(nFlags, point);
1116 bDragMode = true;
1118 SetCapture();
1120 CDC * pDC = GetDC();
1121 DrawXorBar(pDC, point.x + 2, treelistclient.top, 4, treelistclient.bottom - treelistclient.top - 2);
1122 ReleaseDC(pDC);
1124 oldx = point.x;
1125 oldy = point.y;
1127 CStandAloneDialogTmpl<CResizableDialog>::OnLButtonDown(nFlags, point);
1130 void CRepositoryBrowser::OnLButtonUp(UINT nFlags, CPoint point)
1132 if (bDragMode == FALSE)
1133 return;
1135 HandleDividerMove(point, true);
1137 bDragMode = false;
1138 ReleaseCapture();
1140 CStandAloneDialogTmpl<CResizableDialog>::OnLButtonUp(nFlags, point);
1143 void CRepositoryBrowser::OnCaptureChanged(CWnd *pWnd)
1145 bDragMode = false;
1147 __super::OnCaptureChanged(pWnd);
1150 void CRepositoryBrowser::DrawXorBar(CDC * pDC, int x1, int y1, int width, int height)
1152 static WORD _dotPatternBmp[8] =
1154 0x0055, 0x00aa, 0x0055, 0x00aa,
1155 0x0055, 0x00aa, 0x0055, 0x00aa
1158 HBITMAP hbm;
1159 HBRUSH hbr, hbrushOld;
1161 hbm = CreateBitmap(8, 8, 1, 1, _dotPatternBmp);
1162 hbr = CreatePatternBrush(hbm);
1164 pDC->SetBrushOrg(x1, y1);
1165 hbrushOld = (HBRUSH)pDC->SelectObject(hbr);
1167 PatBlt(pDC->GetSafeHdc(), x1, y1, width, height, PATINVERT);
1169 pDC->SelectObject(hbrushOld);
1171 DeleteObject(hbr);
1172 DeleteObject(hbm);
1175 BOOL CRepositoryBrowser::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
1177 if (pWnd == this)
1179 RECT rect;
1180 POINT pt;
1181 GetClientRect(&rect);
1182 GetCursorPos(&pt);
1183 ScreenToClient(&pt);
1184 if (PtInRect(&rect, pt))
1186 ClientToScreen(&pt);
1187 // are we right of the tree control?
1188 GetDlgItem(IDC_REPOTREE)->GetWindowRect(&rect);
1189 if ((pt.x > rect.right) && (pt.y >= rect.top + 3) && (pt.y <= rect.bottom - 3))
1191 // but left of the list control?
1192 GetDlgItem(IDC_REPOLIST)->GetWindowRect(&rect);
1193 if (pt.x < rect.left)
1195 HCURSOR hCur = LoadCursor(nullptr, IDC_SIZEWE);
1196 SetCursor(hCur);
1197 return TRUE;
1202 return CStandAloneDialogTmpl<CResizableDialog>::OnSetCursor(pWnd, nHitTest, message);
1205 void CRepositoryBrowser::FileSaveAs(const CString path)
1207 CTGitPath gitPath(path);
1209 CGitHash hash;
1210 if (g_Git.GetHash(hash, m_sRevision))
1212 MessageBox(g_Git.GetGitLastErr(L"Could not get hash of " + m_sRevision + L'.'), L"TortoiseGit", MB_ICONERROR);
1213 return;
1216 CString filename;
1217 filename.Format(L"%s\\%s-%s%s", (LPCTSTR)g_Git.CombinePath(gitPath.GetContainingDirectory()), (LPCTSTR)gitPath.GetBaseFilename(), (LPCTSTR)hash.ToString().Left(g_Git.GetShortHASHLength()), (LPCTSTR)gitPath.GetFileExtension());
1218 if (!CAppUtils::FileOpenSave(filename, nullptr, 0, 0, false, GetSafeHwnd()))
1219 return;
1221 if (g_Git.GetOneFile(m_sRevision, gitPath, filename))
1223 CString out;
1224 out.Format(IDS_STATUSLIST_CHECKOUTFILEFAILED, (LPCTSTR)gitPath.GetGitPathString(), (LPCTSTR)m_sRevision, (LPCTSTR)filename);
1225 MessageBox(g_Git.GetGitLastErr(out, CGit::GIT_CMD_GETONEFILE), L"TortoiseGit", MB_ICONERROR);
1229 void CRepositoryBrowser::OpenFile(const CString path, eOpenType mode, bool isSubmodule, CGitHash itemHash)
1231 CTGitPath gitPath(path);
1233 CGitHash hash;
1234 if (g_Git.GetHash(hash, m_sRevision))
1236 MessageBox(g_Git.GetGitLastErr(L"Could not get hash of " + m_sRevision + L'.'), L"TortoiseGit", MB_ICONERROR);
1237 return;
1240 CString file = CTempFiles::Instance().GetTempFilePath(false, gitPath, hash).GetWinPathString();
1241 if (isSubmodule)
1243 if (mode == OPEN && !GitAdminDir::IsBareRepo(g_Git.m_CurrentDir))
1245 CTGitPath subPath = CTGitPath(g_Git.m_CurrentDir);
1246 subPath.AppendPathString(gitPath.GetWinPathString());
1247 CAutoRepository repo(subPath.GetGitPathString());
1248 CAutoCommit commit;
1249 if (!repo || git_commit_lookup(commit.GetPointer(), repo, (const git_oid *)itemHash.m_hash))
1251 CString out;
1252 out.Format(IDS_REPOBROWSEASKSUBMODULEUPDATE, (LPCTSTR)itemHash.ToString(), (LPCTSTR)gitPath.GetGitPathString());
1253 if (MessageBox(out, L"TortoiseGit", MB_YESNO | MB_ICONQUESTION) != IDYES)
1254 return;
1256 CString sCmd;
1257 sCmd.Format(L"/command:subupdate /bkpath:\"%s\" /selectedpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)gitPath.GetGitPathString());
1258 CAppUtils::RunTortoiseGitProc(sCmd);
1259 return;
1262 CString cmd;
1263 cmd.Format(L"/command:repobrowser /path:\"%s\" /rev:%s", (LPCTSTR)g_Git.CombinePath(path), (LPCTSTR)itemHash.ToString());
1264 CAppUtils::RunTortoiseGitProc(cmd);
1265 return;
1268 file += L".txt";
1269 CFile submoduleCommit(file, CFile::modeCreate | CFile::modeWrite);
1270 CStringA commitInfo = "Subproject commit " + CStringA(itemHash.ToString());
1271 submoduleCommit.Write(commitInfo, commitInfo.GetLength());
1273 else if (g_Git.GetOneFile(m_sRevision, gitPath, file))
1275 CString out;
1276 out.Format(IDS_STATUSLIST_CHECKOUTFILEFAILED, (LPCTSTR)gitPath.GetGitPathString(), (LPCTSTR)m_sRevision, (LPCTSTR)file);
1277 MessageBox(g_Git.GetGitLastErr(out, CGit::GIT_CMD_GETONEFILE), L"TortoiseGit", MB_ICONERROR);
1278 return;
1281 if (mode == ALTERNATIVEEDITOR)
1283 CAppUtils::LaunchAlternativeEditor(file);
1284 return;
1286 else if (mode == OPEN)
1288 CAppUtils::ShellOpen(file);
1289 return;
1292 CAppUtils::ShowOpenWithDialog(file);
1294 bool CRepositoryBrowser::RevertItemToVersion(const CString &path)
1296 CString cmd, out;
1297 cmd.Format(L"git.exe checkout %s -- \"%s\"", (LPCTSTR)m_sRevision, (LPCTSTR)path);
1298 if (g_Git.Run(cmd, &out, CP_UTF8))
1300 if (MessageBox(out, L"TortoiseGit", MB_ICONEXCLAMATION | MB_OKCANCEL) == IDCANCEL)
1301 return false;
1304 return true;
1307 void CRepositoryBrowser::CopyHashToClipboard(TShadowFilesTreeList &selectedLeafs)
1309 if (!selectedLeafs.empty())
1311 CString sClipdata;
1312 bool first = true;
1313 for (size_t i = 0; i < selectedLeafs.size(); ++i)
1315 if (!first)
1316 sClipdata += L"\r\n";
1317 sClipdata += selectedLeafs[i]->m_hash;
1318 first = false;
1320 CStringUtils::WriteAsciiStringToClipboard(sClipdata, GetSafeHwnd());
1324 void CRepositoryBrowser::OnLvnBegindragRepolist(NMHDR* pNMHDR, LRESULT* pResult)
1326 *pResult = 0;
1328 // get selected paths
1329 POSITION pos = m_RepoList.GetFirstSelectedItemPosition();
1330 if (!pos)
1331 return;
1333 HTREEITEM hTreeItem = m_RepoTree.GetSelectedItem();
1334 if (!hTreeItem)
1336 ASSERT(FALSE);
1337 return;
1339 auto pTree = GetTreeEntry(hTreeItem);
1340 if (!pTree)
1341 return;
1343 CTGitPathList toExport;
1344 int index = -1;
1345 while ((index = m_RepoList.GetNextSelectedItem(pos)) >= 0)
1347 auto item = GetListEntry(index);
1348 if (item->m_bFolder)
1350 RecursivelyAdd(toExport, item);
1351 continue;
1354 CTGitPath path;
1355 path.SetFromGit(item->GetFullName(), item->m_bSubmodule);
1356 toExport.AddPath(path);
1359 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1360 BeginDrag(m_RepoList, toExport, pTree->GetFullName(), pNMLV->ptAction);
1363 void CRepositoryBrowser::RecursivelyAdd(CTGitPathList& toExport, CShadowFilesTree* pTree)
1365 if (!pTree->m_bLoaded)
1366 ReadTree(pTree, pTree->GetFullName(), true);
1368 for (auto itShadowTree = pTree->m_ShadowTree.begin(); itShadowTree != pTree->m_ShadowTree.end(); ++itShadowTree)
1370 if ((*itShadowTree).second.m_bFolder)
1372 RecursivelyAdd(toExport, &(*itShadowTree).second);
1373 continue;
1375 CTGitPath path;
1376 path.SetFromGit((*itShadowTree).second.GetFullName(), (*itShadowTree).second.m_bSubmodule);
1377 toExport.AddPath(path);
1381 void CRepositoryBrowser::OnTvnBegindragRepotree(NMHDR* pNMHDR, LRESULT* pResult)
1383 *pResult = 0;
1385 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
1387 auto pTree = GetTreeEntry(pNMTreeView->itemNew.hItem);
1388 if (!pTree)
1389 return;
1391 CTGitPathList toExport;
1392 RecursivelyAdd(toExport, pTree);
1394 BeginDrag(m_RepoTree, toExport, pTree->m_pParent ? pTree->m_pParent->GetFullName() : L"", pNMTreeView->ptDrag);
1397 void CRepositoryBrowser::BeginDrag(const CWnd& window, CTGitPathList& files, const CString& root, POINT& point)
1399 CGitHash hash;
1400 if (g_Git.GetHash(hash, m_sRevision))
1402 MessageBox(g_Git.GetGitLastErr(L"Could not get hash of " + m_sRevision + L"."), L"TortoiseGit", MB_ICONERROR);
1403 return;
1406 // build copy source / content
1407 auto pdsrc = std::make_unique<CIDropSource>();
1408 if (!pdsrc)
1409 return;
1411 pdsrc->AddRef();
1413 GitDataObject* pdobj = new GitDataObject(files, hash, root.GetLength());
1414 if (!pdobj)
1415 return;
1416 pdobj->AddRef();
1417 pdobj->SetAsyncMode(TRUE);
1418 CDragSourceHelper dragsrchelper;
1419 dragsrchelper.InitializeFromWindow(window.GetSafeHwnd(), point, pdobj);
1420 pdsrc->m_pIDataObj = pdobj;
1421 pdsrc->m_pIDataObj->AddRef();
1423 // Initiate the Drag & Drop
1424 DWORD dwEffect;
1425 ::DoDragDrop(pdobj, pdsrc.get(), DROPEFFECT_MOVE | DROPEFFECT_COPY, &dwEffect);
1426 pdsrc->Release();
1427 pdsrc.release();
1428 pdobj->Release();
1432 CShadowFilesTree* CRepositoryBrowser::GetListEntry(int index)
1434 auto entry = reinterpret_cast<CShadowFilesTree*>(m_RepoList.GetItemData(index));
1435 ASSERT(entry);
1436 return entry;
1439 CShadowFilesTree* CRepositoryBrowser::GetTreeEntry(HTREEITEM treeItem)
1441 auto entry = reinterpret_cast<CShadowFilesTree*>(m_RepoTree.GetItemData(treeItem));
1442 ASSERT(entry);
1443 return entry;
1446 void CRepositoryBrowser::OnSysColorChange()
1448 __super::OnSysColorChange();
1449 CAppUtils::SetListCtrlBackgroundImage(m_RepoList.GetSafeHwnd(), IDI_REPOBROWSER_BKG);