Update libgit2
[TortoiseGit.git] / src / TortoiseProc / RepositoryBrowser.cpp
blob6e66fe3d6413bdbd9fbe755750ad797aa76a6ff1
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2019 - 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"
39 #include "DPIAware.h"
41 #define OVERLAY_EXTERNAL 1
42 #define OVERLAY_EXECUTABLE 2
43 #define OVERLAY_SYMLINK 3
45 void SetSortArrowA(CListCtrl * control, int nColumn, bool bAscending)
47 if (!control)
48 return;
50 // set the sort arrow
51 CHeaderCtrl * pHeader = control->GetHeaderCtrl();
52 HDITEM HeaderItem = {0};
53 HeaderItem.mask = HDI_FORMAT;
54 for (int i = 0; i < pHeader->GetItemCount(); ++i)
56 pHeader->GetItem(i, &HeaderItem);
57 HeaderItem.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
58 pHeader->SetItem(i, &HeaderItem);
60 if (nColumn >= 0)
62 pHeader->GetItem(nColumn, &HeaderItem);
63 HeaderItem.fmt |= (bAscending ? HDF_SORTUP : HDF_SORTDOWN);
64 pHeader->SetItem(nColumn, &HeaderItem);
68 class CRepoListCompareFunc
70 public:
71 CRepoListCompareFunc(CListCtrl* pList, int col, bool desc)
72 : m_col(col)
73 , m_desc(desc)
74 , m_pList(pList)
77 static int CALLBACK StaticCompare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
79 return reinterpret_cast<CRepoListCompareFunc*>(lParamSort)->Compare(lParam1, lParam2);
82 int Compare(LPARAM lParam1, LPARAM lParam2)
84 auto pLeft = reinterpret_cast<CShadowFilesTree*>(m_pList->GetItemData((int)lParam1));
85 auto pRight = reinterpret_cast<CShadowFilesTree*>(m_pList->GetItemData((int)lParam2));
87 int result = 0;
88 switch(m_col)
90 case CRepositoryBrowser::eCol_Name:
91 result = SortStrCmp(pLeft->m_sName, pRight->m_sName);
92 if (result != 0)
93 break;
94 case CRepositoryBrowser::eCol_Extension:
95 result = m_pList->GetItemText(static_cast<int>(lParam1), 1).CompareNoCase(m_pList->GetItemText(static_cast<int>(lParam2), 1));
96 if (result == 0) // if extensions are the same, use the filename to sort
97 result = SortStrCmp(pRight->m_sName, pRight->m_sName);
98 if (result != 0)
99 break;
100 case CRepositoryBrowser::eCol_FileSize:
101 if (pLeft->m_iSize > pRight->m_iSize)
102 result = 1;
103 else if (pLeft->m_iSize < pRight->m_iSize)
104 result = -1;
105 else // fallback
106 result = SortStrCmp(pLeft->m_sName, pRight->m_sName);
109 if (m_desc)
110 result = -result;
112 if (pLeft->m_bFolder != pRight->m_bFolder)
114 if (pRight->m_bFolder)
115 result = 1;
116 else
117 result = -1;
120 return result;
122 int SortStrCmp(const CString &left, const CString &right)
124 if (CRepositoryBrowser::s_bSortLogical)
125 return StrCmpLogicalW(left, right);
126 return StrCmpI(left, right);
129 int m_col;
130 bool m_desc;
131 CListCtrl* m_pList;
134 // CRepositoryBrowser dialog
136 bool CRepositoryBrowser::s_bSortLogical = true;
138 IMPLEMENT_DYNAMIC(CRepositoryBrowser, CResizableStandAloneDialog)
140 CRepositoryBrowser::CRepositoryBrowser(CString rev, CWnd* pParent /*=nullptr*/)
141 : CResizableStandAloneDialog(CRepositoryBrowser::IDD, pParent)
142 , m_currSortCol(0)
143 , m_currSortDesc(false)
144 , m_sRevision(rev)
145 , m_bHasWC(true)
146 , m_ColumnManager(&m_RepoList)
147 , m_nIconFolder(0)
148 , m_nOpenIconFolder(0)
149 , m_nExternalOvl(0)
150 , m_nExecutableOvl(0)
151 , m_nSymlinkOvl(0)
152 , bDragMode(false)
153 , oldy(0)
154 , oldx(0)
158 CRepositoryBrowser::~CRepositoryBrowser()
162 void CRepositoryBrowser::DoDataExchange(CDataExchange* pDX)
164 CDialog::DoDataExchange(pDX);
165 DDX_Control(pDX, IDC_REPOTREE, m_RepoTree);
166 DDX_Control(pDX, IDC_REPOLIST, m_RepoList);
170 BEGIN_MESSAGE_MAP(CRepositoryBrowser, CResizableStandAloneDialog)
171 ON_NOTIFY(TVN_SELCHANGED, IDC_REPOTREE, &CRepositoryBrowser::OnTvnSelchangedRepoTree)
172 ON_NOTIFY(TVN_ITEMEXPANDING, IDC_REPOTREE, &CRepositoryBrowser::OnTvnItemExpandingRepoTree)
173 ON_WM_CONTEXTMENU()
174 ON_NOTIFY(LVN_COLUMNCLICK, IDC_REPOLIST, &CRepositoryBrowser::OnLvnColumnclickRepoList)
175 ON_NOTIFY(LVN_ITEMCHANGED, IDC_REPOLIST, &CRepositoryBrowser::OnLvnItemchangedRepolist)
176 ON_NOTIFY(NM_DBLCLK, IDC_REPOLIST, &CRepositoryBrowser::OnNMDblclk_RepoList)
177 ON_BN_CLICKED(IDC_BUTTON_REVISION, &CRepositoryBrowser::OnBnClickedButtonRevision)
178 ON_WM_SETCURSOR()
179 ON_WM_DESTROY()
180 ON_WM_MOUSEMOVE()
181 ON_WM_LBUTTONDOWN()
182 ON_WM_LBUTTONUP()
183 ON_WM_SYSCOLORCHANGE()
184 ON_NOTIFY(LVN_BEGINDRAG, IDC_REPOLIST, &CRepositoryBrowser::OnLvnBegindragRepolist)
185 ON_NOTIFY(TVN_BEGINDRAG, IDC_REPOTREE, &CRepositoryBrowser::OnTvnBegindragRepotree)
186 END_MESSAGE_MAP()
189 // CRepositoryBrowser message handlers
191 BOOL CRepositoryBrowser::OnInitDialog()
193 CResizableStandAloneDialog::OnInitDialog();
194 CAppUtils::MarkWindowAsUnpinnable(m_hWnd);
196 AddAnchor(IDC_STATIC_REPOURL, TOP_LEFT);
197 AddAnchor(IDC_REPOBROWSER_URL, TOP_LEFT, TOP_RIGHT);
198 AddAnchor(IDC_STATIC_REF, TOP_RIGHT);
199 AddAnchor(IDC_BUTTON_REVISION, TOP_RIGHT);
200 AddAnchor(IDC_REPOTREE, TOP_LEFT, BOTTOM_LEFT);
201 AddAnchor(IDC_REPOLIST, TOP_LEFT, BOTTOM_RIGHT);
202 AddAnchor(IDHELP, BOTTOM_RIGHT);
203 AddAnchor(IDC_INFOLABEL, BOTTOM_LEFT, BOTTOM_RIGHT);
204 AddAnchor(IDOK, BOTTOM_RIGHT);
205 AddAnchor(IDCANCEL, BOTTOM_RIGHT);
207 CRepositoryBrowser::s_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER);
208 if (CRepositoryBrowser::s_bSortLogical)
209 CRepositoryBrowser::s_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_LOCAL_MACHINE);
211 static UINT columnNames[] = { IDS_STATUSLIST_COLFILENAME, IDS_STATUSLIST_COLEXT, IDS_LOG_SIZE };
212 static int columnWidths[] = { 150, 100, 100 };
213 DWORD dwDefaultColumns = (1 << eCol_Name) | (1 << eCol_Extension) | (1 << eCol_FileSize);
214 m_ColumnManager.SetNames(columnNames, _countof(columnNames));
215 m_ColumnManager.ReadSettings(dwDefaultColumns, 0, L"RepoBrowser", _countof(columnNames), columnWidths);
216 m_ColumnManager.SetRightAlign(2);
218 // set up the list control
219 // set the extended style of the list control
220 // 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.
221 CRegDWORD regFullRowSelect(L"Software\\TortoiseGit\\FullRowSelect", TRUE);
222 DWORD exStyle = LVS_EX_HEADERDRAGDROP | LVS_EX_DOUBLEBUFFER | LVS_EX_INFOTIP | LVS_EX_SUBITEMIMAGES;
223 if (DWORD(regFullRowSelect))
224 exStyle |= LVS_EX_FULLROWSELECT;
225 m_RepoList.SetExtendedStyle(exStyle);
226 m_RepoList.SetImageList(&SYS_IMAGE_LIST(), LVSIL_SMALL);
227 CAppUtils::SetListCtrlBackgroundImage(m_RepoList.GetSafeHwnd(), IDI_REPOBROWSER_BKG);
229 m_RepoTree.SetImageList(&SYS_IMAGE_LIST(), TVSIL_NORMAL);
230 exStyle = TVS_EX_FADEINOUTEXPANDOS | TVS_EX_AUTOHSCROLL | TVS_EX_DOUBLEBUFFER;
231 m_RepoTree.SetExtendedStyle(exStyle, exStyle);
233 m_nExternalOvl = SYS_IMAGE_LIST().AddIcon(CCommonAppUtils::LoadIconEx(IDI_EXTERNALOVL, 0, 0));
234 m_nExecutableOvl = SYS_IMAGE_LIST().AddIcon(CCommonAppUtils::LoadIconEx(IDI_EXECUTABLEOVL, 0, 0));
235 m_nSymlinkOvl = SYS_IMAGE_LIST().AddIcon(CCommonAppUtils::LoadIconEx(IDI_SYMLINKOVL, 0, 0));
236 // set externaloverlay in SYS_IMAGE_LIST() in Refresh method, so that it is updated after every launch of the logdialog
238 SetWindowTheme(m_RepoTree.GetSafeHwnd(), L"Explorer", nullptr);
239 SetWindowTheme(m_RepoList.GetSafeHwnd(), L"Explorer", nullptr);
241 int borderWidth = 0;
242 if (IsAppThemed())
244 HTHEME hTheme = OpenThemeData(m_RepoTree, L"TREEVIEW");
245 GetThemeMetric(hTheme, NULL, TVP_TREEITEM, TREIS_NORMAL, TMT_BORDERSIZE, &borderWidth);
246 CloseThemeData(hTheme);
248 else
249 borderWidth = GetSystemMetrics(SM_CYBORDER);
250 m_RepoTree.SetItemHeight((SHORT)(m_RepoTree.GetItemHeight() + 2 * borderWidth));
252 m_nIconFolder = SYS_IMAGE_LIST().GetDirIconIndex();
253 m_nOpenIconFolder = SYS_IMAGE_LIST().GetDirOpenIconIndex();
255 EnableSaveRestore(L"Reposbrowser");
257 DWORD xPos = CRegDWORD(L"Software\\TortoiseGit\\TortoiseProc\\ResizableState\\RepobrowserDivider", 0);
258 if (xPos == 0)
260 RECT rc;
261 GetDlgItem(IDC_REPOTREE)->GetClientRect(&rc);
262 xPos = rc.right - rc.left;
264 HandleDividerMove(CPoint(xPos + CDPIAware::Instance().ScaleX(20), CDPIAware::Instance().ScaleY(10)), false);
266 CString sWindowTitle;
267 GetWindowText(sWindowTitle);
268 CAppUtils::SetWindowTitle(m_hWnd, g_Git.m_CurrentDir, sWindowTitle);
270 m_bHasWC = !GitAdminDir::IsBareRepo(g_Git.m_CurrentDir);
272 Refresh();
274 m_RepoList.SetFocus();
276 return FALSE;
279 void CRepositoryBrowser::OnDestroy()
281 int maxcol = m_ColumnManager.GetColumnCount();
282 for (int col = 0; col < maxcol; ++col)
283 if (m_ColumnManager.IsVisible(col))
284 m_ColumnManager.ColumnResized(col);
285 m_ColumnManager.WriteSettings();
287 CResizableStandAloneDialog::OnDestroy();
290 void CRepositoryBrowser::OnOK()
292 if (GetFocus() == &m_RepoList && (GetKeyState(VK_MENU) & 0x8000) == 0)
294 // list control has focus: 'enter' the folder
295 if (m_RepoList.GetSelectedCount() != 1)
296 return;
298 POSITION pos = m_RepoList.GetFirstSelectedItemPosition();
299 if (pos)
301 auto item = GetListEntry(m_RepoList.GetNextSelectedItem(pos));
302 if (item->m_bFolder)
304 FillListCtrlForShadowTree(item);
305 m_RepoTree.SelectItem(item->m_hTree);
307 else
308 OpenFile(item->GetFullName(), OPEN, item->m_bSubmodule, item->m_hash);
310 return;
313 SaveDividerPosition();
314 CResizableStandAloneDialog::OnOK();
317 void CRepositoryBrowser::OnCancel()
319 SaveDividerPosition();
320 CResizableStandAloneDialog::OnCancel();
323 void CRepositoryBrowser::OnNMDblclk_RepoList(NMHDR *pNMHDR, LRESULT *pResult)
325 *pResult = 0;
327 LPNMITEMACTIVATE pNmItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
328 if (pNmItemActivate->iItem < 0)
329 return;
331 auto pItem = GetListEntry(pNmItemActivate->iItem);
332 if (!pItem )
333 return;
335 if (!pItem->m_bFolder)
337 OpenFile(pItem->GetFullName(), OPEN, pItem->m_bSubmodule, pItem->m_hash);
338 return;
340 else
342 FillListCtrlForShadowTree(pItem);
343 m_RepoTree.SelectItem(pItem->m_hTree);
347 void CRepositoryBrowser::Refresh()
349 BeginWaitCursor();
350 if (m_nExternalOvl >= 0)
351 SYS_IMAGE_LIST().SetOverlayImage(m_nExternalOvl, OVERLAY_EXTERNAL);
352 if (m_nExecutableOvl >= 0)
353 SYS_IMAGE_LIST().SetOverlayImage(m_nExecutableOvl, OVERLAY_EXECUTABLE);
354 if (m_nSymlinkOvl >= 0)
355 SYS_IMAGE_LIST().SetOverlayImage(m_nSymlinkOvl, OVERLAY_SYMLINK);
357 m_RepoTree.DeleteAllItems();
358 m_RepoList.DeleteAllItems();
359 m_TreeRoot.m_ShadowTree.clear();
360 m_TreeRoot.m_sName.Empty();
361 m_TreeRoot.m_bFolder = true;
363 TVINSERTSTRUCT tvinsert = {0};
364 tvinsert.hParent = TVI_ROOT;
365 tvinsert.hInsertAfter = TVI_ROOT;
366 tvinsert.itemex.mask = TVIF_DI_SETITEM | TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_STATE;
367 tvinsert.itemex.pszText = L"/";
368 tvinsert.itemex.lParam = (LPARAM)&m_TreeRoot;
369 tvinsert.itemex.iImage = m_nIconFolder;
370 tvinsert.itemex.iSelectedImage = m_nOpenIconFolder;
371 m_TreeRoot.m_hTree= m_RepoTree.InsertItem(&tvinsert);
373 ReadTree(&m_TreeRoot);
374 m_RepoTree.Expand(m_TreeRoot.m_hTree, TVE_EXPAND);
375 FillListCtrlForShadowTree(&m_TreeRoot);
376 m_RepoTree.SelectItem(m_TreeRoot.m_hTree);
377 EndWaitCursor();
380 int CRepositoryBrowser::ReadTreeRecursive(git_repository& repo, const git_tree* tree, CShadowFilesTree* treeroot, bool recursive)
382 size_t count = git_tree_entrycount(tree);
383 bool hasSubfolders = false;
384 treeroot->m_bLoaded = true;
386 for (size_t i = 0; i < count; ++i)
388 const git_tree_entry *entry = git_tree_entry_byindex(tree, i);
389 if (!entry)
390 continue;
392 const int mode = git_tree_entry_filemode(entry);
394 CString base = CUnicodeUtils::GetUnicode(git_tree_entry_name(entry), CP_UTF8);
396 const git_oid *oid = git_tree_entry_id(entry);
397 CShadowFilesTree * pNextTree = &treeroot->m_ShadowTree[base];
398 pNextTree->m_sName = base;
399 pNextTree->m_pParent = treeroot;
400 pNextTree->m_hash = oid;
402 if (mode == GIT_FILEMODE_COMMIT)
403 pNextTree->m_bSubmodule = true;
404 else if (mode & S_IFDIR)
406 hasSubfolders = true;
407 pNextTree->m_bFolder = true;
408 pNextTree->m_bLoaded = false;
410 TVINSERTSTRUCT tvinsert = {0};
411 tvinsert.hParent = treeroot->m_hTree;
412 tvinsert.hInsertAfter = TVI_SORT;
413 tvinsert.itemex.mask = TVIF_DI_SETITEM | TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_STATE | TVIF_CHILDREN;
414 tvinsert.itemex.pszText = base.GetBuffer(base.GetLength());
415 tvinsert.itemex.cChildren = 1;
416 tvinsert.itemex.lParam = (LPARAM)pNextTree;
417 tvinsert.itemex.iImage = m_nIconFolder;
418 tvinsert.itemex.iSelectedImage = m_nOpenIconFolder;
419 pNextTree->m_hTree = m_RepoTree.InsertItem(&tvinsert);
420 base.ReleaseBuffer();
421 if (recursive)
423 CAutoTree subtree;
424 if (git_tree_lookup(subtree.GetPointer(), &repo, oid))
426 MessageBox(CGit::GetLibGit2LastErr(L"Could not lookup path."), L"TortoiseGit", MB_ICONERROR);
427 return -1;
430 ReadTreeRecursive(repo, subtree, pNextTree, recursive);
433 else
435 if (mode == GIT_FILEMODE_BLOB_EXECUTABLE)
436 pNextTree->m_bExecutable = true;
437 if (mode == GIT_FILEMODE_LINK)
438 pNextTree->m_bSymlink = true;
439 CAutoBlob blob;
440 git_blob_lookup(blob.GetPointer(), &repo, oid);
441 if (!blob)
442 continue;
444 pNextTree->m_iSize = git_blob_rawsize(blob);
448 if (!hasSubfolders)
450 TVITEM tvitem = { 0 };
451 tvitem.hItem = treeroot->m_hTree;
452 tvitem.mask = TVIF_CHILDREN;
453 tvitem.cChildren = 0;
454 m_RepoTree.SetItem(&tvitem);
457 return 0;
460 int CRepositoryBrowser::ReadTree(CShadowFilesTree* treeroot, const CString& root, bool recursive)
462 CWaitCursor wait;
463 CAutoRepository repository(g_Git.GetGitRepository());
464 if (!repository)
466 MessageBox(CGit::GetLibGit2LastErr(L"Could not open repository."), L"TortoiseGit", MB_ICONERROR);
467 return -1;
470 if (m_sRevision == L"HEAD")
472 int ret = git_repository_head_unborn(repository);
473 if (ret == 1) // is orphan
474 return ret;
475 else if (ret != 0)
477 MessageBox(g_Git.GetLibGit2LastErr(L"Could not check HEAD."), L"TortoiseGit", MB_ICONERROR);
478 return ret;
482 CGitHash hash;
483 if (CGit::GetHash(repository, hash, m_sRevision))
485 MessageBox(CGit::GetLibGit2LastErr(L"Could not get hash of " + m_sRevision + L'.'), L"TortoiseGit", MB_ICONERROR);
486 return -1;
489 CAutoCommit commit;
490 if (git_commit_lookup(commit.GetPointer(), repository, hash))
492 MessageBox(CGit::GetLibGit2LastErr(L"Could not lookup commit."), L"TortoiseGit", MB_ICONERROR);
493 return -1;
496 CAutoTree tree;
497 if (git_commit_tree(tree.GetPointer(), commit))
499 MessageBox(CGit::GetLibGit2LastErr(L"Could not get tree of commit."), L"TortoiseGit", MB_ICONERROR);
500 return -1;
503 if (!root.IsEmpty())
505 CAutoTreeEntry treeEntry;
506 if (git_tree_entry_bypath(treeEntry.GetPointer(), tree, CUnicodeUtils::GetUTF8(root)))
508 MessageBox(CGit::GetLibGit2LastErr(L"Could not lookup path."), L"TortoiseGit", MB_ICONERROR);
509 return -1;
511 if (git_tree_entry_type(treeEntry) != GIT_OBJECT_TREE)
513 MessageBox(CGit::GetLibGit2LastErr(L"Could not lookup path."), L"TortoiseGit", MB_ICONERROR);
514 return -1;
517 CAutoObject object;
518 if (git_tree_entry_to_object(object.GetPointer(), repository, treeEntry))
520 MessageBox(CGit::GetLibGit2LastErr(L"Could not lookup path."), L"TortoiseGit", MB_ICONERROR);
521 return -1;
524 tree.ConvertFrom(std::move(object));
527 treeroot->m_hash = git_tree_id(tree);
528 ReadTreeRecursive(*repository, tree, treeroot, recursive);
530 // try to resolve hash to a branch name
531 if (m_sRevision == hash.ToString())
533 MAP_HASH_NAME map;
534 if (CGit::GetMapHashToFriendName(repository, map))
535 MessageBox(g_Git.GetLibGit2LastErr(L"Could not get all refs."), L"TortoiseGit", MB_ICONERROR);
536 if (!map[hash].empty())
537 m_sRevision = map[hash].at(0);
539 this->GetDlgItem(IDC_BUTTON_REVISION)->SetWindowText(m_sRevision);
541 return 0;
544 void CRepositoryBrowser::OnTvnSelchangedRepoTree(NMHDR *pNMHDR, LRESULT *pResult)
546 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
547 *pResult = 0;
549 FillListCtrlForTreeNode(pNMTreeView->itemNew.hItem);
552 void CRepositoryBrowser::OnTvnItemExpandingRepoTree(NMHDR *pNMHDR, LRESULT *pResult)
554 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
555 *pResult = 0;
557 auto pTree = GetTreeEntry(pNMTreeView->itemNew.hItem);
558 if (!pTree)
559 return;
561 if (!pTree->m_bLoaded)
562 ReadTree(pTree, pTree->GetFullName());
565 void CRepositoryBrowser::FillListCtrlForTreeNode(HTREEITEM treeNode)
567 m_RepoList.DeleteAllItems();
569 auto pTree = GetTreeEntry(treeNode);
570 if (!pTree)
571 return;
573 CString url = L'/' + pTree->GetFullName();
574 GetDlgItem(IDC_REPOBROWSER_URL)->SetWindowText(url);
576 if (!pTree->m_bLoaded)
577 ReadTree(pTree, pTree->GetFullName());
579 FillListCtrlForShadowTree(pTree);
582 void CRepositoryBrowser::FillListCtrlForShadowTree(CShadowFilesTree* pTree)
584 for (auto itShadowTree = pTree->m_ShadowTree.cbegin(); itShadowTree != pTree->m_ShadowTree.cend(); ++itShadowTree)
586 int icon = m_nIconFolder;
587 if (!(*itShadowTree).second.m_bFolder && !(*itShadowTree).second.m_bSubmodule)
589 icon = SYS_IMAGE_LIST().GetPathIconIndex((*itShadowTree).second.m_sName);
592 int indexItem = m_RepoList.InsertItem(m_RepoList.GetItemCount(), (*itShadowTree).second.m_sName, icon);
594 if ((*itShadowTree).second.m_bSubmodule)
596 m_RepoList.SetItemState(indexItem, INDEXTOOVERLAYMASK(OVERLAY_EXTERNAL), LVIS_OVERLAYMASK);
598 if ((*itShadowTree).second.m_bExecutable)
599 m_RepoList.SetItemState(indexItem, INDEXTOOVERLAYMASK(OVERLAY_EXECUTABLE), LVIS_OVERLAYMASK);
600 if ((*itShadowTree).second.m_bSymlink)
601 m_RepoList.SetItemState(indexItem, INDEXTOOVERLAYMASK(OVERLAY_SYMLINK), LVIS_OVERLAYMASK);
602 m_RepoList.SetItemData(indexItem, (DWORD_PTR)&(*itShadowTree).second);
603 if (!(*itShadowTree).second.m_bFolder && !(*itShadowTree).second.m_bSubmodule)
605 CString temp;
607 temp = CPathUtils::GetFileExtFromPath((*itShadowTree).second.m_sName);
608 m_RepoList.SetItemText(indexItem, eCol_Extension, temp);
610 StrFormatByteSize64((*itShadowTree).second.m_iSize, CStrBuf(temp, 20), 20);
611 m_RepoList.SetItemText(indexItem, eCol_FileSize, temp);
615 CRepoListCompareFunc compareFunc(&m_RepoList, m_currSortCol, m_currSortDesc);
616 m_RepoList.SortItemsEx(&CRepoListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
618 SetSortArrowA(&m_RepoList, m_currSortCol, !m_currSortDesc);
620 UpdateInfoLabel();
623 void CRepositoryBrowser::UpdateInfoLabel()
625 CString temp;
626 POSITION pos = m_RepoList.GetFirstSelectedItemPosition();
627 if (pos)
629 if (m_RepoList.GetSelectedCount() > 1)
631 temp.FormatMessage(IDS_REPOBROWSE_INFOMULTI, m_RepoList.GetSelectedCount());
633 else
635 int index = m_RepoList.GetNextSelectedItem(pos);
636 auto item = GetListEntry(index);
637 if (item->m_bSubmodule)
638 temp.FormatMessage(IDS_REPOBROWSE_INFOEXT, (LPCTSTR)m_RepoList.GetItemText(index, eCol_Name), (LPCTSTR)item->m_hash.ToString());
639 else if (item->m_bFolder)
640 temp = m_RepoList.GetItemText(index, eCol_Name);
641 else
642 temp.FormatMessage(IDS_REPOBROWSE_INFOFILE, (LPCTSTR)m_RepoList.GetItemText(index, eCol_Name), (LPCTSTR)m_RepoList.GetItemText(index, eCol_FileSize));
645 else
647 HTREEITEM hTreeItem = m_RepoTree.GetSelectedItem();
648 if (hTreeItem != nullptr)
650 auto pTree = GetTreeEntry(hTreeItem);
651 if (pTree != nullptr)
653 size_t files = 0, submodules = 0;
654 for (auto itShadowTree = pTree->m_ShadowTree.cbegin(); itShadowTree != pTree->m_ShadowTree.cend(); ++itShadowTree)
656 if (!(*itShadowTree).second.m_bFolder && !(*itShadowTree).second.m_bSubmodule)
657 ++files;
658 if ((*itShadowTree).second.m_bSubmodule)
659 ++submodules;
661 temp.FormatMessage(IDS_REPOBROWSE_INFO, (LPCTSTR)pTree->m_sName, files, submodules, pTree->m_ShadowTree.size() - files - submodules, pTree->m_ShadowTree.size());
665 SetDlgItemText(IDC_INFOLABEL, temp);
668 void CRepositoryBrowser::OnLvnItemchangedRepolist(NMHDR * /* pNMHDR */, LRESULT *pResult)
670 *pResult = 0;
671 UpdateInfoLabel();
674 void CRepositoryBrowser::OnContextMenu(CWnd* pWndFrom, CPoint point)
676 if (pWndFrom == &m_RepoList)
678 CRect headerPosition;
679 m_RepoList.GetHeaderCtrl()->GetWindowRect(headerPosition);
680 if (!headerPosition.PtInRect(point))
681 OnContextMenu_RepoList(point);
683 else if (pWndFrom == &m_RepoTree)
684 OnContextMenu_RepoTree(point);
687 void CRepositoryBrowser::OnContextMenu_RepoTree(CPoint point)
689 CPoint clientPoint = point;
690 m_RepoTree.ScreenToClient(&clientPoint);
692 HTREEITEM hTreeItem = m_RepoTree.HitTest(clientPoint);
693 if (!hTreeItem)
694 return;
696 TShadowFilesTreeList selectedLeafs;
697 selectedLeafs.push_back(GetTreeEntry(hTreeItem));
699 ShowContextMenu(point, selectedLeafs, ONLY_FOLDERS);
702 void CRepositoryBrowser::OnContextMenu_RepoList(CPoint point)
704 TShadowFilesTreeList selectedLeafs;
705 selectedLeafs.reserve(m_RepoList.GetSelectedCount());
707 bool folderSelected = false;
708 bool filesSelected = false;
709 bool submodulesSelected = false;
711 POSITION pos = m_RepoList.GetFirstSelectedItemPosition();
712 while (pos)
714 auto item = GetListEntry(m_RepoList.GetNextSelectedItem(pos));
715 if (item->m_bSubmodule)
716 submodulesSelected = true;
717 if (item->m_bFolder)
718 folderSelected = true;
719 else
720 filesSelected = true;
721 selectedLeafs.push_back(item);
724 eSelectionType selType = ONLY_FILES;
725 if (folderSelected && filesSelected)
726 selType = MIXED_FOLDERS_FILES;
727 else if (folderSelected)
728 selType = ONLY_FOLDERS;
729 else if (submodulesSelected)
730 selType = ONLY_FILESSUBMODULES;
731 ShowContextMenu(point, selectedLeafs, selType);
734 void CRepositoryBrowser::ShowContextMenu(CPoint point, TShadowFilesTreeList &selectedLeafs, eSelectionType selType)
736 CIconMenu popupMenu;
737 popupMenu.CreatePopupMenu();
739 bool bAddSeparator = false;
741 if (selectedLeafs.size() == 1)
743 popupMenu.AppendMenuIcon(eCmd_Open, IDS_REPOBROWSE_OPEN, IDI_OPEN);
744 popupMenu.SetDefaultItem(eCmd_Open, FALSE);
745 if (selType == ONLY_FILES || selType == ONLY_FILESSUBMODULES)
747 popupMenu.AppendMenuIcon(eCmd_OpenWith, IDS_LOG_POPUP_OPENWITH, IDI_OPEN);
748 popupMenu.AppendMenuIcon(eCmd_OpenWithAlternativeEditor, IDS_LOG_POPUP_VIEWREV, IDI_NOTEPAD);
751 popupMenu.AppendMenu(MF_SEPARATOR);
753 if (m_bHasWC && (selType == ONLY_FILES || selType == ONLY_FILESSUBMODULES))
755 popupMenu.AppendMenuIcon(eCmd_CompareWC, IDS_LOG_POPUP_COMPARE, IDI_DIFF);
756 bAddSeparator = true;
759 if (bAddSeparator)
760 popupMenu.AppendMenu(MF_SEPARATOR);
761 bAddSeparator = false;
763 CString temp;
764 temp.LoadString(IDS_MENULOG);
765 popupMenu.AppendMenuIcon(eCmd_ViewLog, temp, IDI_LOG);
766 if (selectedLeafs[0]->m_bSubmodule)
768 temp.LoadString(IDS_MENULOGSUBMODULE);
769 popupMenu.AppendMenuIcon(eCmd_ViewLogSubmodule, temp, IDI_LOG);
772 if (selType == ONLY_FILES)
774 if (m_bHasWC)
775 popupMenu.AppendMenuIcon(eCmd_Blame, IDS_LOG_POPUP_BLAME, IDI_BLAME);
777 popupMenu.AppendMenu(MF_SEPARATOR);
778 temp.LoadString(IDS_LOG_POPUP_SAVE);
779 popupMenu.AppendMenuIcon(eCmd_SaveAs, temp, IDI_SAVEAS);
782 bAddSeparator = true;
785 if (!selectedLeafs.empty() && selType == ONLY_FILES && m_bHasWC)
787 popupMenu.AppendMenuIcon(eCmd_Revert, IDS_LOG_POPUP_REVERTTOREV, IDI_REVERT);
788 bAddSeparator = true;
791 if (bAddSeparator)
792 popupMenu.AppendMenu(MF_SEPARATOR);
794 if (selectedLeafs.size() == 1 && selType == ONLY_FILES)
796 popupMenu.AppendMenuIcon(eCmd_PrepareDiff, IDS_PREPAREDIFF, IDI_DIFF);
797 if (!m_sMarkForDiffFilename.IsEmpty())
799 CString diffWith;
800 if (selectedLeafs.at(0)->GetFullName() == m_sMarkForDiffFilename)
801 diffWith = m_sMarkForDiffVersion.ToString();
802 else
804 PathCompactPathEx(CStrBuf(diffWith, 2 * GIT_HASH_SIZE), m_sMarkForDiffFilename, 2 * GIT_HASH_SIZE, 0);
805 diffWith += L':' + m_sMarkForDiffVersion.ToString().Left(g_Git.GetShortHASHLength());
807 CString menuEntry;
808 menuEntry.Format(IDS_MENUDIFFNOW, (LPCTSTR)diffWith);
809 popupMenu.AppendMenuIcon(eCmd_PrepareDiff_Compare, menuEntry, IDI_DIFF);
811 popupMenu.AppendMenu(MF_SEPARATOR);
814 if (!selectedLeafs.empty())
816 popupMenu.AppendMenuIcon(eCmd_CopyPath, IDS_STATUSLIST_CONTEXT_COPY, IDI_COPYCLIP);
817 popupMenu.AppendMenuIcon(eCmd_CopyHash, IDS_COPY_COMMIT_HASH, IDI_COPYCLIP);
820 eCmd cmd = (eCmd)popupMenu.TrackPopupMenuEx(TPM_LEFTALIGN | TPM_RETURNCMD, point.x, point.y, this, nullptr);
821 switch(cmd)
823 case eCmd_ViewLog:
824 case eCmd_ViewLogSubmodule:
826 CString sCmd;
827 sCmd.Format(L"/command:log /path:\"%s\"", (LPCTSTR)g_Git.CombinePath(selectedLeafs.at(0)->GetFullName()));
828 if (cmd == eCmd_ViewLog && selectedLeafs.at(0)->m_bSubmodule)
829 sCmd += L" /submodule";
830 if (cmd == eCmd_ViewLog)
831 sCmd += L" /endrev:" + m_sRevision;
832 CAppUtils::RunTortoiseGitProc(sCmd);
834 break;
835 case eCmd_Blame:
837 CAppUtils::LaunchTortoiseBlame(g_Git.CombinePath(selectedLeafs.at(0)->GetFullName()), m_sRevision);
839 break;
840 case eCmd_Open:
841 if (!selectedLeafs.at(0)->m_bSubmodule && selectedLeafs.at(0)->m_bFolder)
843 FillListCtrlForTreeNode(selectedLeafs.at(0)->m_hTree);
844 m_RepoTree.SelectItem(selectedLeafs.at(0)->m_hTree);
845 return;
847 OpenFile(selectedLeafs.at(0)->GetFullName(), OPEN, selectedLeafs.at(0)->m_bSubmodule, selectedLeafs.at(0)->m_hash);
848 break;
849 case eCmd_OpenWith:
850 OpenFile(selectedLeafs.at(0)->GetFullName(), OPEN_WITH, selectedLeafs.at(0)->m_bSubmodule, selectedLeafs.at(0)->m_hash);
851 break;
852 case eCmd_OpenWithAlternativeEditor:
853 OpenFile(selectedLeafs.at(0)->GetFullName(), ALTERNATIVEEDITOR, selectedLeafs.at(0)->m_bSubmodule, selectedLeafs.at(0)->m_hash);
854 break;
855 case eCmd_CompareWC:
857 CTGitPath file(selectedLeafs.at(0)->GetFullName());
858 CGitDiff::Diff(GetSafeHwnd(), &file, &file, GIT_REV_ZERO, m_sRevision);
860 break;
861 case eCmd_Revert:
863 int count = 0;
864 for (auto itShadowTree = selectedLeafs.cbegin(); itShadowTree != selectedLeafs.cend(); ++itShadowTree)
866 if (RevertItemToVersion((*itShadowTree)->GetFullName()))
867 ++count;
868 else
869 break;
871 CString msg;
872 msg.FormatMessage(IDS_STATUSLIST_FILESREVERTED, count, (LPCTSTR)m_sRevision);
873 MessageBox(msg, L"TortoiseGit", MB_OK);
875 break;
876 case eCmd_SaveAs:
877 FileSaveAs(selectedLeafs.at(0)->GetFullName());
878 break;
879 case eCmd_CopyPath:
881 CString sClipboard;
882 for (auto itShadowTree = selectedLeafs.cbegin(); itShadowTree != selectedLeafs.cend(); ++itShadowTree)
884 sClipboard += (*itShadowTree)->m_sName + L"\r\n";
886 CStringUtils::WriteAsciiStringToClipboard(sClipboard);
888 break;
889 case eCmd_CopyHash:
891 CopyHashToClipboard(selectedLeafs);
893 break;
894 case eCmd_PrepareDiff:
895 m_sMarkForDiffFilename = selectedLeafs.at(0)->GetFullName();
896 if (g_Git.GetHash(m_sMarkForDiffVersion, m_sRevision))
898 m_sMarkForDiffFilename.Empty();
899 MessageBox(g_Git.GetGitLastErr(L"Could not get SHA-1 for " + m_sRevision), L"TortoiseGit", MB_ICONERROR);
901 break;
902 case eCmd_PrepareDiff_Compare:
904 CTGitPath savedFile(m_sMarkForDiffFilename);
905 CTGitPath selectedFile(selectedLeafs.at(0)->GetFullName());
906 CGitHash currentHash;
907 if (g_Git.GetHash(currentHash, m_sRevision))
909 MessageBox(g_Git.GetGitLastErr(L"Could not get SHA-1 for " + m_sRevision), L"TortoiseGit", MB_ICONERROR);
910 return;
912 CGitDiff::Diff(GetSafeHwnd(), &selectedFile, &savedFile, currentHash.ToString(), m_sMarkForDiffVersion.ToString());
914 break;
918 BOOL CRepositoryBrowser::PreTranslateMessage(MSG* pMsg)
920 if (pMsg->message == WM_KEYDOWN)
922 switch (pMsg->wParam)
924 case VK_F5:
926 Refresh();
928 break;
932 return CResizableStandAloneDialog::PreTranslateMessage(pMsg);
935 void CRepositoryBrowser::OnLvnColumnclickRepoList(NMHDR *pNMHDR, LRESULT *pResult)
937 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
938 if (pResult)
939 *pResult = 0;
941 if (m_currSortCol == pNMLV->iSubItem)
942 m_currSortDesc = !m_currSortDesc;
943 else
945 m_currSortCol = pNMLV->iSubItem;
946 m_currSortDesc = false;
949 CRepoListCompareFunc compareFunc(&m_RepoList, m_currSortCol, m_currSortDesc);
950 m_RepoList.SortItemsEx(&CRepoListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
952 SetSortArrowA(&m_RepoList, m_currSortCol, !m_currSortDesc);
955 void CRepositoryBrowser::OnBnClickedButtonRevision()
957 // use the git log to allow selection of a version
958 CLogDlg dlg;
959 dlg.SetParams(CTGitPath(), CTGitPath(), m_sRevision, m_sRevision, 0);
960 // tell the dialog to use mode for selecting revisions
961 dlg.SetSelect(true);
962 dlg.ShowWorkingTreeChanges(false);
963 // only one revision must be selected however
964 dlg.SingleSelection(true);
965 if (dlg.DoModal() == IDOK && !dlg.GetSelectedHash().empty())
967 m_sRevision = dlg.GetSelectedHash().at(0).ToString();
968 Refresh();
972 void CRepositoryBrowser::SaveDividerPosition()
974 RECT rc;
975 GetDlgItem(IDC_REPOTREE)->GetClientRect(&rc);
976 CRegDWORD xPos(L"Software\\TortoiseGit\\TortoiseProc\\ResizableState\\RepobrowserDivider");
977 xPos = rc.right - rc.left;
980 void CRepositoryBrowser::HandleDividerMove(CPoint point, bool bDraw)
982 RECT rect, tree, list, treelist, treelistclient;
984 // create an union of the tree and list control rectangle
985 GetDlgItem(IDC_REPOLIST)->GetWindowRect(&list);
986 GetDlgItem(IDC_REPOTREE)->GetWindowRect(&tree);
987 UnionRect(&treelist, &tree, &list);
988 treelistclient = treelist;
989 ScreenToClient(&treelistclient);
991 ClientToScreen(&point);
992 GetClientRect(&rect);
993 ClientToScreen(&rect);
995 auto minWidth = CDPIAware::Instance().ScaleX(REPOBROWSER_CTRL_MIN_WIDTH);
996 CPoint point2 = point;
997 if (point2.x < treelist.left + minWidth)
998 point2.x = treelist.left + minWidth;
999 if (point2.x > treelist.right - minWidth)
1000 point2.x = treelist.right - minWidth;
1002 point.x -= rect.left;
1003 point.y -= treelist.top;
1005 OffsetRect(&treelist, -treelist.left, -treelist.top);
1007 if (point.x < treelist.left + minWidth)
1008 point.x = treelist.left + minWidth;
1009 if (point.x > treelist.right - minWidth)
1010 point.x = treelist.right - minWidth;
1012 auto divWidth = CDPIAware::Instance().ScaleX(2);
1014 if (bDraw)
1016 CDC * pDC = GetDC();
1017 DrawXorBar(pDC, oldx - divWidth, treelistclient.top, 2 * divWidth, treelistclient.bottom - treelistclient.top - CDPIAware::Instance().ScaleY(2));
1018 ReleaseDC(pDC);
1021 oldx = point.x;
1022 oldy = point.y;
1024 //position the child controls
1025 GetDlgItem(IDC_REPOTREE)->GetWindowRect(&treelist);
1026 treelist.right = point2.x - divWidth;
1027 ScreenToClient(&treelist);
1028 RemoveAnchor(IDC_REPOTREE);
1029 GetDlgItem(IDC_REPOTREE)->MoveWindow(&treelist);
1030 GetDlgItem(IDC_REPOLIST)->GetWindowRect(&treelist);
1031 treelist.left = point2.x + divWidth;
1032 ScreenToClient(&treelist);
1033 RemoveAnchor(IDC_REPOLIST);
1034 GetDlgItem(IDC_REPOLIST)->MoveWindow(&treelist);
1036 AddAnchor(IDC_REPOTREE, TOP_LEFT, BOTTOM_LEFT);
1037 AddAnchor(IDC_REPOLIST, TOP_LEFT, BOTTOM_RIGHT);
1040 void CRepositoryBrowser::OnMouseMove(UINT nFlags, CPoint point)
1042 if (bDragMode == FALSE)
1043 return;
1045 RECT rect, tree, list, treelist, treelistclient;
1046 // create an union of the tree and list control rectangle
1047 GetDlgItem(IDC_REPOLIST)->GetWindowRect(&list);
1048 GetDlgItem(IDC_REPOTREE)->GetWindowRect(&tree);
1049 UnionRect(&treelist, &tree, &list);
1050 treelistclient = treelist;
1051 ScreenToClient(&treelistclient);
1053 //convert the mouse coordinates relative to the top-left of
1054 //the window
1055 ClientToScreen(&point);
1056 GetClientRect(&rect);
1057 ClientToScreen(&rect);
1058 point.x -= rect.left;
1059 point.y -= treelist.top;
1061 //same for the window coordinates - make them relative to 0,0
1062 OffsetRect(&treelist, -treelist.left, -treelist.top);
1064 auto minWidth = CDPIAware::Instance().ScaleX(REPOBROWSER_CTRL_MIN_WIDTH);
1065 if (point.x < treelist.left + minWidth)
1066 point.x = treelist.left + minWidth;
1067 if (point.x > treelist.right - minWidth)
1068 point.x = treelist.right - minWidth;
1070 if ((nFlags & MK_LBUTTON) && (point.x != oldx))
1072 CDC * pDC = GetDC();
1074 if (pDC)
1076 auto divWidth = CDPIAware::Instance().ScaleX(2);
1077 auto divHeight = CDPIAware::Instance().ScaleY(2);
1078 DrawXorBar(pDC, oldx - divWidth, treelistclient.top, 2 * divWidth, treelistclient.bottom - treelistclient.top - divHeight);
1079 DrawXorBar(pDC, point.x - divWidth, treelistclient.top, 2 * divWidth, treelistclient.bottom - treelistclient.top - divHeight);
1081 ReleaseDC(pDC);
1084 oldx = point.x;
1085 oldy = point.y;
1088 CStandAloneDialogTmpl<CResizableDialog>::OnMouseMove(nFlags, point);
1091 void CRepositoryBrowser::OnLButtonDown(UINT nFlags, CPoint point)
1093 RECT rect, tree, list, treelist, treelistclient;
1095 // create an union of the tree and list control rectangle
1096 GetDlgItem(IDC_REPOLIST)->GetWindowRect(&list);
1097 GetDlgItem(IDC_REPOTREE)->GetWindowRect(&tree);
1098 UnionRect(&treelist, &tree, &list);
1099 treelistclient = treelist;
1100 ScreenToClient(&treelistclient);
1102 //convert the mouse coordinates relative to the top-left of
1103 //the window
1104 ClientToScreen(&point);
1105 GetClientRect(&rect);
1106 ClientToScreen(&rect);
1107 point.x -= rect.left;
1108 point.y -= treelist.top;
1110 //same for the window coordinates - make them relative to 0,0
1111 OffsetRect(&treelist, -treelist.left, -treelist.top);
1113 auto minWidth = CDPIAware::Instance().ScaleX(REPOBROWSER_CTRL_MIN_WIDTH);
1115 if (point.x < treelist.left + minWidth)
1116 return CStandAloneDialogTmpl < CResizableDialog>::OnLButtonDown(nFlags, point);
1117 if (point.x > treelist.right - CDPIAware::Instance().ScaleX(3))
1118 return CStandAloneDialogTmpl < CResizableDialog>::OnLButtonDown(nFlags, point);
1119 if (point.x > treelist.right - minWidth)
1120 point.x = treelist.right - minWidth;
1122 auto divHeight = CDPIAware::Instance().ScaleY(3);
1123 if ((point.y < treelist.top + divHeight) || (point.y > treelist.bottom - divHeight))
1124 return CStandAloneDialogTmpl<CResizableDialog>::OnLButtonDown(nFlags, point);
1126 bDragMode = true;
1128 SetCapture();
1130 auto divWidth = CDPIAware::Instance().ScaleX(2);
1131 CDC * pDC = GetDC();
1132 DrawXorBar(pDC, point.x - divWidth, treelistclient.top, 2 * divWidth, treelistclient.bottom - treelistclient.top - CDPIAware::Instance().ScaleY(2));
1133 ReleaseDC(pDC);
1135 oldx = point.x;
1136 oldy = point.y;
1138 CStandAloneDialogTmpl<CResizableDialog>::OnLButtonDown(nFlags, point);
1141 void CRepositoryBrowser::OnLButtonUp(UINT nFlags, CPoint point)
1143 if (bDragMode == FALSE)
1144 return;
1146 HandleDividerMove(point, true);
1148 bDragMode = false;
1149 ReleaseCapture();
1151 CStandAloneDialogTmpl<CResizableDialog>::OnLButtonUp(nFlags, point);
1154 void CRepositoryBrowser::OnCaptureChanged(CWnd *pWnd)
1156 bDragMode = false;
1158 __super::OnCaptureChanged(pWnd);
1161 void CRepositoryBrowser::DrawXorBar(CDC * pDC, int x1, int y1, int width, int height)
1163 static WORD _dotPatternBmp[8] =
1165 0x0055, 0x00aa, 0x0055, 0x00aa,
1166 0x0055, 0x00aa, 0x0055, 0x00aa
1169 HBITMAP hbm;
1170 HBRUSH hbr, hbrushOld;
1172 hbm = CreateBitmap(8, 8, 1, 1, _dotPatternBmp);
1173 hbr = CreatePatternBrush(hbm);
1175 pDC->SetBrushOrg(x1, y1);
1176 hbrushOld = (HBRUSH)pDC->SelectObject(hbr);
1178 PatBlt(pDC->GetSafeHdc(), x1, y1, width, height, PATINVERT);
1180 pDC->SelectObject(hbrushOld);
1182 DeleteObject(hbr);
1183 DeleteObject(hbm);
1186 BOOL CRepositoryBrowser::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
1188 if (pWnd == this)
1190 RECT rect;
1191 POINT pt;
1192 GetClientRect(&rect);
1193 GetCursorPos(&pt);
1194 ScreenToClient(&pt);
1195 if (PtInRect(&rect, pt))
1197 ClientToScreen(&pt);
1198 // are we right of the tree control?
1199 GetDlgItem(IDC_REPOTREE)->GetWindowRect(&rect);
1200 auto divHeight = CDPIAware::Instance().ScaleY(3);
1201 if ((pt.x > rect.right) && (pt.y >= rect.top + divHeight) && (pt.y <= rect.bottom - divHeight))
1203 // but left of the list control?
1204 GetDlgItem(IDC_REPOLIST)->GetWindowRect(&rect);
1205 if (pt.x < rect.left)
1207 HCURSOR hCur = LoadCursor(nullptr, IDC_SIZEWE);
1208 SetCursor(hCur);
1209 return TRUE;
1214 return CStandAloneDialogTmpl<CResizableDialog>::OnSetCursor(pWnd, nHitTest, message);
1217 void CRepositoryBrowser::FileSaveAs(const CString path)
1219 CTGitPath gitPath(path);
1221 CGitHash hash;
1222 if (g_Git.GetHash(hash, m_sRevision))
1224 MessageBox(g_Git.GetGitLastErr(L"Could not get hash of " + m_sRevision + L'.'), L"TortoiseGit", MB_ICONERROR);
1225 return;
1228 CString filename;
1229 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());
1230 if (!CAppUtils::FileOpenSave(filename, nullptr, 0, 0, false, GetSafeHwnd()))
1231 return;
1233 if (g_Git.GetOneFile(m_sRevision, gitPath, filename))
1235 CString out;
1236 out.FormatMessage(IDS_STATUSLIST_CHECKOUTFILEFAILED, (LPCTSTR)gitPath.GetGitPathString(), (LPCTSTR)m_sRevision, (LPCTSTR)filename);
1237 MessageBox(g_Git.GetGitLastErr(out, CGit::GIT_CMD_GETONEFILE), L"TortoiseGit", MB_ICONERROR);
1241 void CRepositoryBrowser::OpenFile(const CString path, eOpenType mode, bool isSubmodule, const CGitHash& itemHash)
1243 CTGitPath gitPath(path);
1245 CGitHash hash;
1246 if (g_Git.GetHash(hash, m_sRevision))
1248 MessageBox(g_Git.GetGitLastErr(L"Could not get hash of " + m_sRevision + L'.'), L"TortoiseGit", MB_ICONERROR);
1249 return;
1252 CString file = CTempFiles::Instance().GetTempFilePath(false, gitPath, hash).GetWinPathString();
1253 if (isSubmodule)
1255 if (mode == OPEN && !GitAdminDir::IsBareRepo(g_Git.m_CurrentDir))
1257 CTGitPath subPath = CTGitPath(g_Git.m_CurrentDir);
1258 subPath.AppendPathString(gitPath.GetWinPathString());
1259 CAutoRepository repo(subPath.GetGitPathString());
1260 CAutoCommit commit;
1261 if (!repo || git_commit_lookup(commit.GetPointer(), repo, itemHash))
1263 CString out;
1264 out.FormatMessage(IDS_REPOBROWSEASKSUBMODULEUPDATE, (LPCTSTR)itemHash.ToString(), (LPCTSTR)gitPath.GetGitPathString());
1265 if (MessageBox(out, L"TortoiseGit", MB_YESNO | MB_ICONQUESTION) != IDYES)
1266 return;
1268 CString sCmd;
1269 sCmd.Format(L"/command:subupdate /bkpath:\"%s\" /selectedpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)gitPath.GetGitPathString());
1270 CAppUtils::RunTortoiseGitProc(sCmd);
1271 return;
1274 CString cmd;
1275 cmd.Format(L"/command:repobrowser /path:\"%s\" /rev:%s", (LPCTSTR)g_Git.CombinePath(path), (LPCTSTR)itemHash.ToString());
1276 CAppUtils::RunTortoiseGitProc(cmd);
1277 return;
1280 file += L".txt";
1281 CFile submoduleCommit(file, CFile::modeCreate | CFile::modeWrite);
1282 CStringA commitInfo = "Subproject commit " + CStringA(itemHash.ToString());
1283 submoduleCommit.Write(commitInfo, commitInfo.GetLength());
1285 else if (g_Git.GetOneFile(m_sRevision, gitPath, file))
1287 CString out;
1288 out.FormatMessage(IDS_STATUSLIST_CHECKOUTFILEFAILED, (LPCTSTR)gitPath.GetGitPathString(), (LPCTSTR)m_sRevision, (LPCTSTR)file);
1289 MessageBox(g_Git.GetGitLastErr(out, CGit::GIT_CMD_GETONEFILE), L"TortoiseGit", MB_ICONERROR);
1290 return;
1293 if (mode == ALTERNATIVEEDITOR)
1295 CAppUtils::LaunchAlternativeEditor(file);
1296 return;
1298 else if (mode == OPEN)
1300 CAppUtils::ShellOpen(file);
1301 return;
1304 CAppUtils::ShowOpenWithDialog(file);
1306 bool CRepositoryBrowser::RevertItemToVersion(const CString &path)
1308 CString cmd, out;
1309 cmd.Format(L"git.exe checkout %s -- \"%s\"", (LPCTSTR)m_sRevision, (LPCTSTR)path);
1310 if (g_Git.Run(cmd, &out, CP_UTF8))
1312 if (MessageBox(out, L"TortoiseGit", MB_ICONEXCLAMATION | MB_OKCANCEL) == IDCANCEL)
1313 return false;
1316 return true;
1319 void CRepositoryBrowser::CopyHashToClipboard(TShadowFilesTreeList &selectedLeafs)
1321 if (!selectedLeafs.empty())
1323 CString sClipdata;
1324 bool first = true;
1325 for (size_t i = 0; i < selectedLeafs.size(); ++i)
1327 if (!first)
1328 sClipdata += L"\r\n";
1329 sClipdata += selectedLeafs[i]->m_hash.ToString();
1330 first = false;
1332 CStringUtils::WriteAsciiStringToClipboard(sClipdata, GetSafeHwnd());
1336 void CRepositoryBrowser::OnLvnBegindragRepolist(NMHDR* pNMHDR, LRESULT* pResult)
1338 *pResult = 0;
1340 // get selected paths
1341 POSITION pos = m_RepoList.GetFirstSelectedItemPosition();
1342 if (!pos)
1343 return;
1345 HTREEITEM hTreeItem = m_RepoTree.GetSelectedItem();
1346 if (!hTreeItem)
1348 ASSERT(FALSE);
1349 return;
1351 auto pTree = GetTreeEntry(hTreeItem);
1352 if (!pTree)
1353 return;
1355 CTGitPathList toExport;
1356 int index = -1;
1357 while ((index = m_RepoList.GetNextSelectedItem(pos)) >= 0)
1359 auto item = GetListEntry(index);
1360 if (item->m_bFolder)
1362 RecursivelyAdd(toExport, item);
1363 continue;
1366 CTGitPath path;
1367 path.SetFromGit(item->GetFullName(), item->m_bSubmodule);
1368 toExport.AddPath(path);
1371 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1372 BeginDrag(m_RepoList, toExport, pTree->GetFullName(), pNMLV->ptAction);
1375 void CRepositoryBrowser::RecursivelyAdd(CTGitPathList& toExport, CShadowFilesTree* pTree)
1377 if (!pTree->m_bLoaded)
1378 ReadTree(pTree, pTree->GetFullName(), true);
1380 for (auto itShadowTree = pTree->m_ShadowTree.begin(); itShadowTree != pTree->m_ShadowTree.end(); ++itShadowTree)
1382 if ((*itShadowTree).second.m_bFolder)
1384 RecursivelyAdd(toExport, &(*itShadowTree).second);
1385 continue;
1387 CTGitPath path;
1388 path.SetFromGit((*itShadowTree).second.GetFullName(), (*itShadowTree).second.m_bSubmodule);
1389 toExport.AddPath(path);
1393 void CRepositoryBrowser::OnTvnBegindragRepotree(NMHDR* pNMHDR, LRESULT* pResult)
1395 *pResult = 0;
1397 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
1399 auto pTree = GetTreeEntry(pNMTreeView->itemNew.hItem);
1400 if (!pTree)
1401 return;
1403 CTGitPathList toExport;
1404 RecursivelyAdd(toExport, pTree);
1406 BeginDrag(m_RepoTree, toExport, pTree->m_pParent ? pTree->m_pParent->GetFullName() : L"", pNMTreeView->ptDrag);
1409 void CRepositoryBrowser::BeginDrag(const CWnd& window, CTGitPathList& files, const CString& root, POINT& point)
1411 CGitHash hash;
1412 if (g_Git.GetHash(hash, m_sRevision))
1414 MessageBox(g_Git.GetGitLastErr(L"Could not get hash of " + m_sRevision + L"."), L"TortoiseGit", MB_ICONERROR);
1415 return;
1418 // build copy source / content
1419 auto pdsrc = std::make_unique<CIDropSource>();
1420 if (!pdsrc)
1421 return;
1423 pdsrc->AddRef();
1425 GitDataObject* pdobj = new GitDataObject(files, hash, root.GetLength());
1426 if (!pdobj)
1427 return;
1428 pdobj->AddRef();
1429 pdobj->SetAsyncMode(TRUE);
1430 CDragSourceHelper dragsrchelper;
1431 dragsrchelper.InitializeFromWindow(window.GetSafeHwnd(), point, pdobj);
1432 pdsrc->m_pIDataObj = pdobj;
1433 pdsrc->m_pIDataObj->AddRef();
1435 // Initiate the Drag & Drop
1436 DWORD dwEffect;
1437 ::DoDragDrop(pdobj, pdsrc.get(), DROPEFFECT_MOVE | DROPEFFECT_COPY, &dwEffect);
1438 pdsrc->Release();
1439 pdsrc.release();
1440 pdobj->Release();
1444 CShadowFilesTree* CRepositoryBrowser::GetListEntry(int index)
1446 auto entry = reinterpret_cast<CShadowFilesTree*>(m_RepoList.GetItemData(index));
1447 ASSERT(entry);
1448 return entry;
1451 CShadowFilesTree* CRepositoryBrowser::GetTreeEntry(HTREEITEM treeItem)
1453 auto entry = reinterpret_cast<CShadowFilesTree*>(m_RepoTree.GetItemData(treeItem));
1454 ASSERT(entry);
1455 return entry;
1458 void CRepositoryBrowser::OnSysColorChange()
1460 __super::OnSysColorChange();
1461 CAppUtils::SetListCtrlBackgroundImage(m_RepoList.GetSafeHwnd(), IDI_REPOBROWSER_BKG);