1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2013 - TortoiseGit
4 // Copyright (C) 2012-2013 Sven Strickroth <email@cs-ware.de>
5 // Copyright (C) 2003-2012 - TortoiseSVN
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License
9 // as published by the Free Software Foundation; either version 2
10 // of the License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software Foundation,
19 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 // RepositoryBrowser.cpp : implementation file
25 #include "TortoiseProc.h"
26 #include "RepositoryBrowser.h"
30 #include "UnicodeUtils.h"
31 #include "SysImageList.h"
35 #include "PathUtils.h"
36 #include "StringUtils.h"
39 void SetSortArrowA(CListCtrl
* control
, int nColumn
, bool bAscending
)
45 CHeaderCtrl
* pHeader
= control
->GetHeaderCtrl();
46 HDITEM HeaderItem
= {0};
47 HeaderItem
.mask
= HDI_FORMAT
;
48 for (int i
= 0; i
< pHeader
->GetItemCount(); ++i
)
50 pHeader
->GetItem(i
, &HeaderItem
);
51 HeaderItem
.fmt
&= ~(HDF_SORTDOWN
| HDF_SORTUP
);
52 pHeader
->SetItem(i
, &HeaderItem
);
56 pHeader
->GetItem(nColumn
, &HeaderItem
);
57 HeaderItem
.fmt
|= (bAscending
? HDF_SORTUP
: HDF_SORTDOWN
);
58 pHeader
->SetItem(nColumn
, &HeaderItem
);
62 class CRepoListCompareFunc
65 CRepoListCompareFunc(CListCtrl
* pList
, int col
, bool desc
)
71 static int CALLBACK
StaticCompare(LPARAM lParam1
, LPARAM lParam2
, LPARAM lParamSort
)
73 return ((CRepoListCompareFunc
*) lParamSort
)->Compare(lParam1
, lParam2
);
76 int Compare(LPARAM lParam1
, LPARAM lParam2
)
78 CShadowFilesTree
* pLeft
= (CShadowFilesTree
*)m_pList
->GetItemData((int)lParam1
);
79 CShadowFilesTree
* pRight
= (CShadowFilesTree
*)m_pList
->GetItemData((int)lParam2
);
84 case CRepositoryBrowser::eCol_Name
:
85 result
= SortStrCmp(pLeft
->m_sName
, pRight
->m_sName
);
88 case CRepositoryBrowser::eCol_Extension
:
89 result
= m_pList
->GetItemText(static_cast<int>(lParam1
), 1).CompareNoCase(m_pList
->GetItemText(static_cast<int>(lParam2
), 1));
90 if (result
== 0) // if extensions are the same, use the filename to sort
91 result
= SortStrCmp(pRight
->m_sName
, pRight
->m_sName
);
94 case CRepositoryBrowser::eCol_FileSize
:
95 if (pLeft
->m_iSize
> pRight
->m_iSize
)
97 else if (pLeft
->m_iSize
< pRight
->m_iSize
)
100 result
= SortStrCmp(pLeft
->m_sName
, pRight
->m_sName
);
106 if (pLeft
->m_bFolder
!= pRight
->m_bFolder
)
108 if (pRight
->m_bFolder
)
116 int SortStrCmp(CString
&left
, CString
&right
)
118 if (CRepositoryBrowser::s_bSortLogical
)
119 return StrCmpLogicalW(left
, right
);
120 return StrCmpI(left
, right
);
128 // CRepositoryBrowser dialog
130 bool CRepositoryBrowser::s_bSortLogical
= true;
132 IMPLEMENT_DYNAMIC(CRepositoryBrowser
, CResizableStandAloneDialog
)
134 CRepositoryBrowser::CRepositoryBrowser(CString rev
, CWnd
* pParent
/*=NULL*/)
135 : CResizableStandAloneDialog(CRepositoryBrowser::IDD
, pParent
)
137 , m_currSortDesc(false)
143 CRepositoryBrowser::~CRepositoryBrowser()
147 void CRepositoryBrowser::DoDataExchange(CDataExchange
* pDX
)
149 CDialog::DoDataExchange(pDX
);
150 DDX_Control(pDX
, IDC_REPOTREE
, m_RepoTree
);
151 DDX_Control(pDX
, IDC_REPOLIST
, m_RepoList
);
155 BEGIN_MESSAGE_MAP(CRepositoryBrowser
, CResizableStandAloneDialog
)
156 ON_NOTIFY(TVN_SELCHANGED
, IDC_REPOTREE
, &CRepositoryBrowser::OnTvnSelchangedRepoTree
)
158 ON_NOTIFY(LVN_COLUMNCLICK
, IDC_REPOLIST
, &CRepositoryBrowser::OnLvnColumnclickRepoList
)
159 ON_NOTIFY(NM_DBLCLK
, IDC_REPOLIST
, &CRepositoryBrowser::OnNMDblclk_RepoList
)
160 ON_BN_CLICKED(IDC_BUTTON_REVISION
, &CRepositoryBrowser::OnBnClickedButtonRevision
)
168 // CRepositoryBrowser message handlers
170 BOOL
CRepositoryBrowser::OnInitDialog()
172 CResizableStandAloneDialog::OnInitDialog();
173 CAppUtils::MarkWindowAsUnpinnable(m_hWnd
);
175 AddAnchor(IDC_STATIC_REPOURL
, TOP_LEFT
);
176 AddAnchor(IDC_REPOBROWSER_URL
, TOP_LEFT
, TOP_RIGHT
);
177 AddAnchor(IDC_STATIC_REF
, TOP_RIGHT
);
178 AddAnchor(IDC_BUTTON_REVISION
, TOP_RIGHT
);
179 AddAnchor(IDC_REPOTREE
, TOP_LEFT
, BOTTOM_LEFT
);
180 AddAnchor(IDC_REPOLIST
, TOP_LEFT
, BOTTOM_RIGHT
);
181 AddAnchor(IDHELP
, BOTTOM_RIGHT
);
182 AddAnchor(IDOK
, BOTTOM_RIGHT
);
183 AddAnchor(IDCANCEL
, BOTTOM_RIGHT
);
185 CRepositoryBrowser::s_bSortLogical
= !CRegDWORD(L
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER
);
186 if (CRepositoryBrowser::s_bSortLogical
)
187 CRepositoryBrowser::s_bSortLogical
= !CRegDWORD(L
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_LOCAL_MACHINE
);
190 temp
.LoadString(IDS_STATUSLIST_COLFILENAME
);
191 m_RepoList
.InsertColumn(eCol_Name
, temp
, 0, 150);
192 temp
.LoadString(IDS_STATUSLIST_COLEXT
);
193 m_RepoList
.InsertColumn(eCol_Extension
, temp
, 0, 100);
194 temp
.LoadString(IDS_LOG_SIZE
);
195 m_RepoList
.InsertColumn(eCol_FileSize
, temp
, 0, 100);
197 // set up the list control
198 // set the extended style of the list control
199 // 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.
200 CRegDWORD
regFullRowSelect(_T("Software\\TortoiseGit\\FullRowSelect"), TRUE
);
201 DWORD exStyle
= LVS_EX_HEADERDRAGDROP
| LVS_EX_DOUBLEBUFFER
| LVS_EX_INFOTIP
| LVS_EX_SUBITEMIMAGES
;
202 if (DWORD(regFullRowSelect
))
203 exStyle
|= LVS_EX_FULLROWSELECT
;
204 m_RepoList
.SetExtendedStyle(exStyle
);
205 m_RepoList
.SetImageList(&SYS_IMAGE_LIST(), LVSIL_SMALL
);
206 CAppUtils::SetListCtrlBackgroundImage(m_RepoList
.GetSafeHwnd(), IDI_REPOBROWSER_BKG
);
208 m_RepoTree
.SetImageList(&SYS_IMAGE_LIST(), TVSIL_NORMAL
);
209 if (SysInfo::Instance().IsVistaOrLater())
211 DWORD exStyle
= TVS_EX_FADEINOUTEXPANDOS
| TVS_EX_AUTOHSCROLL
| TVS_EX_DOUBLEBUFFER
;
212 m_RepoTree
.SetExtendedStyle(exStyle
, exStyle
);
215 SetWindowTheme(m_RepoTree
.GetSafeHwnd(), L
"Explorer", NULL
);
216 SetWindowTheme(m_RepoList
.GetSafeHwnd(), L
"Explorer", NULL
);
218 m_nIconFolder
= SYS_IMAGE_LIST().GetDirIconIndex();
219 m_nOpenIconFolder
= SYS_IMAGE_LIST().GetDirOpenIconIndex();
221 EnableSaveRestore(L
"Reposbrowser");
223 DWORD xPos
= CRegDWORD(_T("Software\\TortoiseGit\\TortoiseProc\\ResizableState\\RepobrowserDivider"), 0);
227 GetDlgItem(IDC_REPOTREE
)->GetClientRect(&rc
);
228 xPos
= rc
.right
- rc
.left
;
231 HandleDividerMove(CPoint(xPos
+ 20, 10), false);
233 CString sWindowTitle
;
234 GetWindowText(sWindowTitle
);
235 CAppUtils::SetWindowTitle(m_hWnd
, g_Git
.m_CurrentDir
, sWindowTitle
);
237 m_bHasWC
= !g_GitAdminDir
.IsBareRepo(g_Git
.m_CurrentDir
);
241 m_RepoList
.SetFocus();
246 void CRepositoryBrowser::OnOK()
248 SaveDividerPosition();
249 CResizableStandAloneDialog::OnOK();
252 void CRepositoryBrowser::OnCancel()
254 SaveDividerPosition();
255 CResizableStandAloneDialog::OnCancel();
258 void CRepositoryBrowser::OnNMDblclk_RepoList(NMHDR
*pNMHDR
, LRESULT
*pResult
)
262 LPNMITEMACTIVATE pNmItemActivate
= reinterpret_cast<LPNMITEMACTIVATE
>(pNMHDR
);
263 if (pNmItemActivate
->iItem
< 0)
266 CShadowFilesTree
* pItem
= (CShadowFilesTree
*)m_RepoList
.GetItemData(pNmItemActivate
->iItem
);
270 if (!pItem
->m_bFolder
)
272 OpenFile(pItem
->GetFullName(), OPEN
);
277 FillListCtrlForShadowTree(pItem
);
278 m_RepoTree
.SelectItem(pItem
->m_hTree
);
282 void CRepositoryBrowser::Refresh()
284 m_RepoTree
.DeleteAllItems();
285 m_RepoList
.DeleteAllItems();
286 m_TreeRoot
.m_ShadowTree
.clear();
287 m_TreeRoot
.m_sName
= "";
288 m_TreeRoot
.m_bFolder
= true;
290 TVINSERTSTRUCT tvinsert
= {0};
291 tvinsert
.hParent
= TVI_ROOT
;
292 tvinsert
.hInsertAfter
= TVI_ROOT
;
293 tvinsert
.itemex
.mask
= TVIF_DI_SETITEM
| TVIF_PARAM
| TVIF_TEXT
| TVIF_IMAGE
| TVIF_SELECTEDIMAGE
| TVIF_STATE
;
294 tvinsert
.itemex
.pszText
= L
"/";
295 tvinsert
.itemex
.lParam
= (LPARAM
)&m_TreeRoot
;
296 tvinsert
.itemex
.iImage
= m_nIconFolder
;
297 tvinsert
.itemex
.iSelectedImage
= m_nOpenIconFolder
;
298 m_TreeRoot
.m_hTree
= m_RepoTree
.InsertItem(&tvinsert
);
300 ReadTree(&m_TreeRoot
);
301 m_RepoTree
.Expand(m_TreeRoot
.m_hTree
, TVE_EXPAND
);
302 FillListCtrlForShadowTree(&m_TreeRoot
);
303 m_RepoTree
.SelectItem(m_TreeRoot
.m_hTree
);
306 int CRepositoryBrowser::ReadTreeRecursive(git_repository
&repo
, git_tree
* tree
, CShadowFilesTree
* treeroot
)
308 size_t count
= git_tree_entrycount(tree
);
310 for (int i
= 0; i
< count
; ++i
)
312 const git_tree_entry
*entry
= git_tree_entry_byindex(tree
, i
);
315 int mode
= git_tree_entry_filemode(entry
);
317 CString base
= CUnicodeUtils::GetUnicode(git_tree_entry_name(entry
), CP_UTF8
);
319 git_object
*object
= NULL
;
320 git_tree_entry_to_object(&object
, &repo
, entry
);
324 const git_oid
*oid
= git_object_id(object
);
325 CShadowFilesTree
* pNextTree
= &treeroot
->m_ShadowTree
[base
];
326 pNextTree
->m_sName
= base
;
327 pNextTree
->m_pParent
= treeroot
;
328 pNextTree
->m_hash
= CGitHash((char *)oid
->id
);
332 pNextTree
->m_bFolder
= true;
334 TVINSERTSTRUCT tvinsert
= {0};
335 tvinsert
.hParent
= treeroot
->m_hTree
;
336 tvinsert
.hInsertAfter
= TVI_SORT
;
337 tvinsert
.itemex
.mask
= TVIF_DI_SETITEM
| TVIF_PARAM
| TVIF_TEXT
| TVIF_IMAGE
| TVIF_SELECTEDIMAGE
| TVIF_STATE
;
338 tvinsert
.itemex
.pszText
= base
.GetBuffer(base
.GetLength());
339 tvinsert
.itemex
.lParam
= (LPARAM
)pNextTree
;
340 tvinsert
.itemex
.iImage
= m_nIconFolder
;
341 tvinsert
.itemex
.iSelectedImage
= m_nOpenIconFolder
;
342 pNextTree
->m_hTree
= m_RepoTree
.InsertItem(&tvinsert
);
343 base
.ReleaseBuffer();
345 ReadTreeRecursive(repo
, (git_tree
*)object
, pNextTree
);
350 git_blob_lookup(&blob
, &repo
, oid
);
354 pNextTree
->m_iSize
= git_blob_rawsize(blob
);
358 git_object_free(object
);
364 int CRepositoryBrowser::ReadTree(CShadowFilesTree
* treeroot
)
366 CStringA gitdir
= CUnicodeUtils::GetMulti(g_Git
.m_CurrentDir
, CP_UTF8
);
367 git_repository
*repository
= NULL
;
368 git_commit
*commit
= NULL
;
369 git_tree
* tree
= NULL
;
373 ret
= git_repository_open(&repository
, gitdir
.GetBuffer());
376 MessageBox(CGit::GetLibGit2LastErr(_T("Could not open repository.")), _T("TortoiseGit"), MB_ICONERROR
);
380 if (m_sRevision
== _T("HEAD"))
382 ret
= git_repository_head_orphan(repository
);
383 if (ret
== 1) // is orphan
387 MessageBox(g_Git
.GetGitLastErr(_T("Could not check HEAD.")), _T("TortoiseGit"), MB_ICONERROR
);
393 if (g_Git
.GetHash(hash
, m_sRevision
))
395 MessageBox(g_Git
.GetGitLastErr(_T("Could not get hash of ") + m_sRevision
+ _T(".")), _T("TortoiseGit"), MB_ICONERROR
);
398 ret
= git_commit_lookup(&commit
, repository
, (git_oid
*) hash
.m_hash
);
401 MessageBox(CGit::GetLibGit2LastErr(_T("Could not lookup commit.")), _T("TortoiseGit"), MB_ICONERROR
);
405 ret
= git_commit_tree(&tree
, commit
);
408 MessageBox(CGit::GetLibGit2LastErr(_T("Could not get tree of commit.")), _T("TortoiseGit"), MB_ICONERROR
);
412 ReadTreeRecursive(*repository
, tree
, treeroot
);
414 // try to resolve hash to a branch name
415 if (m_sRevision
== hash
.ToString())
418 if (g_Git
.GetMapHashToFriendName(map
))
419 MessageBox(g_Git
.GetGitLastErr(_T("Could not get all refs.")), _T("TortoiseGit"), MB_ICONERROR
);
420 if (!map
[hash
].empty())
421 m_sRevision
= map
[hash
].at(0);
423 this->GetDlgItem(IDC_BUTTON_REVISION
)->SetWindowText(m_sRevision
);
430 git_commit_free(commit
);
433 git_repository_free(repository
);
438 void CRepositoryBrowser::OnTvnSelchangedRepoTree(NMHDR
*pNMHDR
, LRESULT
*pResult
)
440 LPNMTREEVIEW pNMTreeView
= reinterpret_cast<LPNMTREEVIEW
>(pNMHDR
);
443 FillListCtrlForTreeNode(pNMTreeView
->itemNew
.hItem
);
446 void CRepositoryBrowser::FillListCtrlForTreeNode(HTREEITEM treeNode
)
448 m_RepoList
.DeleteAllItems();
450 CShadowFilesTree
* pTree
= (CShadowFilesTree
*)(m_RepoTree
.GetItemData(treeNode
));
457 CString url
= _T("/") + pTree
->GetFullName();
458 GetDlgItem(IDC_REPOBROWSER_URL
)->SetWindowText(url
);
460 FillListCtrlForShadowTree(pTree
);
463 void CRepositoryBrowser::FillListCtrlForShadowTree(CShadowFilesTree
* pTree
)
465 for (TShadowFilesTreeMap::iterator itShadowTree
= pTree
->m_ShadowTree
.begin(); itShadowTree
!= pTree
->m_ShadowTree
.end(); ++itShadowTree
)
467 int icon
= m_nIconFolder
;
468 if (!(*itShadowTree
).second
.m_bFolder
)
469 icon
= SYS_IMAGE_LIST().GetFileIconIndex((*itShadowTree
).second
.m_sName
);
471 int indexItem
= m_RepoList
.InsertItem(m_RepoList
.GetItemCount(), (*itShadowTree
).second
.m_sName
, icon
);
473 m_RepoList
.SetItemData(indexItem
, (DWORD_PTR
)&(*itShadowTree
).second
);
474 if (!(*itShadowTree
).second
.m_bFolder
)
478 temp
= CPathUtils::GetFileExtFromPath((*itShadowTree
).second
.m_sName
);
479 m_RepoList
.SetItemText(indexItem
, eCol_Extension
, temp
);
481 StrFormatByteSize((*itShadowTree
).second
.m_iSize
, temp
.GetBuffer(20), 20);
482 temp
.ReleaseBuffer();
483 m_RepoList
.SetItemText(indexItem
, eCol_FileSize
, temp
);
487 CRepoListCompareFunc
compareFunc(&m_RepoList
, m_currSortCol
, m_currSortDesc
);
488 m_RepoList
.SortItemsEx(&CRepoListCompareFunc::StaticCompare
, (DWORD_PTR
)&compareFunc
);
490 SetSortArrowA(&m_RepoList
, m_currSortCol
, !m_currSortDesc
);
493 void CRepositoryBrowser::OnContextMenu(CWnd
* pWndFrom
, CPoint point
)
495 if (pWndFrom
== &m_RepoList
)
496 OnContextMenu_RepoList(point
);
497 else if (pWndFrom
== &m_RepoTree
)
498 OnContextMenu_RepoTree(point
);
501 void CRepositoryBrowser::OnContextMenu_RepoTree(CPoint point
)
503 CPoint clientPoint
= point
;
504 m_RepoTree
.ScreenToClient(&clientPoint
);
506 HTREEITEM hTreeItem
= m_RepoTree
.HitTest(clientPoint
);
507 if (hTreeItem
== NULL
)
510 TShadowFilesTreeList selectedLeafs
;
511 selectedLeafs
.push_back((CShadowFilesTree
*)m_RepoTree
.GetItemData(hTreeItem
));
513 ShowContextMenu(point
, selectedLeafs
, ONLY_FOLDERS
);
516 void CRepositoryBrowser::OnContextMenu_RepoList(CPoint point
)
518 TShadowFilesTreeList selectedLeafs
;
519 selectedLeafs
.reserve(m_RepoList
.GetSelectedCount());
521 bool folderSelected
= false;
522 bool filesSelected
= false;
524 POSITION pos
= m_RepoList
.GetFirstSelectedItemPosition();
527 CShadowFilesTree
* item
= (CShadowFilesTree
*)m_RepoList
.GetItemData(m_RepoList
.GetNextSelectedItem(pos
));
529 folderSelected
= true;
531 filesSelected
= true;
532 selectedLeafs
.push_back(item
);
535 eSelectionType selType
= ONLY_FILES
;
536 if (folderSelected
&& filesSelected
)
538 else if (folderSelected
)
539 selType
= ONLY_FOLDERS
;
541 ShowContextMenu(point
, selectedLeafs
, selType
);
544 void CRepositoryBrowser::ShowContextMenu(CPoint point
, TShadowFilesTreeList
&selectedLeafs
, eSelectionType selType
)
547 popupMenu
.CreatePopupMenu();
549 bool bAddSeparator
= false;
551 if (selectedLeafs
.size() == 1)
553 popupMenu
.AppendMenuIcon(eCmd_Open
, IDS_REPOBROWSE_OPEN
, IDI_OPEN
);
554 popupMenu
.SetDefaultItem(eCmd_Open
, FALSE
);
555 if (selType
== ONLY_FILES
)
557 popupMenu
.AppendMenuIcon(eCmd_OpenWith
, IDS_LOG_POPUP_OPENWITH
, IDI_OPEN
);
558 popupMenu
.AppendMenuIcon(eCmd_OpenWithAlternativeEditor
, IDS_LOG_POPUP_VIEWREV
);
561 popupMenu
.AppendMenu(MF_SEPARATOR
);
563 if (m_bHasWC
&& selType
== ONLY_FILES
)
565 popupMenu
.AppendMenuIcon(eCmd_CompareWC
, IDS_LOG_POPUP_COMPARE
, IDI_DIFF
);
566 bAddSeparator
= true;
570 popupMenu
.AppendMenu(MF_SEPARATOR
);
571 bAddSeparator
= false;
574 temp
.LoadString(IDS_MENULOG
);
575 popupMenu
.AppendMenuIcon(eCmd_ViewLog
, temp
, IDI_LOG
);
577 if (selType
== ONLY_FILES
)
580 popupMenu
.AppendMenuIcon(eCmd_Blame
, IDS_LOG_POPUP_BLAME
, IDI_BLAME
);
582 popupMenu
.AppendMenu(MF_SEPARATOR
);
583 temp
.LoadString(IDS_LOG_POPUP_SAVE
);
584 popupMenu
.AppendMenuIcon(eCmd_SaveAs
, temp
, IDI_SAVEAS
);
587 bAddSeparator
= true;
590 if (selType
== ONLY_FILES
&& m_bHasWC
)
592 popupMenu
.AppendMenuIcon(eCmd_Revert
, IDS_LOG_POPUP_REVERTTOREV
, IDI_REVERT
);
593 bAddSeparator
= true;
597 popupMenu
.AppendMenu(MF_SEPARATOR
);
598 bAddSeparator
= false;
600 popupMenu
.AppendMenuIcon(eCmd_CopyPath
, IDS_STATUSLIST_CONTEXT_COPY
, IDI_COPYCLIP
);
601 popupMenu
.AppendMenuIcon(eCmd_CopyHash
, IDS_COPY_COMMIT_HASH
, IDI_COPYCLIP
);
603 eCmd cmd
= (eCmd
)popupMenu
.TrackPopupMenuEx(TPM_LEFTALIGN
|TPM_RETURNCMD
, point
.x
, point
.y
, this, 0);
609 sCmd
.Format(_T("/command:log /path:\"%s\\%s\""), g_Git
.m_CurrentDir
, selectedLeafs
.at(0)->GetFullName());
610 CAppUtils::RunTortoiseGitProc(sCmd
);
615 CAppUtils::LaunchTortoiseBlame(g_Git
.m_CurrentDir
+ _T("\\") + selectedLeafs
.at(0)->GetFullName(), m_sRevision
);
619 if (selectedLeafs
.at(0)->m_bFolder
)
621 FillListCtrlForTreeNode(selectedLeafs
.at(0)->m_hTree
);
622 m_RepoTree
.SelectItem(selectedLeafs
.at(0)->m_hTree
);
625 OpenFile(selectedLeafs
.at(0)->GetFullName(), OPEN
);
628 OpenFile(selectedLeafs
.at(0)->GetFullName(), OPEN_WITH
);
630 case eCmd_OpenWithAlternativeEditor
:
631 OpenFile(selectedLeafs
.at(0)->GetFullName(), ALTERNATIVEEDITOR
);
635 CTGitPath
file(selectedLeafs
.at(0)->GetFullName());
636 CGitDiff::Diff(&file
, &file
, GIT_REV_ZERO
, m_sRevision
);
642 for (TShadowFilesTreeList::iterator itShadowTree
= selectedLeafs
.begin(); itShadowTree
!= selectedLeafs
.end(); ++itShadowTree
)
644 if (RevertItemToVersion((*itShadowTree
)->GetFullName()))
650 msg
.Format(IDS_STATUSLIST_FILESREVERTED
, count
, m_sRevision
);
651 MessageBox(msg
, _T("TortoiseGit"), MB_OK
);
655 FileSaveAs(selectedLeafs
.at(0)->GetFullName());
660 for (TShadowFilesTreeList::iterator itShadowTree
= selectedLeafs
.begin(); itShadowTree
!= selectedLeafs
.end(); ++itShadowTree
)
662 sClipboard
+= (*itShadowTree
)->m_sName
+ _T("\r\n");
664 CStringUtils::WriteAsciiStringToClipboard(sClipboard
);
669 CopyHashToClipboard(selectedLeafs
);
675 BOOL
CRepositoryBrowser::PreTranslateMessage(MSG
* pMsg
)
677 if (pMsg
->message
== WM_KEYDOWN
)
679 switch (pMsg
->wParam
)
689 return CResizableStandAloneDialog::PreTranslateMessage(pMsg
);
692 void CRepositoryBrowser::OnLvnColumnclickRepoList(NMHDR
*pNMHDR
, LRESULT
*pResult
)
694 LPNMLISTVIEW pNMLV
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
698 if (m_currSortCol
== pNMLV
->iSubItem
)
699 m_currSortDesc
= !m_currSortDesc
;
702 m_currSortCol
= pNMLV
->iSubItem
;
703 m_currSortDesc
= false;
706 CRepoListCompareFunc
compareFunc(&m_RepoList
, m_currSortCol
, m_currSortDesc
);
707 m_RepoList
.SortItemsEx(&CRepoListCompareFunc::StaticCompare
, (DWORD_PTR
)&compareFunc
);
709 SetSortArrowA(&m_RepoList
, m_currSortCol
, !m_currSortDesc
);
712 void CRepositoryBrowser::OnBnClickedButtonRevision()
714 // use the git log to allow selection of a version
716 // tell the dialog to use mode for selecting revisions
718 // only one revision must be selected however
719 dlg
.SingleSelection(true);
720 if (dlg
.DoModal() == IDOK
)
722 // get selected hash if any
723 m_sRevision
= dlg
.GetSelectedHash();
728 void CRepositoryBrowser::SaveDividerPosition()
731 GetDlgItem(IDC_REPOTREE
)->GetClientRect(&rc
);
732 CRegDWORD xPos
= CRegDWORD(_T("Software\\TortoiseGit\\TortoiseProc\\ResizableState\\RepobrowserDivider"));
733 xPos
= rc
.right
- rc
.left
;
736 void CRepositoryBrowser::HandleDividerMove(CPoint point
, bool bDraw
)
738 RECT rect
, tree
, list
, treelist
, treelistclient
;
740 // create an union of the tree and list control rectangle
741 GetDlgItem(IDC_REPOLIST
)->GetWindowRect(&list
);
742 GetDlgItem(IDC_REPOTREE
)->GetWindowRect(&tree
);
743 UnionRect(&treelist
, &tree
, &list
);
744 treelistclient
= treelist
;
745 ScreenToClient(&treelistclient
);
747 ClientToScreen(&point
);
748 GetClientRect(&rect
);
749 ClientToScreen(&rect
);
751 CPoint point2
= point
;
752 if (point2
.x
< treelist
.left
+ REPOBROWSER_CTRL_MIN_WIDTH
)
753 point2
.x
= treelist
.left
+ REPOBROWSER_CTRL_MIN_WIDTH
;
754 if (point2
.x
> treelist
.right
- REPOBROWSER_CTRL_MIN_WIDTH
)
755 point2
.x
= treelist
.right
- REPOBROWSER_CTRL_MIN_WIDTH
;
757 point
.x
-= rect
.left
;
758 point
.y
-= treelist
.top
;
760 OffsetRect(&treelist
, -treelist
.left
, -treelist
.top
);
762 if (point
.x
< treelist
.left
+REPOBROWSER_CTRL_MIN_WIDTH
)
763 point
.x
= treelist
.left
+REPOBROWSER_CTRL_MIN_WIDTH
;
764 if (point
.x
> treelist
.right
-REPOBROWSER_CTRL_MIN_WIDTH
)
765 point
.x
= treelist
.right
-REPOBROWSER_CTRL_MIN_WIDTH
;
770 DrawXorBar(pDC
, oldx
+ 2, treelistclient
.top
, 4, treelistclient
.bottom
- treelistclient
.top
- 2);
777 //position the child controls
778 GetDlgItem(IDC_REPOTREE
)->GetWindowRect(&treelist
);
779 treelist
.right
= point2
.x
- 2;
780 ScreenToClient(&treelist
);
781 RemoveAnchor(IDC_REPOTREE
);
782 GetDlgItem(IDC_REPOTREE
)->MoveWindow(&treelist
);
783 GetDlgItem(IDC_REPOLIST
)->GetWindowRect(&treelist
);
784 treelist
.left
= point2
.x
+ 2;
785 ScreenToClient(&treelist
);
786 RemoveAnchor(IDC_REPOLIST
);
787 GetDlgItem(IDC_REPOLIST
)->MoveWindow(&treelist
);
789 AddAnchor(IDC_REPOTREE
, TOP_LEFT
, BOTTOM_LEFT
);
790 AddAnchor(IDC_REPOLIST
, TOP_LEFT
, BOTTOM_RIGHT
);
793 void CRepositoryBrowser::OnMouseMove(UINT nFlags
, CPoint point
)
795 if (bDragMode
== FALSE
)
798 RECT rect
, tree
, list
, treelist
, treelistclient
;
799 // create an union of the tree and list control rectangle
800 GetDlgItem(IDC_REPOLIST
)->GetWindowRect(&list
);
801 GetDlgItem(IDC_REPOTREE
)->GetWindowRect(&tree
);
802 UnionRect(&treelist
, &tree
, &list
);
803 treelistclient
= treelist
;
804 ScreenToClient(&treelistclient
);
806 //convert the mouse coordinates relative to the top-left of
808 ClientToScreen(&point
);
809 GetClientRect(&rect
);
810 ClientToScreen(&rect
);
811 point
.x
-= rect
.left
;
812 point
.y
-= treelist
.top
;
814 //same for the window coordinates - make them relative to 0,0
815 OffsetRect(&treelist
, -treelist
.left
, -treelist
.top
);
817 if (point
.x
< treelist
.left
+ REPOBROWSER_CTRL_MIN_WIDTH
)
818 point
.x
= treelist
.left
+ REPOBROWSER_CTRL_MIN_WIDTH
;
819 if (point
.x
> treelist
.right
- REPOBROWSER_CTRL_MIN_WIDTH
)
820 point
.x
= treelist
.right
- REPOBROWSER_CTRL_MIN_WIDTH
;
822 if ((nFlags
& MK_LBUTTON
) && (point
.x
!= oldx
))
828 DrawXorBar(pDC
, oldx
+ 2, treelistclient
.top
, 4, treelistclient
.bottom
- treelistclient
.top
- 2);
829 DrawXorBar(pDC
, point
.x
+ 2, treelistclient
.top
, 4, treelistclient
.bottom
- treelistclient
.top
- 2);
838 CStandAloneDialogTmpl
<CResizableDialog
>::OnMouseMove(nFlags
, point
);
841 void CRepositoryBrowser::OnLButtonDown(UINT nFlags
, CPoint point
)
843 RECT rect
, tree
, list
, treelist
, treelistclient
;
845 // create an union of the tree and list control rectangle
846 GetDlgItem(IDC_REPOLIST
)->GetWindowRect(&list
);
847 GetDlgItem(IDC_REPOTREE
)->GetWindowRect(&tree
);
848 UnionRect(&treelist
, &tree
, &list
);
849 treelistclient
= treelist
;
850 ScreenToClient(&treelistclient
);
852 //convert the mouse coordinates relative to the top-left of
854 ClientToScreen(&point
);
855 GetClientRect(&rect
);
856 ClientToScreen(&rect
);
857 point
.x
-= rect
.left
;
858 point
.y
-= treelist
.top
;
860 //same for the window coordinates - make them relative to 0,0
861 OffsetRect(&treelist
, -treelist
.left
, -treelist
.top
);
863 if (point
.x
< treelist
.left
+ REPOBROWSER_CTRL_MIN_WIDTH
)
864 return CStandAloneDialogTmpl
< CResizableDialog
>::OnLButtonDown(nFlags
, point
);
865 if (point
.x
> treelist
.right
- 3)
866 return CStandAloneDialogTmpl
< CResizableDialog
>::OnLButtonDown(nFlags
, point
);
867 if (point
.x
> treelist
.right
- REPOBROWSER_CTRL_MIN_WIDTH
)
868 point
.x
= treelist
.right
- REPOBROWSER_CTRL_MIN_WIDTH
;
870 if ((point
.y
< treelist
.top
+ 3) || (point
.y
> treelist
.bottom
- 3))
871 return CStandAloneDialogTmpl
<CResizableDialog
>::OnLButtonDown(nFlags
, point
);
878 DrawXorBar(pDC
, point
.x
+ 2, treelistclient
.top
, 4, treelistclient
.bottom
- treelistclient
.top
- 2);
884 CStandAloneDialogTmpl
<CResizableDialog
>::OnLButtonDown(nFlags
, point
);
887 void CRepositoryBrowser::OnLButtonUp(UINT nFlags
, CPoint point
)
889 if (bDragMode
== FALSE
)
892 HandleDividerMove(point
, true);
897 CStandAloneDialogTmpl
<CResizableDialog
>::OnLButtonUp(nFlags
, point
);
900 void CRepositoryBrowser::OnCaptureChanged(CWnd
*pWnd
)
904 __super::OnCaptureChanged(pWnd
);
907 void CRepositoryBrowser::DrawXorBar(CDC
* pDC
, int x1
, int y1
, int width
, int height
)
909 static WORD _dotPatternBmp
[8] =
911 0x0055, 0x00aa, 0x0055, 0x00aa,
912 0x0055, 0x00aa, 0x0055, 0x00aa
916 HBRUSH hbr
, hbrushOld
;
918 hbm
= CreateBitmap(8, 8, 1, 1, _dotPatternBmp
);
919 hbr
= CreatePatternBrush(hbm
);
921 pDC
->SetBrushOrg(x1
, y1
);
922 hbrushOld
= (HBRUSH
)pDC
->SelectObject(hbr
);
924 PatBlt(pDC
->GetSafeHdc(), x1
, y1
, width
, height
, PATINVERT
);
926 pDC
->SelectObject(hbrushOld
);
932 BOOL
CRepositoryBrowser::OnSetCursor(CWnd
* pWnd
, UINT nHitTest
, UINT message
)
938 GetClientRect(&rect
);
941 if (PtInRect(&rect
, pt
))
944 // are we right of the tree control?
945 GetDlgItem(IDC_REPOTREE
)->GetWindowRect(&rect
);
946 if ((pt
.x
> rect
.right
) && (pt
.y
>= rect
.top
+ 3) && (pt
.y
<= rect
.bottom
- 3))
948 // but left of the list control?
949 GetDlgItem(IDC_REPOLIST
)->GetWindowRect(&rect
);
950 if (pt
.x
< rect
.left
)
952 HCURSOR hCur
= LoadCursor(NULL
, MAKEINTRESOURCE(IDC_SIZEWE
));
959 return CStandAloneDialogTmpl
<CResizableDialog
>::OnSetCursor(pWnd
, nHitTest
, message
);
962 void CRepositoryBrowser::FileSaveAs(const CString path
)
964 CTGitPath
gitPath(path
);
967 filename
.Format(_T("%s-%s%s"), gitPath
.GetBaseFilename(), CGitHash(m_sRevision
).ToString().Left(g_Git
.GetShortHASHLength()), gitPath
.GetFileExtension());
968 CFileDialog
dlg(FALSE
, NULL
, filename
, OFN_HIDEREADONLY
| OFN_OVERWRITEPROMPT
, NULL
);
971 INT_PTR ret
= dlg
.DoModal();
972 SetCurrentDirectory(g_Git
.m_CurrentDir
);
975 filename
= dlg
.GetPathName();
976 if (g_Git
.GetOneFile(m_sRevision
, gitPath
, filename
))
978 out
.Format(IDS_STATUSLIST_CHECKOUTFILEFAILED
, gitPath
.GetGitPathString(), m_sRevision
, filename
);
979 MessageBox(out
, _T("TortoiseGit"), MB_OK
);
985 void CRepositoryBrowser::OpenFile(const CString path
, eOpenType mode
)
987 CTGitPath
gitPath(path
);
991 GetTempPath(temppath
);
992 file
.Format(_T("%s%s_%s%s"), temppath
, gitPath
.GetBaseFilename(), CGitHash(m_sRevision
).ToString().Left(g_Git
.GetShortHASHLength()), gitPath
.GetFileExtension());
995 if(g_Git
.GetOneFile(m_sRevision
, gitPath
, file
))
997 out
.Format(IDS_STATUSLIST_CHECKOUTFILEFAILED
, gitPath
.GetGitPathString(), m_sRevision
, file
);
998 MessageBox(out
, _T("TortoiseGit"), MB_OK
);
1002 if (mode
== ALTERNATIVEEDITOR
)
1004 CAppUtils::LaunchAlternativeEditor(file
);
1007 else if (mode
== OPEN
)
1009 int ret
= HINSTANCE_ERROR
;
1010 ret
= (int)ShellExecute(this->m_hWnd
, NULL
, file
, NULL
, NULL
, SW_SHOW
);
1012 if (ret
> HINSTANCE_ERROR
)
1016 CString cmd
= _T("RUNDLL32 Shell32,OpenAs_RunDLL ") + file
;
1017 CAppUtils::LaunchApplication(cmd
, NULL
, false);
1019 bool CRepositoryBrowser::RevertItemToVersion(const CString
&path
)
1022 cmd
.Format(_T("git.exe checkout %s -- \"%s\""), m_sRevision
, path
);
1023 if (g_Git
.Run(cmd
, &out
, CP_UTF8
))
1025 if (MessageBox(out
, _T("TortoiseGit"), MB_ICONEXCLAMATION
| MB_OKCANCEL
) == IDCANCEL
)
1032 void CRepositoryBrowser::CopyHashToClipboard(TShadowFilesTreeList
&selectedLeafs
)
1034 if (!selectedLeafs
.empty())
1038 for (int i
= 0; i
< selectedLeafs
.size(); i
++)
1041 sClipdata
+= _T("\r\n");
1042 sClipdata
+= selectedLeafs
[i
]->m_hash
;
1045 CStringUtils::WriteAsciiStringToClipboard(sClipdata
, GetSafeHwnd());