1
// TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2019 - TortoiseGit
4 // Copyright (C) 2003-2013 - TortoiseSVN
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 // RepositoryBrowser.cpp : implementation file
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"
41 #define OVERLAY_EXTERNAL 1
42 #define OVERLAY_EXECUTABLE 2
43 #define OVERLAY_SYMLINK 3
45 void SetSortArrowA(CListCtrl
* control
, int nColumn
, bool bAscending
)
51 CHeaderCtrl
* pHeader
= control
->GetHeaderCtrl();
52 HDITEM HeaderItem
= {0};
53 HeaderItem
.mask
= HDI_FORMAT
;
54 for (int i
= 0; i
< pHeader
->GetItemCount(); ++i
)
56 pHeader
->GetItem(i
, &HeaderItem
);
57 HeaderItem
.fmt
&= ~(HDF_SORTDOWN
| HDF_SORTUP
);
58 pHeader
->SetItem(i
, &HeaderItem
);
62 pHeader
->GetItem(nColumn
, &HeaderItem
);
63 HeaderItem
.fmt
|= (bAscending
? HDF_SORTUP
: HDF_SORTDOWN
);
64 pHeader
->SetItem(nColumn
, &HeaderItem
);
68 class CRepoListCompareFunc
71 CRepoListCompareFunc(CListCtrl
* pList
, int col
, bool desc
)
77 static int CALLBACK
StaticCompare(LPARAM lParam1
, LPARAM lParam2
, LPARAM lParamSort
)
79 return reinterpret_cast<CRepoListCompareFunc
*>(lParamSort
)->Compare(lParam1
, lParam2
);
82 int Compare(LPARAM lParam1
, LPARAM lParam2
)
84 auto pLeft
= reinterpret_cast<CShadowFilesTree
*>(m_pList
->GetItemData((int)lParam1
));
85 auto pRight
= reinterpret_cast<CShadowFilesTree
*>(m_pList
->GetItemData((int)lParam2
));
90 case CRepositoryBrowser::eCol_Name
:
91 result
= SortStrCmp(pLeft
->m_sName
, pRight
->m_sName
);
94 case CRepositoryBrowser::eCol_Extension
:
95 result
= m_pList
->GetItemText(static_cast<int>(lParam1
), 1).CompareNoCase(m_pList
->GetItemText(static_cast<int>(lParam2
), 1));
96 if (result
== 0) // if extensions are the same, use the filename to sort
97 result
= SortStrCmp(pRight
->m_sName
, pRight
->m_sName
);
100 case CRepositoryBrowser::eCol_FileSize
:
101 if (pLeft
->m_iSize
> pRight
->m_iSize
)
103 else if (pLeft
->m_iSize
< pRight
->m_iSize
)
106 result
= SortStrCmp(pLeft
->m_sName
, pRight
->m_sName
);
112 if (pLeft
->m_bFolder
!= pRight
->m_bFolder
)
114 if (pRight
->m_bFolder
)
122 int SortStrCmp(const CString
&left
, const CString
&right
)
124 if (CRepositoryBrowser::s_bSortLogical
)
125 return StrCmpLogicalW(left
, right
);
126 return StrCmpI(left
, right
);
134 // CRepositoryBrowser dialog
136 bool CRepositoryBrowser::s_bSortLogical
= true;
138 IMPLEMENT_DYNAMIC(CRepositoryBrowser
, CResizableStandAloneDialog
)
140 CRepositoryBrowser::CRepositoryBrowser(CString rev
, CWnd
* pParent
/*=nullptr*/)
141 : CResizableStandAloneDialog(CRepositoryBrowser::IDD
, pParent
)
143 , m_currSortDesc(false)
146 , m_ColumnManager(&m_RepoList
)
148 , m_nOpenIconFolder(0)
150 , m_nExecutableOvl(0)
158 CRepositoryBrowser::~CRepositoryBrowser()
162 void CRepositoryBrowser::DoDataExchange(CDataExchange
* pDX
)
164 CDialog::DoDataExchange(pDX
);
165 DDX_Control(pDX
, IDC_REPOTREE
, m_RepoTree
);
166 DDX_Control(pDX
, IDC_REPOLIST
, m_RepoList
);
170 BEGIN_MESSAGE_MAP(CRepositoryBrowser
, CResizableStandAloneDialog
)
171 ON_NOTIFY(TVN_SELCHANGED
, IDC_REPOTREE
, &CRepositoryBrowser::OnTvnSelchangedRepoTree
)
172 ON_NOTIFY(TVN_ITEMEXPANDING
, IDC_REPOTREE
, &CRepositoryBrowser::OnTvnItemExpandingRepoTree
)
174 ON_NOTIFY(LVN_COLUMNCLICK
, IDC_REPOLIST
, &CRepositoryBrowser::OnLvnColumnclickRepoList
)
175 ON_NOTIFY(LVN_ITEMCHANGED
, IDC_REPOLIST
, &CRepositoryBrowser::OnLvnItemchangedRepolist
)
176 ON_NOTIFY(NM_DBLCLK
, IDC_REPOLIST
, &CRepositoryBrowser::OnNMDblclk_RepoList
)
177 ON_BN_CLICKED(IDC_BUTTON_REVISION
, &CRepositoryBrowser::OnBnClickedButtonRevision
)
183 ON_WM_SYSCOLORCHANGE()
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
[] = { 150, 100, 100 };
213 DWORD dwDefaultColumns
= (1 << eCol_Name
) | (1 << eCol_Extension
) | (1 << eCol_FileSize
);
214 m_ColumnManager
.SetNames(columnNames
, _countof(columnNames
));
215 m_ColumnManager
.ReadSettings(dwDefaultColumns
, 0, L
"RepoBrowser", _countof(columnNames
), columnWidths
);
216 m_ColumnManager
.SetRightAlign(2);
218 // set up the list control
219 // set the extended style of the list control
220 // the style LVS_EX_FULLROWSELECT interferes with the background watermark image but it's more important to be able to select in the whole row.
221 CRegDWORD
regFullRowSelect(L
"Software\\TortoiseGit\\FullRowSelect", TRUE
);
222 DWORD exStyle
= LVS_EX_HEADERDRAGDROP
| LVS_EX_DOUBLEBUFFER
| LVS_EX_INFOTIP
| LVS_EX_SUBITEMIMAGES
;
223 if (DWORD(regFullRowSelect
))
224 exStyle
|= LVS_EX_FULLROWSELECT
;
225 m_RepoList
.SetExtendedStyle(exStyle
);
226 m_RepoList
.SetImageList(&SYS_IMAGE_LIST(), LVSIL_SMALL
);
227 CAppUtils::SetListCtrlBackgroundImage(m_RepoList
.GetSafeHwnd(), IDI_REPOBROWSER_BKG
);
229 m_RepoTree
.SetImageList(&SYS_IMAGE_LIST(), TVSIL_NORMAL
);
230 exStyle
= TVS_EX_FADEINOUTEXPANDOS
| TVS_EX_AUTOHSCROLL
| TVS_EX_DOUBLEBUFFER
;
231 m_RepoTree
.SetExtendedStyle(exStyle
, exStyle
);
233 m_nExternalOvl
= SYS_IMAGE_LIST().AddIcon(CCommonAppUtils::LoadIconEx(IDI_EXTERNALOVL
, 0, 0));
234 m_nExecutableOvl
= SYS_IMAGE_LIST().AddIcon(CCommonAppUtils::LoadIconEx(IDI_EXECUTABLEOVL
, 0, 0));
235 m_nSymlinkOvl
= SYS_IMAGE_LIST().AddIcon(CCommonAppUtils::LoadIconEx(IDI_SYMLINKOVL
, 0, 0));
236 // set externaloverlay in SYS_IMAGE_LIST() in Refresh method, so that it is updated after every launch of the logdialog
238 SetWindowTheme(m_RepoTree
.GetSafeHwnd(), L
"Explorer", nullptr);
239 SetWindowTheme(m_RepoList
.GetSafeHwnd(), L
"Explorer", nullptr);
244 HTHEME hTheme
= OpenThemeData(m_RepoTree
, L
"TREEVIEW");
245 GetThemeMetric(hTheme
, NULL
, TVP_TREEITEM
, TREIS_NORMAL
, TMT_BORDERSIZE
, &borderWidth
);
246 CloseThemeData(hTheme
);
249 borderWidth
= GetSystemMetrics(SM_CYBORDER
);
250 m_RepoTree
.SetItemHeight((SHORT
)(m_RepoTree
.GetItemHeight() + 2 * borderWidth
));
252 m_nIconFolder
= SYS_IMAGE_LIST().GetDirIconIndex();
253 m_nOpenIconFolder
= SYS_IMAGE_LIST().GetDirOpenIconIndex();
255 EnableSaveRestore(L
"Reposbrowser");
257 DWORD xPos
= CRegDWORD(L
"Software\\TortoiseGit\\TortoiseProc\\ResizableState\\RepobrowserDivider", 0);
261 GetDlgItem(IDC_REPOTREE
)->GetClientRect(&rc
);
262 xPos
= rc
.right
- rc
.left
;
264 HandleDividerMove(CPoint(xPos
+ CDPIAware::Instance().ScaleX(20), CDPIAware::Instance().ScaleY(10)), false);
266 CString sWindowTitle
;
267 GetWindowText(sWindowTitle
);
268 CAppUtils::SetWindowTitle(m_hWnd
, g_Git
.m_CurrentDir
, sWindowTitle
);
270 m_bHasWC
= !GitAdminDir::IsBareRepo(g_Git
.m_CurrentDir
);
274 m_RepoList
.SetFocus();
279 void CRepositoryBrowser::OnDestroy()
281 int maxcol
= m_ColumnManager
.GetColumnCount();
282 for (int col
= 0; col
< maxcol
; ++col
)
283 if (m_ColumnManager
.IsVisible(col
))
284 m_ColumnManager
.ColumnResized(col
);
285 m_ColumnManager
.WriteSettings();
287 CResizableStandAloneDialog::OnDestroy();
290 void CRepositoryBrowser::OnOK()
292 if (GetFocus() == &m_RepoList
&& (GetKeyState(VK_MENU
) & 0x8000) == 0)
294 // list control has focus: 'enter' the folder
295 if (m_RepoList
.GetSelectedCount() != 1)
298 POSITION pos
= m_RepoList
.GetFirstSelectedItemPosition();
301 auto item
= GetListEntry(m_RepoList
.GetNextSelectedItem(pos
));
304 FillListCtrlForShadowTree(item
);
305 m_RepoTree
.SelectItem(item
->m_hTree
);
308 OpenFile(item
->GetFullName(), OPEN
, item
->m_bSubmodule
, item
->m_hash
);
313 SaveDividerPosition();
314 CResizableStandAloneDialog::OnOK();
317 void CRepositoryBrowser::OnCancel()
319 SaveDividerPosition();
320 CResizableStandAloneDialog::OnCancel();
323 void CRepositoryBrowser::OnNMDblclk_RepoList(NMHDR
*pNMHDR
, LRESULT
*pResult
)
327 LPNMITEMACTIVATE pNmItemActivate
= reinterpret_cast<LPNMITEMACTIVATE
>(pNMHDR
);
328 if (pNmItemActivate
->iItem
< 0)
331 auto pItem
= GetListEntry(pNmItemActivate
->iItem
);
335 if (!pItem
->m_bFolder
)
337 OpenFile(pItem
->GetFullName(), OPEN
, pItem
->m_bSubmodule
, pItem
->m_hash
);
342 FillListCtrlForShadowTree(pItem
);
343 m_RepoTree
.SelectItem(pItem
->m_hTree
);
347 void CRepositoryBrowser::Refresh()
350 if (m_nExternalOvl
>= 0)
351 SYS_IMAGE_LIST().SetOverlayImage(m_nExternalOvl
, OVERLAY_EXTERNAL
);
352 if (m_nExecutableOvl
>= 0)
353 SYS_IMAGE_LIST().SetOverlayImage(m_nExecutableOvl
, OVERLAY_EXECUTABLE
);
354 if (m_nSymlinkOvl
>= 0)
355 SYS_IMAGE_LIST().SetOverlayImage(m_nSymlinkOvl
, OVERLAY_SYMLINK
);
357 m_RepoTree
.DeleteAllItems();
358 m_RepoList
.DeleteAllItems();
359 m_TreeRoot
.m_ShadowTree
.clear();
360 m_TreeRoot
.m_sName
.Empty();
361 m_TreeRoot
.m_bFolder
= true;
363 TVINSERTSTRUCT tvinsert
= {0};
364 tvinsert
.hParent
= TVI_ROOT
;
365 tvinsert
.hInsertAfter
= TVI_ROOT
;
366 tvinsert
.itemex
.mask
= TVIF_DI_SETITEM
| TVIF_PARAM
| TVIF_TEXT
| TVIF_IMAGE
| TVIF_SELECTEDIMAGE
| TVIF_STATE
;
367 tvinsert
.itemex
.pszText
= L
"/";
368 tvinsert
.itemex
.lParam
= (LPARAM
)&m_TreeRoot
;
369 tvinsert
.itemex
.iImage
= m_nIconFolder
;
370 tvinsert
.itemex
.iSelectedImage
= m_nOpenIconFolder
;
371 m_TreeRoot
.m_hTree
= m_RepoTree
.InsertItem(&tvinsert
);
373 ReadTree(&m_TreeRoot
);
374 m_RepoTree
.Expand(m_TreeRoot
.m_hTree
, TVE_EXPAND
);
375 FillListCtrlForShadowTree(&m_TreeRoot
);
376 m_RepoTree
.SelectItem(m_TreeRoot
.m_hTree
);
380 int CRepositoryBrowser::ReadTreeRecursive(git_repository
& repo
, const git_tree
* tree
, CShadowFilesTree
* treeroot
, bool recursive
)
382 size_t count
= git_tree_entrycount(tree
);
383 bool hasSubfolders
= false;
384 treeroot
->m_bLoaded
= true;
386 for (size_t i
= 0; i
< count
; ++i
)
388 const git_tree_entry
*entry
= git_tree_entry_byindex(tree
, i
);
392 const int mode
= git_tree_entry_filemode(entry
);
394 CString base
= CUnicodeUtils::GetUnicode(git_tree_entry_name(entry
), CP_UTF8
);
396 const git_oid
*oid
= git_tree_entry_id(entry
);
397 CShadowFilesTree
* pNextTree
= &treeroot
->m_ShadowTree
[base
];
398 pNextTree
->m_sName
= base
;
399 pNextTree
->m_pParent
= treeroot
;
400 pNextTree
->m_hash
= oid
;
402 if (mode
== GIT_FILEMODE_COMMIT
)
403 pNextTree
->m_bSubmodule
= true;
404 else if (mode
& S_IFDIR
)
406 hasSubfolders
= true;
407 pNextTree
->m_bFolder
= true;
408 pNextTree
->m_bLoaded
= false;
410 TVINSERTSTRUCT tvinsert
= {0};
411 tvinsert
.hParent
= treeroot
->m_hTree
;
412 tvinsert
.hInsertAfter
= TVI_SORT
;
413 tvinsert
.itemex
.mask
= TVIF_DI_SETITEM
| TVIF_PARAM
| TVIF_TEXT
| TVIF_IMAGE
| TVIF_SELECTEDIMAGE
| TVIF_STATE
| TVIF_CHILDREN
;
414 tvinsert
.itemex
.pszText
= base
.GetBuffer(base
.GetLength());
415 tvinsert
.itemex
.cChildren
= 1;
416 tvinsert
.itemex
.lParam
= (LPARAM
)pNextTree
;
417 tvinsert
.itemex
.iImage
= m_nIconFolder
;
418 tvinsert
.itemex
.iSelectedImage
= m_nOpenIconFolder
;
419 pNextTree
->m_hTree
= m_RepoTree
.InsertItem(&tvinsert
);
420 base
.ReleaseBuffer();
424 if (git_tree_lookup(subtree
.GetPointer(), &repo
, oid
))
426 MessageBox(CGit::GetLibGit2LastErr(L
"Could not lookup path."), L
"TortoiseGit", MB_ICONERROR
);
430 ReadTreeRecursive(repo
, subtree
, pNextTree
, recursive
);
435 if (mode
== GIT_FILEMODE_BLOB_EXECUTABLE
)
436 pNextTree
->m_bExecutable
= true;
437 if (mode
== GIT_FILEMODE_LINK
)
438 pNextTree
->m_bSymlink
= true;
440 git_blob_lookup(blob
.GetPointer(), &repo
, oid
);
444 pNextTree
->m_iSize
= git_blob_rawsize(blob
);
450 TVITEM tvitem
= { 0 };
451 tvitem
.hItem
= treeroot
->m_hTree
;
452 tvitem
.mask
= TVIF_CHILDREN
;
453 tvitem
.cChildren
= 0;
454 m_RepoTree
.SetItem(&tvitem
);
460 int CRepositoryBrowser::ReadTree(CShadowFilesTree
* treeroot
, const CString
& root
, bool recursive
)
463 CAutoRepository
repository(g_Git
.GetGitRepository());
466 MessageBox(CGit::GetLibGit2LastErr(L
"Could not open repository."), L
"TortoiseGit", MB_ICONERROR
);
470 if (m_sRevision
== L
"HEAD")
472 int ret
= git_repository_head_unborn(repository
);
473 if (ret
== 1) // is orphan
477 MessageBox(g_Git
.GetLibGit2LastErr(L
"Could not check HEAD."), L
"TortoiseGit", MB_ICONERROR
);
483 if (CGit::GetHash(repository
, hash
, m_sRevision
))
485 MessageBox(CGit::GetLibGit2LastErr(L
"Could not get hash of " + m_sRevision
+ L
'.'), L
"TortoiseGit", MB_ICONERROR
);
490 if (git_commit_lookup(commit
.GetPointer(), repository
, hash
))
492 MessageBox(CGit::GetLibGit2LastErr(L
"Could not lookup commit."), L
"TortoiseGit", MB_ICONERROR
);
497 if (git_commit_tree(tree
.GetPointer(), commit
))
499 MessageBox(CGit::GetLibGit2LastErr(L
"Could not get tree of commit."), L
"TortoiseGit", MB_ICONERROR
);
505 CAutoTreeEntry treeEntry
;
506 if (git_tree_entry_bypath(treeEntry
.GetPointer(), tree
, CUnicodeUtils::GetUTF8(root
)))
508 MessageBox(CGit::GetLibGit2LastErr(L
"Could not lookup path."), L
"TortoiseGit", MB_ICONERROR
);
511 if (git_tree_entry_type(treeEntry
) != GIT_OBJECT_TREE
)
513 MessageBox(CGit::GetLibGit2LastErr(L
"Could not lookup path."), L
"TortoiseGit", MB_ICONERROR
);
518 if (git_tree_entry_to_object(object
.GetPointer(), repository
, treeEntry
))
520 MessageBox(CGit::GetLibGit2LastErr(L
"Could not lookup path."), L
"TortoiseGit", MB_ICONERROR
);
524 tree
.ConvertFrom(std::move(object
));
527 treeroot
->m_hash
= git_tree_id(tree
);
528 ReadTreeRecursive(*repository
, tree
, treeroot
, recursive
);
530 // try to resolve hash to a branch name
531 if (m_sRevision
== hash
.ToString())
534 if (CGit::GetMapHashToFriendName(repository
, map
))
535 MessageBox(g_Git
.GetLibGit2LastErr(L
"Could not get all refs."), L
"TortoiseGit", MB_ICONERROR
);
536 if (!map
[hash
].empty())
537 m_sRevision
= map
[hash
].at(0);
539 this->GetDlgItem(IDC_BUTTON_REVISION
)->SetWindowText(m_sRevision
);
544 void CRepositoryBrowser::OnTvnSelchangedRepoTree(NMHDR
*pNMHDR
, LRESULT
*pResult
)
546 LPNMTREEVIEW pNMTreeView
= reinterpret_cast<LPNMTREEVIEW
>(pNMHDR
);
549 FillListCtrlForTreeNode(pNMTreeView
->itemNew
.hItem
);
552 void CRepositoryBrowser::OnTvnItemExpandingRepoTree(NMHDR
*pNMHDR
, LRESULT
*pResult
)
554 LPNMTREEVIEW pNMTreeView
= reinterpret_cast<LPNMTREEVIEW
>(pNMHDR
);
557 auto pTree
= GetTreeEntry(pNMTreeView
->itemNew
.hItem
);
561 if (!pTree
->m_bLoaded
)
562 ReadTree(pTree
, pTree
->GetFullName());
565 void CRepositoryBrowser::FillListCtrlForTreeNode(HTREEITEM treeNode
)
567 m_RepoList
.DeleteAllItems();
569 auto pTree
= GetTreeEntry(treeNode
);
573 CString url
= L
'/' + pTree
->GetFullName();
574 GetDlgItem(IDC_REPOBROWSER_URL
)->SetWindowText(url
);
576 if (!pTree
->m_bLoaded
)
577 ReadTree(pTree
, pTree
->GetFullName());
579 FillListCtrlForShadowTree(pTree
);
582 void CRepositoryBrowser::FillListCtrlForShadowTree(CShadowFilesTree
* pTree
)
584 for (auto itShadowTree
= pTree
->m_ShadowTree
.cbegin(); itShadowTree
!= pTree
->m_ShadowTree
.cend(); ++itShadowTree
)
586 int icon
= m_nIconFolder
;
587 if (!(*itShadowTree
).second
.m_bFolder
&& !(*itShadowTree
).second
.m_bSubmodule
)
589 icon
= SYS_IMAGE_LIST().GetPathIconIndex((*itShadowTree
).second
.m_sName
);
592 int indexItem
= m_RepoList
.InsertItem(m_RepoList
.GetItemCount(), (*itShadowTree
).second
.m_sName
, icon
);
594 if ((*itShadowTree
).second
.m_bSubmodule
)
596 m_RepoList
.SetItemState(indexItem
, INDEXTOOVERLAYMASK(OVERLAY_EXTERNAL
), LVIS_OVERLAYMASK
);
598 if ((*itShadowTree
).second
.m_bExecutable
)
599 m_RepoList
.SetItemState(indexItem
, INDEXTOOVERLAYMASK(OVERLAY_EXECUTABLE
), LVIS_OVERLAYMASK
);
600 if ((*itShadowTree
).second
.m_bSymlink
)
601 m_RepoList
.SetItemState(indexItem
, INDEXTOOVERLAYMASK(OVERLAY_SYMLINK
), LVIS_OVERLAYMASK
);
602 m_RepoList
.SetItemData(indexItem
, (DWORD_PTR
)&(*itShadowTree
).second
);
603 if (!(*itShadowTree
).second
.m_bFolder
&& !(*itShadowTree
).second
.m_bSubmodule
)
607 temp
= CPathUtils::GetFileExtFromPath((*itShadowTree
).second
.m_sName
);
608 m_RepoList
.SetItemText(indexItem
, eCol_Extension
, temp
);
610 StrFormatByteSize64((*itShadowTree
).second
.m_iSize
, CStrBuf(temp
, 20), 20);
611 m_RepoList
.SetItemText(indexItem
, eCol_FileSize
, temp
);
615 CRepoListCompareFunc
compareFunc(&m_RepoList
, m_currSortCol
, m_currSortDesc
);
616 m_RepoList
.SortItemsEx(&CRepoListCompareFunc::StaticCompare
, (DWORD_PTR
)&compareFunc
);
618 SetSortArrowA(&m_RepoList
, m_currSortCol
, !m_currSortDesc
);
623 void CRepositoryBrowser::UpdateInfoLabel()
626 POSITION pos
= m_RepoList
.GetFirstSelectedItemPosition();
629 if (m_RepoList
.GetSelectedCount() > 1)
631 temp
.FormatMessage(IDS_REPOBROWSE_INFOMULTI
, m_RepoList
.GetSelectedCount());
635 int index
= m_RepoList
.GetNextSelectedItem(pos
);
636 auto item
= GetListEntry(index
);
637 if (item
->m_bSubmodule
)
638 temp
.FormatMessage(IDS_REPOBROWSE_INFOEXT
, (LPCTSTR
)m_RepoList
.GetItemText(index
, eCol_Name
), (LPCTSTR
)item
->m_hash
.ToString());
639 else if (item
->m_bFolder
)
640 temp
= m_RepoList
.GetItemText(index
, eCol_Name
);
642 temp
.FormatMessage(IDS_REPOBROWSE_INFOFILE
, (LPCTSTR
)m_RepoList
.GetItemText(index
, eCol_Name
), (LPCTSTR
)m_RepoList
.GetItemText(index
, eCol_FileSize
));
647 HTREEITEM hTreeItem
= m_RepoTree
.GetSelectedItem();
648 if (hTreeItem
!= nullptr)
650 auto pTree
= GetTreeEntry(hTreeItem
);
651 if (pTree
!= nullptr)
653 size_t files
= 0, submodules
= 0;
654 for (auto itShadowTree
= pTree
->m_ShadowTree
.cbegin(); itShadowTree
!= pTree
->m_ShadowTree
.cend(); ++itShadowTree
)
656 if (!(*itShadowTree
).second
.m_bFolder
&& !(*itShadowTree
).second
.m_bSubmodule
)
658 if ((*itShadowTree
).second
.m_bSubmodule
)
661 temp
.FormatMessage(IDS_REPOBROWSE_INFO
, (LPCTSTR
)pTree
->m_sName
, files
, submodules
, pTree
->m_ShadowTree
.size() - files
- submodules
, pTree
->m_ShadowTree
.size());
665 SetDlgItemText(IDC_INFOLABEL
, temp
);
668 void CRepositoryBrowser::OnLvnItemchangedRepolist(NMHDR
* /* pNMHDR */, LRESULT
*pResult
)
674 void CRepositoryBrowser::OnContextMenu(CWnd
* pWndFrom
, CPoint point
)
676 if (pWndFrom
== &m_RepoList
)
678 CRect headerPosition
;
679 m_RepoList
.GetHeaderCtrl()->GetWindowRect(headerPosition
);
680 if (!headerPosition
.PtInRect(point
))
681 OnContextMenu_RepoList(point
);
683 else if (pWndFrom
== &m_RepoTree
)
684 OnContextMenu_RepoTree(point
);
687 void CRepositoryBrowser::OnContextMenu_RepoTree(CPoint point
)
689 CPoint clientPoint
= point
;
690 m_RepoTree
.ScreenToClient(&clientPoint
);
692 HTREEITEM hTreeItem
= m_RepoTree
.HitTest(clientPoint
);
696 TShadowFilesTreeList selectedLeafs
;
697 selectedLeafs
.push_back(GetTreeEntry(hTreeItem
));
699 ShowContextMenu(point
, selectedLeafs
, ONLY_FOLDERS
);
702 void CRepositoryBrowser::OnContextMenu_RepoList(CPoint point
)
704 TShadowFilesTreeList selectedLeafs
;
705 selectedLeafs
.reserve(m_RepoList
.GetSelectedCount());
707 bool folderSelected
= false;
708 bool filesSelected
= false;
709 bool submodulesSelected
= false;
711 POSITION pos
= m_RepoList
.GetFirstSelectedItemPosition();
714 auto item
= GetListEntry(m_RepoList
.GetNextSelectedItem(pos
));
715 if (item
->m_bSubmodule
)
716 submodulesSelected
= true;
718 folderSelected
= true;
720 filesSelected
= true;
721 selectedLeafs
.push_back(item
);
724 eSelectionType selType
= ONLY_FILES
;
725 if (folderSelected
&& filesSelected
)
726 selType
= MIXED_FOLDERS_FILES
;
727 else if (folderSelected
)
728 selType
= ONLY_FOLDERS
;
729 else if (submodulesSelected
)
730 selType
= ONLY_FILESSUBMODULES
;
731 ShowContextMenu(point
, selectedLeafs
, selType
);
734 void CRepositoryBrowser::ShowContextMenu(CPoint point
, TShadowFilesTreeList
&selectedLeafs
, eSelectionType selType
)
737 popupMenu
.CreatePopupMenu();
739 bool bAddSeparator
= false;
741 if (selectedLeafs
.size() == 1)
743 popupMenu
.AppendMenuIcon(eCmd_Open
, IDS_REPOBROWSE_OPEN
, IDI_OPEN
);
744 popupMenu
.SetDefaultItem(eCmd_Open
, FALSE
);
745 if (selType
== ONLY_FILES
|| selType
== ONLY_FILESSUBMODULES
)
747 popupMenu
.AppendMenuIcon(eCmd_OpenWith
, IDS_LOG_POPUP_OPENWITH
, IDI_OPEN
);
748 popupMenu
.AppendMenuIcon(eCmd_OpenWithAlternativeEditor
, IDS_LOG_POPUP_VIEWREV
, IDI_NOTEPAD
);
751 popupMenu
.AppendMenu(MF_SEPARATOR
);
753 if (m_bHasWC
&& (selType
== ONLY_FILES
|| selType
== ONLY_FILESSUBMODULES
))
755 popupMenu
.AppendMenuIcon(eCmd_CompareWC
, IDS_LOG_POPUP_COMPARE
, IDI_DIFF
);
756 bAddSeparator
= true;
760 popupMenu
.AppendMenu(MF_SEPARATOR
);
761 bAddSeparator
= false;
764 temp
.LoadString(IDS_MENULOG
);
765 popupMenu
.AppendMenuIcon(eCmd_ViewLog
, temp
, IDI_LOG
);
766 if (selectedLeafs
[0]->m_bSubmodule
)
768 temp
.LoadString(IDS_MENULOGSUBMODULE
);
769 popupMenu
.AppendMenuIcon(eCmd_ViewLogSubmodule
, temp
, IDI_LOG
);
772 if (selType
== ONLY_FILES
)
775 popupMenu
.AppendMenuIcon(eCmd_Blame
, IDS_LOG_POPUP_BLAME
, IDI_BLAME
);
777 popupMenu
.AppendMenu(MF_SEPARATOR
);
778 temp
.LoadString(IDS_LOG_POPUP_SAVE
);
779 popupMenu
.AppendMenuIcon(eCmd_SaveAs
, temp
, IDI_SAVEAS
);
782 bAddSeparator
= true;
785 if (!selectedLeafs
.empty() && selType
== ONLY_FILES
&& m_bHasWC
)
787 popupMenu
.AppendMenuIcon(eCmd_Revert
, IDS_LOG_POPUP_REVERTTOREV
, IDI_REVERT
);
788 bAddSeparator
= true;
792 popupMenu
.AppendMenu(MF_SEPARATOR
);
794 if (selectedLeafs
.size() == 1 && selType
== ONLY_FILES
)
796 popupMenu
.AppendMenuIcon(eCmd_PrepareDiff
, IDS_PREPAREDIFF
, IDI_DIFF
);
797 if (!m_sMarkForDiffFilename
.IsEmpty())
800 if (selectedLeafs
.at(0)->GetFullName() == m_sMarkForDiffFilename
)
801 diffWith
= m_sMarkForDiffVersion
.ToString();
804 PathCompactPathEx(CStrBuf(diffWith
, 2 * GIT_HASH_SIZE
), m_sMarkForDiffFilename
, 2 * GIT_HASH_SIZE
, 0);
805 diffWith
+= L
':' + m_sMarkForDiffVersion
.ToString().Left(g_Git
.GetShortHASHLength());
808 menuEntry
.Format(IDS_MENUDIFFNOW
, (LPCTSTR
)diffWith
);
809 popupMenu
.AppendMenuIcon(eCmd_PrepareDiff_Compare
, menuEntry
, IDI_DIFF
);
811 popupMenu
.AppendMenu(MF_SEPARATOR
);
814 if (!selectedLeafs
.empty())
816 popupMenu
.AppendMenuIcon(eCmd_CopyPath
, IDS_STATUSLIST_CONTEXT_COPY
, IDI_COPYCLIP
);
817 popupMenu
.AppendMenuIcon(eCmd_CopyHash
, IDS_COPY_COMMIT_HASH
, IDI_COPYCLIP
);
820 eCmd cmd
= (eCmd
)popupMenu
.TrackPopupMenuEx(TPM_LEFTALIGN
| TPM_RETURNCMD
, point
.x
, point
.y
, this, nullptr);
824 case eCmd_ViewLogSubmodule
:
827 sCmd
.Format(L
"/command:log /path:\"%s\"", (LPCTSTR
)g_Git
.CombinePath(selectedLeafs
.at(0)->GetFullName()));
828 if (cmd
== eCmd_ViewLog
&& selectedLeafs
.at(0)->m_bSubmodule
)
829 sCmd
+= L
" /submodule";
830 if (cmd
== eCmd_ViewLog
)
831 sCmd
+= L
" /endrev:" + m_sRevision
;
832 CAppUtils::RunTortoiseGitProc(sCmd
);
837 CAppUtils::LaunchTortoiseBlame(g_Git
.CombinePath(selectedLeafs
.at(0)->GetFullName()), m_sRevision
);
841 if (!selectedLeafs
.at(0)->m_bSubmodule
&& selectedLeafs
.at(0)->m_bFolder
)
843 FillListCtrlForTreeNode(selectedLeafs
.at(0)->m_hTree
);
844 m_RepoTree
.SelectItem(selectedLeafs
.at(0)->m_hTree
);
847 OpenFile(selectedLeafs
.at(0)->GetFullName(), OPEN
, selectedLeafs
.at(0)->m_bSubmodule
, selectedLeafs
.at(0)->m_hash
);
850 OpenFile(selectedLeafs
.at(0)->GetFullName(), OPEN_WITH
, selectedLeafs
.at(0)->m_bSubmodule
, selectedLeafs
.at(0)->m_hash
);
852 case eCmd_OpenWithAlternativeEditor
:
853 OpenFile(selectedLeafs
.at(0)->GetFullName(), ALTERNATIVEEDITOR
, selectedLeafs
.at(0)->m_bSubmodule
, selectedLeafs
.at(0)->m_hash
);
857 CTGitPath
file(selectedLeafs
.at(0)->GetFullName());
858 CGitDiff::Diff(GetSafeHwnd(), &file
, &file
, GIT_REV_ZERO
, m_sRevision
);
864 for (auto itShadowTree
= selectedLeafs
.cbegin(); itShadowTree
!= selectedLeafs
.cend(); ++itShadowTree
)
866 if (RevertItemToVersion((*itShadowTree
)->GetFullName()))
872 msg
.FormatMessage(IDS_STATUSLIST_FILESREVERTED
, count
, (LPCTSTR
)m_sRevision
);
873 MessageBox(msg
, L
"TortoiseGit", MB_OK
);
877 FileSaveAs(selectedLeafs
.at(0)->GetFullName());
882 for (auto itShadowTree
= selectedLeafs
.cbegin(); itShadowTree
!= selectedLeafs
.cend(); ++itShadowTree
)
884 sClipboard
+= (*itShadowTree
)->m_sName
+ L
"\r\n";
886 CStringUtils::WriteAsciiStringToClipboard(sClipboard
);
891 CopyHashToClipboard(selectedLeafs
);
894 case eCmd_PrepareDiff
:
895 m_sMarkForDiffFilename
= selectedLeafs
.at(0)->GetFullName();
896 if (g_Git
.GetHash(m_sMarkForDiffVersion
, m_sRevision
))
898 m_sMarkForDiffFilename
.Empty();
899 MessageBox(g_Git
.GetGitLastErr(L
"Could not get SHA-1 for " + m_sRevision
), L
"TortoiseGit", MB_ICONERROR
);
902 case eCmd_PrepareDiff_Compare
:
904 CTGitPath
savedFile(m_sMarkForDiffFilename
);
905 CTGitPath
selectedFile(selectedLeafs
.at(0)->GetFullName());
906 CGitHash currentHash
;
907 if (g_Git
.GetHash(currentHash
, m_sRevision
))
909 MessageBox(g_Git
.GetGitLastErr(L
"Could not get SHA-1 for " + m_sRevision
), L
"TortoiseGit", MB_ICONERROR
);
912 CGitDiff::Diff(GetSafeHwnd(), &selectedFile
, &savedFile
, currentHash
.ToString(), m_sMarkForDiffVersion
.ToString());
918 BOOL
CRepositoryBrowser::PreTranslateMessage(MSG
* pMsg
)
920 if (pMsg
->message
== WM_KEYDOWN
)
922 switch (pMsg
->wParam
)
932 return CResizableStandAloneDialog::PreTranslateMessage(pMsg
);
935 void CRepositoryBrowser::OnLvnColumnclickRepoList(NMHDR
*pNMHDR
, LRESULT
*pResult
)
937 LPNMLISTVIEW pNMLV
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
941 if (m_currSortCol
== pNMLV
->iSubItem
)
942 m_currSortDesc
= !m_currSortDesc
;
945 m_currSortCol
= pNMLV
->iSubItem
;
946 m_currSortDesc
= false;
949 CRepoListCompareFunc
compareFunc(&m_RepoList
, m_currSortCol
, m_currSortDesc
);
950 m_RepoList
.SortItemsEx(&CRepoListCompareFunc::StaticCompare
, (DWORD_PTR
)&compareFunc
);
952 SetSortArrowA(&m_RepoList
, m_currSortCol
, !m_currSortDesc
);
955 void CRepositoryBrowser::OnBnClickedButtonRevision()
957 // use the git log to allow selection of a version
959 dlg
.SetParams(CTGitPath(), CTGitPath(), m_sRevision
, m_sRevision
, 0);
960 // tell the dialog to use mode for selecting revisions
962 dlg
.ShowWorkingTreeChanges(false);
963 // only one revision must be selected however
964 dlg
.SingleSelection(true);
965 if (dlg
.DoModal() == IDOK
&& !dlg
.GetSelectedHash().empty())
967 m_sRevision
= dlg
.GetSelectedHash().at(0).ToString();
972 void CRepositoryBrowser::SaveDividerPosition()
975 GetDlgItem(IDC_REPOTREE
)->GetClientRect(&rc
);
976 CRegDWORD
xPos(L
"Software\\TortoiseGit\\TortoiseProc\\ResizableState\\RepobrowserDivider");
977 xPos
= rc
.right
- rc
.left
;
980 void CRepositoryBrowser::HandleDividerMove(CPoint point
, bool bDraw
)
982 RECT rect
, tree
, list
, treelist
, treelistclient
;
984 // create an union of the tree and list control rectangle
985 GetDlgItem(IDC_REPOLIST
)->GetWindowRect(&list
);
986 GetDlgItem(IDC_REPOTREE
)->GetWindowRect(&tree
);
987 UnionRect(&treelist
, &tree
, &list
);
988 treelistclient
= treelist
;
989 ScreenToClient(&treelistclient
);
991 ClientToScreen(&point
);
992 GetClientRect(&rect
);
993 ClientToScreen(&rect
);
995 auto minWidth
= CDPIAware::Instance().ScaleX(REPOBROWSER_CTRL_MIN_WIDTH
);
996 CPoint point2
= point
;
997 if (point2
.x
< treelist
.left
+ minWidth
)
998 point2
.x
= treelist
.left
+ minWidth
;
999 if (point2
.x
> treelist
.right
- minWidth
)
1000 point2
.x
= treelist
.right
- minWidth
;
1002 point
.x
-= rect
.left
;
1003 point
.y
-= treelist
.top
;
1005 OffsetRect(&treelist
, -treelist
.left
, -treelist
.top
);
1007 if (point
.x
< treelist
.left
+ minWidth
)
1008 point
.x
= treelist
.left
+ minWidth
;
1009 if (point
.x
> treelist
.right
- minWidth
)
1010 point
.x
= treelist
.right
- minWidth
;
1012 auto divWidth
= CDPIAware::Instance().ScaleX(2);
1016 CDC
* pDC
= GetDC();
1017 DrawXorBar(pDC
, oldx
- divWidth
, treelistclient
.top
, 2 * divWidth
, treelistclient
.bottom
- treelistclient
.top
- CDPIAware::Instance().ScaleY(2));
1024 //position the child controls
1025 GetDlgItem(IDC_REPOTREE
)->GetWindowRect(&treelist
);
1026 treelist
.right
= point2
.x
- divWidth
;
1027 ScreenToClient(&treelist
);
1028 RemoveAnchor(IDC_REPOTREE
);
1029 GetDlgItem(IDC_REPOTREE
)->MoveWindow(&treelist
);
1030 GetDlgItem(IDC_REPOLIST
)->GetWindowRect(&treelist
);
1031 treelist
.left
= point2
.x
+ divWidth
;
1032 ScreenToClient(&treelist
);
1033 RemoveAnchor(IDC_REPOLIST
);
1034 GetDlgItem(IDC_REPOLIST
)->MoveWindow(&treelist
);
1036 AddAnchor(IDC_REPOTREE
, TOP_LEFT
, BOTTOM_LEFT
);
1037 AddAnchor(IDC_REPOLIST
, TOP_LEFT
, BOTTOM_RIGHT
);
1040 void CRepositoryBrowser::OnMouseMove(UINT nFlags
, CPoint point
)
1042 if (bDragMode
== FALSE
)
1045 RECT rect
, tree
, list
, treelist
, treelistclient
;
1046 // create an union of the tree and list control rectangle
1047 GetDlgItem(IDC_REPOLIST
)->GetWindowRect(&list
);
1048 GetDlgItem(IDC_REPOTREE
)->GetWindowRect(&tree
);
1049 UnionRect(&treelist
, &tree
, &list
);
1050 treelistclient
= treelist
;
1051 ScreenToClient(&treelistclient
);
1053 //convert the mouse coordinates relative to the top-left of
1055 ClientToScreen(&point
);
1056 GetClientRect(&rect
);
1057 ClientToScreen(&rect
);
1058 point
.x
-= rect
.left
;
1059 point
.y
-= treelist
.top
;
1061 //same for the window coordinates - make them relative to 0,0
1062 OffsetRect(&treelist
, -treelist
.left
, -treelist
.top
);
1064 auto minWidth
= CDPIAware::Instance().ScaleX(REPOBROWSER_CTRL_MIN_WIDTH
);
1065 if (point
.x
< treelist
.left
+ minWidth
)
1066 point
.x
= treelist
.left
+ minWidth
;
1067 if (point
.x
> treelist
.right
- minWidth
)
1068 point
.x
= treelist
.right
- minWidth
;
1070 if ((nFlags
& MK_LBUTTON
) && (point
.x
!= oldx
))
1072 CDC
* pDC
= GetDC();
1076 auto divWidth
= CDPIAware::Instance().ScaleX(2);
1077 auto divHeight
= CDPIAware::Instance().ScaleY(2);
1078 DrawXorBar(pDC
, oldx
- divWidth
, treelistclient
.top
, 2 * divWidth
, treelistclient
.bottom
- treelistclient
.top
- divHeight
);
1079 DrawXorBar(pDC
, point
.x
- divWidth
, treelistclient
.top
, 2 * divWidth
, treelistclient
.bottom
- treelistclient
.top
- divHeight
);
1088 CStandAloneDialogTmpl
<CResizableDialog
>::OnMouseMove(nFlags
, point
);
1091 void CRepositoryBrowser::OnLButtonDown(UINT nFlags
, CPoint point
)
1093 RECT rect
, tree
, list
, treelist
, treelistclient
;
1095 // create an union of the tree and list control rectangle
1096 GetDlgItem(IDC_REPOLIST
)->GetWindowRect(&list
);
1097 GetDlgItem(IDC_REPOTREE
)->GetWindowRect(&tree
);
1098 UnionRect(&treelist
, &tree
, &list
);
1099 treelistclient
= treelist
;
1100 ScreenToClient(&treelistclient
);
1102 //convert the mouse coordinates relative to the top-left of
1104 ClientToScreen(&point
);
1105 GetClientRect(&rect
);
1106 ClientToScreen(&rect
);
1107 point
.x
-= rect
.left
;
1108 point
.y
-= treelist
.top
;
1110 //same for the window coordinates - make them relative to 0,0
1111 OffsetRect(&treelist
, -treelist
.left
, -treelist
.top
);
1113 auto minWidth
= CDPIAware::Instance().ScaleX(REPOBROWSER_CTRL_MIN_WIDTH
);
1115 if (point
.x
< treelist
.left
+ minWidth
)
1116 return CStandAloneDialogTmpl
< CResizableDialog
>::OnLButtonDown(nFlags
, point
);
1117 if (point
.x
> treelist
.right
- CDPIAware::Instance().ScaleX(3))
1118 return CStandAloneDialogTmpl
< CResizableDialog
>::OnLButtonDown(nFlags
, point
);
1119 if (point
.x
> treelist
.right
- minWidth
)
1120 point
.x
= treelist
.right
- minWidth
;
1122 auto divHeight
= CDPIAware::Instance().ScaleY(3);
1123 if ((point
.y
< treelist
.top
+ divHeight
) || (point
.y
> treelist
.bottom
- divHeight
))
1124 return CStandAloneDialogTmpl
<CResizableDialog
>::OnLButtonDown(nFlags
, point
);
1130 auto divWidth
= CDPIAware::Instance().ScaleX(2);
1131 CDC
* pDC
= GetDC();
1132 DrawXorBar(pDC
, point
.x
- divWidth
, treelistclient
.top
, 2 * divWidth
, treelistclient
.bottom
- treelistclient
.top
- CDPIAware::Instance().ScaleY(2));
1138 CStandAloneDialogTmpl
<CResizableDialog
>::OnLButtonDown(nFlags
, point
);
1141 void CRepositoryBrowser::OnLButtonUp(UINT nFlags
, CPoint point
)
1143 if (bDragMode
== FALSE
)
1146 HandleDividerMove(point
, true);
1151 CStandAloneDialogTmpl
<CResizableDialog
>::OnLButtonUp(nFlags
, point
);
1154 void CRepositoryBrowser::OnCaptureChanged(CWnd
*pWnd
)
1158 __super::OnCaptureChanged(pWnd
);
1161 void CRepositoryBrowser::DrawXorBar(CDC
* pDC
, int x1
, int y1
, int width
, int height
)
1163 static WORD _dotPatternBmp
[8] =
1165 0x0055, 0x00aa, 0x0055, 0x00aa,
1166 0x0055, 0x00aa, 0x0055, 0x00aa
1170 HBRUSH hbr
, hbrushOld
;
1172 hbm
= CreateBitmap(8, 8, 1, 1, _dotPatternBmp
);
1173 hbr
= CreatePatternBrush(hbm
);
1175 pDC
->SetBrushOrg(x1
, y1
);
1176 hbrushOld
= (HBRUSH
)pDC
->SelectObject(hbr
);
1178 PatBlt(pDC
->GetSafeHdc(), x1
, y1
, width
, height
, PATINVERT
);
1180 pDC
->SelectObject(hbrushOld
);
1186 BOOL
CRepositoryBrowser::OnSetCursor(CWnd
* pWnd
, UINT nHitTest
, UINT message
)
1192 GetClientRect(&rect
);
1194 ScreenToClient(&pt
);
1195 if (PtInRect(&rect
, pt
))
1197 ClientToScreen(&pt
);
1198 // are we right of the tree control?
1199 GetDlgItem(IDC_REPOTREE
)->GetWindowRect(&rect
);
1200 auto divHeight
= CDPIAware::Instance().ScaleY(3);
1201 if ((pt
.x
> rect
.right
) && (pt
.y
>= rect
.top
+ divHeight
) && (pt
.y
<= rect
.bottom
- divHeight
))
1203 // but left of the list control?
1204 GetDlgItem(IDC_REPOLIST
)->GetWindowRect(&rect
);
1205 if (pt
.x
< rect
.left
)
1207 HCURSOR hCur
= LoadCursor(nullptr, IDC_SIZEWE
);
1214 return CStandAloneDialogTmpl
<CResizableDialog
>::OnSetCursor(pWnd
, nHitTest
, message
);
1217 void CRepositoryBrowser::FileSaveAs(const CString path
)
1219 CTGitPath
gitPath(path
);
1222 if (g_Git
.GetHash(hash
, m_sRevision
))
1224 MessageBox(g_Git
.GetGitLastErr(L
"Could not get hash of " + m_sRevision
+ L
'.'), L
"TortoiseGit", MB_ICONERROR
);
1229 filename
.Format(L
"%s\\%s-%s%s", (LPCTSTR
)g_Git
.CombinePath(gitPath
.GetContainingDirectory()), (LPCTSTR
)gitPath
.GetBaseFilename(), (LPCTSTR
)hash
.ToString().Left(g_Git
.GetShortHASHLength()), (LPCTSTR
)gitPath
.GetFileExtension());
1230 if (!CAppUtils::FileOpenSave(filename
, nullptr, 0, 0, false, GetSafeHwnd()))
1233 if (g_Git
.GetOneFile(m_sRevision
, gitPath
, filename
))
1236 out
.FormatMessage(IDS_STATUSLIST_CHECKOUTFILEFAILED
, (LPCTSTR
)gitPath
.GetGitPathString(), (LPCTSTR
)m_sRevision
, (LPCTSTR
)filename
);
1237 MessageBox(g_Git
.GetGitLastErr(out
, CGit::GIT_CMD_GETONEFILE
), L
"TortoiseGit", MB_ICONERROR
);
1241 void CRepositoryBrowser::OpenFile(const CString path
, eOpenType mode
, bool isSubmodule
, const CGitHash
& itemHash
)
1243 CTGitPath
gitPath(path
);
1246 if (g_Git
.GetHash(hash
, m_sRevision
))
1248 MessageBox(g_Git
.GetGitLastErr(L
"Could not get hash of " + m_sRevision
+ L
'.'), L
"TortoiseGit", MB_ICONERROR
);
1252 CString file
= CTempFiles::Instance().GetTempFilePath(false, gitPath
, hash
).GetWinPathString();
1255 if (mode
== OPEN
&& !GitAdminDir::IsBareRepo(g_Git
.m_CurrentDir
))
1257 CTGitPath subPath
= CTGitPath(g_Git
.m_CurrentDir
);
1258 subPath
.AppendPathString(gitPath
.GetWinPathString());
1259 CAutoRepository
repo(subPath
.GetGitPathString());
1261 if (!repo
|| git_commit_lookup(commit
.GetPointer(), repo
, itemHash
))
1264 out
.FormatMessage(IDS_REPOBROWSEASKSUBMODULEUPDATE
, (LPCTSTR
)itemHash
.ToString(), (LPCTSTR
)gitPath
.GetGitPathString());
1265 if (MessageBox(out
, L
"TortoiseGit", MB_YESNO
| MB_ICONQUESTION
) != IDYES
)
1269 sCmd
.Format(L
"/command:subupdate /bkpath:\"%s\" /selectedpath:\"%s\"", (LPCTSTR
)g_Git
.m_CurrentDir
, (LPCTSTR
)gitPath
.GetGitPathString());
1270 CAppUtils::RunTortoiseGitProc(sCmd
);
1275 cmd
.Format(L
"/command:repobrowser /path:\"%s\" /rev:%s", (LPCTSTR
)g_Git
.CombinePath(path
), (LPCTSTR
)itemHash
.ToString());
1276 CAppUtils::RunTortoiseGitProc(cmd
);
1281 CFile
submoduleCommit(file
, CFile::modeCreate
| CFile::modeWrite
);
1282 CStringA commitInfo
= "Subproject commit " + CStringA(itemHash
.ToString());
1283 submoduleCommit
.Write(commitInfo
, commitInfo
.GetLength());
1285 else if (g_Git
.GetOneFile(m_sRevision
, gitPath
, file
))
1288 out
.FormatMessage(IDS_STATUSLIST_CHECKOUTFILEFAILED
, (LPCTSTR
)gitPath
.GetGitPathString(), (LPCTSTR
)m_sRevision
, (LPCTSTR
)file
);
1289 MessageBox(g_Git
.GetGitLastErr(out
, CGit::GIT_CMD_GETONEFILE
), L
"TortoiseGit", MB_ICONERROR
);
1293 if (mode
== ALTERNATIVEEDITOR
)
1295 CAppUtils::LaunchAlternativeEditor(file
);
1298 else if (mode
== OPEN
)
1300 CAppUtils::ShellOpen(file
);
1304 CAppUtils::ShowOpenWithDialog(file
);
1306 bool CRepositoryBrowser::RevertItemToVersion(const CString
&path
)
1309 cmd
.Format(L
"git.exe checkout %s -- \"%s\"", (LPCTSTR
)m_sRevision
, (LPCTSTR
)path
);
1310 if (g_Git
.Run(cmd
, &out
, CP_UTF8
))
1312 if (MessageBox(out
, L
"TortoiseGit", MB_ICONEXCLAMATION
| MB_OKCANCEL
) == IDCANCEL
)
1319 void CRepositoryBrowser::CopyHashToClipboard(TShadowFilesTreeList
&selectedLeafs
)
1321 if (!selectedLeafs
.empty())
1325 for (size_t i
= 0; i
< selectedLeafs
.size(); ++i
)
1328 sClipdata
+= L
"\r\n";
1329 sClipdata
+= selectedLeafs
[i
]->m_hash
.ToString();
1332 CStringUtils::WriteAsciiStringToClipboard(sClipdata
, GetSafeHwnd());
1336 void CRepositoryBrowser::OnLvnBegindragRepolist(NMHDR
* pNMHDR
, LRESULT
* pResult
)
1340 // get selected paths
1341 POSITION pos
= m_RepoList
.GetFirstSelectedItemPosition();
1345 HTREEITEM hTreeItem
= m_RepoTree
.GetSelectedItem();
1351 auto pTree
= GetTreeEntry(hTreeItem
);
1355 CTGitPathList toExport
;
1357 while ((index
= m_RepoList
.GetNextSelectedItem(pos
)) >= 0)
1359 auto item
= GetListEntry(index
);
1360 if (item
->m_bFolder
)
1362 RecursivelyAdd(toExport
, item
);
1367 path
.SetFromGit(item
->GetFullName(), item
->m_bSubmodule
);
1368 toExport
.AddPath(path
);
1371 LPNMLISTVIEW pNMLV
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
1372 BeginDrag(m_RepoList
, toExport
, pTree
->GetFullName(), pNMLV
->ptAction
);
1375 void CRepositoryBrowser::RecursivelyAdd(CTGitPathList
& toExport
, CShadowFilesTree
* pTree
)
1377 if (!pTree
->m_bLoaded
)
1378 ReadTree(pTree
, pTree
->GetFullName(), true);
1380 for (auto itShadowTree
= pTree
->m_ShadowTree
.begin(); itShadowTree
!= pTree
->m_ShadowTree
.end(); ++itShadowTree
)
1382 if ((*itShadowTree
).second
.m_bFolder
)
1384 RecursivelyAdd(toExport
, &(*itShadowTree
).second
);
1388 path
.SetFromGit((*itShadowTree
).second
.GetFullName(), (*itShadowTree
).second
.m_bSubmodule
);
1389 toExport
.AddPath(path
);
1393 void CRepositoryBrowser::OnTvnBegindragRepotree(NMHDR
* pNMHDR
, LRESULT
* pResult
)
1397 LPNMTREEVIEW pNMTreeView
= reinterpret_cast<LPNMTREEVIEW
>(pNMHDR
);
1399 auto pTree
= GetTreeEntry(pNMTreeView
->itemNew
.hItem
);
1403 CTGitPathList toExport
;
1404 RecursivelyAdd(toExport
, pTree
);
1406 BeginDrag(m_RepoTree
, toExport
, pTree
->m_pParent
? pTree
->m_pParent
->GetFullName() : L
"", pNMTreeView
->ptDrag
);
1409 void CRepositoryBrowser::BeginDrag(const CWnd
& window
, CTGitPathList
& files
, const CString
& root
, POINT
& point
)
1412 if (g_Git
.GetHash(hash
, m_sRevision
))
1414 MessageBox(g_Git
.GetGitLastErr(L
"Could not get hash of " + m_sRevision
+ L
"."), L
"TortoiseGit", MB_ICONERROR
);
1418 // build copy source / content
1419 auto pdsrc
= std::make_unique
<CIDropSource
>();
1425 GitDataObject
* pdobj
= new GitDataObject(files
, hash
, root
.GetLength());
1429 pdobj
->SetAsyncMode(TRUE
);
1430 CDragSourceHelper dragsrchelper
;
1431 dragsrchelper
.InitializeFromWindow(window
.GetSafeHwnd(), point
, pdobj
);
1432 pdsrc
->m_pIDataObj
= pdobj
;
1433 pdsrc
->m_pIDataObj
->AddRef();
1435 // Initiate the Drag & Drop
1437 ::DoDragDrop(pdobj
, pdsrc
.get(), DROPEFFECT_MOVE
| DROPEFFECT_COPY
, &dwEffect
);
1444 CShadowFilesTree
* CRepositoryBrowser::GetListEntry(int index
)
1446 auto entry
= reinterpret_cast<CShadowFilesTree
*>(m_RepoList
.GetItemData(index
));
1451 CShadowFilesTree
* CRepositoryBrowser::GetTreeEntry(HTREEITEM treeItem
)
1453 auto entry
= reinterpret_cast<CShadowFilesTree
*>(m_RepoTree
.GetItemData(treeItem
));
1458 void CRepositoryBrowser::OnSysColorChange()
1460 __super::OnSysColorChange();
1461 CAppUtils::SetListCtrlBackgroundImage(m_RepoList
.GetSafeHwnd(), IDI_REPOBROWSER_BKG
);