Fixed issue #2830: Log from Repository Browser should use selected branch by default
[TortoiseGit.git] / src / TortoiseProc / RepositoryBrowser.cpp
blob31de6fc29f06a104a5bc52480a8e5f824cb01666
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2016 - 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"
39 #define OVERLAY_EXTERNAL 1
40 #define OVERLAY_EXECUTABLE 2
41 #define OVERLAY_SYMLINK 3
43 void SetSortArrowA(CListCtrl * control, int nColumn, bool bAscending)
45 if (!control)
46 return;
48 // set the sort arrow
49 CHeaderCtrl * pHeader = control->GetHeaderCtrl();
50 HDITEM HeaderItem = {0};
51 HeaderItem.mask = HDI_FORMAT;
52 for (int i = 0; i < pHeader->GetItemCount(); ++i)
54 pHeader->GetItem(i, &HeaderItem);
55 HeaderItem.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
56 pHeader->SetItem(i, &HeaderItem);
58 if (nColumn >= 0)
60 pHeader->GetItem(nColumn, &HeaderItem);
61 HeaderItem.fmt |= (bAscending ? HDF_SORTUP : HDF_SORTDOWN);
62 pHeader->SetItem(nColumn, &HeaderItem);
66 class CRepoListCompareFunc
68 public:
69 CRepoListCompareFunc(CListCtrl* pList, int col, bool desc)
70 : m_col(col)
71 , m_desc(desc)
72 , m_pList(pList)
75 static int CALLBACK StaticCompare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
77 return reinterpret_cast<CRepoListCompareFunc*>(lParamSort)->Compare(lParam1, lParam2);
80 int Compare(LPARAM lParam1, LPARAM lParam2)
82 auto pLeft = reinterpret_cast<CShadowFilesTree*>(m_pList->GetItemData((int)lParam1));
83 auto pRight = reinterpret_cast<CShadowFilesTree*>(m_pList->GetItemData((int)lParam2));
85 int result = 0;
86 switch(m_col)
88 case CRepositoryBrowser::eCol_Name:
89 result = SortStrCmp(pLeft->m_sName, pRight->m_sName);
90 if (result != 0)
91 break;
92 case CRepositoryBrowser::eCol_Extension:
93 result = m_pList->GetItemText(static_cast<int>(lParam1), 1).CompareNoCase(m_pList->GetItemText(static_cast<int>(lParam2), 1));
94 if (result == 0) // if extensions are the same, use the filename to sort
95 result = SortStrCmp(pRight->m_sName, pRight->m_sName);
96 if (result != 0)
97 break;
98 case CRepositoryBrowser::eCol_FileSize:
99 if (pLeft->m_iSize > pRight->m_iSize)
100 result = 1;
101 else if (pLeft->m_iSize < pRight->m_iSize)
102 result = -1;
103 else // fallback
104 result = SortStrCmp(pLeft->m_sName, pRight->m_sName);
107 if (m_desc)
108 result = -result;
110 if (pLeft->m_bFolder != pRight->m_bFolder)
112 if (pRight->m_bFolder)
113 result = 1;
114 else
115 result = -1;
118 return result;
120 int SortStrCmp(const CString &left, const CString &right)
122 if (CRepositoryBrowser::s_bSortLogical)
123 return StrCmpLogicalW(left, right);
124 return StrCmpI(left, right);
127 int m_col;
128 bool m_desc;
129 CListCtrl* m_pList;
132 // CRepositoryBrowser dialog
134 bool CRepositoryBrowser::s_bSortLogical = true;
136 IMPLEMENT_DYNAMIC(CRepositoryBrowser, CResizableStandAloneDialog)
138 CRepositoryBrowser::CRepositoryBrowser(CString rev, CWnd* pParent /*=nullptr*/)
139 : CResizableStandAloneDialog(CRepositoryBrowser::IDD, pParent)
140 , m_currSortCol(0)
141 , m_currSortDesc(false)
142 , m_sRevision(rev)
143 , m_bHasWC(true)
144 , m_ColumnManager(&m_RepoList)
145 , m_nIconFolder(0)
146 , m_nOpenIconFolder(0)
147 , m_nExternalOvl(0)
148 , m_nExecutableOvl(0)
149 , m_nSymlinkOvl(0)
150 , bDragMode(false)
151 , oldy(0)
152 , oldx(0)
156 CRepositoryBrowser::~CRepositoryBrowser()
160 void CRepositoryBrowser::DoDataExchange(CDataExchange* pDX)
162 CDialog::DoDataExchange(pDX);
163 DDX_Control(pDX, IDC_REPOTREE, m_RepoTree);
164 DDX_Control(pDX, IDC_REPOLIST, m_RepoList);
168 BEGIN_MESSAGE_MAP(CRepositoryBrowser, CResizableStandAloneDialog)
169 ON_NOTIFY(TVN_SELCHANGED, IDC_REPOTREE, &CRepositoryBrowser::OnTvnSelchangedRepoTree)
170 ON_NOTIFY(TVN_ITEMEXPANDING, IDC_REPOTREE, &CRepositoryBrowser::OnTvnItemExpandingRepoTree)
171 ON_WM_CONTEXTMENU()
172 ON_NOTIFY(LVN_COLUMNCLICK, IDC_REPOLIST, &CRepositoryBrowser::OnLvnColumnclickRepoList)
173 ON_NOTIFY(LVN_ITEMCHANGED, IDC_REPOLIST, &CRepositoryBrowser::OnLvnItemchangedRepolist)
174 ON_NOTIFY(NM_DBLCLK, IDC_REPOLIST, &CRepositoryBrowser::OnNMDblclk_RepoList)
175 ON_BN_CLICKED(IDC_BUTTON_REVISION, &CRepositoryBrowser::OnBnClickedButtonRevision)
176 ON_WM_SETCURSOR()
177 ON_WM_DESTROY()
178 ON_WM_MOUSEMOVE()
179 ON_WM_LBUTTONDOWN()
180 ON_WM_LBUTTONUP()
181 ON_NOTIFY(LVN_BEGINDRAG, IDC_REPOLIST, &CRepositoryBrowser::OnLvnBegindragRepolist)
182 ON_NOTIFY(TVN_BEGINDRAG, IDC_REPOTREE, &CRepositoryBrowser::OnTvnBegindragRepotree)
183 END_MESSAGE_MAP()
186 // CRepositoryBrowser message handlers
188 BOOL CRepositoryBrowser::OnInitDialog()
190 CResizableStandAloneDialog::OnInitDialog();
191 CAppUtils::MarkWindowAsUnpinnable(m_hWnd);
193 AddAnchor(IDC_STATIC_REPOURL, TOP_LEFT);
194 AddAnchor(IDC_REPOBROWSER_URL, TOP_LEFT, TOP_RIGHT);
195 AddAnchor(IDC_STATIC_REF, TOP_RIGHT);
196 AddAnchor(IDC_BUTTON_REVISION, TOP_RIGHT);
197 AddAnchor(IDC_REPOTREE, TOP_LEFT, BOTTOM_LEFT);
198 AddAnchor(IDC_REPOLIST, TOP_LEFT, BOTTOM_RIGHT);
199 AddAnchor(IDHELP, BOTTOM_RIGHT);
200 AddAnchor(IDC_INFOLABEL, BOTTOM_LEFT, BOTTOM_RIGHT);
201 AddAnchor(IDOK, BOTTOM_RIGHT);
202 AddAnchor(IDCANCEL, BOTTOM_RIGHT);
204 CRepositoryBrowser::s_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER);
205 if (CRepositoryBrowser::s_bSortLogical)
206 CRepositoryBrowser::s_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_LOCAL_MACHINE);
208 static UINT columnNames[] = { IDS_STATUSLIST_COLFILENAME, IDS_STATUSLIST_COLEXT, IDS_LOG_SIZE };
209 static int columnWidths[] = { 150, 100, 100 };
210 DWORD dwDefaultColumns = (1 << eCol_Name) | (1 << eCol_Extension) | (1 << eCol_FileSize);
211 m_ColumnManager.SetNames(columnNames, _countof(columnNames));
212 m_ColumnManager.ReadSettings(dwDefaultColumns, 0, _T("RepoBrowser"), _countof(columnNames), columnWidths);
213 m_ColumnManager.SetRightAlign(2);
215 // set up the list control
216 // set the extended style of the list control
217 // 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.
218 CRegDWORD regFullRowSelect(_T("Software\\TortoiseGit\\FullRowSelect"), TRUE);
219 DWORD exStyle = LVS_EX_HEADERDRAGDROP | LVS_EX_DOUBLEBUFFER | LVS_EX_INFOTIP | LVS_EX_SUBITEMIMAGES;
220 if (DWORD(regFullRowSelect))
221 exStyle |= LVS_EX_FULLROWSELECT;
222 m_RepoList.SetExtendedStyle(exStyle);
223 m_RepoList.SetImageList(&SYS_IMAGE_LIST(), LVSIL_SMALL);
224 CAppUtils::SetListCtrlBackgroundImage(m_RepoList.GetSafeHwnd(), IDI_REPOBROWSER_BKG);
226 m_RepoTree.SetImageList(&SYS_IMAGE_LIST(), TVSIL_NORMAL);
227 exStyle = TVS_EX_FADEINOUTEXPANDOS | TVS_EX_AUTOHSCROLL | TVS_EX_DOUBLEBUFFER;
228 m_RepoTree.SetExtendedStyle(exStyle, exStyle);
230 m_nExternalOvl = SYS_IMAGE_LIST().AddIcon((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_EXTERNALOVL), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE));
231 m_nExecutableOvl = SYS_IMAGE_LIST().AddIcon((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_EXECUTABLEOVL), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE));
232 m_nSymlinkOvl = SYS_IMAGE_LIST().AddIcon((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_SYMLINKOVL), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE));
233 // set externaloverlay in SYS_IMAGE_LIST() in Refresh method, so that it is updated after every launch of the logdialog
235 SetWindowTheme(m_RepoTree.GetSafeHwnd(), L"Explorer", nullptr);
236 SetWindowTheme(m_RepoList.GetSafeHwnd(), L"Explorer", nullptr);
238 int borderWidth = 0;
239 if (IsAppThemed())
241 HTHEME hTheme = OpenThemeData(m_RepoTree, L"TREEVIEW");
242 GetThemeMetric(hTheme, NULL, TVP_TREEITEM, TREIS_NORMAL, TMT_BORDERSIZE, &borderWidth);
243 CloseThemeData(hTheme);
245 else
246 borderWidth = GetSystemMetrics(SM_CYBORDER);
247 m_RepoTree.SetItemHeight((SHORT)(m_RepoTree.GetItemHeight() + 2 * borderWidth));
249 m_nIconFolder = SYS_IMAGE_LIST().GetDirIconIndex();
250 m_nOpenIconFolder = SYS_IMAGE_LIST().GetDirOpenIconIndex();
252 EnableSaveRestore(L"Reposbrowser");
254 DWORD xPos = CRegDWORD(_T("Software\\TortoiseGit\\TortoiseProc\\ResizableState\\RepobrowserDivider"), 0);
255 if (xPos == 0)
257 RECT rc;
258 GetDlgItem(IDC_REPOTREE)->GetClientRect(&rc);
259 xPos = rc.right - rc.left;
261 HandleDividerMove(CPoint(xPos + 20, 10), false);
263 CString sWindowTitle;
264 GetWindowText(sWindowTitle);
265 CAppUtils::SetWindowTitle(m_hWnd, g_Git.m_CurrentDir, sWindowTitle);
267 m_bHasWC = !GitAdminDir::IsBareRepo(g_Git.m_CurrentDir);
269 Refresh();
271 m_RepoList.SetFocus();
273 return FALSE;
276 void CRepositoryBrowser::OnDestroy()
278 int maxcol = m_ColumnManager.GetColumnCount();
279 for (int col = 0; col < maxcol; ++col)
280 if (m_ColumnManager.IsVisible(col))
281 m_ColumnManager.ColumnResized(col);
282 m_ColumnManager.WriteSettings();
284 CResizableStandAloneDialog::OnDestroy();
287 void CRepositoryBrowser::OnOK()
289 if (GetFocus() == &m_RepoList && (GetKeyState(VK_MENU) & 0x8000) == 0)
291 // list control has focus: 'enter' the folder
292 if (m_RepoList.GetSelectedCount() != 1)
293 return;
295 POSITION pos = m_RepoList.GetFirstSelectedItemPosition();
296 if (pos)
298 auto item = GetListEntry(m_RepoList.GetNextSelectedItem(pos));
299 if (item->m_bFolder)
301 FillListCtrlForShadowTree(item);
302 m_RepoTree.SelectItem(item->m_hTree);
304 else
305 OpenFile(item->GetFullName(), OPEN, item->m_bSubmodule, item->m_hash);
307 return;
310 SaveDividerPosition();
311 CResizableStandAloneDialog::OnOK();
314 void CRepositoryBrowser::OnCancel()
316 SaveDividerPosition();
317 CResizableStandAloneDialog::OnCancel();
320 void CRepositoryBrowser::OnNMDblclk_RepoList(NMHDR *pNMHDR, LRESULT *pResult)
322 *pResult = 0;
324 LPNMITEMACTIVATE pNmItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
325 if (pNmItemActivate->iItem < 0)
326 return;
328 auto pItem = GetListEntry(pNmItemActivate->iItem);
329 if (!pItem )
330 return;
332 if (!pItem->m_bFolder)
334 OpenFile(pItem->GetFullName(), OPEN, pItem->m_bSubmodule, pItem->m_hash);
335 return;
337 else
339 FillListCtrlForShadowTree(pItem);
340 m_RepoTree.SelectItem(pItem->m_hTree);
344 void CRepositoryBrowser::Refresh()
346 BeginWaitCursor();
347 if (m_nExternalOvl >= 0)
348 SYS_IMAGE_LIST().SetOverlayImage(m_nExternalOvl, OVERLAY_EXTERNAL);
349 if (m_nExecutableOvl >= 0)
350 SYS_IMAGE_LIST().SetOverlayImage(m_nExecutableOvl, OVERLAY_EXECUTABLE);
351 if (m_nSymlinkOvl >= 0)
352 SYS_IMAGE_LIST().SetOverlayImage(m_nSymlinkOvl, OVERLAY_SYMLINK);
354 m_RepoTree.DeleteAllItems();
355 m_RepoList.DeleteAllItems();
356 m_TreeRoot.m_ShadowTree.clear();
357 m_TreeRoot.m_sName.Empty();
358 m_TreeRoot.m_bFolder = true;
360 TVINSERTSTRUCT tvinsert = {0};
361 tvinsert.hParent = TVI_ROOT;
362 tvinsert.hInsertAfter = TVI_ROOT;
363 tvinsert.itemex.mask = TVIF_DI_SETITEM | TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_STATE;
364 tvinsert.itemex.pszText = L"/";
365 tvinsert.itemex.lParam = (LPARAM)&m_TreeRoot;
366 tvinsert.itemex.iImage = m_nIconFolder;
367 tvinsert.itemex.iSelectedImage = m_nOpenIconFolder;
368 m_TreeRoot.m_hTree= m_RepoTree.InsertItem(&tvinsert);
370 ReadTree(&m_TreeRoot);
371 m_RepoTree.Expand(m_TreeRoot.m_hTree, TVE_EXPAND);
372 FillListCtrlForShadowTree(&m_TreeRoot);
373 m_RepoTree.SelectItem(m_TreeRoot.m_hTree);
374 EndWaitCursor();
377 int CRepositoryBrowser::ReadTreeRecursive(git_repository& repo, const git_tree* tree, CShadowFilesTree* treeroot, bool recursive)
379 size_t count = git_tree_entrycount(tree);
380 bool hasSubfolders = false;
381 treeroot->m_bLoaded = true;
383 for (size_t i = 0; i < count; ++i)
385 const git_tree_entry *entry = git_tree_entry_byindex(tree, i);
386 if (!entry)
387 continue;
389 const int mode = git_tree_entry_filemode(entry);
391 CString base = CUnicodeUtils::GetUnicode(git_tree_entry_name(entry), CP_UTF8);
393 const git_oid *oid = git_tree_entry_id(entry);
394 CShadowFilesTree * pNextTree = &treeroot->m_ShadowTree[base];
395 pNextTree->m_sName = base;
396 pNextTree->m_pParent = treeroot;
397 pNextTree->m_hash = CGitHash((char *)oid->id);
399 if (mode == GIT_FILEMODE_COMMIT)
400 pNextTree->m_bSubmodule = true;
401 else if (mode & S_IFDIR)
403 hasSubfolders = true;
404 pNextTree->m_bFolder = true;
405 pNextTree->m_bLoaded = false;
407 TVINSERTSTRUCT tvinsert = {0};
408 tvinsert.hParent = treeroot->m_hTree;
409 tvinsert.hInsertAfter = TVI_SORT;
410 tvinsert.itemex.mask = TVIF_DI_SETITEM | TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_STATE | TVIF_CHILDREN;
411 tvinsert.itemex.pszText = base.GetBuffer(base.GetLength());
412 tvinsert.itemex.cChildren = 1;
413 tvinsert.itemex.lParam = (LPARAM)pNextTree;
414 tvinsert.itemex.iImage = m_nIconFolder;
415 tvinsert.itemex.iSelectedImage = m_nOpenIconFolder;
416 pNextTree->m_hTree = m_RepoTree.InsertItem(&tvinsert);
417 base.ReleaseBuffer();
418 if (recursive)
420 CAutoTree subtree;
421 if (git_tree_lookup(subtree.GetPointer(), &repo, oid))
423 MessageBox(CGit::GetLibGit2LastErr(L"Could not lookup path."), L"TortoiseGit", MB_ICONERROR);
424 return -1;
427 ReadTreeRecursive(repo, subtree, pNextTree, recursive);
430 else
432 if (mode == GIT_FILEMODE_BLOB_EXECUTABLE)
433 pNextTree->m_bExecutable = true;
434 if (mode == GIT_FILEMODE_LINK)
435 pNextTree->m_bSymlink = true;
436 CAutoBlob blob;
437 git_blob_lookup(blob.GetPointer(), &repo, oid);
438 if (!blob)
439 continue;
441 pNextTree->m_iSize = git_blob_rawsize(blob);
445 if (!hasSubfolders)
447 TVITEM tvitem = { 0 };
448 tvitem.hItem = treeroot->m_hTree;
449 tvitem.mask = TVIF_CHILDREN;
450 tvitem.cChildren = 0;
451 m_RepoTree.SetItem(&tvitem);
454 return 0;
457 int CRepositoryBrowser::ReadTree(CShadowFilesTree* treeroot, const CString& root, bool recursive)
459 CWaitCursor wait;
460 CAutoRepository repository(g_Git.GetGitRepository());
461 if (!repository)
463 MessageBox(CGit::GetLibGit2LastErr(_T("Could not open repository.")), _T("TortoiseGit"), MB_ICONERROR);
464 return -1;
467 if (m_sRevision == _T("HEAD"))
469 int ret = git_repository_head_unborn(repository);
470 if (ret == 1) // is orphan
471 return ret;
472 else if (ret != 0)
474 MessageBox(g_Git.GetLibGit2LastErr(_T("Could not check HEAD.")), _T("TortoiseGit"), MB_ICONERROR);
475 return ret;
479 CGitHash hash;
480 if (CGit::GetHash(repository, hash, m_sRevision))
482 MessageBox(CGit::GetLibGit2LastErr(_T("Could not get hash of ") + m_sRevision + _T(".")), _T("TortoiseGit"), MB_ICONERROR);
483 return -1;
486 CAutoCommit commit;
487 if (git_commit_lookup(commit.GetPointer(), repository, (git_oid *)hash.m_hash))
489 MessageBox(CGit::GetLibGit2LastErr(_T("Could not lookup commit.")), _T("TortoiseGit"), MB_ICONERROR);
490 return -1;
493 CAutoTree tree;
494 if (git_commit_tree(tree.GetPointer(), commit))
496 MessageBox(CGit::GetLibGit2LastErr(_T("Could not get tree of commit.")), _T("TortoiseGit"), MB_ICONERROR);
497 return -1;
500 if (!root.IsEmpty())
502 CAutoTreeEntry treeEntry;
503 if (git_tree_entry_bypath(treeEntry.GetPointer(), tree, CUnicodeUtils::GetUTF8(root)))
505 MessageBox(CGit::GetLibGit2LastErr(_T("Could not lookup path.")), _T("TortoiseGit"), MB_ICONERROR);
506 return -1;
508 if (git_tree_entry_type(treeEntry) != GIT_OBJ_TREE)
510 MessageBox(CGit::GetLibGit2LastErr(_T("Could not lookup path.")), _T("TortoiseGit"), MB_ICONERROR);
511 return -1;
514 CAutoObject object;
515 if (git_tree_entry_to_object(object.GetPointer(), repository, treeEntry))
517 MessageBox(CGit::GetLibGit2LastErr(_T("Could not lookup path.")), _T("TortoiseGit"), MB_ICONERROR);
518 return -1;
521 tree = (git_tree*)object.Detach();
524 treeroot->m_hash = CGitHash((char *)git_tree_id(tree)->id);
525 ReadTreeRecursive(*repository, tree, treeroot, recursive);
527 // try to resolve hash to a branch name
528 if (m_sRevision == hash.ToString())
530 MAP_HASH_NAME map;
531 if (CGit::GetMapHashToFriendName(repository, map))
532 MessageBox(g_Git.GetLibGit2LastErr(_T("Could not get all refs.")), _T("TortoiseGit"), MB_ICONERROR);
533 if (!map[hash].empty())
534 m_sRevision = map[hash].at(0);
536 this->GetDlgItem(IDC_BUTTON_REVISION)->SetWindowText(m_sRevision);
538 return 0;
541 void CRepositoryBrowser::OnTvnSelchangedRepoTree(NMHDR *pNMHDR, LRESULT *pResult)
543 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
544 *pResult = 0;
546 FillListCtrlForTreeNode(pNMTreeView->itemNew.hItem);
549 void CRepositoryBrowser::OnTvnItemExpandingRepoTree(NMHDR *pNMHDR, LRESULT *pResult)
551 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
552 *pResult = 0;
554 auto pTree = GetTreeEntry(pNMTreeView->itemNew.hItem);
555 if (!pTree)
556 return;
558 if (!pTree->m_bLoaded)
559 ReadTree(pTree, pTree->GetFullName());
562 void CRepositoryBrowser::FillListCtrlForTreeNode(HTREEITEM treeNode)
564 m_RepoList.DeleteAllItems();
566 auto pTree = GetTreeEntry(treeNode);
567 if (!pTree)
568 return;
570 CString url = _T("/") + pTree->GetFullName();
571 GetDlgItem(IDC_REPOBROWSER_URL)->SetWindowText(url);
573 if (!pTree->m_bLoaded)
574 ReadTree(pTree, pTree->GetFullName());
576 FillListCtrlForShadowTree(pTree);
579 void CRepositoryBrowser::FillListCtrlForShadowTree(CShadowFilesTree* pTree)
581 for (auto itShadowTree = pTree->m_ShadowTree.cbegin(); itShadowTree != pTree->m_ShadowTree.cend(); ++itShadowTree)
583 int icon = m_nIconFolder;
584 if (!(*itShadowTree).second.m_bFolder && !(*itShadowTree).second.m_bSubmodule)
586 icon = SYS_IMAGE_LIST().GetFileIconIndex((*itShadowTree).second.m_sName);
589 int indexItem = m_RepoList.InsertItem(m_RepoList.GetItemCount(), (*itShadowTree).second.m_sName, icon);
591 if ((*itShadowTree).second.m_bSubmodule)
593 m_RepoList.SetItemState(indexItem, INDEXTOOVERLAYMASK(OVERLAY_EXTERNAL), LVIS_OVERLAYMASK);
595 if ((*itShadowTree).second.m_bExecutable)
596 m_RepoList.SetItemState(indexItem, INDEXTOOVERLAYMASK(OVERLAY_EXECUTABLE), LVIS_OVERLAYMASK);
597 if ((*itShadowTree).second.m_bSymlink)
598 m_RepoList.SetItemState(indexItem, INDEXTOOVERLAYMASK(OVERLAY_SYMLINK), LVIS_OVERLAYMASK);
599 m_RepoList.SetItemData(indexItem, (DWORD_PTR)&(*itShadowTree).second);
600 if (!(*itShadowTree).second.m_bFolder && !(*itShadowTree).second.m_bSubmodule)
602 CString temp;
604 temp = CPathUtils::GetFileExtFromPath((*itShadowTree).second.m_sName);
605 m_RepoList.SetItemText(indexItem, eCol_Extension, temp);
607 StrFormatByteSize64((*itShadowTree).second.m_iSize, temp.GetBuffer(20), 20);
608 temp.ReleaseBuffer();
609 m_RepoList.SetItemText(indexItem, eCol_FileSize, temp);
613 CRepoListCompareFunc compareFunc(&m_RepoList, m_currSortCol, m_currSortDesc);
614 m_RepoList.SortItemsEx(&CRepoListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
616 SetSortArrowA(&m_RepoList, m_currSortCol, !m_currSortDesc);
618 UpdateInfoLabel();
621 void CRepositoryBrowser::UpdateInfoLabel()
623 CString temp;
624 POSITION pos = m_RepoList.GetFirstSelectedItemPosition();
625 if (pos)
627 if (m_RepoList.GetSelectedCount() > 1)
629 temp.FormatMessage(IDS_REPOBROWSE_INFOMULTI, m_RepoList.GetSelectedCount());
631 else
633 int index = m_RepoList.GetNextSelectedItem(pos);
634 auto item = GetListEntry(index);
635 if (item->m_bSubmodule)
636 temp.FormatMessage(IDS_REPOBROWSE_INFOEXT, (LPCTSTR)m_RepoList.GetItemText(index, eCol_Name), item->m_hash.ToString());
637 else if (item->m_bFolder)
638 temp.FormatMessage(IDS_REPOBROWSE_INFODIR, (LPCTSTR)m_RepoList.GetItemText(index, eCol_Name));
639 else
640 temp.FormatMessage(IDS_REPOBROWSE_INFOFILE, (LPCTSTR)m_RepoList.GetItemText(index, eCol_Name), (LPCTSTR)m_RepoList.GetItemText(index, eCol_FileSize));
643 else
645 HTREEITEM hTreeItem = m_RepoTree.GetSelectedItem();
646 if (hTreeItem != nullptr)
648 auto pTree = GetTreeEntry(hTreeItem);
649 if (pTree != nullptr)
651 size_t files = 0, submodules = 0;
652 for (auto itShadowTree = pTree->m_ShadowTree.cbegin(); itShadowTree != pTree->m_ShadowTree.cend(); ++itShadowTree)
654 if (!(*itShadowTree).second.m_bFolder && !(*itShadowTree).second.m_bSubmodule)
655 ++files;
656 if ((*itShadowTree).second.m_bSubmodule)
657 ++submodules;
659 temp.FormatMessage(IDS_REPOBROWSE_INFO, (LPCTSTR)pTree->m_sName, files, submodules, pTree->m_ShadowTree.size() - files - submodules, pTree->m_ShadowTree.size());
663 SetDlgItemText(IDC_INFOLABEL, temp);
666 void CRepositoryBrowser::OnLvnItemchangedRepolist(NMHDR * /* pNMHDR */, LRESULT *pResult)
668 *pResult = 0;
669 UpdateInfoLabel();
672 void CRepositoryBrowser::OnContextMenu(CWnd* pWndFrom, CPoint point)
674 if (pWndFrom == &m_RepoList)
676 CRect headerPosition;
677 m_RepoList.GetHeaderCtrl()->GetWindowRect(headerPosition);
678 if (!headerPosition.PtInRect(point))
679 OnContextMenu_RepoList(point);
681 else if (pWndFrom == &m_RepoTree)
682 OnContextMenu_RepoTree(point);
685 void CRepositoryBrowser::OnContextMenu_RepoTree(CPoint point)
687 CPoint clientPoint = point;
688 m_RepoTree.ScreenToClient(&clientPoint);
690 HTREEITEM hTreeItem = m_RepoTree.HitTest(clientPoint);
691 if (!hTreeItem)
692 return;
694 TShadowFilesTreeList selectedLeafs;
695 selectedLeafs.push_back(GetTreeEntry(hTreeItem));
697 ShowContextMenu(point, selectedLeafs, ONLY_FOLDERS);
700 void CRepositoryBrowser::OnContextMenu_RepoList(CPoint point)
702 TShadowFilesTreeList selectedLeafs;
703 selectedLeafs.reserve(m_RepoList.GetSelectedCount());
705 bool folderSelected = false;
706 bool filesSelected = false;
707 bool submodulesSelected = false;
709 POSITION pos = m_RepoList.GetFirstSelectedItemPosition();
710 while (pos)
712 auto item = GetListEntry(m_RepoList.GetNextSelectedItem(pos));
713 if (item->m_bSubmodule)
714 submodulesSelected = true;
715 if (item->m_bFolder)
716 folderSelected = true;
717 else
718 filesSelected = true;
719 selectedLeafs.push_back(item);
722 eSelectionType selType = ONLY_FILES;
723 if (folderSelected && filesSelected)
724 selType = MIXED_FOLDERS_FILES;
725 else if (folderSelected)
726 selType = ONLY_FOLDERS;
727 else if (submodulesSelected)
728 selType = ONLY_FILESSUBMODULES;
729 ShowContextMenu(point, selectedLeafs, selType);
732 void CRepositoryBrowser::ShowContextMenu(CPoint point, TShadowFilesTreeList &selectedLeafs, eSelectionType selType)
734 CIconMenu popupMenu;
735 popupMenu.CreatePopupMenu();
737 bool bAddSeparator = false;
739 if (selectedLeafs.size() == 1)
741 popupMenu.AppendMenuIcon(eCmd_Open, IDS_REPOBROWSE_OPEN, IDI_OPEN);
742 popupMenu.SetDefaultItem(eCmd_Open, FALSE);
743 if (selType == ONLY_FILES || selType == ONLY_FILESSUBMODULES)
745 popupMenu.AppendMenuIcon(eCmd_OpenWith, IDS_LOG_POPUP_OPENWITH, IDI_OPEN);
746 popupMenu.AppendMenuIcon(eCmd_OpenWithAlternativeEditor, IDS_LOG_POPUP_VIEWREV);
749 popupMenu.AppendMenu(MF_SEPARATOR);
751 if (m_bHasWC && (selType == ONLY_FILES || selType == ONLY_FILESSUBMODULES))
753 popupMenu.AppendMenuIcon(eCmd_CompareWC, IDS_LOG_POPUP_COMPARE, IDI_DIFF);
754 bAddSeparator = true;
757 if (bAddSeparator)
758 popupMenu.AppendMenu(MF_SEPARATOR);
759 bAddSeparator = false;
761 CString temp;
762 temp.LoadString(IDS_MENULOG);
763 popupMenu.AppendMenuIcon(eCmd_ViewLog, temp, IDI_LOG);
764 if (selectedLeafs[0]->m_bSubmodule)
766 temp.LoadString(IDS_MENULOGSUBMODULE);
767 popupMenu.AppendMenuIcon(eCmd_ViewLogSubmodule, temp, IDI_LOG);
770 if (selType == ONLY_FILES)
772 if (m_bHasWC)
773 popupMenu.AppendMenuIcon(eCmd_Blame, IDS_LOG_POPUP_BLAME, IDI_BLAME);
775 popupMenu.AppendMenu(MF_SEPARATOR);
776 temp.LoadString(IDS_LOG_POPUP_SAVE);
777 popupMenu.AppendMenuIcon(eCmd_SaveAs, temp, IDI_SAVEAS);
780 bAddSeparator = true;
783 if (!selectedLeafs.empty() && selType == ONLY_FILES && m_bHasWC)
785 popupMenu.AppendMenuIcon(eCmd_Revert, IDS_LOG_POPUP_REVERTTOREV, IDI_REVERT);
786 bAddSeparator = true;
789 if (bAddSeparator)
790 popupMenu.AppendMenu(MF_SEPARATOR);
792 if (selectedLeafs.size() == 1 && selType == ONLY_FILES)
794 popupMenu.AppendMenuIcon(eCmd_PrepareDiff, IDS_PREPAREDIFF, IDI_DIFF);
795 if (!m_sMarkForDiffFilename.IsEmpty())
797 CString diffWith;
798 if (selectedLeafs.at(0)->GetFullName() == m_sMarkForDiffFilename)
799 diffWith = m_sMarkForDiffVersion;
800 else
802 PathCompactPathEx(diffWith.GetBuffer(40), m_sMarkForDiffFilename, 39, 0);
803 diffWith.ReleaseBuffer();
804 diffWith += _T(":") + 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(_T("/command:log /path:\"%s\""), (LPCTSTR)g_Git.CombinePath(selectedLeafs.at(0)->GetFullName()));
827 if (cmd == eCmd_ViewLog && selectedLeafs.at(0)->m_bSubmodule)
828 sCmd += _T(" /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, _T("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 + _T("\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(_T("Could not get SHA-1 for ") + m_sRevision), _T("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(_T("Could not get SHA-1 for ") + m_sRevision), _T("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)
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(_T("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(_T("Could not get hash of ") + m_sRevision + _T(".")), _T("TortoiseGit"), MB_ICONERROR);
1213 return;
1216 CString filename;
1217 filename.Format(_T("%s-%s%s"), (LPCTSTR)gitPath.GetBaseFilename(), (LPCTSTR)hash.ToString().Left(g_Git.GetShortHASHLength()), (LPCTSTR)gitPath.GetFileExtension());
1218 CFileDialog dlg(FALSE, nullptr, filename, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, nullptr);
1220 CString currentpath(g_Git.CombinePath(gitPath.GetContainingDirectory()));
1221 dlg.m_ofn.lpstrInitialDir = currentpath;
1223 CString cmd, out;
1224 INT_PTR ret = dlg.DoModal();
1225 SetCurrentDirectory(g_Git.m_CurrentDir);
1226 if (ret == IDOK)
1228 filename = dlg.GetPathName();
1229 if (g_Git.GetOneFile(m_sRevision, gitPath, filename))
1231 out.Format(IDS_STATUSLIST_CHECKOUTFILEFAILED, (LPCTSTR)gitPath.GetGitPathString(), (LPCTSTR)m_sRevision, (LPCTSTR)filename);
1232 MessageBox(g_Git.GetGitLastErr(out, CGit::GIT_CMD_GETONEFILE), _T("TortoiseGit"), MB_ICONERROR);
1233 return;
1238 void CRepositoryBrowser::OpenFile(const CString path, eOpenType mode, bool isSubmodule, CGitHash itemHash)
1240 CTGitPath gitPath(path);
1242 CString temppath;
1243 CString file;
1244 GetTempPath(temppath);
1245 CGitHash hash;
1246 if (g_Git.GetHash(hash, m_sRevision))
1248 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of ") + m_sRevision + _T(".")), _T("TortoiseGit"), MB_ICONERROR);
1249 return;
1252 file.Format(_T("%s%s_%s%s"), (LPCTSTR)temppath, (LPCTSTR)gitPath.GetBaseFilename(), (LPCTSTR)hash.ToString().Left(g_Git.GetShortHASHLength()), (LPCTSTR)gitPath.GetFileExtension());
1254 if (isSubmodule)
1256 if (mode == OPEN && !GitAdminDir::IsBareRepo(g_Git.m_CurrentDir))
1258 CTGitPath subPath = CTGitPath(g_Git.m_CurrentDir);
1259 subPath.AppendPathString(gitPath.GetWinPathString());
1260 CAutoRepository repo(subPath.GetGitPathString());
1261 CAutoCommit commit;
1262 if (!repo || git_commit_lookup(commit.GetPointer(), repo, (const git_oid *)itemHash.m_hash))
1264 CString out;
1265 out.Format(IDS_REPOBROWSEASKSUBMODULEUPDATE, (LPCTSTR)itemHash.ToString(), (LPCTSTR)gitPath.GetGitPathString());
1266 if (MessageBox(out, _T("TortoiseGit"), MB_YESNO | MB_ICONQUESTION) != IDYES)
1267 return;
1269 CString sCmd;
1270 sCmd.Format(_T("/command:subupdate /bkpath:\"%s\" /selectedpath:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)gitPath.GetGitPathString());
1271 CAppUtils::RunTortoiseGitProc(sCmd);
1272 return;
1275 CString cmd;
1276 cmd.Format(_T("/command:repobrowser /path:\"%s\" /rev:%s"), (LPCTSTR)g_Git.CombinePath(path), (LPCTSTR)itemHash.ToString());
1277 CAppUtils::RunTortoiseGitProc(cmd);
1278 return;
1281 file += _T(".txt");
1282 CFile submoduleCommit(file, CFile::modeCreate | CFile::modeWrite);
1283 CStringA commitInfo = "Subproject commit " + CStringA(itemHash.ToString());
1284 submoduleCommit.Write(commitInfo, commitInfo.GetLength());
1286 else if (g_Git.GetOneFile(m_sRevision, gitPath, file))
1288 CString out;
1289 out.Format(IDS_STATUSLIST_CHECKOUTFILEFAILED, (LPCTSTR)gitPath.GetGitPathString(), (LPCTSTR)m_sRevision, (LPCTSTR)file);
1290 MessageBox(g_Git.GetGitLastErr(out, CGit::GIT_CMD_GETONEFILE), _T("TortoiseGit"), MB_ICONERROR);
1291 return;
1294 if (mode == ALTERNATIVEEDITOR)
1296 CAppUtils::LaunchAlternativeEditor(file);
1297 return;
1299 else if (mode == OPEN)
1301 CAppUtils::ShellOpen(file);
1302 return;
1305 CAppUtils::ShowOpenWithDialog(file);
1307 bool CRepositoryBrowser::RevertItemToVersion(const CString &path)
1309 CString cmd, out;
1310 cmd.Format(_T("git.exe checkout %s -- \"%s\""), (LPCTSTR)m_sRevision, (LPCTSTR)path);
1311 if (g_Git.Run(cmd, &out, CP_UTF8))
1313 if (MessageBox(out, _T("TortoiseGit"), MB_ICONEXCLAMATION | MB_OKCANCEL) == IDCANCEL)
1314 return false;
1317 return true;
1320 void CRepositoryBrowser::CopyHashToClipboard(TShadowFilesTreeList &selectedLeafs)
1322 if (!selectedLeafs.empty())
1324 CString sClipdata;
1325 bool first = true;
1326 for (size_t i = 0; i < selectedLeafs.size(); ++i)
1328 if (!first)
1329 sClipdata += _T("\r\n");
1330 sClipdata += selectedLeafs[i]->m_hash;
1331 first = false;
1333 CStringUtils::WriteAsciiStringToClipboard(sClipdata, GetSafeHwnd());
1337 void CRepositoryBrowser::OnLvnBegindragRepolist(NMHDR* pNMHDR, LRESULT* pResult)
1339 *pResult = 0;
1341 // get selected paths
1342 POSITION pos = m_RepoList.GetFirstSelectedItemPosition();
1343 if (!pos)
1344 return;
1346 HTREEITEM hTreeItem = m_RepoTree.GetSelectedItem();
1347 if (!hTreeItem)
1349 ASSERT(FALSE);
1350 return;
1352 auto pTree = GetTreeEntry(hTreeItem);
1353 if (!pTree)
1354 return;
1356 CTGitPathList toExport;
1357 int index = -1;
1358 while ((index = m_RepoList.GetNextSelectedItem(pos)) >= 0)
1360 auto item = GetListEntry(index);
1361 if (item->m_bFolder)
1363 RecursivelyAdd(toExport, item);
1364 continue;
1367 CTGitPath path;
1368 path.SetFromGit(item->GetFullName(), item->m_bSubmodule);
1369 toExport.AddPath(path);
1372 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1373 BeginDrag(m_RepoList, toExport, pTree->GetFullName(), pNMLV->ptAction);
1376 void CRepositoryBrowser::RecursivelyAdd(CTGitPathList& toExport, CShadowFilesTree* pTree)
1378 if (!pTree->m_bLoaded)
1379 ReadTree(pTree, pTree->GetFullName(), true);
1381 for (auto itShadowTree = pTree->m_ShadowTree.begin(); itShadowTree != pTree->m_ShadowTree.end(); ++itShadowTree)
1383 if ((*itShadowTree).second.m_bFolder)
1385 RecursivelyAdd(toExport, &(*itShadowTree).second);
1386 continue;
1388 CTGitPath path;
1389 path.SetFromGit((*itShadowTree).second.GetFullName(), (*itShadowTree).second.m_bSubmodule);
1390 toExport.AddPath(path);
1394 void CRepositoryBrowser::OnTvnBegindragRepotree(NMHDR* pNMHDR, LRESULT* pResult)
1396 *pResult = 0;
1398 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
1400 auto pTree = GetTreeEntry(pNMTreeView->itemNew.hItem);
1401 if (!pTree)
1402 return;
1404 CTGitPathList toExport;
1405 RecursivelyAdd(toExport, pTree);
1407 BeginDrag(m_RepoTree, toExport, pTree->m_pParent ? pTree->m_pParent->GetFullName() : L"", pNMTreeView->ptDrag);
1410 void CRepositoryBrowser::BeginDrag(const CWnd& window, CTGitPathList& files, const CString& root, POINT& point)
1412 CGitHash hash;
1413 if (g_Git.GetHash(hash, m_sRevision))
1415 MessageBox(g_Git.GetGitLastErr(L"Could not get hash of " + m_sRevision + L"."), L"TortoiseGit", MB_ICONERROR);
1416 return;
1419 // build copy source / content
1420 auto pdsrc = std::make_unique<CIDropSource>();
1421 if (!pdsrc)
1422 return;
1424 pdsrc->AddRef();
1426 GitDataObject* pdobj = new GitDataObject(files, hash, root.GetLength());
1427 if (!pdobj)
1428 return;
1429 pdobj->AddRef();
1430 pdobj->SetAsyncMode(TRUE);
1431 CDragSourceHelper dragsrchelper;
1432 dragsrchelper.InitializeFromWindow(window.GetSafeHwnd(), point, pdobj);
1433 pdsrc->m_pIDataObj = pdobj;
1434 pdsrc->m_pIDataObj->AddRef();
1436 // Initiate the Drag & Drop
1437 DWORD dwEffect;
1438 ::DoDragDrop(pdobj, pdsrc.get(), DROPEFFECT_MOVE | DROPEFFECT_COPY, &dwEffect);
1439 pdsrc->Release();
1440 pdsrc.release();
1441 pdobj->Release();
1445 CShadowFilesTree* CRepositoryBrowser::GetListEntry(int index)
1447 auto entry = reinterpret_cast<CShadowFilesTree*>(m_RepoList.GetItemData(index));
1448 ASSERT(entry);
1449 return entry;
1452 CShadowFilesTree* CRepositoryBrowser::GetTreeEntry(HTREEITEM treeItem)
1454 auto entry = reinterpret_cast<CShadowFilesTree*>(m_RepoTree.GetItemData(treeItem));
1455 ASSERT(entry);
1456 return entry;