1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2017 - TortoiseGit
4 // Copyright (C) 2003-2013 - TortoiseSVN
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 // RepositoryBrowser.cpp : implementation file
24 #include "TortoiseProc.h"
25 #include "RepositoryBrowser.h"
29 #include "UnicodeUtils.h"
30 #include "SysImageList.h"
33 #include "PathUtils.h"
34 #include "StringUtils.h"
36 #include "DragDropImpl.h"
37 #include "GitDataObject.h"
40 #define OVERLAY_EXTERNAL 1
41 #define OVERLAY_EXECUTABLE 2
42 #define OVERLAY_SYMLINK 3
44 void SetSortArrowA(CListCtrl
* control
, int nColumn
, bool bAscending
)
50 CHeaderCtrl
* pHeader
= control
->GetHeaderCtrl();
51 HDITEM HeaderItem
= {0};
52 HeaderItem
.mask
= HDI_FORMAT
;
53 for (int i
= 0; i
< pHeader
->GetItemCount(); ++i
)
55 pHeader
->GetItem(i
, &HeaderItem
);
56 HeaderItem
.fmt
&= ~(HDF_SORTDOWN
| HDF_SORTUP
);
57 pHeader
->SetItem(i
, &HeaderItem
);
61 pHeader
->GetItem(nColumn
, &HeaderItem
);
62 HeaderItem
.fmt
|= (bAscending
? HDF_SORTUP
: HDF_SORTDOWN
);
63 pHeader
->SetItem(nColumn
, &HeaderItem
);
67 class CRepoListCompareFunc
70 CRepoListCompareFunc(CListCtrl
* pList
, int col
, bool desc
)
76 static int CALLBACK
StaticCompare(LPARAM lParam1
, LPARAM lParam2
, LPARAM lParamSort
)
78 return reinterpret_cast<CRepoListCompareFunc
*>(lParamSort
)->Compare(lParam1
, lParam2
);
81 int Compare(LPARAM lParam1
, LPARAM lParam2
)
83 auto pLeft
= reinterpret_cast<CShadowFilesTree
*>(m_pList
->GetItemData((int)lParam1
));
84 auto pRight
= reinterpret_cast<CShadowFilesTree
*>(m_pList
->GetItemData((int)lParam2
));
89 case CRepositoryBrowser::eCol_Name
:
90 result
= SortStrCmp(pLeft
->m_sName
, pRight
->m_sName
);
93 case CRepositoryBrowser::eCol_Extension
:
94 result
= m_pList
->GetItemText(static_cast<int>(lParam1
), 1).CompareNoCase(m_pList
->GetItemText(static_cast<int>(lParam2
), 1));
95 if (result
== 0) // if extensions are the same, use the filename to sort
96 result
= SortStrCmp(pRight
->m_sName
, pRight
->m_sName
);
99 case CRepositoryBrowser::eCol_FileSize
:
100 if (pLeft
->m_iSize
> pRight
->m_iSize
)
102 else if (pLeft
->m_iSize
< pRight
->m_iSize
)
105 result
= SortStrCmp(pLeft
->m_sName
, pRight
->m_sName
);
111 if (pLeft
->m_bFolder
!= pRight
->m_bFolder
)
113 if (pRight
->m_bFolder
)
121 int SortStrCmp(const CString
&left
, const CString
&right
)
123 if (CRepositoryBrowser::s_bSortLogical
)
124 return StrCmpLogicalW(left
, right
);
125 return StrCmpI(left
, right
);
133 // CRepositoryBrowser dialog
135 bool CRepositoryBrowser::s_bSortLogical
= true;
137 IMPLEMENT_DYNAMIC(CRepositoryBrowser
, CResizableStandAloneDialog
)
139 CRepositoryBrowser::CRepositoryBrowser(CString rev
, CWnd
* pParent
/*=nullptr*/)
140 : CResizableStandAloneDialog(CRepositoryBrowser::IDD
, pParent
)
142 , m_currSortDesc(false)
145 , m_ColumnManager(&m_RepoList
)
147 , m_nOpenIconFolder(0)
149 , m_nExecutableOvl(0)
157 CRepositoryBrowser::~CRepositoryBrowser()
161 void CRepositoryBrowser::DoDataExchange(CDataExchange
* pDX
)
163 CDialog::DoDataExchange(pDX
);
164 DDX_Control(pDX
, IDC_REPOTREE
, m_RepoTree
);
165 DDX_Control(pDX
, IDC_REPOLIST
, m_RepoList
);
169 BEGIN_MESSAGE_MAP(CRepositoryBrowser
, CResizableStandAloneDialog
)
170 ON_NOTIFY(TVN_SELCHANGED
, IDC_REPOTREE
, &CRepositoryBrowser::OnTvnSelchangedRepoTree
)
171 ON_NOTIFY(TVN_ITEMEXPANDING
, IDC_REPOTREE
, &CRepositoryBrowser::OnTvnItemExpandingRepoTree
)
173 ON_NOTIFY(LVN_COLUMNCLICK
, IDC_REPOLIST
, &CRepositoryBrowser::OnLvnColumnclickRepoList
)
174 ON_NOTIFY(LVN_ITEMCHANGED
, IDC_REPOLIST
, &CRepositoryBrowser::OnLvnItemchangedRepolist
)
175 ON_NOTIFY(NM_DBLCLK
, IDC_REPOLIST
, &CRepositoryBrowser::OnNMDblclk_RepoList
)
176 ON_BN_CLICKED(IDC_BUTTON_REVISION
, &CRepositoryBrowser::OnBnClickedButtonRevision
)
182 ON_WM_SYSCOLORCHANGE()
183 ON_NOTIFY(LVN_BEGINDRAG
, IDC_REPOLIST
, &CRepositoryBrowser::OnLvnBegindragRepolist
)
184 ON_NOTIFY(TVN_BEGINDRAG
, IDC_REPOTREE
, &CRepositoryBrowser::OnTvnBegindragRepotree
)
188 // CRepositoryBrowser message handlers
190 BOOL
CRepositoryBrowser::OnInitDialog()
192 CResizableStandAloneDialog::OnInitDialog();
193 CAppUtils::MarkWindowAsUnpinnable(m_hWnd
);
195 AddAnchor(IDC_STATIC_REPOURL
, TOP_LEFT
);
196 AddAnchor(IDC_REPOBROWSER_URL
, TOP_LEFT
, TOP_RIGHT
);
197 AddAnchor(IDC_STATIC_REF
, TOP_RIGHT
);
198 AddAnchor(IDC_BUTTON_REVISION
, TOP_RIGHT
);
199 AddAnchor(IDC_REPOTREE
, TOP_LEFT
, BOTTOM_LEFT
);
200 AddAnchor(IDC_REPOLIST
, TOP_LEFT
, BOTTOM_RIGHT
);
201 AddAnchor(IDHELP
, BOTTOM_RIGHT
);
202 AddAnchor(IDC_INFOLABEL
, BOTTOM_LEFT
, BOTTOM_RIGHT
);
203 AddAnchor(IDOK
, BOTTOM_RIGHT
);
204 AddAnchor(IDCANCEL
, BOTTOM_RIGHT
);
206 CRepositoryBrowser::s_bSortLogical
= !CRegDWORD(L
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER
);
207 if (CRepositoryBrowser::s_bSortLogical
)
208 CRepositoryBrowser::s_bSortLogical
= !CRegDWORD(L
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_LOCAL_MACHINE
);
210 static UINT columnNames
[] = { IDS_STATUSLIST_COLFILENAME
, IDS_STATUSLIST_COLEXT
, IDS_LOG_SIZE
};
211 static int columnWidths
[] = { 150, 100, 100 };
212 DWORD dwDefaultColumns
= (1 << eCol_Name
) | (1 << eCol_Extension
) | (1 << eCol_FileSize
);
213 m_ColumnManager
.SetNames(columnNames
, _countof(columnNames
));
214 m_ColumnManager
.ReadSettings(dwDefaultColumns
, 0, L
"RepoBrowser", _countof(columnNames
), columnWidths
);
215 m_ColumnManager
.SetRightAlign(2);
217 // set up the list control
218 // set the extended style of the list control
219 // the style LVS_EX_FULLROWSELECT interferes with the background watermark image but it's more important to be able to select in the whole row.
220 CRegDWORD
regFullRowSelect(L
"Software\\TortoiseGit\\FullRowSelect", TRUE
);
221 DWORD exStyle
= LVS_EX_HEADERDRAGDROP
| LVS_EX_DOUBLEBUFFER
| LVS_EX_INFOTIP
| LVS_EX_SUBITEMIMAGES
;
222 if (DWORD(regFullRowSelect
))
223 exStyle
|= LVS_EX_FULLROWSELECT
;
224 m_RepoList
.SetExtendedStyle(exStyle
);
225 m_RepoList
.SetImageList(&SYS_IMAGE_LIST(), LVSIL_SMALL
);
226 CAppUtils::SetListCtrlBackgroundImage(m_RepoList
.GetSafeHwnd(), IDI_REPOBROWSER_BKG
);
228 m_RepoTree
.SetImageList(&SYS_IMAGE_LIST(), TVSIL_NORMAL
);
229 exStyle
= TVS_EX_FADEINOUTEXPANDOS
| TVS_EX_AUTOHSCROLL
| TVS_EX_DOUBLEBUFFER
;
230 m_RepoTree
.SetExtendedStyle(exStyle
, exStyle
);
232 m_nExternalOvl
= SYS_IMAGE_LIST().AddIcon((HICON
)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_EXTERNALOVL
), IMAGE_ICON
, 0, 0, LR_DEFAULTSIZE
));
233 m_nExecutableOvl
= SYS_IMAGE_LIST().AddIcon((HICON
)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_EXECUTABLEOVL
), IMAGE_ICON
, 0, 0, LR_DEFAULTSIZE
));
234 m_nSymlinkOvl
= SYS_IMAGE_LIST().AddIcon((HICON
)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_SYMLINKOVL
), IMAGE_ICON
, 0, 0, LR_DEFAULTSIZE
));
235 // set externaloverlay in SYS_IMAGE_LIST() in Refresh method, so that it is updated after every launch of the logdialog
237 SetWindowTheme(m_RepoTree
.GetSafeHwnd(), L
"Explorer", nullptr);
238 SetWindowTheme(m_RepoList
.GetSafeHwnd(), L
"Explorer", nullptr);
243 HTHEME hTheme
= OpenThemeData(m_RepoTree
, L
"TREEVIEW");
244 GetThemeMetric(hTheme
, NULL
, TVP_TREEITEM
, TREIS_NORMAL
, TMT_BORDERSIZE
, &borderWidth
);
245 CloseThemeData(hTheme
);
248 borderWidth
= GetSystemMetrics(SM_CYBORDER
);
249 m_RepoTree
.SetItemHeight((SHORT
)(m_RepoTree
.GetItemHeight() + 2 * borderWidth
));
251 m_nIconFolder
= SYS_IMAGE_LIST().GetDirIconIndex();
252 m_nOpenIconFolder
= SYS_IMAGE_LIST().GetDirOpenIconIndex();
254 EnableSaveRestore(L
"Reposbrowser");
256 DWORD xPos
= CRegDWORD(L
"Software\\TortoiseGit\\TortoiseProc\\ResizableState\\RepobrowserDivider", 0);
260 GetDlgItem(IDC_REPOTREE
)->GetClientRect(&rc
);
261 xPos
= rc
.right
- rc
.left
;
263 HandleDividerMove(CPoint(xPos
+ 20, 10), false);
265 CString sWindowTitle
;
266 GetWindowText(sWindowTitle
);
267 CAppUtils::SetWindowTitle(m_hWnd
, g_Git
.m_CurrentDir
, sWindowTitle
);
269 m_bHasWC
= !GitAdminDir::IsBareRepo(g_Git
.m_CurrentDir
);
273 m_RepoList
.SetFocus();
278 void CRepositoryBrowser::OnDestroy()
280 int maxcol
= m_ColumnManager
.GetColumnCount();
281 for (int col
= 0; col
< maxcol
; ++col
)
282 if (m_ColumnManager
.IsVisible(col
))
283 m_ColumnManager
.ColumnResized(col
);
284 m_ColumnManager
.WriteSettings();
286 CResizableStandAloneDialog::OnDestroy();
289 void CRepositoryBrowser::OnOK()
291 if (GetFocus() == &m_RepoList
&& (GetKeyState(VK_MENU
) & 0x8000) == 0)
293 // list control has focus: 'enter' the folder
294 if (m_RepoList
.GetSelectedCount() != 1)
297 POSITION pos
= m_RepoList
.GetFirstSelectedItemPosition();
300 auto item
= GetListEntry(m_RepoList
.GetNextSelectedItem(pos
));
303 FillListCtrlForShadowTree(item
);
304 m_RepoTree
.SelectItem(item
->m_hTree
);
307 OpenFile(item
->GetFullName(), OPEN
, item
->m_bSubmodule
, item
->m_hash
);
312 SaveDividerPosition();
313 CResizableStandAloneDialog::OnOK();
316 void CRepositoryBrowser::OnCancel()
318 SaveDividerPosition();
319 CResizableStandAloneDialog::OnCancel();
322 void CRepositoryBrowser::OnNMDblclk_RepoList(NMHDR
*pNMHDR
, LRESULT
*pResult
)
326 LPNMITEMACTIVATE pNmItemActivate
= reinterpret_cast<LPNMITEMACTIVATE
>(pNMHDR
);
327 if (pNmItemActivate
->iItem
< 0)
330 auto pItem
= GetListEntry(pNmItemActivate
->iItem
);
334 if (!pItem
->m_bFolder
)
336 OpenFile(pItem
->GetFullName(), OPEN
, pItem
->m_bSubmodule
, pItem
->m_hash
);
341 FillListCtrlForShadowTree(pItem
);
342 m_RepoTree
.SelectItem(pItem
->m_hTree
);
346 void CRepositoryBrowser::Refresh()
349 if (m_nExternalOvl
>= 0)
350 SYS_IMAGE_LIST().SetOverlayImage(m_nExternalOvl
, OVERLAY_EXTERNAL
);
351 if (m_nExecutableOvl
>= 0)
352 SYS_IMAGE_LIST().SetOverlayImage(m_nExecutableOvl
, OVERLAY_EXECUTABLE
);
353 if (m_nSymlinkOvl
>= 0)
354 SYS_IMAGE_LIST().SetOverlayImage(m_nSymlinkOvl
, OVERLAY_SYMLINK
);
356 m_RepoTree
.DeleteAllItems();
357 m_RepoList
.DeleteAllItems();
358 m_TreeRoot
.m_ShadowTree
.clear();
359 m_TreeRoot
.m_sName
.Empty();
360 m_TreeRoot
.m_bFolder
= true;
362 TVINSERTSTRUCT tvinsert
= {0};
363 tvinsert
.hParent
= TVI_ROOT
;
364 tvinsert
.hInsertAfter
= TVI_ROOT
;
365 tvinsert
.itemex
.mask
= TVIF_DI_SETITEM
| TVIF_PARAM
| TVIF_TEXT
| TVIF_IMAGE
| TVIF_SELECTEDIMAGE
| TVIF_STATE
;
366 tvinsert
.itemex
.pszText
= L
"/";
367 tvinsert
.itemex
.lParam
= (LPARAM
)&m_TreeRoot
;
368 tvinsert
.itemex
.iImage
= m_nIconFolder
;
369 tvinsert
.itemex
.iSelectedImage
= m_nOpenIconFolder
;
370 m_TreeRoot
.m_hTree
= m_RepoTree
.InsertItem(&tvinsert
);
372 ReadTree(&m_TreeRoot
);
373 m_RepoTree
.Expand(m_TreeRoot
.m_hTree
, TVE_EXPAND
);
374 FillListCtrlForShadowTree(&m_TreeRoot
);
375 m_RepoTree
.SelectItem(m_TreeRoot
.m_hTree
);
379 int CRepositoryBrowser::ReadTreeRecursive(git_repository
& repo
, const git_tree
* tree
, CShadowFilesTree
* treeroot
, bool recursive
)
381 size_t count
= git_tree_entrycount(tree
);
382 bool hasSubfolders
= false;
383 treeroot
->m_bLoaded
= true;
385 for (size_t i
= 0; i
< count
; ++i
)
387 const git_tree_entry
*entry
= git_tree_entry_byindex(tree
, i
);
391 const int mode
= git_tree_entry_filemode(entry
);
393 CString base
= CUnicodeUtils::GetUnicode(git_tree_entry_name(entry
), CP_UTF8
);
395 const git_oid
*oid
= git_tree_entry_id(entry
);
396 CShadowFilesTree
* pNextTree
= &treeroot
->m_ShadowTree
[base
];
397 pNextTree
->m_sName
= base
;
398 pNextTree
->m_pParent
= treeroot
;
399 pNextTree
->m_hash
= oid
->id
;
401 if (mode
== GIT_FILEMODE_COMMIT
)
402 pNextTree
->m_bSubmodule
= true;
403 else if (mode
& S_IFDIR
)
405 hasSubfolders
= true;
406 pNextTree
->m_bFolder
= true;
407 pNextTree
->m_bLoaded
= false;
409 TVINSERTSTRUCT tvinsert
= {0};
410 tvinsert
.hParent
= treeroot
->m_hTree
;
411 tvinsert
.hInsertAfter
= TVI_SORT
;
412 tvinsert
.itemex
.mask
= TVIF_DI_SETITEM
| TVIF_PARAM
| TVIF_TEXT
| TVIF_IMAGE
| TVIF_SELECTEDIMAGE
| TVIF_STATE
| TVIF_CHILDREN
;
413 tvinsert
.itemex
.pszText
= base
.GetBuffer(base
.GetLength());
414 tvinsert
.itemex
.cChildren
= 1;
415 tvinsert
.itemex
.lParam
= (LPARAM
)pNextTree
;
416 tvinsert
.itemex
.iImage
= m_nIconFolder
;
417 tvinsert
.itemex
.iSelectedImage
= m_nOpenIconFolder
;
418 pNextTree
->m_hTree
= m_RepoTree
.InsertItem(&tvinsert
);
419 base
.ReleaseBuffer();
423 if (git_tree_lookup(subtree
.GetPointer(), &repo
, oid
))
425 MessageBox(CGit::GetLibGit2LastErr(L
"Could not lookup path."), L
"TortoiseGit", MB_ICONERROR
);
429 ReadTreeRecursive(repo
, subtree
, pNextTree
, recursive
);
434 if (mode
== GIT_FILEMODE_BLOB_EXECUTABLE
)
435 pNextTree
->m_bExecutable
= true;
436 if (mode
== GIT_FILEMODE_LINK
)
437 pNextTree
->m_bSymlink
= true;
439 git_blob_lookup(blob
.GetPointer(), &repo
, oid
);
443 pNextTree
->m_iSize
= git_blob_rawsize(blob
);
449 TVITEM tvitem
= { 0 };
450 tvitem
.hItem
= treeroot
->m_hTree
;
451 tvitem
.mask
= TVIF_CHILDREN
;
452 tvitem
.cChildren
= 0;
453 m_RepoTree
.SetItem(&tvitem
);
459 int CRepositoryBrowser::ReadTree(CShadowFilesTree
* treeroot
, const CString
& root
, bool recursive
)
462 CAutoRepository
repository(g_Git
.GetGitRepository());
465 MessageBox(CGit::GetLibGit2LastErr(L
"Could not open repository."), L
"TortoiseGit", MB_ICONERROR
);
469 if (m_sRevision
== L
"HEAD")
471 int ret
= git_repository_head_unborn(repository
);
472 if (ret
== 1) // is orphan
476 MessageBox(g_Git
.GetLibGit2LastErr(L
"Could not check HEAD."), L
"TortoiseGit", MB_ICONERROR
);
482 if (CGit::GetHash(repository
, hash
, m_sRevision
))
484 MessageBox(CGit::GetLibGit2LastErr(L
"Could not get hash of " + m_sRevision
+ L
'.'), L
"TortoiseGit", MB_ICONERROR
);
489 if (git_commit_lookup(commit
.GetPointer(), repository
, (git_oid
*)hash
.m_hash
))
491 MessageBox(CGit::GetLibGit2LastErr(L
"Could not lookup commit."), L
"TortoiseGit", MB_ICONERROR
);
496 if (git_commit_tree(tree
.GetPointer(), commit
))
498 MessageBox(CGit::GetLibGit2LastErr(L
"Could not get tree of commit."), L
"TortoiseGit", MB_ICONERROR
);
504 CAutoTreeEntry treeEntry
;
505 if (git_tree_entry_bypath(treeEntry
.GetPointer(), tree
, CUnicodeUtils::GetUTF8(root
)))
507 MessageBox(CGit::GetLibGit2LastErr(L
"Could not lookup path."), L
"TortoiseGit", MB_ICONERROR
);
510 if (git_tree_entry_type(treeEntry
) != GIT_OBJ_TREE
)
512 MessageBox(CGit::GetLibGit2LastErr(L
"Could not lookup path."), L
"TortoiseGit", MB_ICONERROR
);
517 if (git_tree_entry_to_object(object
.GetPointer(), repository
, treeEntry
))
519 MessageBox(CGit::GetLibGit2LastErr(L
"Could not lookup path."), L
"TortoiseGit", MB_ICONERROR
);
523 tree
.ConvertFrom(std::move(object
));
526 treeroot
->m_hash
= git_tree_id(tree
)->id
;
527 ReadTreeRecursive(*repository
, tree
, treeroot
, recursive
);
529 // try to resolve hash to a branch name
530 if (m_sRevision
== hash
.ToString())
533 if (CGit::GetMapHashToFriendName(repository
, map
))
534 MessageBox(g_Git
.GetLibGit2LastErr(L
"Could not get all refs."), L
"TortoiseGit", MB_ICONERROR
);
535 if (!map
[hash
].empty())
536 m_sRevision
= map
[hash
].at(0);
538 this->GetDlgItem(IDC_BUTTON_REVISION
)->SetWindowText(m_sRevision
);
543 void CRepositoryBrowser::OnTvnSelchangedRepoTree(NMHDR
*pNMHDR
, LRESULT
*pResult
)
545 LPNMTREEVIEW pNMTreeView
= reinterpret_cast<LPNMTREEVIEW
>(pNMHDR
);
548 FillListCtrlForTreeNode(pNMTreeView
->itemNew
.hItem
);
551 void CRepositoryBrowser::OnTvnItemExpandingRepoTree(NMHDR
*pNMHDR
, LRESULT
*pResult
)
553 LPNMTREEVIEW pNMTreeView
= reinterpret_cast<LPNMTREEVIEW
>(pNMHDR
);
556 auto pTree
= GetTreeEntry(pNMTreeView
->itemNew
.hItem
);
560 if (!pTree
->m_bLoaded
)
561 ReadTree(pTree
, pTree
->GetFullName());
564 void CRepositoryBrowser::FillListCtrlForTreeNode(HTREEITEM treeNode
)
566 m_RepoList
.DeleteAllItems();
568 auto pTree
= GetTreeEntry(treeNode
);
572 CString url
= L
'/' + pTree
->GetFullName();
573 GetDlgItem(IDC_REPOBROWSER_URL
)->SetWindowText(url
);
575 if (!pTree
->m_bLoaded
)
576 ReadTree(pTree
, pTree
->GetFullName());
578 FillListCtrlForShadowTree(pTree
);
581 void CRepositoryBrowser::FillListCtrlForShadowTree(CShadowFilesTree
* pTree
)
583 for (auto itShadowTree
= pTree
->m_ShadowTree
.cbegin(); itShadowTree
!= pTree
->m_ShadowTree
.cend(); ++itShadowTree
)
585 int icon
= m_nIconFolder
;
586 if (!(*itShadowTree
).second
.m_bFolder
&& !(*itShadowTree
).second
.m_bSubmodule
)
588 icon
= SYS_IMAGE_LIST().GetFileIconIndex((*itShadowTree
).second
.m_sName
);
591 int indexItem
= m_RepoList
.InsertItem(m_RepoList
.GetItemCount(), (*itShadowTree
).second
.m_sName
, icon
);
593 if ((*itShadowTree
).second
.m_bSubmodule
)
595 m_RepoList
.SetItemState(indexItem
, INDEXTOOVERLAYMASK(OVERLAY_EXTERNAL
), LVIS_OVERLAYMASK
);
597 if ((*itShadowTree
).second
.m_bExecutable
)
598 m_RepoList
.SetItemState(indexItem
, INDEXTOOVERLAYMASK(OVERLAY_EXECUTABLE
), LVIS_OVERLAYMASK
);
599 if ((*itShadowTree
).second
.m_bSymlink
)
600 m_RepoList
.SetItemState(indexItem
, INDEXTOOVERLAYMASK(OVERLAY_SYMLINK
), LVIS_OVERLAYMASK
);
601 m_RepoList
.SetItemData(indexItem
, (DWORD_PTR
)&(*itShadowTree
).second
);
602 if (!(*itShadowTree
).second
.m_bFolder
&& !(*itShadowTree
).second
.m_bSubmodule
)
606 temp
= CPathUtils::GetFileExtFromPath((*itShadowTree
).second
.m_sName
);
607 m_RepoList
.SetItemText(indexItem
, eCol_Extension
, temp
);
609 StrFormatByteSize64((*itShadowTree
).second
.m_iSize
, CStrBuf(temp
, 20), 20);
610 m_RepoList
.SetItemText(indexItem
, eCol_FileSize
, temp
);
614 CRepoListCompareFunc
compareFunc(&m_RepoList
, m_currSortCol
, m_currSortDesc
);
615 m_RepoList
.SortItemsEx(&CRepoListCompareFunc::StaticCompare
, (DWORD_PTR
)&compareFunc
);
617 SetSortArrowA(&m_RepoList
, m_currSortCol
, !m_currSortDesc
);
622 void CRepositoryBrowser::UpdateInfoLabel()
625 POSITION pos
= m_RepoList
.GetFirstSelectedItemPosition();
628 if (m_RepoList
.GetSelectedCount() > 1)
630 temp
.FormatMessage(IDS_REPOBROWSE_INFOMULTI
, m_RepoList
.GetSelectedCount());
634 int index
= m_RepoList
.GetNextSelectedItem(pos
);
635 auto item
= GetListEntry(index
);
636 if (item
->m_bSubmodule
)
637 temp
.FormatMessage(IDS_REPOBROWSE_INFOEXT
, (LPCTSTR
)m_RepoList
.GetItemText(index
, eCol_Name
), (LPCTSTR
)item
->m_hash
.ToString());
638 else if (item
->m_bFolder
)
639 temp
= m_RepoList
.GetItemText(index
, eCol_Name
);
641 temp
.FormatMessage(IDS_REPOBROWSE_INFOFILE
, (LPCTSTR
)m_RepoList
.GetItemText(index
, eCol_Name
), (LPCTSTR
)m_RepoList
.GetItemText(index
, eCol_FileSize
));
646 HTREEITEM hTreeItem
= m_RepoTree
.GetSelectedItem();
647 if (hTreeItem
!= nullptr)
649 auto pTree
= GetTreeEntry(hTreeItem
);
650 if (pTree
!= nullptr)
652 size_t files
= 0, submodules
= 0;
653 for (auto itShadowTree
= pTree
->m_ShadowTree
.cbegin(); itShadowTree
!= pTree
->m_ShadowTree
.cend(); ++itShadowTree
)
655 if (!(*itShadowTree
).second
.m_bFolder
&& !(*itShadowTree
).second
.m_bSubmodule
)
657 if ((*itShadowTree
).second
.m_bSubmodule
)
660 temp
.FormatMessage(IDS_REPOBROWSE_INFO
, (LPCTSTR
)pTree
->m_sName
, files
, submodules
, pTree
->m_ShadowTree
.size() - files
- submodules
, pTree
->m_ShadowTree
.size());
664 SetDlgItemText(IDC_INFOLABEL
, temp
);
667 void CRepositoryBrowser::OnLvnItemchangedRepolist(NMHDR
* /* pNMHDR */, LRESULT
*pResult
)
673 void CRepositoryBrowser::OnContextMenu(CWnd
* pWndFrom
, CPoint point
)
675 if (pWndFrom
== &m_RepoList
)
677 CRect headerPosition
;
678 m_RepoList
.GetHeaderCtrl()->GetWindowRect(headerPosition
);
679 if (!headerPosition
.PtInRect(point
))
680 OnContextMenu_RepoList(point
);
682 else if (pWndFrom
== &m_RepoTree
)
683 OnContextMenu_RepoTree(point
);
686 void CRepositoryBrowser::OnContextMenu_RepoTree(CPoint point
)
688 CPoint clientPoint
= point
;
689 m_RepoTree
.ScreenToClient(&clientPoint
);
691 HTREEITEM hTreeItem
= m_RepoTree
.HitTest(clientPoint
);
695 TShadowFilesTreeList selectedLeafs
;
696 selectedLeafs
.push_back(GetTreeEntry(hTreeItem
));
698 ShowContextMenu(point
, selectedLeafs
, ONLY_FOLDERS
);
701 void CRepositoryBrowser::OnContextMenu_RepoList(CPoint point
)
703 TShadowFilesTreeList selectedLeafs
;
704 selectedLeafs
.reserve(m_RepoList
.GetSelectedCount());
706 bool folderSelected
= false;
707 bool filesSelected
= false;
708 bool submodulesSelected
= false;
710 POSITION pos
= m_RepoList
.GetFirstSelectedItemPosition();
713 auto item
= GetListEntry(m_RepoList
.GetNextSelectedItem(pos
));
714 if (item
->m_bSubmodule
)
715 submodulesSelected
= true;
717 folderSelected
= true;
719 filesSelected
= true;
720 selectedLeafs
.push_back(item
);
723 eSelectionType selType
= ONLY_FILES
;
724 if (folderSelected
&& filesSelected
)
725 selType
= MIXED_FOLDERS_FILES
;
726 else if (folderSelected
)
727 selType
= ONLY_FOLDERS
;
728 else if (submodulesSelected
)
729 selType
= ONLY_FILESSUBMODULES
;
730 ShowContextMenu(point
, selectedLeafs
, selType
);
733 void CRepositoryBrowser::ShowContextMenu(CPoint point
, TShadowFilesTreeList
&selectedLeafs
, eSelectionType selType
)
736 popupMenu
.CreatePopupMenu();
738 bool bAddSeparator
= false;
740 if (selectedLeafs
.size() == 1)
742 popupMenu
.AppendMenuIcon(eCmd_Open
, IDS_REPOBROWSE_OPEN
, IDI_OPEN
);
743 popupMenu
.SetDefaultItem(eCmd_Open
, FALSE
);
744 if (selType
== ONLY_FILES
|| selType
== ONLY_FILESSUBMODULES
)
746 popupMenu
.AppendMenuIcon(eCmd_OpenWith
, IDS_LOG_POPUP_OPENWITH
, IDI_OPEN
);
747 popupMenu
.AppendMenuIcon(eCmd_OpenWithAlternativeEditor
, IDS_LOG_POPUP_VIEWREV
);
750 popupMenu
.AppendMenu(MF_SEPARATOR
);
752 if (m_bHasWC
&& (selType
== ONLY_FILES
|| selType
== ONLY_FILESSUBMODULES
))
754 popupMenu
.AppendMenuIcon(eCmd_CompareWC
, IDS_LOG_POPUP_COMPARE
, IDI_DIFF
);
755 bAddSeparator
= true;
759 popupMenu
.AppendMenu(MF_SEPARATOR
);
760 bAddSeparator
= false;
763 temp
.LoadString(IDS_MENULOG
);
764 popupMenu
.AppendMenuIcon(eCmd_ViewLog
, temp
, IDI_LOG
);
765 if (selectedLeafs
[0]->m_bSubmodule
)
767 temp
.LoadString(IDS_MENULOGSUBMODULE
);
768 popupMenu
.AppendMenuIcon(eCmd_ViewLogSubmodule
, temp
, IDI_LOG
);
771 if (selType
== ONLY_FILES
)
774 popupMenu
.AppendMenuIcon(eCmd_Blame
, IDS_LOG_POPUP_BLAME
, IDI_BLAME
);
776 popupMenu
.AppendMenu(MF_SEPARATOR
);
777 temp
.LoadString(IDS_LOG_POPUP_SAVE
);
778 popupMenu
.AppendMenuIcon(eCmd_SaveAs
, temp
, IDI_SAVEAS
);
781 bAddSeparator
= true;
784 if (!selectedLeafs
.empty() && selType
== ONLY_FILES
&& m_bHasWC
)
786 popupMenu
.AppendMenuIcon(eCmd_Revert
, IDS_LOG_POPUP_REVERTTOREV
, IDI_REVERT
);
787 bAddSeparator
= true;
791 popupMenu
.AppendMenu(MF_SEPARATOR
);
793 if (selectedLeafs
.size() == 1 && selType
== ONLY_FILES
)
795 popupMenu
.AppendMenuIcon(eCmd_PrepareDiff
, IDS_PREPAREDIFF
, IDI_DIFF
);
796 if (!m_sMarkForDiffFilename
.IsEmpty())
799 if (selectedLeafs
.at(0)->GetFullName() == m_sMarkForDiffFilename
)
800 diffWith
= m_sMarkForDiffVersion
;
803 PathCompactPathEx(CStrBuf(diffWith
, 2 * GIT_HASH_SIZE
), m_sMarkForDiffFilename
, 2 * GIT_HASH_SIZE
, 0);
804 diffWith
+= L
':' + m_sMarkForDiffVersion
.ToString().Left(g_Git
.GetShortHASHLength());
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);
823 case eCmd_ViewLogSubmodule
:
826 sCmd
.Format(L
"/command:log /path:\"%s\"", (LPCTSTR
)g_Git
.CombinePath(selectedLeafs
.at(0)->GetFullName()));
827 if (cmd
== eCmd_ViewLog
&& selectedLeafs
.at(0)->m_bSubmodule
)
828 sCmd
+= L
" /submodule";
829 if (cmd
== eCmd_ViewLog
)
830 sCmd
+= L
" /endrev:" + m_sRevision
;
831 CAppUtils::RunTortoiseGitProc(sCmd
);
836 CAppUtils::LaunchTortoiseBlame(g_Git
.CombinePath(selectedLeafs
.at(0)->GetFullName()), m_sRevision
);
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
);
846 OpenFile(selectedLeafs
.at(0)->GetFullName(), OPEN
, selectedLeafs
.at(0)->m_bSubmodule
, selectedLeafs
.at(0)->m_hash
);
849 OpenFile(selectedLeafs
.at(0)->GetFullName(), OPEN_WITH
, selectedLeafs
.at(0)->m_bSubmodule
, selectedLeafs
.at(0)->m_hash
);
851 case eCmd_OpenWithAlternativeEditor
:
852 OpenFile(selectedLeafs
.at(0)->GetFullName(), ALTERNATIVEEDITOR
, selectedLeafs
.at(0)->m_bSubmodule
, selectedLeafs
.at(0)->m_hash
);
856 CTGitPath
file(selectedLeafs
.at(0)->GetFullName());
857 CGitDiff::Diff(&file
, &file
, GIT_REV_ZERO
, m_sRevision
);
863 for (auto itShadowTree
= selectedLeafs
.cbegin(); itShadowTree
!= selectedLeafs
.cend(); ++itShadowTree
)
865 if (RevertItemToVersion((*itShadowTree
)->GetFullName()))
871 msg
.Format(IDS_STATUSLIST_FILESREVERTED
, count
, (LPCTSTR
)m_sRevision
);
872 MessageBox(msg
, L
"TortoiseGit", MB_OK
);
876 FileSaveAs(selectedLeafs
.at(0)->GetFullName());
881 for (auto itShadowTree
= selectedLeafs
.cbegin(); itShadowTree
!= selectedLeafs
.cend(); ++itShadowTree
)
883 sClipboard
+= (*itShadowTree
)->m_sName
+ L
"\r\n";
885 CStringUtils::WriteAsciiStringToClipboard(sClipboard
);
890 CopyHashToClipboard(selectedLeafs
);
893 case eCmd_PrepareDiff
:
894 m_sMarkForDiffFilename
= selectedLeafs
.at(0)->GetFullName();
895 if (g_Git
.GetHash(m_sMarkForDiffVersion
, m_sRevision
))
897 m_sMarkForDiffFilename
.Empty();
898 MessageBox(g_Git
.GetGitLastErr(L
"Could not get SHA-1 for " + m_sRevision
), L
"TortoiseGit", MB_ICONERROR
);
901 case eCmd_PrepareDiff_Compare
:
903 CTGitPath
savedFile(m_sMarkForDiffFilename
);
904 CTGitPath
selectedFile(selectedLeafs
.at(0)->GetFullName());
905 CGitHash currentHash
;
906 if (g_Git
.GetHash(currentHash
, m_sRevision
))
908 MessageBox(g_Git
.GetGitLastErr(L
"Could not get SHA-1 for " + m_sRevision
), L
"TortoiseGit", MB_ICONERROR
);
911 CGitDiff::Diff(&selectedFile
, &savedFile
, currentHash
, m_sMarkForDiffVersion
);
917 BOOL
CRepositoryBrowser::PreTranslateMessage(MSG
* pMsg
)
919 if (pMsg
->message
== WM_KEYDOWN
)
921 switch (pMsg
->wParam
)
931 return CResizableStandAloneDialog::PreTranslateMessage(pMsg
);
934 void CRepositoryBrowser::OnLvnColumnclickRepoList(NMHDR
*pNMHDR
, LRESULT
*pResult
)
936 LPNMLISTVIEW pNMLV
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
940 if (m_currSortCol
== pNMLV
->iSubItem
)
941 m_currSortDesc
= !m_currSortDesc
;
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
958 dlg
.SetParams(CTGitPath(), CTGitPath(), m_sRevision
, m_sRevision
, 0);
959 // tell the dialog to use mode for selecting revisions
961 dlg
.ShowWorkingTreeChanges(false);
962 // only one revision must be selected however
963 dlg
.SingleSelection(true);
964 if (dlg
.DoModal() == IDOK
&& !dlg
.GetSelectedHash().empty())
966 m_sRevision
= dlg
.GetSelectedHash().at(0).ToString();
971 void CRepositoryBrowser::SaveDividerPosition()
974 GetDlgItem(IDC_REPOTREE
)->GetClientRect(&rc
);
975 CRegDWORD
xPos(L
"Software\\TortoiseGit\\TortoiseProc\\ResizableState\\RepobrowserDivider");
976 xPos
= rc
.right
- rc
.left
;
979 void CRepositoryBrowser::HandleDividerMove(CPoint point
, bool bDraw
)
981 RECT rect
, tree
, list
, treelist
, treelistclient
;
983 // create an union of the tree and list control rectangle
984 GetDlgItem(IDC_REPOLIST
)->GetWindowRect(&list
);
985 GetDlgItem(IDC_REPOTREE
)->GetWindowRect(&tree
);
986 UnionRect(&treelist
, &tree
, &list
);
987 treelistclient
= treelist
;
988 ScreenToClient(&treelistclient
);
990 ClientToScreen(&point
);
991 GetClientRect(&rect
);
992 ClientToScreen(&rect
);
994 CPoint point2
= point
;
995 if (point2
.x
< treelist
.left
+ REPOBROWSER_CTRL_MIN_WIDTH
)
996 point2
.x
= treelist
.left
+ REPOBROWSER_CTRL_MIN_WIDTH
;
997 if (point2
.x
> treelist
.right
- REPOBROWSER_CTRL_MIN_WIDTH
)
998 point2
.x
= treelist
.right
- REPOBROWSER_CTRL_MIN_WIDTH
;
1000 point
.x
-= rect
.left
;
1001 point
.y
-= treelist
.top
;
1003 OffsetRect(&treelist
, -treelist
.left
, -treelist
.top
);
1005 if (point
.x
< treelist
.left
+REPOBROWSER_CTRL_MIN_WIDTH
)
1006 point
.x
= treelist
.left
+REPOBROWSER_CTRL_MIN_WIDTH
;
1007 if (point
.x
> treelist
.right
-REPOBROWSER_CTRL_MIN_WIDTH
)
1008 point
.x
= treelist
.right
-REPOBROWSER_CTRL_MIN_WIDTH
;
1012 CDC
* pDC
= GetDC();
1013 DrawXorBar(pDC
, oldx
+ 2, treelistclient
.top
, 4, treelistclient
.bottom
- treelistclient
.top
- 2);
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
)
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
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();
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);
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
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
);
1120 CDC
* pDC
= GetDC();
1121 DrawXorBar(pDC
, point
.x
+ 2, treelistclient
.top
, 4, treelistclient
.bottom
- treelistclient
.top
- 2);
1127 CStandAloneDialogTmpl
<CResizableDialog
>::OnLButtonDown(nFlags
, point
);
1130 void CRepositoryBrowser::OnLButtonUp(UINT nFlags
, CPoint point
)
1132 if (bDragMode
== FALSE
)
1135 HandleDividerMove(point
, true);
1140 CStandAloneDialogTmpl
<CResizableDialog
>::OnLButtonUp(nFlags
, point
);
1143 void CRepositoryBrowser::OnCaptureChanged(CWnd
*pWnd
)
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
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
);
1175 BOOL
CRepositoryBrowser::OnSetCursor(CWnd
* pWnd
, UINT nHitTest
, UINT message
)
1181 GetClientRect(&rect
);
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
);
1202 return CStandAloneDialogTmpl
<CResizableDialog
>::OnSetCursor(pWnd
, nHitTest
, message
);
1205 void CRepositoryBrowser::FileSaveAs(const CString path
)
1207 CTGitPath
gitPath(path
);
1210 if (g_Git
.GetHash(hash
, m_sRevision
))
1212 MessageBox(g_Git
.GetGitLastErr(L
"Could not get hash of " + m_sRevision
+ L
'.'), L
"TortoiseGit", MB_ICONERROR
);
1217 filename
.Format(L
"%s\\%s-%s%s", (LPCTSTR
)g_Git
.CombinePath(gitPath
.GetContainingDirectory()), (LPCTSTR
)gitPath
.GetBaseFilename(), (LPCTSTR
)hash
.ToString().Left(g_Git
.GetShortHASHLength()), (LPCTSTR
)gitPath
.GetFileExtension());
1218 if (!CAppUtils::FileOpenSave(filename
, nullptr, 0, 0, false, GetSafeHwnd()))
1221 if (g_Git
.GetOneFile(m_sRevision
, gitPath
, filename
))
1224 out
.Format(IDS_STATUSLIST_CHECKOUTFILEFAILED
, (LPCTSTR
)gitPath
.GetGitPathString(), (LPCTSTR
)m_sRevision
, (LPCTSTR
)filename
);
1225 MessageBox(g_Git
.GetGitLastErr(out
, CGit::GIT_CMD_GETONEFILE
), L
"TortoiseGit", MB_ICONERROR
);
1229 void CRepositoryBrowser::OpenFile(const CString path
, eOpenType mode
, bool isSubmodule
, CGitHash itemHash
)
1231 CTGitPath
gitPath(path
);
1234 if (g_Git
.GetHash(hash
, m_sRevision
))
1236 MessageBox(g_Git
.GetGitLastErr(L
"Could not get hash of " + m_sRevision
+ L
'.'), L
"TortoiseGit", MB_ICONERROR
);
1240 CString file
= CTempFiles::Instance().GetTempFilePath(false, gitPath
, hash
).GetWinPathString();
1243 if (mode
== OPEN
&& !GitAdminDir::IsBareRepo(g_Git
.m_CurrentDir
))
1245 CTGitPath subPath
= CTGitPath(g_Git
.m_CurrentDir
);
1246 subPath
.AppendPathString(gitPath
.GetWinPathString());
1247 CAutoRepository
repo(subPath
.GetGitPathString());
1249 if (!repo
|| git_commit_lookup(commit
.GetPointer(), repo
, (const git_oid
*)itemHash
.m_hash
))
1252 out
.Format(IDS_REPOBROWSEASKSUBMODULEUPDATE
, (LPCTSTR
)itemHash
.ToString(), (LPCTSTR
)gitPath
.GetGitPathString());
1253 if (MessageBox(out
, L
"TortoiseGit", MB_YESNO
| MB_ICONQUESTION
) != IDYES
)
1257 sCmd
.Format(L
"/command:subupdate /bkpath:\"%s\" /selectedpath:\"%s\"", (LPCTSTR
)g_Git
.m_CurrentDir
, (LPCTSTR
)gitPath
.GetGitPathString());
1258 CAppUtils::RunTortoiseGitProc(sCmd
);
1263 cmd
.Format(L
"/command:repobrowser /path:\"%s\" /rev:%s", (LPCTSTR
)g_Git
.CombinePath(path
), (LPCTSTR
)itemHash
.ToString());
1264 CAppUtils::RunTortoiseGitProc(cmd
);
1269 CFile
submoduleCommit(file
, CFile::modeCreate
| CFile::modeWrite
);
1270 CStringA commitInfo
= "Subproject commit " + CStringA(itemHash
.ToString());
1271 submoduleCommit
.Write(commitInfo
, commitInfo
.GetLength());
1273 else if (g_Git
.GetOneFile(m_sRevision
, gitPath
, file
))
1276 out
.Format(IDS_STATUSLIST_CHECKOUTFILEFAILED
, (LPCTSTR
)gitPath
.GetGitPathString(), (LPCTSTR
)m_sRevision
, (LPCTSTR
)file
);
1277 MessageBox(g_Git
.GetGitLastErr(out
, CGit::GIT_CMD_GETONEFILE
), L
"TortoiseGit", MB_ICONERROR
);
1281 if (mode
== ALTERNATIVEEDITOR
)
1283 CAppUtils::LaunchAlternativeEditor(file
);
1286 else if (mode
== OPEN
)
1288 CAppUtils::ShellOpen(file
);
1292 CAppUtils::ShowOpenWithDialog(file
);
1294 bool CRepositoryBrowser::RevertItemToVersion(const CString
&path
)
1297 cmd
.Format(L
"git.exe checkout %s -- \"%s\"", (LPCTSTR
)m_sRevision
, (LPCTSTR
)path
);
1298 if (g_Git
.Run(cmd
, &out
, CP_UTF8
))
1300 if (MessageBox(out
, L
"TortoiseGit", MB_ICONEXCLAMATION
| MB_OKCANCEL
) == IDCANCEL
)
1307 void CRepositoryBrowser::CopyHashToClipboard(TShadowFilesTreeList
&selectedLeafs
)
1309 if (!selectedLeafs
.empty())
1313 for (size_t i
= 0; i
< selectedLeafs
.size(); ++i
)
1316 sClipdata
+= L
"\r\n";
1317 sClipdata
+= selectedLeafs
[i
]->m_hash
;
1320 CStringUtils::WriteAsciiStringToClipboard(sClipdata
, GetSafeHwnd());
1324 void CRepositoryBrowser::OnLvnBegindragRepolist(NMHDR
* pNMHDR
, LRESULT
* pResult
)
1328 // get selected paths
1329 POSITION pos
= m_RepoList
.GetFirstSelectedItemPosition();
1333 HTREEITEM hTreeItem
= m_RepoTree
.GetSelectedItem();
1339 auto pTree
= GetTreeEntry(hTreeItem
);
1343 CTGitPathList toExport
;
1345 while ((index
= m_RepoList
.GetNextSelectedItem(pos
)) >= 0)
1347 auto item
= GetListEntry(index
);
1348 if (item
->m_bFolder
)
1350 RecursivelyAdd(toExport
, item
);
1355 path
.SetFromGit(item
->GetFullName(), item
->m_bSubmodule
);
1356 toExport
.AddPath(path
);
1359 LPNMLISTVIEW pNMLV
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
1360 BeginDrag(m_RepoList
, toExport
, pTree
->GetFullName(), pNMLV
->ptAction
);
1363 void CRepositoryBrowser::RecursivelyAdd(CTGitPathList
& toExport
, CShadowFilesTree
* pTree
)
1365 if (!pTree
->m_bLoaded
)
1366 ReadTree(pTree
, pTree
->GetFullName(), true);
1368 for (auto itShadowTree
= pTree
->m_ShadowTree
.begin(); itShadowTree
!= pTree
->m_ShadowTree
.end(); ++itShadowTree
)
1370 if ((*itShadowTree
).second
.m_bFolder
)
1372 RecursivelyAdd(toExport
, &(*itShadowTree
).second
);
1376 path
.SetFromGit((*itShadowTree
).second
.GetFullName(), (*itShadowTree
).second
.m_bSubmodule
);
1377 toExport
.AddPath(path
);
1381 void CRepositoryBrowser::OnTvnBegindragRepotree(NMHDR
* pNMHDR
, LRESULT
* pResult
)
1385 LPNMTREEVIEW pNMTreeView
= reinterpret_cast<LPNMTREEVIEW
>(pNMHDR
);
1387 auto pTree
= GetTreeEntry(pNMTreeView
->itemNew
.hItem
);
1391 CTGitPathList toExport
;
1392 RecursivelyAdd(toExport
, pTree
);
1394 BeginDrag(m_RepoTree
, toExport
, pTree
->m_pParent
? pTree
->m_pParent
->GetFullName() : L
"", pNMTreeView
->ptDrag
);
1397 void CRepositoryBrowser::BeginDrag(const CWnd
& window
, CTGitPathList
& files
, const CString
& root
, POINT
& point
)
1400 if (g_Git
.GetHash(hash
, m_sRevision
))
1402 MessageBox(g_Git
.GetGitLastErr(L
"Could not get hash of " + m_sRevision
+ L
"."), L
"TortoiseGit", MB_ICONERROR
);
1406 // build copy source / content
1407 auto pdsrc
= std::make_unique
<CIDropSource
>();
1413 GitDataObject
* pdobj
= new GitDataObject(files
, hash
, root
.GetLength());
1417 pdobj
->SetAsyncMode(TRUE
);
1418 CDragSourceHelper dragsrchelper
;
1419 dragsrchelper
.InitializeFromWindow(window
.GetSafeHwnd(), point
, pdobj
);
1420 pdsrc
->m_pIDataObj
= pdobj
;
1421 pdsrc
->m_pIDataObj
->AddRef();
1423 // Initiate the Drag & Drop
1425 ::DoDragDrop(pdobj
, pdsrc
.get(), DROPEFFECT_MOVE
| DROPEFFECT_COPY
, &dwEffect
);
1432 CShadowFilesTree
* CRepositoryBrowser::GetListEntry(int index
)
1434 auto entry
= reinterpret_cast<CShadowFilesTree
*>(m_RepoList
.GetItemData(index
));
1439 CShadowFilesTree
* CRepositoryBrowser::GetTreeEntry(HTREEITEM treeItem
)
1441 auto entry
= reinterpret_cast<CShadowFilesTree
*>(m_RepoTree
.GetItemData(treeItem
));
1446 void CRepositoryBrowser::OnSysColorChange()
1448 __super::OnSysColorChange();
1449 CAppUtils::SetListCtrlBackgroundImage(m_RepoList
.GetSafeHwnd(), IDI_REPOBROWSER_BKG
);