1
// TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2021 - 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 #include "MessageBox.h"
42 #define OVERLAY_EXTERNAL 1
43 #define OVERLAY_EXECUTABLE 2
44 #define OVERLAY_SYMLINK 3
46 void SetSortArrowA(CListCtrl
* control
, int nColumn
, bool bAscending
)
52 CHeaderCtrl
* pHeader
= control
->GetHeaderCtrl();
53 HDITEM HeaderItem
= {0};
54 HeaderItem
.mask
= HDI_FORMAT
;
55 for (int i
= 0; i
< pHeader
->GetItemCount(); ++i
)
57 pHeader
->GetItem(i
, &HeaderItem
);
58 HeaderItem
.fmt
&= ~(HDF_SORTDOWN
| HDF_SORTUP
);
59 pHeader
->SetItem(i
, &HeaderItem
);
63 pHeader
->GetItem(nColumn
, &HeaderItem
);
64 HeaderItem
.fmt
|= (bAscending
? HDF_SORTUP
: HDF_SORTDOWN
);
65 pHeader
->SetItem(nColumn
, &HeaderItem
);
69 class CRepoListCompareFunc
72 CRepoListCompareFunc(CListCtrl
* pList
, int col
, bool desc
)
78 static int CALLBACK
StaticCompare(LPARAM lParam1
, LPARAM lParam2
, LPARAM lParamSort
)
80 return reinterpret_cast<CRepoListCompareFunc
*>(lParamSort
)->Compare(lParam1
, lParam2
);
83 int Compare(LPARAM lParam1
, LPARAM lParam2
)
85 auto pLeft
= reinterpret_cast<CShadowFilesTree
*>(m_pList
->GetItemData(static_cast<int>(lParam1
)));
86 auto pRight
= reinterpret_cast<CShadowFilesTree
*>(m_pList
->GetItemData(static_cast<int>(lParam2
)));
91 case CRepositoryBrowser::eCol_Name
:
92 result
= SortStrCmp(pLeft
->m_sName
, pRight
->m_sName
);
95 case CRepositoryBrowser::eCol_Extension
:
96 result
= m_pList
->GetItemText(static_cast<int>(lParam1
), 1).CompareNoCase(m_pList
->GetItemText(static_cast<int>(lParam2
), 1));
97 if (result
== 0) // if extensions are the same, use the filename to sort
98 result
= SortStrCmp(pRight
->m_sName
, pRight
->m_sName
);
101 case CRepositoryBrowser::eCol_FileSize
:
102 if (pLeft
->m_iSize
> pRight
->m_iSize
)
104 else if (pLeft
->m_iSize
< pRight
->m_iSize
)
107 result
= SortStrCmp(pLeft
->m_sName
, pRight
->m_sName
);
113 if (pLeft
->m_bFolder
!= pRight
->m_bFolder
)
115 if (pRight
->m_bFolder
)
123 int SortStrCmp(const CString
&left
, const CString
&right
)
125 if (CRepositoryBrowser::s_bSortLogical
)
126 return StrCmpLogicalW(left
, right
);
127 return StrCmpI(left
, right
);
135 // CRepositoryBrowser dialog
137 bool CRepositoryBrowser::s_bSortLogical
= true;
139 IMPLEMENT_DYNAMIC(CRepositoryBrowser
, CResizableStandAloneDialog
)
141 CRepositoryBrowser::CRepositoryBrowser(CString rev
, CWnd
* pParent
/*=nullptr*/)
142 : CResizableStandAloneDialog(CRepositoryBrowser::IDD
, pParent
)
144 , m_currSortDesc(false)
147 , m_ColumnManager(&m_RepoList
)
149 , m_nOpenIconFolder(0)
151 , m_nExecutableOvl(0)
159 CRepositoryBrowser::~CRepositoryBrowser()
163 void CRepositoryBrowser::DoDataExchange(CDataExchange
* pDX
)
165 CDialog::DoDataExchange(pDX
);
166 DDX_Control(pDX
, IDC_REPOTREE
, m_RepoTree
);
167 DDX_Control(pDX
, IDC_REPOLIST
, m_RepoList
);
171 BEGIN_MESSAGE_MAP(CRepositoryBrowser
, CResizableStandAloneDialog
)
172 ON_NOTIFY(TVN_SELCHANGED
, IDC_REPOTREE
, &CRepositoryBrowser::OnTvnSelchangedRepoTree
)
173 ON_NOTIFY(TVN_ITEMEXPANDING
, IDC_REPOTREE
, &CRepositoryBrowser::OnTvnItemExpandingRepoTree
)
175 ON_NOTIFY(LVN_COLUMNCLICK
, IDC_REPOLIST
, &CRepositoryBrowser::OnLvnColumnclickRepoList
)
176 ON_NOTIFY(LVN_ITEMCHANGED
, IDC_REPOLIST
, &CRepositoryBrowser::OnLvnItemchangedRepolist
)
177 ON_NOTIFY(NM_DBLCLK
, IDC_REPOLIST
, &CRepositoryBrowser::OnNMDblclk_RepoList
)
178 ON_BN_CLICKED(IDC_BUTTON_REVISION
, &CRepositoryBrowser::OnBnClickedButtonRevision
)
184 ON_NOTIFY(LVN_BEGINDRAG
, IDC_REPOLIST
, &CRepositoryBrowser::OnLvnBegindragRepolist
)
185 ON_NOTIFY(TVN_BEGINDRAG
, IDC_REPOTREE
, &CRepositoryBrowser::OnTvnBegindragRepotree
)
189 // CRepositoryBrowser message handlers
191 BOOL
CRepositoryBrowser::OnInitDialog()
193 CResizableStandAloneDialog::OnInitDialog();
194 CAppUtils::MarkWindowAsUnpinnable(m_hWnd
);
196 AddAnchor(IDC_STATIC_REPOURL
, TOP_LEFT
);
197 AddAnchor(IDC_REPOBROWSER_URL
, TOP_LEFT
, TOP_RIGHT
);
198 AddAnchor(IDC_STATIC_REF
, TOP_RIGHT
);
199 AddAnchor(IDC_BUTTON_REVISION
, TOP_RIGHT
);
200 AddAnchor(IDC_REPOTREE
, TOP_LEFT
, BOTTOM_LEFT
);
201 AddAnchor(IDC_REPOLIST
, TOP_LEFT
, BOTTOM_RIGHT
);
202 AddAnchor(IDHELP
, BOTTOM_RIGHT
);
203 AddAnchor(IDC_INFOLABEL
, BOTTOM_LEFT
, BOTTOM_RIGHT
);
204 AddAnchor(IDOK
, BOTTOM_RIGHT
);
205 AddAnchor(IDCANCEL
, BOTTOM_RIGHT
);
207 CRepositoryBrowser::s_bSortLogical
= !CRegDWORD(L
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER
);
208 if (CRepositoryBrowser::s_bSortLogical
)
209 CRepositoryBrowser::s_bSortLogical
= !CRegDWORD(L
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_LOCAL_MACHINE
);
211 static UINT columnNames
[] = { IDS_STATUSLIST_COLFILENAME
, IDS_STATUSLIST_COLEXT
, IDS_LOG_SIZE
};
212 static int columnWidths
[] = { CDPIAware::Instance().ScaleX(150), CDPIAware::Instance().ScaleX(100), CDPIAware::Instance().ScaleX(100) };
213 DWORD dwDefaultColumns
= (1 << eCol_Name
) | (1 << eCol_Extension
) | (1 << eCol_FileSize
);
214 m_ColumnManager
.SetNames(columnNames
, _countof(columnNames
));
215 constexpr int columnVersion
= 6; // adjust when changing number/names/etc. of columns
216 m_ColumnManager
.ReadSettings(dwDefaultColumns
, 0, L
"RepoBrowser", columnVersion
, _countof(columnNames
), columnWidths
);
217 m_ColumnManager
.SetRightAlign(m_ColumnManager
.GetColumnByName(IDS_LOG_SIZE
));
219 // set up the list control
220 // set the extended style of the list control
221 // 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.
222 CRegDWORD
regFullRowSelect(L
"Software\\TortoiseGit\\FullRowSelect", TRUE
);
223 DWORD exStyle
= LVS_EX_HEADERDRAGDROP
| LVS_EX_DOUBLEBUFFER
| LVS_EX_INFOTIP
| LVS_EX_SUBITEMIMAGES
;
224 if (DWORD(regFullRowSelect
))
225 exStyle
|= LVS_EX_FULLROWSELECT
;
226 m_RepoList
.SetExtendedStyle(exStyle
);
227 m_RepoList
.SetImageList(&SYS_IMAGE_LIST(), LVSIL_SMALL
);
228 CAppUtils::SetListCtrlBackgroundImage(m_RepoList
.GetSafeHwnd(), IDI_REPOBROWSER_BKG
);
230 m_RepoTree
.SetImageList(&SYS_IMAGE_LIST(), TVSIL_NORMAL
);
231 exStyle
= TVS_EX_FADEINOUTEXPANDOS
| TVS_EX_AUTOHSCROLL
| TVS_EX_DOUBLEBUFFER
;
232 m_RepoTree
.SetExtendedStyle(exStyle
, exStyle
);
234 m_nExternalOvl
= SYS_IMAGE_LIST().AddIcon(CCommonAppUtils::LoadIconEx(IDI_EXTERNALOVL
, 0, 0));
235 m_nExecutableOvl
= SYS_IMAGE_LIST().AddIcon(CCommonAppUtils::LoadIconEx(IDI_EXECUTABLEOVL
, 0, 0));
236 m_nSymlinkOvl
= SYS_IMAGE_LIST().AddIcon(CCommonAppUtils::LoadIconEx(IDI_SYMLINKOVL
, 0, 0));
237 // set externaloverlay in SYS_IMAGE_LIST() in Refresh method, so that it is updated after every launch of the logdialog
239 SetWindowTheme(m_RepoTree
.GetSafeHwnd(), L
"Explorer", nullptr);
240 SetWindowTheme(m_RepoList
.GetSafeHwnd(), L
"Explorer", nullptr);
245 CAutoThemeData hTheme
= OpenThemeData(m_RepoTree
, L
"TREEVIEW");
246 GetThemeMetric(hTheme
, NULL
, TVP_TREEITEM
, TREIS_NORMAL
, TMT_BORDERSIZE
, &borderWidth
);
249 borderWidth
= GetSystemMetrics(SM_CYBORDER
);
250 m_RepoTree
.SetItemHeight(static_cast<SHORT
>(m_RepoTree
.GetItemHeight() + 2 * borderWidth
));
252 m_nIconFolder
= SYS_IMAGE_LIST().GetDirIconIndex();
253 m_nOpenIconFolder
= SYS_IMAGE_LIST().GetDirOpenIconIndex();
255 EnableSaveRestore(L
"Reposbrowser");
257 DWORD xPos
= CRegDWORD(L
"Software\\TortoiseGit\\TortoiseProc\\ResizableState\\RepobrowserDivider", 0);
261 GetDlgItem(IDC_REPOTREE
)->GetClientRect(&rc
);
262 xPos
= rc
.right
- rc
.left
;
264 HandleDividerMove(CPoint(CDPIAware::Instance().ScaleX(xPos
+ 20), CDPIAware::Instance().ScaleY(10)), false);
266 CString sWindowTitle
;
267 GetWindowText(sWindowTitle
);
268 CAppUtils::SetWindowTitle(m_hWnd
, g_Git
.m_CurrentDir
, sWindowTitle
);
270 m_bHasWC
= !GitAdminDir::IsBareRepo(g_Git
.m_CurrentDir
);
274 UpdateDiffWithFileFromReg();
276 m_RepoList
.SetFocus();
281 void CRepositoryBrowser::UpdateDiffWithFileFromReg()
283 static CString lastDiffLaterFile
;
284 if (CString diffLaterFile
= CRegString(L
"Software\\TortoiseGit\\DiffLater", L
""); !diffLaterFile
.IsEmpty() && lastDiffLaterFile
!= diffLaterFile
)
286 lastDiffLaterFile
= diffLaterFile
;
287 m_sMarkForDiffFilename
= diffLaterFile
;
288 m_sMarkForDiffVersion
.Empty();
292 void CRepositoryBrowser::OnDestroy()
294 int maxcol
= m_ColumnManager
.GetColumnCount();
295 for (int col
= 0; col
< maxcol
; ++col
)
296 if (m_ColumnManager
.IsVisible(col
))
297 m_ColumnManager
.ColumnResized(col
);
298 m_ColumnManager
.WriteSettings();
300 CResizableStandAloneDialog::OnDestroy();
303 void CRepositoryBrowser::OnOK()
305 if (GetFocus() == &m_RepoList
&& (GetKeyState(VK_MENU
) & 0x8000) == 0)
307 // list control has focus: 'enter' the folder
308 if (m_RepoList
.GetSelectedCount() != 1)
311 POSITION pos
= m_RepoList
.GetFirstSelectedItemPosition();
314 auto item
= GetListEntry(m_RepoList
.GetNextSelectedItem(pos
));
317 FillListCtrlForShadowTree(item
);
318 m_RepoTree
.SelectItem(item
->m_hTree
);
321 OpenFile(item
->GetFullName(), OPEN
, item
->m_bSubmodule
, item
->m_hash
);
326 SaveDividerPosition();
327 CResizableStandAloneDialog::OnOK();
330 void CRepositoryBrowser::OnCancel()
332 SaveDividerPosition();
333 CResizableStandAloneDialog::OnCancel();
336 void CRepositoryBrowser::OnNMDblclk_RepoList(NMHDR
*pNMHDR
, LRESULT
*pResult
)
340 LPNMITEMACTIVATE pNmItemActivate
= reinterpret_cast<LPNMITEMACTIVATE
>(pNMHDR
);
341 if (pNmItemActivate
->iItem
< 0)
344 auto pItem
= GetListEntry(pNmItemActivate
->iItem
);
348 if (!pItem
->m_bFolder
)
350 OpenFile(pItem
->GetFullName(), OPEN
, pItem
->m_bSubmodule
, pItem
->m_hash
);
355 FillListCtrlForShadowTree(pItem
);
356 m_RepoTree
.SelectItem(pItem
->m_hTree
);
360 void CRepositoryBrowser::Refresh()
363 if (m_nExternalOvl
>= 0)
364 SYS_IMAGE_LIST().SetOverlayImage(m_nExternalOvl
, OVERLAY_EXTERNAL
);
365 if (m_nExecutableOvl
>= 0)
366 SYS_IMAGE_LIST().SetOverlayImage(m_nExecutableOvl
, OVERLAY_EXECUTABLE
);
367 if (m_nSymlinkOvl
>= 0)
368 SYS_IMAGE_LIST().SetOverlayImage(m_nSymlinkOvl
, OVERLAY_SYMLINK
);
370 m_RepoTree
.DeleteAllItems();
371 m_RepoList
.DeleteAllItems();
372 m_TreeRoot
.m_ShadowTree
.clear();
373 m_TreeRoot
.m_sName
.Empty();
374 m_TreeRoot
.m_bFolder
= true;
376 TVINSERTSTRUCT tvinsert
= {0};
377 tvinsert
.hParent
= TVI_ROOT
;
378 tvinsert
.hInsertAfter
= TVI_ROOT
;
379 tvinsert
.itemex
.mask
= TVIF_DI_SETITEM
| TVIF_PARAM
| TVIF_TEXT
| TVIF_IMAGE
| TVIF_SELECTEDIMAGE
| TVIF_STATE
;
380 tvinsert
.itemex
.pszText
= const_cast<LPWSTR
>(L
"/");
381 tvinsert
.itemex
.lParam
= reinterpret_cast<LPARAM
>(&m_TreeRoot
);
382 tvinsert
.itemex
.iImage
= m_nIconFolder
;
383 tvinsert
.itemex
.iSelectedImage
= m_nOpenIconFolder
;
384 m_TreeRoot
.m_hTree
= m_RepoTree
.InsertItem(&tvinsert
);
386 ReadTree(&m_TreeRoot
);
387 m_RepoTree
.Expand(m_TreeRoot
.m_hTree
, TVE_EXPAND
);
388 FillListCtrlForShadowTree(&m_TreeRoot
);
389 m_RepoTree
.SelectItem(m_TreeRoot
.m_hTree
);
393 int CRepositoryBrowser::ReadTreeRecursive(git_repository
& repo
, const git_tree
* tree
, CShadowFilesTree
* treeroot
, bool recursive
)
395 size_t count
= git_tree_entrycount(tree
);
396 bool hasSubfolders
= false;
397 treeroot
->m_bLoaded
= true;
399 for (size_t i
= 0; i
< count
; ++i
)
401 const git_tree_entry
*entry
= git_tree_entry_byindex(tree
, i
);
405 const int mode
= git_tree_entry_filemode(entry
);
407 CString base
= CUnicodeUtils::GetUnicode(git_tree_entry_name(entry
));
409 const git_oid
*oid
= git_tree_entry_id(entry
);
410 CShadowFilesTree
* pNextTree
= &treeroot
->m_ShadowTree
[base
];
411 pNextTree
->m_sName
= base
;
412 pNextTree
->m_pParent
= treeroot
;
413 pNextTree
->m_hash
= oid
;
415 if (mode
== GIT_FILEMODE_COMMIT
)
416 pNextTree
->m_bSubmodule
= true;
417 else if (mode
& S_IFDIR
)
419 hasSubfolders
= true;
420 pNextTree
->m_bFolder
= true;
421 pNextTree
->m_bLoaded
= false;
423 TVINSERTSTRUCT tvinsert
= {0};
424 tvinsert
.hParent
= treeroot
->m_hTree
;
425 tvinsert
.hInsertAfter
= TVI_SORT
;
426 tvinsert
.itemex
.mask
= TVIF_DI_SETITEM
| TVIF_PARAM
| TVIF_TEXT
| TVIF_IMAGE
| TVIF_SELECTEDIMAGE
| TVIF_STATE
| TVIF_CHILDREN
;
427 tvinsert
.itemex
.pszText
= base
.GetBuffer(base
.GetLength());
428 tvinsert
.itemex
.cChildren
= 1;
429 tvinsert
.itemex
.lParam
= reinterpret_cast<LPARAM
>(pNextTree
);
430 tvinsert
.itemex
.iImage
= m_nIconFolder
;
431 tvinsert
.itemex
.iSelectedImage
= m_nOpenIconFolder
;
432 pNextTree
->m_hTree
= m_RepoTree
.InsertItem(&tvinsert
);
433 base
.ReleaseBuffer();
437 if (git_tree_lookup(subtree
.GetPointer(), &repo
, oid
))
439 MessageBox(CGit::GetLibGit2LastErr(L
"Could not lookup path."), L
"TortoiseGit", MB_ICONERROR
);
443 ReadTreeRecursive(repo
, subtree
, pNextTree
, recursive
);
448 if (mode
== GIT_FILEMODE_BLOB_EXECUTABLE
)
449 pNextTree
->m_bExecutable
= true;
450 if (mode
== GIT_FILEMODE_LINK
)
451 pNextTree
->m_bSymlink
= true;
453 git_blob_lookup(blob
.GetPointer(), &repo
, oid
);
457 pNextTree
->m_iSize
= git_blob_rawsize(blob
);
463 TVITEM tvitem
= { 0 };
464 tvitem
.hItem
= treeroot
->m_hTree
;
465 tvitem
.mask
= TVIF_CHILDREN
;
466 tvitem
.cChildren
= 0;
467 m_RepoTree
.SetItem(&tvitem
);
473 int CRepositoryBrowser::ReadTree(CShadowFilesTree
* treeroot
, const CString
& root
, bool recursive
)
476 CAutoRepository
repository(g_Git
.GetGitRepository());
479 MessageBox(CGit::GetLibGit2LastErr(L
"Could not open repository."), L
"TortoiseGit", MB_ICONERROR
);
483 if (m_sRevision
== L
"HEAD")
485 int ret
= git_repository_head_unborn(repository
);
486 if (ret
== 1) // is orphan
490 MessageBox(g_Git
.GetLibGit2LastErr(L
"Could not check HEAD."), L
"TortoiseGit", MB_ICONERROR
);
496 if (CGit::GetHash(repository
, hash
, m_sRevision
+ L
"^{}")) // add ^{} in order to dereference signed tags
498 MessageBox(CGit::GetLibGit2LastErr(L
"Could not get hash of \"" + m_sRevision
+ L
"^{}\"."), L
"TortoiseGit", MB_ICONERROR
);
503 if (git_commit_lookup(commit
.GetPointer(), repository
, hash
))
505 MessageBox(CGit::GetLibGit2LastErr(L
"Could not lookup commit."), L
"TortoiseGit", MB_ICONERROR
);
510 if (git_commit_tree(tree
.GetPointer(), commit
))
512 MessageBox(CGit::GetLibGit2LastErr(L
"Could not get tree of commit."), L
"TortoiseGit", MB_ICONERROR
);
518 CAutoTreeEntry treeEntry
;
519 if (git_tree_entry_bypath(treeEntry
.GetPointer(), tree
, CUnicodeUtils::GetUTF8(root
)))
521 MessageBox(CGit::GetLibGit2LastErr(L
"Could not lookup path."), L
"TortoiseGit", MB_ICONERROR
);
524 if (git_tree_entry_type(treeEntry
) != GIT_OBJECT_TREE
)
526 MessageBox(CGit::GetLibGit2LastErr(L
"Could not lookup path."), L
"TortoiseGit", MB_ICONERROR
);
531 if (git_tree_entry_to_object(object
.GetPointer(), repository
, treeEntry
))
533 MessageBox(CGit::GetLibGit2LastErr(L
"Could not lookup path."), L
"TortoiseGit", MB_ICONERROR
);
537 tree
.ConvertFrom(std::move(object
));
540 treeroot
->m_hash
= git_tree_id(tree
);
541 ReadTreeRecursive(*repository
, tree
, treeroot
, recursive
);
543 // try to resolve hash to a branch name
544 if (m_sRevision
== hash
.ToString())
547 if (CGit::GetMapHashToFriendName(repository
, map
))
548 MessageBox(g_Git
.GetLibGit2LastErr(L
"Could not get all refs."), L
"TortoiseGit", MB_ICONERROR
);
549 if (!map
[hash
].empty())
550 m_sRevision
= map
[hash
].at(0);
552 this->GetDlgItem(IDC_BUTTON_REVISION
)->SetWindowText(m_sRevision
);
557 void CRepositoryBrowser::OnTvnSelchangedRepoTree(NMHDR
*pNMHDR
, LRESULT
*pResult
)
559 LPNMTREEVIEW pNMTreeView
= reinterpret_cast<LPNMTREEVIEW
>(pNMHDR
);
562 FillListCtrlForTreeNode(pNMTreeView
->itemNew
.hItem
);
565 void CRepositoryBrowser::OnTvnItemExpandingRepoTree(NMHDR
*pNMHDR
, LRESULT
*pResult
)
567 LPNMTREEVIEW pNMTreeView
= reinterpret_cast<LPNMTREEVIEW
>(pNMHDR
);
570 auto pTree
= GetTreeEntry(pNMTreeView
->itemNew
.hItem
);
574 if (!pTree
->m_bLoaded
)
575 ReadTree(pTree
, pTree
->GetFullName());
578 void CRepositoryBrowser::FillListCtrlForTreeNode(HTREEITEM treeNode
)
580 m_RepoList
.DeleteAllItems();
582 auto pTree
= GetTreeEntry(treeNode
);
586 CString url
= L
'/' + pTree
->GetFullName();
587 GetDlgItem(IDC_REPOBROWSER_URL
)->SetWindowText(url
);
589 if (!pTree
->m_bLoaded
)
590 ReadTree(pTree
, pTree
->GetFullName());
592 FillListCtrlForShadowTree(pTree
);
595 void CRepositoryBrowser::FillListCtrlForShadowTree(CShadowFilesTree
* pTree
)
597 for (auto itShadowTree
= pTree
->m_ShadowTree
.cbegin(); itShadowTree
!= pTree
->m_ShadowTree
.cend(); ++itShadowTree
)
599 int icon
= m_nIconFolder
;
600 if (!(*itShadowTree
).second
.m_bFolder
&& !(*itShadowTree
).second
.m_bSubmodule
)
602 icon
= SYS_IMAGE_LIST().GetPathIconIndex((*itShadowTree
).second
.m_sName
);
605 int indexItem
= m_RepoList
.InsertItem(m_RepoList
.GetItemCount(), (*itShadowTree
).second
.m_sName
, icon
);
607 if ((*itShadowTree
).second
.m_bSubmodule
)
609 m_RepoList
.SetItemState(indexItem
, INDEXTOOVERLAYMASK(OVERLAY_EXTERNAL
), LVIS_OVERLAYMASK
);
611 if ((*itShadowTree
).second
.m_bExecutable
)
612 m_RepoList
.SetItemState(indexItem
, INDEXTOOVERLAYMASK(OVERLAY_EXECUTABLE
), LVIS_OVERLAYMASK
);
613 if ((*itShadowTree
).second
.m_bSymlink
)
614 m_RepoList
.SetItemState(indexItem
, INDEXTOOVERLAYMASK(OVERLAY_SYMLINK
), LVIS_OVERLAYMASK
);
615 m_RepoList
.SetItemData(indexItem
, reinterpret_cast<DWORD_PTR
>(&(*itShadowTree
).second
));
616 if (!(*itShadowTree
).second
.m_bFolder
&& !(*itShadowTree
).second
.m_bSubmodule
)
620 temp
= CPathUtils::GetFileExtFromPath((*itShadowTree
).second
.m_sName
);
621 m_RepoList
.SetItemText(indexItem
, eCol_Extension
, temp
);
623 StrFormatByteSize64((*itShadowTree
).second
.m_iSize
, CStrBuf(temp
, 20), 20);
624 m_RepoList
.SetItemText(indexItem
, eCol_FileSize
, temp
);
628 CRepoListCompareFunc
compareFunc(&m_RepoList
, m_currSortCol
, m_currSortDesc
);
629 m_RepoList
.SortItemsEx(&CRepoListCompareFunc::StaticCompare
, reinterpret_cast<DWORD_PTR
>(&compareFunc
));
631 SetSortArrowA(&m_RepoList
, m_currSortCol
, !m_currSortDesc
);
636 void CRepositoryBrowser::UpdateInfoLabel()
639 POSITION pos
= m_RepoList
.GetFirstSelectedItemPosition();
642 if (m_RepoList
.GetSelectedCount() > 1)
644 temp
.FormatMessage(IDS_REPOBROWSE_INFOMULTI
, m_RepoList
.GetSelectedCount());
648 int index
= m_RepoList
.GetNextSelectedItem(pos
);
649 auto item
= GetListEntry(index
);
650 if (item
->m_bSubmodule
)
651 temp
.FormatMessage(IDS_REPOBROWSE_INFOEXT
, static_cast<LPCWSTR
>(m_RepoList
.GetItemText(index
, eCol_Name
)), static_cast<LPCWSTR
>(item
->m_hash
.ToString()));
652 else if (item
->m_bFolder
)
653 temp
= m_RepoList
.GetItemText(index
, eCol_Name
);
655 temp
.FormatMessage(IDS_REPOBROWSE_INFOFILE
, static_cast<LPCWSTR
>(m_RepoList
.GetItemText(index
, eCol_Name
)), static_cast<LPCWSTR
>(m_RepoList
.GetItemText(index
, eCol_FileSize
)));
660 HTREEITEM hTreeItem
= m_RepoTree
.GetSelectedItem();
661 if (hTreeItem
!= nullptr)
663 auto pTree
= GetTreeEntry(hTreeItem
);
664 if (pTree
!= nullptr)
666 size_t files
= 0, submodules
= 0;
667 for (auto itShadowTree
= pTree
->m_ShadowTree
.cbegin(); itShadowTree
!= pTree
->m_ShadowTree
.cend(); ++itShadowTree
)
669 if (!(*itShadowTree
).second
.m_bFolder
&& !(*itShadowTree
).second
.m_bSubmodule
)
671 if ((*itShadowTree
).second
.m_bSubmodule
)
674 temp
.FormatMessage(IDS_REPOBROWSE_INFO
, static_cast<LPCWSTR
>(pTree
->m_sName
), files
, submodules
, pTree
->m_ShadowTree
.size() - files
- submodules
, pTree
->m_ShadowTree
.size());
678 SetDlgItemText(IDC_INFOLABEL
, temp
);
681 void CRepositoryBrowser::OnLvnItemchangedRepolist(NMHDR
* /* pNMHDR */, LRESULT
*pResult
)
687 void CRepositoryBrowser::OnContextMenu(CWnd
* pWndFrom
, CPoint point
)
689 if (pWndFrom
== &m_RepoList
)
691 CRect headerPosition
;
692 m_RepoList
.GetHeaderCtrl()->GetWindowRect(headerPosition
);
693 if (!headerPosition
.PtInRect(point
))
694 OnContextMenu_RepoList(point
);
696 else if (pWndFrom
== &m_RepoTree
)
697 OnContextMenu_RepoTree(point
);
700 void CRepositoryBrowser::OnContextMenu_RepoTree(CPoint point
)
702 CPoint clientPoint
= point
;
703 m_RepoTree
.ScreenToClient(&clientPoint
);
705 HTREEITEM hTreeItem
= m_RepoTree
.HitTest(clientPoint
);
709 TShadowFilesTreeList selectedLeafs
;
710 selectedLeafs
.push_back(GetTreeEntry(hTreeItem
));
712 ShowContextMenu(point
, selectedLeafs
, ONLY_FOLDERS
);
715 void CRepositoryBrowser::OnContextMenu_RepoList(CPoint point
)
717 TShadowFilesTreeList selectedLeafs
;
718 selectedLeafs
.reserve(m_RepoList
.GetSelectedCount());
720 bool folderSelected
= false;
721 bool filesSelected
= false;
722 bool submodulesSelected
= false;
724 POSITION pos
= m_RepoList
.GetFirstSelectedItemPosition();
727 auto item
= GetListEntry(m_RepoList
.GetNextSelectedItem(pos
));
728 if (item
->m_bSubmodule
)
729 submodulesSelected
= true;
731 folderSelected
= true;
733 filesSelected
= true;
734 selectedLeafs
.push_back(item
);
737 eSelectionType selType
= ONLY_FILES
;
738 if (folderSelected
&& filesSelected
)
739 selType
= MIXED_FOLDERS_FILES
;
740 else if (folderSelected
)
741 selType
= ONLY_FOLDERS
;
742 else if (submodulesSelected
)
743 selType
= ONLY_FILESSUBMODULES
;
744 ShowContextMenu(point
, selectedLeafs
, selType
);
747 void CRepositoryBrowser::ShowContextMenu(CPoint point
, TShadowFilesTreeList
&selectedLeafs
, eSelectionType selType
)
750 popupMenu
.CreatePopupMenu();
752 bool bAddSeparator
= false;
754 if (selectedLeafs
.size() == 1)
756 popupMenu
.AppendMenuIcon(eCmd_Open
, IDS_REPOBROWSE_OPEN
, IDI_OPEN
);
757 popupMenu
.SetDefaultItem(eCmd_Open
, FALSE
);
758 if (selType
== ONLY_FILES
|| selType
== ONLY_FILESSUBMODULES
)
760 popupMenu
.AppendMenuIcon(eCmd_OpenWith
, IDS_LOG_POPUP_OPENWITH
, IDI_OPEN
);
761 popupMenu
.AppendMenuIcon(eCmd_OpenWithAlternativeEditor
, IDS_LOG_POPUP_VIEWREV
, IDI_NOTEPAD
);
764 popupMenu
.AppendMenu(MF_SEPARATOR
);
766 if (m_bHasWC
&& (selType
== ONLY_FILES
|| selType
== ONLY_FILESSUBMODULES
))
768 popupMenu
.AppendMenuIcon(eCmd_CompareWC
, IDS_LOG_POPUP_COMPARE
, IDI_DIFF
);
769 bAddSeparator
= true;
773 popupMenu
.AppendMenu(MF_SEPARATOR
);
774 bAddSeparator
= false;
777 temp
.LoadString(IDS_MENULOG
);
778 popupMenu
.AppendMenuIcon(eCmd_ViewLog
, temp
, IDI_LOG
);
779 if (selectedLeafs
[0]->m_bSubmodule
)
781 temp
.LoadString(IDS_MENULOGSUBMODULE
);
782 popupMenu
.AppendMenuIcon(eCmd_ViewLogSubmodule
, temp
, IDI_LOG
);
785 if (selType
== ONLY_FILES
)
788 popupMenu
.AppendMenuIcon(eCmd_Blame
, IDS_LOG_POPUP_BLAME
, IDI_BLAME
);
790 popupMenu
.AppendMenu(MF_SEPARATOR
);
791 temp
.LoadString(IDS_LOG_POPUP_SAVE
);
792 popupMenu
.AppendMenuIcon(eCmd_SaveAs
, temp
, IDI_SAVEAS
);
795 bAddSeparator
= true;
798 if (!selectedLeafs
.empty() && selType
== ONLY_FILES
&& m_bHasWC
)
800 popupMenu
.AppendMenuIcon(eCmd_Revert
, IDS_LOG_POPUP_REVERTTOREV
, IDI_REVERT
);
801 bAddSeparator
= true;
805 popupMenu
.AppendMenu(MF_SEPARATOR
);
807 if (selectedLeafs
.size() == 1 && selType
== ONLY_FILES
)
809 popupMenu
.AppendMenuIcon(eCmd_PrepareDiff
, IDS_PREPAREDIFF
, IDI_DIFF
);
810 UpdateDiffWithFileFromReg();
811 if (!m_sMarkForDiffFilename
.IsEmpty())
814 if (selectedLeafs
.at(0)->GetFullName() == m_sMarkForDiffFilename
)
815 diffWith
= m_sMarkForDiffVersion
.ToString();
818 PathCompactPathEx(CStrBuf(diffWith
, 2 * GIT_HASH_SIZE
), m_sMarkForDiffFilename
, 2 * GIT_HASH_SIZE
, 0);
819 if (!m_sMarkForDiffVersion
.IsEmpty() || PathIsRelative(m_sMarkForDiffFilename
))
820 diffWith
+= L
':' + m_sMarkForDiffVersion
.ToString(g_Git
.GetShortHASHLength());
823 menuEntry
.Format(IDS_MENUDIFFNOW
, static_cast<LPCWSTR
>(diffWith
));
824 popupMenu
.AppendMenuIcon(eCmd_PrepareDiff_Compare
, menuEntry
, IDI_DIFF
);
826 popupMenu
.AppendMenu(MF_SEPARATOR
);
829 if (!selectedLeafs
.empty())
831 popupMenu
.AppendMenuIcon(eCmd_CopyPath
, IDS_STATUSLIST_CONTEXT_COPY
, IDI_COPYCLIP
);
832 popupMenu
.AppendMenuIcon(eCmd_CopyHash
, IDS_COPY_COMMIT_HASH
, IDI_COPYCLIP
);
835 eCmd cmd
= static_cast<eCmd
>(popupMenu
.TrackPopupMenuEx(TPM_LEFTALIGN
| TPM_RETURNCMD
, point
.x
, point
.y
, this, nullptr));
839 case eCmd_ViewLogSubmodule
:
842 sCmd
.Format(L
"/command:log /path:\"%s\"", static_cast<LPCWSTR
>(g_Git
.CombinePath(selectedLeafs
.at(0)->GetFullName())));
843 if (cmd
== eCmd_ViewLog
&& selectedLeafs
.at(0)->m_bSubmodule
)
844 sCmd
+= L
" /submodule";
845 if (cmd
== eCmd_ViewLog
)
846 sCmd
+= L
" /endrev:" + m_sRevision
;
847 CAppUtils::RunTortoiseGitProc(sCmd
);
852 CAppUtils::LaunchTortoiseBlame(g_Git
.CombinePath(selectedLeafs
.at(0)->GetFullName()), m_sRevision
);
856 if (!selectedLeafs
.at(0)->m_bSubmodule
&& selectedLeafs
.at(0)->m_bFolder
)
858 FillListCtrlForTreeNode(selectedLeafs
.at(0)->m_hTree
);
859 m_RepoTree
.SelectItem(selectedLeafs
.at(0)->m_hTree
);
862 OpenFile(selectedLeafs
.at(0)->GetFullName(), OPEN
, selectedLeafs
.at(0)->m_bSubmodule
, selectedLeafs
.at(0)->m_hash
);
865 OpenFile(selectedLeafs
.at(0)->GetFullName(), OPEN_WITH
, selectedLeafs
.at(0)->m_bSubmodule
, selectedLeafs
.at(0)->m_hash
);
867 case eCmd_OpenWithAlternativeEditor
:
868 OpenFile(selectedLeafs
.at(0)->GetFullName(), ALTERNATIVEEDITOR
, selectedLeafs
.at(0)->m_bSubmodule
, selectedLeafs
.at(0)->m_hash
);
872 CTGitPath
file(selectedLeafs
.at(0)->GetFullName());
873 CGitDiff::Diff(GetSafeHwnd(), &file
, &file
, GIT_REV_ZERO
, m_sRevision
);
879 for (auto itShadowTree
= selectedLeafs
.cbegin(); itShadowTree
!= selectedLeafs
.cend(); ++itShadowTree
)
881 if (RevertItemToVersion((*itShadowTree
)->GetFullName()))
887 msg
.FormatMessage(IDS_STATUSLIST_FILESREVERTED
, count
, static_cast<LPCWSTR
>(m_sRevision
));
888 MessageBox(msg
, L
"TortoiseGit", MB_OK
);
892 FileSaveAs(selectedLeafs
.at(0)->GetFullName());
897 for (auto itShadowTree
= selectedLeafs
.cbegin(); itShadowTree
!= selectedLeafs
.cend(); ++itShadowTree
)
899 sClipboard
+= (*itShadowTree
)->m_sName
+ L
"\r\n";
901 CStringUtils::WriteAsciiStringToClipboard(sClipboard
);
906 CopyHashToClipboard(selectedLeafs
);
909 case eCmd_PrepareDiff
:
910 m_sMarkForDiffFilename
= selectedLeafs
.at(0)->GetFullName();
911 if (g_Git
.GetHash(m_sMarkForDiffVersion
, m_sRevision
+ L
"^{}")) // add ^{} in order to dereference signed tags
913 m_sMarkForDiffFilename
.Empty();
914 MessageBox(g_Git
.GetGitLastErr(L
"Could not get SHA-1 for \"" + m_sRevision
+ L
"^{}\"."), L
"TortoiseGit", MB_ICONERROR
);
917 case eCmd_PrepareDiff_Compare
:
919 if (auto reg
= CRegString(L
"Software\\TortoiseGit\\DiffLater", L
""); m_sMarkForDiffFilename
== reg
)
921 CTGitPath
savedFile(m_sMarkForDiffFilename
);
922 CTGitPath
selectedFile(selectedLeafs
.at(0)->GetFullName());
923 CGitHash currentHash
;
924 if (g_Git
.GetHash(currentHash
, m_sRevision
+ L
"^{}")) // add ^{} in order to dereference signed tags
926 MessageBox(g_Git
.GetGitLastErr(L
"Could not get SHA-1 for \"" + m_sRevision
+ L
"^{}\"."), L
"TortoiseGit", MB_ICONERROR
);
929 CGitDiff::Diff(GetSafeHwnd(), &selectedFile
, &savedFile
, currentHash
.ToString(), m_sMarkForDiffVersion
.ToString());
935 BOOL
CRepositoryBrowser::PreTranslateMessage(MSG
* pMsg
)
937 if (pMsg
->message
== WM_KEYDOWN
)
939 switch (pMsg
->wParam
)
947 if (pMsg
->hwnd
== m_RepoList
.m_hWnd
&& (GetAsyncKeyState(VK_CONTROL
) & 0x8000))
949 // select all entries
950 for (int i
= 0; i
< m_RepoList
.GetItemCount(); ++i
)
951 m_RepoList
.SetItemState(i
, LVIS_SELECTED
, LVIS_SELECTED
);
958 return CResizableStandAloneDialog::PreTranslateMessage(pMsg
);
961 void CRepositoryBrowser::OnLvnColumnclickRepoList(NMHDR
*pNMHDR
, LRESULT
*pResult
)
963 LPNMLISTVIEW pNMLV
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
967 if (m_currSortCol
== pNMLV
->iSubItem
)
968 m_currSortDesc
= !m_currSortDesc
;
971 m_currSortCol
= pNMLV
->iSubItem
;
972 m_currSortDesc
= false;
975 CRepoListCompareFunc
compareFunc(&m_RepoList
, m_currSortCol
, m_currSortDesc
);
976 m_RepoList
.SortItemsEx(&CRepoListCompareFunc::StaticCompare
, reinterpret_cast<DWORD_PTR
>(&compareFunc
));
978 SetSortArrowA(&m_RepoList
, m_currSortCol
, !m_currSortDesc
);
981 void CRepositoryBrowser::OnBnClickedButtonRevision()
983 // use the git log to allow selection of a version
985 if (dlg
.IsThreadRunning())
987 CMessageBox::Show(GetSafeHwnd(), IDS_PROC_LOG_ONLYONCE
, IDS_APPNAME
, MB_ICONEXCLAMATION
);
990 dlg
.SetParams(CTGitPath(), CTGitPath(), m_sRevision
, m_sRevision
, 0);
991 // tell the dialog to use mode for selecting revisions
993 dlg
.ShowWorkingTreeChanges(false);
994 // only one revision must be selected however
995 dlg
.SingleSelection(true);
996 if (dlg
.DoModal() == IDOK
&& !dlg
.GetSelectedHash().empty())
998 m_sRevision
= dlg
.GetSelectedHash().at(0).ToString();
1001 BringWindowToTop(); /* cf. issue #3493 */
1004 void CRepositoryBrowser::SaveDividerPosition()
1007 GetDlgItem(IDC_REPOTREE
)->GetClientRect(&rc
);
1008 CRegDWORD
xPos(L
"Software\\TortoiseGit\\TortoiseProc\\ResizableState\\RepobrowserDivider");
1009 xPos
= CDPIAware::Instance().UnscaleX(rc
.right
- rc
.left
);
1012 void CRepositoryBrowser::HandleDividerMove(CPoint point
, bool bDraw
)
1014 RECT rect
, tree
, list
, treelist
, treelistclient
;
1016 // create an union of the tree and list control rectangle
1017 GetDlgItem(IDC_REPOLIST
)->GetWindowRect(&list
);
1018 GetDlgItem(IDC_REPOTREE
)->GetWindowRect(&tree
);
1019 UnionRect(&treelist
, &tree
, &list
);
1020 treelistclient
= treelist
;
1021 ScreenToClient(&treelistclient
);
1023 ClientToScreen(&point
);
1024 GetClientRect(&rect
);
1025 ClientToScreen(&rect
);
1027 auto minWidth
= CDPIAware::Instance().ScaleX(REPOBROWSER_CTRL_MIN_WIDTH
);
1028 CPoint point2
= point
;
1029 if (point2
.x
< treelist
.left
+ minWidth
)
1030 point2
.x
= treelist
.left
+ minWidth
;
1031 if (point2
.x
> treelist
.right
- minWidth
)
1032 point2
.x
= treelist
.right
- minWidth
;
1034 point
.x
-= rect
.left
;
1035 point
.y
-= treelist
.top
;
1037 OffsetRect(&treelist
, -treelist
.left
, -treelist
.top
);
1039 if (point
.x
< treelist
.left
+ minWidth
)
1040 point
.x
= treelist
.left
+ minWidth
;
1041 if (point
.x
> treelist
.right
- minWidth
)
1042 point
.x
= treelist
.right
- minWidth
;
1044 auto divWidth
= CDPIAware::Instance().ScaleX(2);
1048 CDC
* pDC
= GetDC();
1049 DrawXorBar(pDC
, oldx
- divWidth
, treelistclient
.top
, 2 * divWidth
, treelistclient
.bottom
- treelistclient
.top
- CDPIAware::Instance().ScaleY(2));
1056 //position the child controls
1057 GetDlgItem(IDC_REPOTREE
)->GetWindowRect(&treelist
);
1058 treelist
.right
= point2
.x
- divWidth
;
1059 ScreenToClient(&treelist
);
1060 RemoveAnchor(IDC_REPOTREE
);
1061 GetDlgItem(IDC_REPOTREE
)->MoveWindow(&treelist
);
1062 GetDlgItem(IDC_REPOLIST
)->GetWindowRect(&treelist
);
1063 treelist
.left
= point2
.x
+ divWidth
;
1064 ScreenToClient(&treelist
);
1065 RemoveAnchor(IDC_REPOLIST
);
1066 GetDlgItem(IDC_REPOLIST
)->MoveWindow(&treelist
);
1068 AddAnchor(IDC_REPOTREE
, TOP_LEFT
, BOTTOM_LEFT
);
1069 AddAnchor(IDC_REPOLIST
, TOP_LEFT
, BOTTOM_RIGHT
);
1072 void CRepositoryBrowser::OnMouseMove(UINT nFlags
, CPoint point
)
1074 if (bDragMode
== FALSE
)
1077 RECT rect
, tree
, list
, treelist
, treelistclient
;
1078 // create an union of the tree and list control rectangle
1079 GetDlgItem(IDC_REPOLIST
)->GetWindowRect(&list
);
1080 GetDlgItem(IDC_REPOTREE
)->GetWindowRect(&tree
);
1081 UnionRect(&treelist
, &tree
, &list
);
1082 treelistclient
= treelist
;
1083 ScreenToClient(&treelistclient
);
1085 //convert the mouse coordinates relative to the top-left of
1087 ClientToScreen(&point
);
1088 GetClientRect(&rect
);
1089 ClientToScreen(&rect
);
1090 point
.x
-= rect
.left
;
1091 point
.y
-= treelist
.top
;
1093 //same for the window coordinates - make them relative to 0,0
1094 OffsetRect(&treelist
, -treelist
.left
, -treelist
.top
);
1096 auto minWidth
= CDPIAware::Instance().ScaleX(REPOBROWSER_CTRL_MIN_WIDTH
);
1097 if (point
.x
< treelist
.left
+ minWidth
)
1098 point
.x
= treelist
.left
+ minWidth
;
1099 if (point
.x
> treelist
.right
- minWidth
)
1100 point
.x
= treelist
.right
- minWidth
;
1102 if ((nFlags
& MK_LBUTTON
) && (point
.x
!= oldx
))
1104 CDC
* pDC
= GetDC();
1108 auto divWidth
= CDPIAware::Instance().ScaleX(2);
1109 auto divHeight
= CDPIAware::Instance().ScaleY(2);
1110 DrawXorBar(pDC
, oldx
- divWidth
, treelistclient
.top
, 2 * divWidth
, treelistclient
.bottom
- treelistclient
.top
- divHeight
);
1111 DrawXorBar(pDC
, point
.x
- divWidth
, treelistclient
.top
, 2 * divWidth
, treelistclient
.bottom
- treelistclient
.top
- divHeight
);
1120 CStandAloneDialogTmpl
<CResizableDialog
>::OnMouseMove(nFlags
, point
);
1123 void CRepositoryBrowser::OnLButtonDown(UINT nFlags
, CPoint point
)
1125 RECT rect
, tree
, list
, treelist
, treelistclient
;
1127 // create an union of the tree and list control rectangle
1128 GetDlgItem(IDC_REPOLIST
)->GetWindowRect(&list
);
1129 GetDlgItem(IDC_REPOTREE
)->GetWindowRect(&tree
);
1130 UnionRect(&treelist
, &tree
, &list
);
1131 treelistclient
= treelist
;
1132 ScreenToClient(&treelistclient
);
1134 //convert the mouse coordinates relative to the top-left of
1136 ClientToScreen(&point
);
1137 GetClientRect(&rect
);
1138 ClientToScreen(&rect
);
1139 point
.x
-= rect
.left
;
1140 point
.y
-= treelist
.top
;
1142 //same for the window coordinates - make them relative to 0,0
1143 OffsetRect(&treelist
, -treelist
.left
, -treelist
.top
);
1145 auto minWidth
= CDPIAware::Instance().ScaleX(REPOBROWSER_CTRL_MIN_WIDTH
);
1147 if (point
.x
< treelist
.left
+ minWidth
)
1148 return CStandAloneDialogTmpl
< CResizableDialog
>::OnLButtonDown(nFlags
, point
);
1149 if (point
.x
> treelist
.right
- CDPIAware::Instance().ScaleX(3))
1150 return CStandAloneDialogTmpl
< CResizableDialog
>::OnLButtonDown(nFlags
, point
);
1151 if (point
.x
> treelist
.right
- minWidth
)
1152 point
.x
= treelist
.right
- minWidth
;
1154 auto divHeight
= CDPIAware::Instance().ScaleY(3);
1155 if ((point
.y
< treelist
.top
+ divHeight
) || (point
.y
> treelist
.bottom
- divHeight
))
1156 return CStandAloneDialogTmpl
<CResizableDialog
>::OnLButtonDown(nFlags
, point
);
1162 auto divWidth
= CDPIAware::Instance().ScaleX(2);
1163 CDC
* pDC
= GetDC();
1164 DrawXorBar(pDC
, point
.x
- divWidth
, treelistclient
.top
, 2 * divWidth
, treelistclient
.bottom
- treelistclient
.top
- CDPIAware::Instance().ScaleY(2));
1170 CStandAloneDialogTmpl
<CResizableDialog
>::OnLButtonDown(nFlags
, point
);
1173 void CRepositoryBrowser::OnLButtonUp(UINT nFlags
, CPoint point
)
1175 if (bDragMode
== FALSE
)
1178 HandleDividerMove(point
, true);
1183 CStandAloneDialogTmpl
<CResizableDialog
>::OnLButtonUp(nFlags
, point
);
1186 void CRepositoryBrowser::OnCaptureChanged(CWnd
*pWnd
)
1190 __super::OnCaptureChanged(pWnd
);
1193 void CRepositoryBrowser::DrawXorBar(CDC
* pDC
, int x1
, int y1
, int width
, int height
)
1195 static WORD _dotPatternBmp
[8] =
1197 0x0055, 0x00aa, 0x0055, 0x00aa,
1198 0x0055, 0x00aa, 0x0055, 0x00aa
1202 HBRUSH hbr
, hbrushOld
;
1204 hbm
= CreateBitmap(8, 8, 1, 1, _dotPatternBmp
);
1205 hbr
= CreatePatternBrush(hbm
);
1207 pDC
->SetBrushOrg(x1
, y1
);
1208 hbrushOld
= static_cast<HBRUSH
>(pDC
->SelectObject(hbr
));
1210 PatBlt(pDC
->GetSafeHdc(), x1
, y1
, width
, height
, PATINVERT
);
1212 pDC
->SelectObject(hbrushOld
);
1218 BOOL
CRepositoryBrowser::OnSetCursor(CWnd
* pWnd
, UINT nHitTest
, UINT message
)
1224 GetClientRect(&rect
);
1226 ScreenToClient(&pt
);
1227 if (PtInRect(&rect
, pt
))
1229 ClientToScreen(&pt
);
1230 // are we right of the tree control?
1231 GetDlgItem(IDC_REPOTREE
)->GetWindowRect(&rect
);
1232 auto divHeight
= CDPIAware::Instance().ScaleY(3);
1233 if ((pt
.x
> rect
.right
) && (pt
.y
>= rect
.top
+ divHeight
) && (pt
.y
<= rect
.bottom
- divHeight
))
1235 // but left of the list control?
1236 GetDlgItem(IDC_REPOLIST
)->GetWindowRect(&rect
);
1237 if (pt
.x
< rect
.left
)
1239 HCURSOR hCur
= LoadCursor(nullptr, IDC_SIZEWE
);
1246 return CStandAloneDialogTmpl
<CResizableDialog
>::OnSetCursor(pWnd
, nHitTest
, message
);
1249 void CRepositoryBrowser::FileSaveAs(const CString path
)
1251 CTGitPath
gitPath(path
);
1254 if (g_Git
.GetHash(hash
, m_sRevision
+ L
"^{}")) // add ^{} in order to dereference signed tags
1256 MessageBox(g_Git
.GetGitLastErr(L
"Could not get hash of \"" + m_sRevision
+ L
"^{}\"."), L
"TortoiseGit", MB_ICONERROR
);
1261 filename
.Format(L
"%s\\%s-%s%s", static_cast<LPCWSTR
>(g_Git
.CombinePath(gitPath
.GetContainingDirectory())), static_cast<LPCWSTR
>(gitPath
.GetBaseFilename()), static_cast<LPCWSTR
>(hash
.ToString(g_Git
.GetShortHASHLength())), static_cast<LPCWSTR
>(gitPath
.GetFileExtension()));
1262 if (!CAppUtils::FileOpenSave(filename
, nullptr, 0, 0, false, GetSafeHwnd()))
1265 if (g_Git
.GetOneFile(m_sRevision
, gitPath
, filename
))
1268 out
.FormatMessage(IDS_STATUSLIST_CHECKOUTFILEFAILED
, static_cast<LPCWSTR
>(gitPath
.GetGitPathString()), static_cast<LPCWSTR
>(m_sRevision
), static_cast<LPCWSTR
>(filename
));
1269 MessageBox(g_Git
.GetGitLastErr(out
, CGit::GIT_CMD_GETONEFILE
), L
"TortoiseGit", MB_ICONERROR
);
1273 void CRepositoryBrowser::OpenFile(const CString path
, eOpenType mode
, bool isSubmodule
, const CGitHash
& itemHash
)
1275 CTGitPath
gitPath(path
);
1278 if (g_Git
.GetHash(hash
, m_sRevision
+ L
"^{}")) // add ^{} in order to dereference signed tags
1280 MessageBox(g_Git
.GetGitLastErr(L
"Could not get hash of \"" + m_sRevision
+ L
"^{}\"."), L
"TortoiseGit", MB_ICONERROR
);
1284 CString file
= CTempFiles::Instance().GetTempFilePath(false, gitPath
, hash
).GetWinPathString();
1287 if (mode
== OPEN
&& !GitAdminDir::IsBareRepo(g_Git
.m_CurrentDir
))
1289 CTGitPath subPath
= CTGitPath(g_Git
.m_CurrentDir
);
1290 subPath
.AppendPathString(gitPath
.GetWinPathString());
1291 CAutoRepository
repo(subPath
.GetGitPathString());
1293 if (!repo
|| git_commit_lookup(commit
.GetPointer(), repo
, itemHash
))
1296 out
.FormatMessage(IDS_REPOBROWSEASKSUBMODULEUPDATE
, static_cast<LPCWSTR
>(itemHash
.ToString()), static_cast<LPCWSTR
>(gitPath
.GetGitPathString()));
1297 if (MessageBox(out
, L
"TortoiseGit", MB_YESNO
| MB_ICONQUESTION
) != IDYES
)
1301 sCmd
.Format(L
"/command:subupdate /bkpath:\"%s\" /selectedpath:\"%s\"", static_cast<LPCWSTR
>(g_Git
.m_CurrentDir
), static_cast<LPCWSTR
>(gitPath
.GetGitPathString()));
1302 CAppUtils::RunTortoiseGitProc(sCmd
);
1307 cmd
.Format(L
"/command:repobrowser /path:\"%s\" /rev:%s", static_cast<LPCWSTR
>(g_Git
.CombinePath(path
)), static_cast<LPCWSTR
>(itemHash
.ToString()));
1308 CAppUtils::RunTortoiseGitProc(cmd
);
1313 CFile
submoduleCommit(file
, CFile::modeCreate
| CFile::modeWrite
);
1314 CStringA commitInfo
= "Subproject commit " + CStringA(itemHash
.ToString());
1315 submoduleCommit
.Write(commitInfo
, commitInfo
.GetLength());
1317 else if (g_Git
.GetOneFile(m_sRevision
, gitPath
, file
))
1320 out
.FormatMessage(IDS_STATUSLIST_CHECKOUTFILEFAILED
, static_cast<LPCWSTR
>(gitPath
.GetGitPathString()), static_cast<LPCWSTR
>(m_sRevision
), static_cast<LPCWSTR
>(file
));
1321 MessageBox(g_Git
.GetGitLastErr(out
, CGit::GIT_CMD_GETONEFILE
), L
"TortoiseGit", MB_ICONERROR
);
1325 if (mode
== ALTERNATIVEEDITOR
)
1327 CAppUtils::LaunchAlternativeEditor(file
);
1330 else if (mode
== OPEN
)
1332 CAppUtils::ShellOpen(file
);
1336 CAppUtils::ShowOpenWithDialog(file
);
1338 bool CRepositoryBrowser::RevertItemToVersion(const CString
&path
)
1341 cmd
.Format(L
"git.exe checkout %s -- \"%s\"", static_cast<LPCWSTR
>(m_sRevision
), static_cast<LPCWSTR
>(path
));
1342 if (g_Git
.Run(cmd
, &out
, CP_UTF8
))
1344 if (MessageBox(out
, L
"TortoiseGit", MB_ICONEXCLAMATION
| MB_OKCANCEL
) == IDCANCEL
)
1351 void CRepositoryBrowser::CopyHashToClipboard(TShadowFilesTreeList
&selectedLeafs
)
1353 if (!selectedLeafs
.empty())
1357 for (size_t i
= 0; i
< selectedLeafs
.size(); ++i
)
1360 sClipdata
+= L
"\r\n";
1361 sClipdata
+= selectedLeafs
[i
]->m_hash
.ToString();
1364 CStringUtils::WriteAsciiStringToClipboard(sClipdata
, GetSafeHwnd());
1368 void CRepositoryBrowser::OnLvnBegindragRepolist(NMHDR
* pNMHDR
, LRESULT
* pResult
)
1372 // get selected paths
1373 POSITION pos
= m_RepoList
.GetFirstSelectedItemPosition();
1377 HTREEITEM hTreeItem
= m_RepoTree
.GetSelectedItem();
1383 auto pTree
= GetTreeEntry(hTreeItem
);
1387 CTGitPathList toExport
;
1389 while ((index
= m_RepoList
.GetNextSelectedItem(pos
)) >= 0)
1391 auto item
= GetListEntry(index
);
1392 if (item
->m_bFolder
)
1394 RecursivelyAdd(toExport
, item
);
1399 path
.SetFromGit(item
->GetFullName(), item
->m_bSubmodule
);
1400 toExport
.AddPath(path
);
1403 LPNMLISTVIEW pNMLV
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
1404 BeginDrag(m_RepoList
, toExport
, pTree
->GetFullName(), pNMLV
->ptAction
);
1407 void CRepositoryBrowser::RecursivelyAdd(CTGitPathList
& toExport
, CShadowFilesTree
* pTree
)
1409 if (!pTree
->m_bLoaded
)
1410 ReadTree(pTree
, pTree
->GetFullName(), true);
1412 for (auto itShadowTree
= pTree
->m_ShadowTree
.begin(); itShadowTree
!= pTree
->m_ShadowTree
.end(); ++itShadowTree
)
1414 if ((*itShadowTree
).second
.m_bFolder
)
1416 RecursivelyAdd(toExport
, &(*itShadowTree
).second
);
1420 path
.SetFromGit((*itShadowTree
).second
.GetFullName(), (*itShadowTree
).second
.m_bSubmodule
);
1421 toExport
.AddPath(path
);
1425 void CRepositoryBrowser::OnTvnBegindragRepotree(NMHDR
* pNMHDR
, LRESULT
* pResult
)
1429 LPNMTREEVIEW pNMTreeView
= reinterpret_cast<LPNMTREEVIEW
>(pNMHDR
);
1431 auto pTree
= GetTreeEntry(pNMTreeView
->itemNew
.hItem
);
1435 CTGitPathList toExport
;
1436 RecursivelyAdd(toExport
, pTree
);
1438 BeginDrag(m_RepoTree
, toExport
, pTree
->m_pParent
? pTree
->m_pParent
->GetFullName() : CString(), pNMTreeView
->ptDrag
);
1441 void CRepositoryBrowser::BeginDrag(const CWnd
& window
, CTGitPathList
& files
, const CString
& root
, POINT
& point
)
1444 if (g_Git
.GetHash(hash
, m_sRevision
+ L
"^{}")) // add ^{} in order to dereference signed tags
1446 MessageBox(g_Git
.GetGitLastErr(L
"Could not get hash of \"" + m_sRevision
+ L
"^{}\"."), L
"TortoiseGit", MB_ICONERROR
);
1450 // build copy source / content
1451 auto pdsrc
= std::make_unique
<CIDropSource
>();
1457 GitDataObject
* pdobj
= new GitDataObject(files
, hash
, root
.GetLength());
1461 pdobj
->SetAsyncMode(TRUE
);
1462 CDragSourceHelper dragsrchelper
;
1463 dragsrchelper
.InitializeFromWindow(window
.GetSafeHwnd(), point
, pdobj
);
1464 pdsrc
->m_pIDataObj
= pdobj
;
1465 pdsrc
->m_pIDataObj
->AddRef();
1467 // Initiate the Drag & Drop
1469 ::DoDragDrop(pdobj
, pdsrc
.get(), DROPEFFECT_MOVE
| DROPEFFECT_COPY
, &dwEffect
);
1476 CShadowFilesTree
* CRepositoryBrowser::GetListEntry(int index
)
1478 auto entry
= reinterpret_cast<CShadowFilesTree
*>(m_RepoList
.GetItemData(index
));
1483 CShadowFilesTree
* CRepositoryBrowser::GetTreeEntry(HTREEITEM treeItem
)
1485 auto entry
= reinterpret_cast<CShadowFilesTree
*>(m_RepoTree
.GetItemData(treeItem
));