Fix off by one error
[TortoiseGit.git] / src / TortoiseProc / RepositoryBrowser.cpp
blobd736931cb7addce7061d9a4f41ab94d20f2a847a
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"
37 #define OVERLAY_EXTERNAL 1
38 #define OVERLAY_EXECUTABLE 2
39 #define OVERLAY_SYMLINK 3
41 void SetSortArrowA(CListCtrl * control, int nColumn, bool bAscending)
43 if (!control)
44 return;
46 // set the sort arrow
47 CHeaderCtrl * pHeader = control->GetHeaderCtrl();
48 HDITEM HeaderItem = {0};
49 HeaderItem.mask = HDI_FORMAT;
50 for (int i = 0; i < pHeader->GetItemCount(); ++i)
52 pHeader->GetItem(i, &HeaderItem);
53 HeaderItem.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
54 pHeader->SetItem(i, &HeaderItem);
56 if (nColumn >= 0)
58 pHeader->GetItem(nColumn, &HeaderItem);
59 HeaderItem.fmt |= (bAscending ? HDF_SORTUP : HDF_SORTDOWN);
60 pHeader->SetItem(nColumn, &HeaderItem);
64 class CRepoListCompareFunc
66 public:
67 CRepoListCompareFunc(CListCtrl* pList, int col, bool desc)
68 : m_col(col)
69 , m_desc(desc)
70 , m_pList(pList)
73 static int CALLBACK StaticCompare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
75 return ((CRepoListCompareFunc *) lParamSort)->Compare(lParam1, lParam2);
78 int Compare(LPARAM lParam1, LPARAM lParam2)
80 CShadowFilesTree * pLeft = (CShadowFilesTree *)m_pList->GetItemData((int)lParam1);
81 CShadowFilesTree * pRight = (CShadowFilesTree *)m_pList->GetItemData((int)lParam2);
83 int result = 0;
84 switch(m_col)
86 case CRepositoryBrowser::eCol_Name:
87 result = SortStrCmp(pLeft->m_sName, pRight->m_sName);
88 if (result != 0)
89 break;
90 case CRepositoryBrowser::eCol_Extension:
91 result = m_pList->GetItemText(static_cast<int>(lParam1), 1).CompareNoCase(m_pList->GetItemText(static_cast<int>(lParam2), 1));
92 if (result == 0) // if extensions are the same, use the filename to sort
93 result = SortStrCmp(pRight->m_sName, pRight->m_sName);
94 if (result != 0)
95 break;
96 case CRepositoryBrowser::eCol_FileSize:
97 if (pLeft->m_iSize > pRight->m_iSize)
98 result = 1;
99 else if (pLeft->m_iSize < pRight->m_iSize)
100 result = -1;
101 else // fallback
102 result = SortStrCmp(pLeft->m_sName, pRight->m_sName);
105 if (m_desc)
106 result = -result;
108 if (pLeft->m_bFolder != pRight->m_bFolder)
110 if (pRight->m_bFolder)
111 result = 1;
112 else
113 result = -1;
116 return result;
118 int SortStrCmp(const CString &left, const CString &right)
120 if (CRepositoryBrowser::s_bSortLogical)
121 return StrCmpLogicalW(left, right);
122 return StrCmpI(left, right);
125 int m_col;
126 bool m_desc;
127 CListCtrl* m_pList;
130 // CRepositoryBrowser dialog
132 bool CRepositoryBrowser::s_bSortLogical = true;
134 IMPLEMENT_DYNAMIC(CRepositoryBrowser, CResizableStandAloneDialog)
136 CRepositoryBrowser::CRepositoryBrowser(CString rev, CWnd* pParent /*=nullptr*/)
137 : CResizableStandAloneDialog(CRepositoryBrowser::IDD, pParent)
138 , m_currSortCol(0)
139 , m_currSortDesc(false)
140 , m_sRevision(rev)
141 , m_bHasWC(true)
142 , m_ColumnManager(&m_RepoList)
143 , m_nIconFolder(0)
144 , m_nOpenIconFolder(0)
145 , m_nExternalOvl(0)
146 , m_nExecutableOvl(0)
147 , m_nSymlinkOvl(0)
148 , bDragMode(false)
149 , oldy(0)
150 , oldx(0)
154 CRepositoryBrowser::~CRepositoryBrowser()
158 void CRepositoryBrowser::DoDataExchange(CDataExchange* pDX)
160 CDialog::DoDataExchange(pDX);
161 DDX_Control(pDX, IDC_REPOTREE, m_RepoTree);
162 DDX_Control(pDX, IDC_REPOLIST, m_RepoList);
166 BEGIN_MESSAGE_MAP(CRepositoryBrowser, CResizableStandAloneDialog)
167 ON_NOTIFY(TVN_SELCHANGED, IDC_REPOTREE, &CRepositoryBrowser::OnTvnSelchangedRepoTree)
168 ON_NOTIFY(TVN_ITEMEXPANDING, IDC_REPOTREE, &CRepositoryBrowser::OnTvnItemExpandingRepoTree)
169 ON_WM_CONTEXTMENU()
170 ON_NOTIFY(LVN_COLUMNCLICK, IDC_REPOLIST, &CRepositoryBrowser::OnLvnColumnclickRepoList)
171 ON_NOTIFY(LVN_ITEMCHANGED, IDC_REPOLIST, &CRepositoryBrowser::OnLvnItemchangedRepolist)
172 ON_NOTIFY(NM_DBLCLK, IDC_REPOLIST, &CRepositoryBrowser::OnNMDblclk_RepoList)
173 ON_BN_CLICKED(IDC_BUTTON_REVISION, &CRepositoryBrowser::OnBnClickedButtonRevision)
174 ON_WM_SETCURSOR()
175 ON_WM_DESTROY()
176 ON_WM_MOUSEMOVE()
177 ON_WM_LBUTTONDOWN()
178 ON_WM_LBUTTONUP()
179 END_MESSAGE_MAP()
182 // CRepositoryBrowser message handlers
184 BOOL CRepositoryBrowser::OnInitDialog()
186 CResizableStandAloneDialog::OnInitDialog();
187 CAppUtils::MarkWindowAsUnpinnable(m_hWnd);
189 AddAnchor(IDC_STATIC_REPOURL, TOP_LEFT);
190 AddAnchor(IDC_REPOBROWSER_URL, TOP_LEFT, TOP_RIGHT);
191 AddAnchor(IDC_STATIC_REF, TOP_RIGHT);
192 AddAnchor(IDC_BUTTON_REVISION, TOP_RIGHT);
193 AddAnchor(IDC_REPOTREE, TOP_LEFT, BOTTOM_LEFT);
194 AddAnchor(IDC_REPOLIST, TOP_LEFT, BOTTOM_RIGHT);
195 AddAnchor(IDHELP, BOTTOM_RIGHT);
196 AddAnchor(IDC_INFOLABEL, BOTTOM_LEFT, BOTTOM_RIGHT);
197 AddAnchor(IDOK, BOTTOM_RIGHT);
198 AddAnchor(IDCANCEL, BOTTOM_RIGHT);
200 CRepositoryBrowser::s_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER);
201 if (CRepositoryBrowser::s_bSortLogical)
202 CRepositoryBrowser::s_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_LOCAL_MACHINE);
204 static UINT columnNames[] = { IDS_STATUSLIST_COLFILENAME, IDS_STATUSLIST_COLEXT, IDS_LOG_SIZE };
205 static int columnWidths[] = { 150, 100, 100 };
206 DWORD dwDefaultColumns = (1 << eCol_Name) | (1 << eCol_Extension) | (1 << eCol_FileSize);
207 m_ColumnManager.SetNames(columnNames, _countof(columnNames));
208 m_ColumnManager.ReadSettings(dwDefaultColumns, 0, _T("RepoBrowser"), _countof(columnNames), columnWidths);
209 m_ColumnManager.SetRightAlign(2);
211 // set up the list control
212 // set the extended style of the list control
213 // 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.
214 CRegDWORD regFullRowSelect(_T("Software\\TortoiseGit\\FullRowSelect"), TRUE);
215 DWORD exStyle = LVS_EX_HEADERDRAGDROP | LVS_EX_DOUBLEBUFFER | LVS_EX_INFOTIP | LVS_EX_SUBITEMIMAGES;
216 if (DWORD(regFullRowSelect))
217 exStyle |= LVS_EX_FULLROWSELECT;
218 m_RepoList.SetExtendedStyle(exStyle);
219 m_RepoList.SetImageList(&SYS_IMAGE_LIST(), LVSIL_SMALL);
220 CAppUtils::SetListCtrlBackgroundImage(m_RepoList.GetSafeHwnd(), IDI_REPOBROWSER_BKG);
222 m_RepoTree.SetImageList(&SYS_IMAGE_LIST(), TVSIL_NORMAL);
223 exStyle = TVS_EX_FADEINOUTEXPANDOS | TVS_EX_AUTOHSCROLL | TVS_EX_DOUBLEBUFFER;
224 m_RepoTree.SetExtendedStyle(exStyle, exStyle);
226 m_nExternalOvl = SYS_IMAGE_LIST().AddIcon((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_EXTERNALOVL), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE));
227 m_nExecutableOvl = SYS_IMAGE_LIST().AddIcon((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_EXECUTABLEOVL), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE));
228 m_nSymlinkOvl = SYS_IMAGE_LIST().AddIcon((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_SYMLINKOVL), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE));
229 // set externaloverlay in SYS_IMAGE_LIST() in Refresh method, so that it is updated after every launch of the logdialog
231 SetWindowTheme(m_RepoTree.GetSafeHwnd(), L"Explorer", nullptr);
232 SetWindowTheme(m_RepoList.GetSafeHwnd(), L"Explorer", nullptr);
234 int borderWidth = 0;
235 if (IsAppThemed())
237 HTHEME hTheme = OpenThemeData(m_RepoTree, L"TREEVIEW");
238 GetThemeMetric(hTheme, NULL, TVP_TREEITEM, TREIS_NORMAL, TMT_BORDERSIZE, &borderWidth);
239 CloseThemeData(hTheme);
241 else
242 borderWidth = GetSystemMetrics(SM_CYBORDER);
243 m_RepoTree.SetItemHeight((SHORT)(m_RepoTree.GetItemHeight() + 2 * borderWidth));
245 m_nIconFolder = SYS_IMAGE_LIST().GetDirIconIndex();
246 m_nOpenIconFolder = SYS_IMAGE_LIST().GetDirOpenIconIndex();
248 EnableSaveRestore(L"Reposbrowser");
250 DWORD xPos = CRegDWORD(_T("Software\\TortoiseGit\\TortoiseProc\\ResizableState\\RepobrowserDivider"), 0);
251 if (xPos == 0)
253 RECT rc;
254 GetDlgItem(IDC_REPOTREE)->GetClientRect(&rc);
255 xPos = rc.right - rc.left;
257 HandleDividerMove(CPoint(xPos + 20, 10), false);
259 CString sWindowTitle;
260 GetWindowText(sWindowTitle);
261 CAppUtils::SetWindowTitle(m_hWnd, g_Git.m_CurrentDir, sWindowTitle);
263 m_bHasWC = !GitAdminDir::IsBareRepo(g_Git.m_CurrentDir);
265 Refresh();
267 m_RepoList.SetFocus();
269 return FALSE;
272 void CRepositoryBrowser::OnDestroy()
274 int maxcol = m_ColumnManager.GetColumnCount();
275 for (int col = 0; col < maxcol; ++col)
276 if (m_ColumnManager.IsVisible(col))
277 m_ColumnManager.ColumnResized(col);
278 m_ColumnManager.WriteSettings();
280 CResizableStandAloneDialog::OnDestroy();
283 void CRepositoryBrowser::OnOK()
285 if (GetFocus() == &m_RepoList && (GetKeyState(VK_MENU) & 0x8000) == 0)
287 // list control has focus: 'enter' the folder
288 if (m_RepoList.GetSelectedCount() != 1)
289 return;
291 POSITION pos = m_RepoList.GetFirstSelectedItemPosition();
292 if (pos)
294 CShadowFilesTree *item = (CShadowFilesTree *)m_RepoList.GetItemData(m_RepoList.GetNextSelectedItem(pos));
295 if (item->m_bFolder)
297 FillListCtrlForShadowTree(item);
298 m_RepoTree.SelectItem(item->m_hTree);
300 else
301 OpenFile(item->GetFullName(), OPEN, item->m_bSubmodule, item->m_hash);
303 return;
306 SaveDividerPosition();
307 CResizableStandAloneDialog::OnOK();
310 void CRepositoryBrowser::OnCancel()
312 SaveDividerPosition();
313 CResizableStandAloneDialog::OnCancel();
316 void CRepositoryBrowser::OnNMDblclk_RepoList(NMHDR *pNMHDR, LRESULT *pResult)
318 *pResult = 0;
320 LPNMITEMACTIVATE pNmItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
321 if (pNmItemActivate->iItem < 0)
322 return;
324 CShadowFilesTree * pItem = (CShadowFilesTree *)m_RepoList.GetItemData(pNmItemActivate->iItem);
325 if (!pItem )
326 return;
328 if (!pItem->m_bFolder)
330 OpenFile(pItem->GetFullName(), OPEN, pItem->m_bSubmodule, pItem->m_hash);
331 return;
333 else
335 FillListCtrlForShadowTree(pItem);
336 m_RepoTree.SelectItem(pItem->m_hTree);
340 void CRepositoryBrowser::Refresh()
342 BeginWaitCursor();
343 if (m_nExternalOvl >= 0)
344 SYS_IMAGE_LIST().SetOverlayImage(m_nExternalOvl, OVERLAY_EXTERNAL);
345 if (m_nExecutableOvl >= 0)
346 SYS_IMAGE_LIST().SetOverlayImage(m_nExecutableOvl, OVERLAY_EXECUTABLE);
347 if (m_nSymlinkOvl >= 0)
348 SYS_IMAGE_LIST().SetOverlayImage(m_nSymlinkOvl, OVERLAY_SYMLINK);
350 m_RepoTree.DeleteAllItems();
351 m_RepoList.DeleteAllItems();
352 m_TreeRoot.m_ShadowTree.clear();
353 m_TreeRoot.m_sName.Empty();
354 m_TreeRoot.m_bFolder = true;
356 TVINSERTSTRUCT tvinsert = {0};
357 tvinsert.hParent = TVI_ROOT;
358 tvinsert.hInsertAfter = TVI_ROOT;
359 tvinsert.itemex.mask = TVIF_DI_SETITEM | TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_STATE;
360 tvinsert.itemex.pszText = L"/";
361 tvinsert.itemex.lParam = (LPARAM)&m_TreeRoot;
362 tvinsert.itemex.iImage = m_nIconFolder;
363 tvinsert.itemex.iSelectedImage = m_nOpenIconFolder;
364 m_TreeRoot.m_hTree= m_RepoTree.InsertItem(&tvinsert);
366 ReadTree(&m_TreeRoot);
367 m_RepoTree.Expand(m_TreeRoot.m_hTree, TVE_EXPAND);
368 FillListCtrlForShadowTree(&m_TreeRoot);
369 m_RepoTree.SelectItem(m_TreeRoot.m_hTree);
370 EndWaitCursor();
373 int CRepositoryBrowser::ReadTreeRecursive(git_repository &repo, const git_tree * tree, CShadowFilesTree * treeroot)
375 size_t count = git_tree_entrycount(tree);
376 bool hasSubfolders = false;
378 for (size_t i = 0; i < count; ++i)
380 const git_tree_entry *entry = git_tree_entry_byindex(tree, i);
381 if (!entry)
382 continue;
384 const int mode = git_tree_entry_filemode(entry);
386 CString base = CUnicodeUtils::GetUnicode(git_tree_entry_name(entry), CP_UTF8);
388 const git_oid *oid = git_tree_entry_id(entry);
389 CShadowFilesTree * pNextTree = &treeroot->m_ShadowTree[base];
390 pNextTree->m_sName = base;
391 pNextTree->m_pParent = treeroot;
392 pNextTree->m_hash = CGitHash((char *)oid->id);
394 if (mode == GIT_FILEMODE_COMMIT)
395 pNextTree->m_bSubmodule = true;
396 else if (mode & S_IFDIR)
398 hasSubfolders = true;
399 pNextTree->m_bFolder = true;
400 pNextTree->m_bLoaded = false;
402 TVINSERTSTRUCT tvinsert = {0};
403 tvinsert.hParent = treeroot->m_hTree;
404 tvinsert.hInsertAfter = TVI_SORT;
405 tvinsert.itemex.mask = TVIF_DI_SETITEM | TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_STATE | TVIF_CHILDREN;
406 tvinsert.itemex.pszText = base.GetBuffer(base.GetLength());
407 tvinsert.itemex.cChildren = 1;
408 tvinsert.itemex.lParam = (LPARAM)pNextTree;
409 tvinsert.itemex.iImage = m_nIconFolder;
410 tvinsert.itemex.iSelectedImage = m_nOpenIconFolder;
411 pNextTree->m_hTree = m_RepoTree.InsertItem(&tvinsert);
412 base.ReleaseBuffer();
414 else
416 if (mode == GIT_FILEMODE_BLOB_EXECUTABLE)
417 pNextTree->m_bExecutable = true;
418 if (mode == GIT_FILEMODE_LINK)
419 pNextTree->m_bSymlink = true;
420 CAutoBlob blob;
421 git_blob_lookup(blob.GetPointer(), &repo, oid);
422 if (!blob)
423 continue;
425 pNextTree->m_iSize = git_blob_rawsize(blob);
429 if (!hasSubfolders)
431 TVITEM tvitem = { 0 };
432 tvitem.hItem = treeroot->m_hTree;
433 tvitem.mask = TVIF_CHILDREN;
434 tvitem.cChildren = 0;
435 m_RepoTree.SetItem(&tvitem);
438 return 0;
441 int CRepositoryBrowser::ReadTree(CShadowFilesTree * treeroot, const CString& root)
443 CWaitCursor wait;
444 CAutoRepository repository(g_Git.GetGitRepository());
445 if (!repository)
447 MessageBox(CGit::GetLibGit2LastErr(_T("Could not open repository.")), _T("TortoiseGit"), MB_ICONERROR);
448 return -1;
451 if (m_sRevision == _T("HEAD"))
453 int ret = git_repository_head_unborn(repository);
454 if (ret == 1) // is orphan
455 return ret;
456 else if (ret != 0)
458 MessageBox(g_Git.GetLibGit2LastErr(_T("Could not check HEAD.")), _T("TortoiseGit"), MB_ICONERROR);
459 return ret;
463 CGitHash hash;
464 if (CGit::GetHash(repository, hash, m_sRevision))
466 MessageBox(CGit::GetLibGit2LastErr(_T("Could not get hash of ") + m_sRevision + _T(".")), _T("TortoiseGit"), MB_ICONERROR);
467 return -1;
470 CAutoCommit commit;
471 if (git_commit_lookup(commit.GetPointer(), repository, (git_oid *)hash.m_hash))
473 MessageBox(CGit::GetLibGit2LastErr(_T("Could not lookup commit.")), _T("TortoiseGit"), MB_ICONERROR);
474 return -1;
477 CAutoTree tree;
478 if (git_commit_tree(tree.GetPointer(), commit))
480 MessageBox(CGit::GetLibGit2LastErr(_T("Could not get tree of commit.")), _T("TortoiseGit"), MB_ICONERROR);
481 return -1;
484 if (!root.IsEmpty())
486 CAutoTreeEntry treeEntry;
487 if (git_tree_entry_bypath(treeEntry.GetPointer(), tree, CUnicodeUtils::GetUTF8(root)))
489 MessageBox(CGit::GetLibGit2LastErr(_T("Could not lookup path.")), _T("TortoiseGit"), MB_ICONERROR);
490 return -1;
492 if (git_tree_entry_type(treeEntry) != GIT_OBJ_TREE)
494 MessageBox(CGit::GetLibGit2LastErr(_T("Could not lookup path.")), _T("TortoiseGit"), MB_ICONERROR);
495 return -1;
498 CAutoObject object;
499 if (git_tree_entry_to_object(object.GetPointer(), repository, treeEntry))
501 MessageBox(CGit::GetLibGit2LastErr(_T("Could not lookup path.")), _T("TortoiseGit"), MB_ICONERROR);
502 return -1;
505 tree = (git_tree*)object.Detach();
508 treeroot->m_hash = CGitHash((char *)git_tree_id(tree)->id);
509 ReadTreeRecursive(*repository, tree, treeroot);
511 // try to resolve hash to a branch name
512 if (m_sRevision == hash.ToString())
514 MAP_HASH_NAME map;
515 if (CGit::GetMapHashToFriendName(repository, map))
516 MessageBox(g_Git.GetLibGit2LastErr(_T("Could not get all refs.")), _T("TortoiseGit"), MB_ICONERROR);
517 if (!map[hash].empty())
518 m_sRevision = map[hash].at(0);
520 this->GetDlgItem(IDC_BUTTON_REVISION)->SetWindowText(m_sRevision);
522 return 0;
525 void CRepositoryBrowser::OnTvnSelchangedRepoTree(NMHDR *pNMHDR, LRESULT *pResult)
527 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
528 *pResult = 0;
530 FillListCtrlForTreeNode(pNMTreeView->itemNew.hItem);
533 void CRepositoryBrowser::OnTvnItemExpandingRepoTree(NMHDR *pNMHDR, LRESULT *pResult)
535 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
536 *pResult = 0;
538 CShadowFilesTree* pTree = (CShadowFilesTree*)(m_RepoTree.GetItemData(pNMTreeView->itemNew.hItem));
539 if (!pTree)
541 ASSERT(FALSE);
542 return;
545 if (!pTree->m_bLoaded)
547 pTree->m_bLoaded = true;
548 ReadTree(pTree, pTree->GetFullName());
552 void CRepositoryBrowser::FillListCtrlForTreeNode(HTREEITEM treeNode)
554 m_RepoList.DeleteAllItems();
556 CShadowFilesTree* pTree = (CShadowFilesTree*)(m_RepoTree.GetItemData(treeNode));
557 if (!pTree)
559 ASSERT(FALSE);
560 return;
563 CString url = _T("/") + pTree->GetFullName();
564 GetDlgItem(IDC_REPOBROWSER_URL)->SetWindowText(url);
566 if (!pTree->m_bLoaded)
568 pTree->m_bLoaded = true;
569 ReadTree(pTree, pTree->GetFullName());
572 FillListCtrlForShadowTree(pTree);
575 void CRepositoryBrowser::FillListCtrlForShadowTree(CShadowFilesTree* pTree)
577 for (auto itShadowTree = pTree->m_ShadowTree.cbegin(); itShadowTree != pTree->m_ShadowTree.cend(); ++itShadowTree)
579 int icon = m_nIconFolder;
580 if (!(*itShadowTree).second.m_bFolder && !(*itShadowTree).second.m_bSubmodule)
582 icon = SYS_IMAGE_LIST().GetFileIconIndex((*itShadowTree).second.m_sName);
585 int indexItem = m_RepoList.InsertItem(m_RepoList.GetItemCount(), (*itShadowTree).second.m_sName, icon);
587 if ((*itShadowTree).second.m_bSubmodule)
589 m_RepoList.SetItemState(indexItem, INDEXTOOVERLAYMASK(OVERLAY_EXTERNAL), LVIS_OVERLAYMASK);
591 if ((*itShadowTree).second.m_bExecutable)
592 m_RepoList.SetItemState(indexItem, INDEXTOOVERLAYMASK(OVERLAY_EXECUTABLE), LVIS_OVERLAYMASK);
593 if ((*itShadowTree).second.m_bSymlink)
594 m_RepoList.SetItemState(indexItem, INDEXTOOVERLAYMASK(OVERLAY_SYMLINK), LVIS_OVERLAYMASK);
595 m_RepoList.SetItemData(indexItem, (DWORD_PTR)&(*itShadowTree).second);
596 if (!(*itShadowTree).second.m_bFolder && !(*itShadowTree).second.m_bSubmodule)
598 CString temp;
600 temp = CPathUtils::GetFileExtFromPath((*itShadowTree).second.m_sName);
601 m_RepoList.SetItemText(indexItem, eCol_Extension, temp);
603 StrFormatByteSize64((*itShadowTree).second.m_iSize, temp.GetBuffer(20), 20);
604 temp.ReleaseBuffer();
605 m_RepoList.SetItemText(indexItem, eCol_FileSize, temp);
609 CRepoListCompareFunc compareFunc(&m_RepoList, m_currSortCol, m_currSortDesc);
610 m_RepoList.SortItemsEx(&CRepoListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
612 SetSortArrowA(&m_RepoList, m_currSortCol, !m_currSortDesc);
614 UpdateInfoLabel();
617 void CRepositoryBrowser::UpdateInfoLabel()
619 CString temp;
620 POSITION pos = m_RepoList.GetFirstSelectedItemPosition();
621 if (pos)
623 if (m_RepoList.GetSelectedCount() > 1)
625 temp.FormatMessage(IDS_REPOBROWSE_INFOMULTI, m_RepoList.GetSelectedCount());
627 else
629 int index = m_RepoList.GetNextSelectedItem(pos);
630 CShadowFilesTree *item = (CShadowFilesTree *)m_RepoList.GetItemData(index);
631 if (item->m_bSubmodule)
632 temp.FormatMessage(IDS_REPOBROWSE_INFOEXT, (LPCTSTR)m_RepoList.GetItemText(index, eCol_Name), item->m_hash.ToString());
633 else if (item->m_bFolder)
634 temp.FormatMessage(IDS_REPOBROWSE_INFODIR, (LPCTSTR)m_RepoList.GetItemText(index, eCol_Name));
635 else
636 temp.FormatMessage(IDS_REPOBROWSE_INFOFILE, (LPCTSTR)m_RepoList.GetItemText(index, eCol_Name), (LPCTSTR)m_RepoList.GetItemText(index, eCol_FileSize));
639 else
641 HTREEITEM hTreeItem = m_RepoTree.GetSelectedItem();
642 if (hTreeItem != nullptr)
644 CShadowFilesTree* pTree = (CShadowFilesTree*)m_RepoTree.GetItemData(hTreeItem);
645 if (pTree != nullptr)
647 size_t files = 0, submodules = 0;
648 for (auto itShadowTree = pTree->m_ShadowTree.cbegin(); itShadowTree != pTree->m_ShadowTree.cend(); ++itShadowTree)
650 if (!(*itShadowTree).second.m_bFolder && !(*itShadowTree).second.m_bSubmodule)
651 ++files;
652 if ((*itShadowTree).second.m_bSubmodule)
653 ++submodules;
655 temp.FormatMessage(IDS_REPOBROWSE_INFO, (LPCTSTR)pTree->m_sName, files, submodules, pTree->m_ShadowTree.size() - files - submodules, pTree->m_ShadowTree.size());
659 SetDlgItemText(IDC_INFOLABEL, temp);
662 void CRepositoryBrowser::OnLvnItemchangedRepolist(NMHDR * /* pNMHDR */, LRESULT *pResult)
664 *pResult = 0;
665 UpdateInfoLabel();
668 void CRepositoryBrowser::OnContextMenu(CWnd* pWndFrom, CPoint point)
670 if (pWndFrom == &m_RepoList)
672 CRect headerPosition;
673 m_RepoList.GetHeaderCtrl()->GetWindowRect(headerPosition);
674 if (!headerPosition.PtInRect(point))
675 OnContextMenu_RepoList(point);
677 else if (pWndFrom == &m_RepoTree)
678 OnContextMenu_RepoTree(point);
681 void CRepositoryBrowser::OnContextMenu_RepoTree(CPoint point)
683 CPoint clientPoint = point;
684 m_RepoTree.ScreenToClient(&clientPoint);
686 HTREEITEM hTreeItem = m_RepoTree.HitTest(clientPoint);
687 if (!hTreeItem)
688 return;
690 TShadowFilesTreeList selectedLeafs;
691 selectedLeafs.push_back((CShadowFilesTree *)m_RepoTree.GetItemData(hTreeItem));
693 ShowContextMenu(point, selectedLeafs, ONLY_FOLDERS);
696 void CRepositoryBrowser::OnContextMenu_RepoList(CPoint point)
698 TShadowFilesTreeList selectedLeafs;
699 selectedLeafs.reserve(m_RepoList.GetSelectedCount());
701 bool folderSelected = false;
702 bool filesSelected = false;
703 bool submodulesSelected = false;
705 POSITION pos = m_RepoList.GetFirstSelectedItemPosition();
706 while (pos)
708 CShadowFilesTree * item = (CShadowFilesTree *)m_RepoList.GetItemData(m_RepoList.GetNextSelectedItem(pos));
709 if (item->m_bSubmodule)
710 submodulesSelected = true;
711 if (item->m_bFolder)
712 folderSelected = true;
713 else
714 filesSelected = true;
715 selectedLeafs.push_back(item);
718 eSelectionType selType = ONLY_FILES;
719 if (folderSelected && filesSelected)
720 selType = MIXED_FOLDERS_FILES;
721 else if (folderSelected)
722 selType = ONLY_FOLDERS;
723 else if (submodulesSelected)
724 selType = ONLY_FILESSUBMODULES;
725 ShowContextMenu(point, selectedLeafs, selType);
728 void CRepositoryBrowser::ShowContextMenu(CPoint point, TShadowFilesTreeList &selectedLeafs, eSelectionType selType)
730 CIconMenu popupMenu;
731 popupMenu.CreatePopupMenu();
733 bool bAddSeparator = false;
735 if (selectedLeafs.size() == 1)
737 popupMenu.AppendMenuIcon(eCmd_Open, IDS_REPOBROWSE_OPEN, IDI_OPEN);
738 popupMenu.SetDefaultItem(eCmd_Open, FALSE);
739 if (selType == ONLY_FILES || selType == ONLY_FILESSUBMODULES)
741 popupMenu.AppendMenuIcon(eCmd_OpenWith, IDS_LOG_POPUP_OPENWITH, IDI_OPEN);
742 popupMenu.AppendMenuIcon(eCmd_OpenWithAlternativeEditor, IDS_LOG_POPUP_VIEWREV);
745 popupMenu.AppendMenu(MF_SEPARATOR);
747 if (m_bHasWC && (selType == ONLY_FILES || selType == ONLY_FILESSUBMODULES))
749 popupMenu.AppendMenuIcon(eCmd_CompareWC, IDS_LOG_POPUP_COMPARE, IDI_DIFF);
750 bAddSeparator = true;
753 if (bAddSeparator)
754 popupMenu.AppendMenu(MF_SEPARATOR);
755 bAddSeparator = false;
757 CString temp;
758 temp.LoadString(IDS_MENULOG);
759 popupMenu.AppendMenuIcon(eCmd_ViewLog, temp, IDI_LOG);
760 if (selectedLeafs[0]->m_bSubmodule)
762 temp.LoadString(IDS_MENULOGSUBMODULE);
763 popupMenu.AppendMenuIcon(eCmd_ViewLogSubmodule, temp, IDI_LOG);
766 if (selType == ONLY_FILES)
768 if (m_bHasWC)
769 popupMenu.AppendMenuIcon(eCmd_Blame, IDS_LOG_POPUP_BLAME, IDI_BLAME);
771 popupMenu.AppendMenu(MF_SEPARATOR);
772 temp.LoadString(IDS_LOG_POPUP_SAVE);
773 popupMenu.AppendMenuIcon(eCmd_SaveAs, temp, IDI_SAVEAS);
776 bAddSeparator = true;
779 if (!selectedLeafs.empty() && selType == ONLY_FILES && m_bHasWC)
781 popupMenu.AppendMenuIcon(eCmd_Revert, IDS_LOG_POPUP_REVERTTOREV, IDI_REVERT);
782 bAddSeparator = true;
785 if (bAddSeparator)
786 popupMenu.AppendMenu(MF_SEPARATOR);
787 bAddSeparator = false;
789 if (selectedLeafs.size() == 1 && selType == ONLY_FILES)
791 popupMenu.AppendMenuIcon(eCmd_PrepareDiff, IDS_PREPAREDIFF, IDI_DIFF);
792 if (!m_sMarkForDiffFilename.IsEmpty())
794 CString diffWith;
795 if (selectedLeafs.at(0)->GetFullName() == m_sMarkForDiffFilename)
796 diffWith = m_sMarkForDiffVersion;
797 else
799 PathCompactPathEx(diffWith.GetBuffer(40), m_sMarkForDiffFilename, 39, 0);
800 diffWith.ReleaseBuffer();
801 diffWith += _T(":") + m_sMarkForDiffVersion.ToString().Left(g_Git.GetShortHASHLength());
803 CString menuEntry;
804 menuEntry.Format(IDS_MENUDIFFNOW, (LPCTSTR)diffWith);
805 popupMenu.AppendMenuIcon(eCmd_PrepareDiff_Compare, menuEntry, IDI_DIFF);
807 popupMenu.AppendMenu(MF_SEPARATOR);
810 if (!selectedLeafs.empty())
812 popupMenu.AppendMenuIcon(eCmd_CopyPath, IDS_STATUSLIST_CONTEXT_COPY, IDI_COPYCLIP);
813 popupMenu.AppendMenuIcon(eCmd_CopyHash, IDS_COPY_COMMIT_HASH, IDI_COPYCLIP);
816 eCmd cmd = (eCmd)popupMenu.TrackPopupMenuEx(TPM_LEFTALIGN|TPM_RETURNCMD, point.x, point.y, this, 0);
817 switch(cmd)
819 case eCmd_ViewLog:
820 case eCmd_ViewLogSubmodule:
822 CString sCmd;
823 sCmd.Format(_T("/command:log /path:\"%s\""), (LPCTSTR)g_Git.CombinePath(selectedLeafs.at(0)->GetFullName()));
824 if (cmd == eCmd_ViewLog && selectedLeafs.at(0)->m_bSubmodule)
825 sCmd += _T(" /submodule");
826 CAppUtils::RunTortoiseGitProc(sCmd);
828 break;
829 case eCmd_Blame:
831 CAppUtils::LaunchTortoiseBlame(g_Git.CombinePath(selectedLeafs.at(0)->GetFullName()), m_sRevision);
833 break;
834 case eCmd_Open:
835 if (!selectedLeafs.at(0)->m_bSubmodule && selectedLeafs.at(0)->m_bFolder)
837 FillListCtrlForTreeNode(selectedLeafs.at(0)->m_hTree);
838 m_RepoTree.SelectItem(selectedLeafs.at(0)->m_hTree);
839 return;
841 OpenFile(selectedLeafs.at(0)->GetFullName(), OPEN, selectedLeafs.at(0)->m_bSubmodule, selectedLeafs.at(0)->m_hash);
842 break;
843 case eCmd_OpenWith:
844 OpenFile(selectedLeafs.at(0)->GetFullName(), OPEN_WITH, selectedLeafs.at(0)->m_bSubmodule, selectedLeafs.at(0)->m_hash);
845 break;
846 case eCmd_OpenWithAlternativeEditor:
847 OpenFile(selectedLeafs.at(0)->GetFullName(), ALTERNATIVEEDITOR, selectedLeafs.at(0)->m_bSubmodule, selectedLeafs.at(0)->m_hash);
848 break;
849 case eCmd_CompareWC:
851 CTGitPath file(selectedLeafs.at(0)->GetFullName());
852 CGitDiff::Diff(&file, &file, GIT_REV_ZERO, m_sRevision);
854 break;
855 case eCmd_Revert:
857 int count = 0;
858 for (auto itShadowTree = selectedLeafs.cbegin(); itShadowTree != selectedLeafs.cend(); ++itShadowTree)
860 if (RevertItemToVersion((*itShadowTree)->GetFullName()))
861 ++count;
862 else
863 break;
865 CString msg;
866 msg.Format(IDS_STATUSLIST_FILESREVERTED, count, (LPCTSTR)m_sRevision);
867 MessageBox(msg, _T("TortoiseGit"), MB_OK);
869 break;
870 case eCmd_SaveAs:
871 FileSaveAs(selectedLeafs.at(0)->GetFullName());
872 break;
873 case eCmd_CopyPath:
875 CString sClipboard;
876 for (auto itShadowTree = selectedLeafs.cbegin(); itShadowTree != selectedLeafs.cend(); ++itShadowTree)
878 sClipboard += (*itShadowTree)->m_sName + _T("\r\n");
880 CStringUtils::WriteAsciiStringToClipboard(sClipboard);
882 break;
883 case eCmd_CopyHash:
885 CopyHashToClipboard(selectedLeafs);
887 break;
888 case eCmd_PrepareDiff:
889 m_sMarkForDiffFilename = selectedLeafs.at(0)->GetFullName();
890 if (g_Git.GetHash(m_sMarkForDiffVersion, m_sRevision))
892 m_sMarkForDiffFilename.Empty();
893 MessageBox(g_Git.GetGitLastErr(_T("Could not get SHA-1 for ") + m_sRevision), _T("TortoiseGit"), MB_ICONERROR);
895 break;
896 case eCmd_PrepareDiff_Compare:
898 CTGitPath savedFile(m_sMarkForDiffFilename);
899 CTGitPath selectedFile(selectedLeafs.at(0)->GetFullName());
900 CGitHash currentHash;
901 if (g_Git.GetHash(currentHash, m_sRevision))
903 MessageBox(g_Git.GetGitLastErr(_T("Could not get SHA-1 for ") + m_sRevision), _T("TortoiseGit"), MB_ICONERROR);
904 return;
906 CGitDiff::Diff(&selectedFile, &savedFile, currentHash, m_sMarkForDiffVersion);
908 break;
912 BOOL CRepositoryBrowser::PreTranslateMessage(MSG* pMsg)
914 if (pMsg->message == WM_KEYDOWN)
916 switch (pMsg->wParam)
918 case VK_F5:
920 Refresh();
922 break;
926 return CResizableStandAloneDialog::PreTranslateMessage(pMsg);
929 void CRepositoryBrowser::OnLvnColumnclickRepoList(NMHDR *pNMHDR, LRESULT *pResult)
931 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
932 if (pResult)
933 *pResult = 0;
935 if (m_currSortCol == pNMLV->iSubItem)
936 m_currSortDesc = !m_currSortDesc;
937 else
939 m_currSortCol = pNMLV->iSubItem;
940 m_currSortDesc = false;
943 CRepoListCompareFunc compareFunc(&m_RepoList, m_currSortCol, m_currSortDesc);
944 m_RepoList.SortItemsEx(&CRepoListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
946 SetSortArrowA(&m_RepoList, m_currSortCol, !m_currSortDesc);
949 void CRepositoryBrowser::OnBnClickedButtonRevision()
951 // use the git log to allow selection of a version
952 CLogDlg dlg;
953 dlg.SetParams(CTGitPath(), CTGitPath(), m_sRevision, m_sRevision, 0);
954 // tell the dialog to use mode for selecting revisions
955 dlg.SetSelect(true);
956 dlg.ShowWorkingTreeChanges(false);
957 // only one revision must be selected however
958 dlg.SingleSelection(true);
959 if (dlg.DoModal() == IDOK)
961 m_sRevision = dlg.GetSelectedHash().at(0).ToString();
962 Refresh();
966 void CRepositoryBrowser::SaveDividerPosition()
968 RECT rc;
969 GetDlgItem(IDC_REPOTREE)->GetClientRect(&rc);
970 CRegDWORD xPos(_T("Software\\TortoiseGit\\TortoiseProc\\ResizableState\\RepobrowserDivider"));
971 xPos = rc.right - rc.left;
974 void CRepositoryBrowser::HandleDividerMove(CPoint point, bool bDraw)
976 RECT rect, tree, list, treelist, treelistclient;
978 // create an union of the tree and list control rectangle
979 GetDlgItem(IDC_REPOLIST)->GetWindowRect(&list);
980 GetDlgItem(IDC_REPOTREE)->GetWindowRect(&tree);
981 UnionRect(&treelist, &tree, &list);
982 treelistclient = treelist;
983 ScreenToClient(&treelistclient);
985 ClientToScreen(&point);
986 GetClientRect(&rect);
987 ClientToScreen(&rect);
989 CPoint point2 = point;
990 if (point2.x < treelist.left + REPOBROWSER_CTRL_MIN_WIDTH)
991 point2.x = treelist.left + REPOBROWSER_CTRL_MIN_WIDTH;
992 if (point2.x > treelist.right - REPOBROWSER_CTRL_MIN_WIDTH)
993 point2.x = treelist.right - REPOBROWSER_CTRL_MIN_WIDTH;
995 point.x -= rect.left;
996 point.y -= treelist.top;
998 OffsetRect(&treelist, -treelist.left, -treelist.top);
1000 if (point.x < treelist.left+REPOBROWSER_CTRL_MIN_WIDTH)
1001 point.x = treelist.left+REPOBROWSER_CTRL_MIN_WIDTH;
1002 if (point.x > treelist.right-REPOBROWSER_CTRL_MIN_WIDTH)
1003 point.x = treelist.right-REPOBROWSER_CTRL_MIN_WIDTH;
1005 if (bDraw)
1007 CDC * pDC = GetDC();
1008 DrawXorBar(pDC, oldx + 2, treelistclient.top, 4, treelistclient.bottom - treelistclient.top - 2);
1009 ReleaseDC(pDC);
1012 oldx = point.x;
1013 oldy = point.y;
1015 //position the child controls
1016 GetDlgItem(IDC_REPOTREE)->GetWindowRect(&treelist);
1017 treelist.right = point2.x - 2;
1018 ScreenToClient(&treelist);
1019 RemoveAnchor(IDC_REPOTREE);
1020 GetDlgItem(IDC_REPOTREE)->MoveWindow(&treelist);
1021 GetDlgItem(IDC_REPOLIST)->GetWindowRect(&treelist);
1022 treelist.left = point2.x + 2;
1023 ScreenToClient(&treelist);
1024 RemoveAnchor(IDC_REPOLIST);
1025 GetDlgItem(IDC_REPOLIST)->MoveWindow(&treelist);
1027 AddAnchor(IDC_REPOTREE, TOP_LEFT, BOTTOM_LEFT);
1028 AddAnchor(IDC_REPOLIST, TOP_LEFT, BOTTOM_RIGHT);
1031 void CRepositoryBrowser::OnMouseMove(UINT nFlags, CPoint point)
1033 if (bDragMode == FALSE)
1034 return;
1036 RECT rect, tree, list, treelist, treelistclient;
1037 // create an union of the tree and list control rectangle
1038 GetDlgItem(IDC_REPOLIST)->GetWindowRect(&list);
1039 GetDlgItem(IDC_REPOTREE)->GetWindowRect(&tree);
1040 UnionRect(&treelist, &tree, &list);
1041 treelistclient = treelist;
1042 ScreenToClient(&treelistclient);
1044 //convert the mouse coordinates relative to the top-left of
1045 //the window
1046 ClientToScreen(&point);
1047 GetClientRect(&rect);
1048 ClientToScreen(&rect);
1049 point.x -= rect.left;
1050 point.y -= treelist.top;
1052 //same for the window coordinates - make them relative to 0,0
1053 OffsetRect(&treelist, -treelist.left, -treelist.top);
1055 if (point.x < treelist.left + REPOBROWSER_CTRL_MIN_WIDTH)
1056 point.x = treelist.left + REPOBROWSER_CTRL_MIN_WIDTH;
1057 if (point.x > treelist.right - REPOBROWSER_CTRL_MIN_WIDTH)
1058 point.x = treelist.right - REPOBROWSER_CTRL_MIN_WIDTH;
1060 if ((nFlags & MK_LBUTTON) && (point.x != oldx))
1062 CDC * pDC = GetDC();
1064 if (pDC)
1066 DrawXorBar(pDC, oldx + 2, treelistclient.top, 4, treelistclient.bottom - treelistclient.top - 2);
1067 DrawXorBar(pDC, point.x + 2, treelistclient.top, 4, treelistclient.bottom - treelistclient.top - 2);
1069 ReleaseDC(pDC);
1072 oldx = point.x;
1073 oldy = point.y;
1076 CStandAloneDialogTmpl<CResizableDialog>::OnMouseMove(nFlags, point);
1079 void CRepositoryBrowser::OnLButtonDown(UINT nFlags, CPoint point)
1081 RECT rect, tree, list, treelist, treelistclient;
1083 // create an union of the tree and list control rectangle
1084 GetDlgItem(IDC_REPOLIST)->GetWindowRect(&list);
1085 GetDlgItem(IDC_REPOTREE)->GetWindowRect(&tree);
1086 UnionRect(&treelist, &tree, &list);
1087 treelistclient = treelist;
1088 ScreenToClient(&treelistclient);
1090 //convert the mouse coordinates relative to the top-left of
1091 //the window
1092 ClientToScreen(&point);
1093 GetClientRect(&rect);
1094 ClientToScreen(&rect);
1095 point.x -= rect.left;
1096 point.y -= treelist.top;
1098 //same for the window coordinates - make them relative to 0,0
1099 OffsetRect(&treelist, -treelist.left, -treelist.top);
1101 if (point.x < treelist.left + REPOBROWSER_CTRL_MIN_WIDTH)
1102 return CStandAloneDialogTmpl < CResizableDialog>::OnLButtonDown(nFlags, point);
1103 if (point.x > treelist.right - 3)
1104 return CStandAloneDialogTmpl < CResizableDialog>::OnLButtonDown(nFlags, point);
1105 if (point.x > treelist.right - REPOBROWSER_CTRL_MIN_WIDTH)
1106 point.x = treelist.right - REPOBROWSER_CTRL_MIN_WIDTH;
1108 if ((point.y < treelist.top + 3) || (point.y > treelist.bottom - 3))
1109 return CStandAloneDialogTmpl<CResizableDialog>::OnLButtonDown(nFlags, point);
1111 bDragMode = true;
1113 SetCapture();
1115 CDC * pDC = GetDC();
1116 DrawXorBar(pDC, point.x + 2, treelistclient.top, 4, treelistclient.bottom - treelistclient.top - 2);
1117 ReleaseDC(pDC);
1119 oldx = point.x;
1120 oldy = point.y;
1122 CStandAloneDialogTmpl<CResizableDialog>::OnLButtonDown(nFlags, point);
1125 void CRepositoryBrowser::OnLButtonUp(UINT nFlags, CPoint point)
1127 if (bDragMode == FALSE)
1128 return;
1130 HandleDividerMove(point, true);
1132 bDragMode = false;
1133 ReleaseCapture();
1135 CStandAloneDialogTmpl<CResizableDialog>::OnLButtonUp(nFlags, point);
1138 void CRepositoryBrowser::OnCaptureChanged(CWnd *pWnd)
1140 bDragMode = false;
1142 __super::OnCaptureChanged(pWnd);
1145 void CRepositoryBrowser::DrawXorBar(CDC * pDC, int x1, int y1, int width, int height)
1147 static WORD _dotPatternBmp[8] =
1149 0x0055, 0x00aa, 0x0055, 0x00aa,
1150 0x0055, 0x00aa, 0x0055, 0x00aa
1153 HBITMAP hbm;
1154 HBRUSH hbr, hbrushOld;
1156 hbm = CreateBitmap(8, 8, 1, 1, _dotPatternBmp);
1157 hbr = CreatePatternBrush(hbm);
1159 pDC->SetBrushOrg(x1, y1);
1160 hbrushOld = (HBRUSH)pDC->SelectObject(hbr);
1162 PatBlt(pDC->GetSafeHdc(), x1, y1, width, height, PATINVERT);
1164 pDC->SelectObject(hbrushOld);
1166 DeleteObject(hbr);
1167 DeleteObject(hbm);
1170 BOOL CRepositoryBrowser::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
1172 if (pWnd == this)
1174 RECT rect;
1175 POINT pt;
1176 GetClientRect(&rect);
1177 GetCursorPos(&pt);
1178 ScreenToClient(&pt);
1179 if (PtInRect(&rect, pt))
1181 ClientToScreen(&pt);
1182 // are we right of the tree control?
1183 GetDlgItem(IDC_REPOTREE)->GetWindowRect(&rect);
1184 if ((pt.x > rect.right) && (pt.y >= rect.top + 3) && (pt.y <= rect.bottom - 3))
1186 // but left of the list control?
1187 GetDlgItem(IDC_REPOLIST)->GetWindowRect(&rect);
1188 if (pt.x < rect.left)
1190 HCURSOR hCur = LoadCursor(nullptr, IDC_SIZEWE);
1191 SetCursor(hCur);
1192 return TRUE;
1197 return CStandAloneDialogTmpl<CResizableDialog>::OnSetCursor(pWnd, nHitTest, message);
1200 void CRepositoryBrowser::FileSaveAs(const CString path)
1202 CTGitPath gitPath(path);
1204 CGitHash hash;
1205 if (g_Git.GetHash(hash, m_sRevision))
1207 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of ") + m_sRevision + _T(".")), _T("TortoiseGit"), MB_ICONERROR);
1208 return;
1211 CString filename;
1212 filename.Format(_T("%s-%s%s"), (LPCTSTR)gitPath.GetBaseFilename(), (LPCTSTR)hash.ToString().Left(g_Git.GetShortHASHLength()), (LPCTSTR)gitPath.GetFileExtension());
1213 CFileDialog dlg(FALSE, nullptr, filename, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, nullptr);
1215 CString currentpath(g_Git.CombinePath(gitPath.GetContainingDirectory()));
1216 dlg.m_ofn.lpstrInitialDir = currentpath;
1218 CString cmd, out;
1219 INT_PTR ret = dlg.DoModal();
1220 SetCurrentDirectory(g_Git.m_CurrentDir);
1221 if (ret == IDOK)
1223 filename = dlg.GetPathName();
1224 if (g_Git.GetOneFile(m_sRevision, gitPath, filename))
1226 out.Format(IDS_STATUSLIST_CHECKOUTFILEFAILED, (LPCTSTR)gitPath.GetGitPathString(), (LPCTSTR)m_sRevision, (LPCTSTR)filename);
1227 MessageBox(g_Git.GetGitLastErr(out, CGit::GIT_CMD_GETONEFILE), _T("TortoiseGit"), MB_ICONERROR);
1228 return;
1233 void CRepositoryBrowser::OpenFile(const CString path, eOpenType mode, bool isSubmodule, CGitHash itemHash)
1235 CTGitPath gitPath(path);
1237 CString temppath;
1238 CString file;
1239 GetTempPath(temppath);
1240 CGitHash hash;
1241 if (g_Git.GetHash(hash, m_sRevision))
1243 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of ") + m_sRevision + _T(".")), _T("TortoiseGit"), MB_ICONERROR);
1244 return;
1247 file.Format(_T("%s%s_%s%s"), (LPCTSTR)temppath, (LPCTSTR)gitPath.GetBaseFilename(), (LPCTSTR)hash.ToString().Left(g_Git.GetShortHASHLength()), (LPCTSTR)gitPath.GetFileExtension());
1249 if (isSubmodule)
1251 if (mode == OPEN && !GitAdminDir::IsBareRepo(g_Git.m_CurrentDir))
1253 CTGitPath subPath = CTGitPath(g_Git.m_CurrentDir);
1254 subPath.AppendPathString(gitPath.GetWinPathString());
1255 CAutoRepository repo(subPath.GetGitPathString());
1256 CAutoCommit commit;
1257 if (!repo || git_commit_lookup(commit.GetPointer(), repo, (const git_oid *)itemHash.m_hash))
1259 CString out;
1260 out.Format(IDS_REPOBROWSEASKSUBMODULEUPDATE, (LPCTSTR)itemHash.ToString(), (LPCTSTR)gitPath.GetGitPathString());
1261 if (MessageBox(out, _T("TortoiseGit"), MB_YESNO | MB_ICONQUESTION) != IDYES)
1262 return;
1264 CString sCmd;
1265 sCmd.Format(_T("/command:subupdate /bkpath:\"%s\" /selectedpath:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)gitPath.GetGitPathString());
1266 CAppUtils::RunTortoiseGitProc(sCmd);
1267 return;
1270 CString cmd;
1271 cmd.Format(_T("/command:repobrowser /path:\"%s\" /rev:%s"), (LPCTSTR)g_Git.CombinePath(path), (LPCTSTR)itemHash.ToString());
1272 CAppUtils::RunTortoiseGitProc(cmd);
1273 return;
1276 file += _T(".txt");
1277 CFile submoduleCommit(file, CFile::modeCreate | CFile::modeWrite);
1278 CStringA commitInfo = "Subproject commit " + CStringA(itemHash.ToString());
1279 submoduleCommit.Write(commitInfo, commitInfo.GetLength());
1281 else if (g_Git.GetOneFile(m_sRevision, gitPath, file))
1283 CString out;
1284 out.Format(IDS_STATUSLIST_CHECKOUTFILEFAILED, (LPCTSTR)gitPath.GetGitPathString(), (LPCTSTR)m_sRevision, (LPCTSTR)file);
1285 MessageBox(g_Git.GetGitLastErr(out, CGit::GIT_CMD_GETONEFILE), _T("TortoiseGit"), MB_ICONERROR);
1286 return;
1289 if (mode == ALTERNATIVEEDITOR)
1291 CAppUtils::LaunchAlternativeEditor(file);
1292 return;
1294 else if (mode == OPEN)
1296 CAppUtils::ShellOpen(file);
1297 return;
1300 CAppUtils::ShowOpenWithDialog(file);
1302 bool CRepositoryBrowser::RevertItemToVersion(const CString &path)
1304 CString cmd, out;
1305 cmd.Format(_T("git.exe checkout %s -- \"%s\""), (LPCTSTR)m_sRevision, (LPCTSTR)path);
1306 if (g_Git.Run(cmd, &out, CP_UTF8))
1308 if (MessageBox(out, _T("TortoiseGit"), MB_ICONEXCLAMATION | MB_OKCANCEL) == IDCANCEL)
1309 return false;
1312 return true;
1315 void CRepositoryBrowser::CopyHashToClipboard(TShadowFilesTreeList &selectedLeafs)
1317 if (!selectedLeafs.empty())
1319 CString sClipdata;
1320 bool first = true;
1321 for (size_t i = 0; i < selectedLeafs.size(); ++i)
1323 if (!first)
1324 sClipdata += _T("\r\n");
1325 sClipdata += selectedLeafs[i]->m_hash;
1326 first = false;
1328 CStringUtils::WriteAsciiStringToClipboard(sClipdata, GetSafeHwnd());