Repository Browser: Do not show revert, copy path, copy hash menu items if no item...
[TortoiseGit.git] / src / TortoiseProc / RepositoryBrowser.cpp
blob63747750ed9ebb8a86a850def67be80b2ae957ce
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
24 #include "stdafx.h"
25 #include "TortoiseProc.h"
26 #include "RepositoryBrowser.h"
27 #include "LogDlg.h"
28 #include "AppUtils.h"
29 #include "IconMenu.h"
30 #include "UnicodeUtils.h"
31 #include "SysImageList.h"
32 #include <sys/stat.h>
33 #include "SysInfo.h"
34 #include "registry.h"
35 #include "PathUtils.h"
36 #include "StringUtils.h"
37 #include "GitDiff.h"
39 void SetSortArrowA(CListCtrl * control, int nColumn, bool bAscending)
41 if (control == NULL)
42 return;
44 // set the sort arrow
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);
54 if (nColumn >= 0)
56 pHeader->GetItem(nColumn, &HeaderItem);
57 HeaderItem.fmt |= (bAscending ? HDF_SORTUP : HDF_SORTDOWN);
58 pHeader->SetItem(nColumn, &HeaderItem);
62 class CRepoListCompareFunc
64 public:
65 CRepoListCompareFunc(CListCtrl* pList, int col, bool desc)
66 : m_col(col)
67 , m_desc(desc)
68 , m_pList(pList)
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);
81 int result = 0;
82 switch(m_col)
84 case CRepositoryBrowser::eCol_Name:
85 result = SortStrCmp(pLeft->m_sName, pRight->m_sName);
86 if (result != 0)
87 break;
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);
92 if (result != 0)
93 break;
94 case CRepositoryBrowser::eCol_FileSize:
95 if (pLeft->m_iSize > pRight->m_iSize)
96 result = 1;
97 else if (pLeft->m_iSize < pRight->m_iSize)
98 result = -1;
99 else // fallback
100 result = SortStrCmp(pLeft->m_sName, pRight->m_sName);
103 if (m_desc)
104 result = -result;
106 if (pLeft->m_bFolder != pRight->m_bFolder)
108 if (pRight->m_bFolder)
109 result = 1;
110 else
111 result = -1;
114 return result;
116 int SortStrCmp(CString &left, CString &right)
118 if (CRepositoryBrowser::s_bSortLogical)
119 return StrCmpLogicalW(left, right);
120 return StrCmpI(left, right);
123 int m_col;
124 bool m_desc;
125 CListCtrl* m_pList;
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)
136 , m_currSortCol(0)
137 , m_currSortDesc(false)
138 , m_sRevision(rev)
139 , m_bHasWC(true)
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)
157 ON_WM_CONTEXTMENU()
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)
161 ON_WM_SETCURSOR()
162 ON_WM_MOUSEMOVE()
163 ON_WM_LBUTTONDOWN()
164 ON_WM_LBUTTONUP()
165 END_MESSAGE_MAP()
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);
189 CString temp;
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);
224 if (xPos == 0)
226 RECT rc;
227 GetDlgItem(IDC_REPOTREE)->GetClientRect(&rc);
228 xPos = rc.right - rc.left;
230 bDragMode = false;
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);
239 Refresh();
241 m_RepoList.SetFocus();
243 return FALSE;
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)
260 *pResult = 0;
262 LPNMITEMACTIVATE pNmItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
263 if (pNmItemActivate->iItem < 0)
264 return;
266 CShadowFilesTree * pItem = (CShadowFilesTree *)m_RepoList.GetItemData(pNmItemActivate->iItem);
267 if (pItem == NULL)
268 return;
270 if (!pItem->m_bFolder)
272 OpenFile(pItem->GetFullName(), OPEN);
273 return;
275 else
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);
313 if (entry == NULL)
314 continue;
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);
321 if (object == NULL)
322 continue;
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);
330 if (mode & S_IFDIR)
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);
347 else
349 git_blob * blob;
350 git_blob_lookup(&blob, &repo, oid);
351 if (blob == NULL)
352 continue;
354 pNextTree->m_iSize = git_blob_rawsize(blob);
355 git_blob_free(blob);
358 git_object_free(object);
361 return 0;
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;
370 int ret = 0;
373 ret = git_repository_open(&repository, gitdir.GetBuffer());
374 if (ret)
376 MessageBox(CGit::GetLibGit2LastErr(_T("Could not open repository.")), _T("TortoiseGit"), MB_ICONERROR);
377 break;
380 if (m_sRevision == _T("HEAD"))
382 ret = git_repository_head_orphan(repository);
383 if (ret == 1) // is orphan
384 break;
385 else if (ret != 0)
387 MessageBox(g_Git.GetLibGit2LastErr(_T("Could not check HEAD.")), _T("TortoiseGit"), MB_ICONERROR);
388 break;
392 CGitHash hash;
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);
396 break;
398 ret = git_commit_lookup(&commit, repository, (git_oid *) hash.m_hash);
399 if (ret)
401 MessageBox(CGit::GetLibGit2LastErr(_T("Could not lookup commit.")), _T("TortoiseGit"), MB_ICONERROR);
402 break;
405 ret = git_commit_tree(&tree, commit);
406 if (ret)
408 MessageBox(CGit::GetLibGit2LastErr(_T("Could not get tree of commit.")), _T("TortoiseGit"), MB_ICONERROR);
409 break;
412 ReadTreeRecursive(*repository, tree, treeroot);
414 // try to resolve hash to a branch name
415 if (m_sRevision == hash.ToString())
417 MAP_HASH_NAME map;
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);
424 } while(0);
426 if (tree)
427 git_tree_free(tree);
429 if (commit)
430 git_commit_free(commit);
432 if (repository)
433 git_repository_free(repository);
435 return ret;
438 void CRepositoryBrowser::OnTvnSelchangedRepoTree(NMHDR *pNMHDR, LRESULT *pResult)
440 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
441 *pResult = 0;
443 FillListCtrlForTreeNode(pNMTreeView->itemNew.hItem);
446 void CRepositoryBrowser::FillListCtrlForTreeNode(HTREEITEM treeNode)
448 m_RepoList.DeleteAllItems();
450 CShadowFilesTree* pTree = (CShadowFilesTree*)(m_RepoTree.GetItemData(treeNode));
451 if (pTree == NULL)
453 ASSERT(FALSE);
454 return;
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)
476 CString temp;
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)
508 return;
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();
525 while (pos)
527 CShadowFilesTree * item = (CShadowFilesTree *)m_RepoList.GetItemData(m_RepoList.GetNextSelectedItem(pos));
528 if (item->m_bFolder)
529 folderSelected = true;
530 else
531 filesSelected = true;
532 selectedLeafs.push_back(item);
535 eSelectionType selType = ONLY_FILES;
536 if (folderSelected && filesSelected)
537 selType = MIXED;
538 else if (folderSelected)
539 selType = ONLY_FOLDERS;
541 ShowContextMenu(point, selectedLeafs, selType);
544 void CRepositoryBrowser::ShowContextMenu(CPoint point, TShadowFilesTreeList &selectedLeafs, eSelectionType selType)
546 CIconMenu popupMenu;
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;
569 if (bAddSeparator)
570 popupMenu.AppendMenu(MF_SEPARATOR);
571 bAddSeparator = false;
573 CString temp;
574 temp.LoadString(IDS_MENULOG);
575 popupMenu.AppendMenuIcon(eCmd_ViewLog, temp, IDI_LOG);
577 if (selType == ONLY_FILES)
579 if (m_bHasWC)
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 (!selectedLeafs.empty() && selType == ONLY_FILES && m_bHasWC)
592 popupMenu.AppendMenuIcon(eCmd_Revert, IDS_LOG_POPUP_REVERTTOREV, IDI_REVERT);
593 bAddSeparator = true;
596 if (bAddSeparator)
597 popupMenu.AppendMenu(MF_SEPARATOR);
598 bAddSeparator = false;
600 if (!selectedLeafs.empty())
602 popupMenu.AppendMenuIcon(eCmd_CopyPath, IDS_STATUSLIST_CONTEXT_COPY, IDI_COPYCLIP);
603 popupMenu.AppendMenuIcon(eCmd_CopyHash, IDS_COPY_COMMIT_HASH, IDI_COPYCLIP);
606 eCmd cmd = (eCmd)popupMenu.TrackPopupMenuEx(TPM_LEFTALIGN|TPM_RETURNCMD, point.x, point.y, this, 0);
607 switch(cmd)
609 case eCmd_ViewLog:
611 CString sCmd;
612 sCmd.Format(_T("/command:log /path:\"%s\\%s\""), g_Git.m_CurrentDir, selectedLeafs.at(0)->GetFullName());
613 CAppUtils::RunTortoiseGitProc(sCmd);
615 break;
616 case eCmd_Blame:
618 CAppUtils::LaunchTortoiseBlame(g_Git.m_CurrentDir + _T("\\") + selectedLeafs.at(0)->GetFullName(), m_sRevision);
620 break;
621 case eCmd_Open:
622 if (selectedLeafs.at(0)->m_bFolder)
624 FillListCtrlForTreeNode(selectedLeafs.at(0)->m_hTree);
625 m_RepoTree.SelectItem(selectedLeafs.at(0)->m_hTree);
626 return;
628 OpenFile(selectedLeafs.at(0)->GetFullName(), OPEN);
629 break;
630 case eCmd_OpenWith:
631 OpenFile(selectedLeafs.at(0)->GetFullName(), OPEN_WITH);
632 break;
633 case eCmd_OpenWithAlternativeEditor:
634 OpenFile(selectedLeafs.at(0)->GetFullName(), ALTERNATIVEEDITOR);
635 break;
636 case eCmd_CompareWC:
638 CTGitPath file(selectedLeafs.at(0)->GetFullName());
639 CGitDiff::Diff(&file, &file, GIT_REV_ZERO, m_sRevision);
641 break;
642 case eCmd_Revert:
644 int count = 0;
645 for (TShadowFilesTreeList::iterator itShadowTree = selectedLeafs.begin(); itShadowTree != selectedLeafs.end(); ++itShadowTree)
647 if (RevertItemToVersion((*itShadowTree)->GetFullName()))
648 ++count;
649 else
650 break;
652 CString msg;
653 msg.Format(IDS_STATUSLIST_FILESREVERTED, count, m_sRevision);
654 MessageBox(msg, _T("TortoiseGit"), MB_OK);
656 break;
657 case eCmd_SaveAs:
658 FileSaveAs(selectedLeafs.at(0)->GetFullName());
659 break;
660 case eCmd_CopyPath:
662 CString sClipboard;
663 for (TShadowFilesTreeList::iterator itShadowTree = selectedLeafs.begin(); itShadowTree != selectedLeafs.end(); ++itShadowTree)
665 sClipboard += (*itShadowTree)->m_sName + _T("\r\n");
667 CStringUtils::WriteAsciiStringToClipboard(sClipboard);
669 break;
670 case eCmd_CopyHash:
672 CopyHashToClipboard(selectedLeafs);
674 break;
678 BOOL CRepositoryBrowser::PreTranslateMessage(MSG* pMsg)
680 if (pMsg->message == WM_KEYDOWN)
682 switch (pMsg->wParam)
684 case VK_F5:
686 Refresh();
688 break;
692 return CResizableStandAloneDialog::PreTranslateMessage(pMsg);
695 void CRepositoryBrowser::OnLvnColumnclickRepoList(NMHDR *pNMHDR, LRESULT *pResult)
697 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
698 if (pResult)
699 *pResult = 0;
701 if (m_currSortCol == pNMLV->iSubItem)
702 m_currSortDesc = !m_currSortDesc;
703 else
705 m_currSortCol = pNMLV->iSubItem;
706 m_currSortDesc = false;
709 CRepoListCompareFunc compareFunc(&m_RepoList, m_currSortCol, m_currSortDesc);
710 m_RepoList.SortItemsEx(&CRepoListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
712 SetSortArrowA(&m_RepoList, m_currSortCol, !m_currSortDesc);
715 void CRepositoryBrowser::OnBnClickedButtonRevision()
717 // use the git log to allow selection of a version
718 CLogDlg dlg;
719 // tell the dialog to use mode for selecting revisions
720 dlg.SetSelect(true);
721 // only one revision must be selected however
722 dlg.SingleSelection(true);
723 if (dlg.DoModal() == IDOK)
725 // get selected hash if any
726 m_sRevision = dlg.GetSelectedHash();
727 Refresh();
731 void CRepositoryBrowser::SaveDividerPosition()
733 RECT rc;
734 GetDlgItem(IDC_REPOTREE)->GetClientRect(&rc);
735 CRegDWORD xPos = CRegDWORD(_T("Software\\TortoiseGit\\TortoiseProc\\ResizableState\\RepobrowserDivider"));
736 xPos = rc.right - rc.left;
739 void CRepositoryBrowser::HandleDividerMove(CPoint point, bool bDraw)
741 RECT rect, tree, list, treelist, treelistclient;
743 // create an union of the tree and list control rectangle
744 GetDlgItem(IDC_REPOLIST)->GetWindowRect(&list);
745 GetDlgItem(IDC_REPOTREE)->GetWindowRect(&tree);
746 UnionRect(&treelist, &tree, &list);
747 treelistclient = treelist;
748 ScreenToClient(&treelistclient);
750 ClientToScreen(&point);
751 GetClientRect(&rect);
752 ClientToScreen(&rect);
754 CPoint point2 = point;
755 if (point2.x < treelist.left + REPOBROWSER_CTRL_MIN_WIDTH)
756 point2.x = treelist.left + REPOBROWSER_CTRL_MIN_WIDTH;
757 if (point2.x > treelist.right - REPOBROWSER_CTRL_MIN_WIDTH)
758 point2.x = treelist.right - REPOBROWSER_CTRL_MIN_WIDTH;
760 point.x -= rect.left;
761 point.y -= treelist.top;
763 OffsetRect(&treelist, -treelist.left, -treelist.top);
765 if (point.x < treelist.left+REPOBROWSER_CTRL_MIN_WIDTH)
766 point.x = treelist.left+REPOBROWSER_CTRL_MIN_WIDTH;
767 if (point.x > treelist.right-REPOBROWSER_CTRL_MIN_WIDTH)
768 point.x = treelist.right-REPOBROWSER_CTRL_MIN_WIDTH;
770 if (bDraw)
772 CDC * pDC = GetDC();
773 DrawXorBar(pDC, oldx + 2, treelistclient.top, 4, treelistclient.bottom - treelistclient.top - 2);
774 ReleaseDC(pDC);
777 oldx = point.x;
778 oldy = point.y;
780 //position the child controls
781 GetDlgItem(IDC_REPOTREE)->GetWindowRect(&treelist);
782 treelist.right = point2.x - 2;
783 ScreenToClient(&treelist);
784 RemoveAnchor(IDC_REPOTREE);
785 GetDlgItem(IDC_REPOTREE)->MoveWindow(&treelist);
786 GetDlgItem(IDC_REPOLIST)->GetWindowRect(&treelist);
787 treelist.left = point2.x + 2;
788 ScreenToClient(&treelist);
789 RemoveAnchor(IDC_REPOLIST);
790 GetDlgItem(IDC_REPOLIST)->MoveWindow(&treelist);
792 AddAnchor(IDC_REPOTREE, TOP_LEFT, BOTTOM_LEFT);
793 AddAnchor(IDC_REPOLIST, TOP_LEFT, BOTTOM_RIGHT);
796 void CRepositoryBrowser::OnMouseMove(UINT nFlags, CPoint point)
798 if (bDragMode == FALSE)
799 return;
801 RECT rect, tree, list, treelist, treelistclient;
802 // create an union of the tree and list control rectangle
803 GetDlgItem(IDC_REPOLIST)->GetWindowRect(&list);
804 GetDlgItem(IDC_REPOTREE)->GetWindowRect(&tree);
805 UnionRect(&treelist, &tree, &list);
806 treelistclient = treelist;
807 ScreenToClient(&treelistclient);
809 //convert the mouse coordinates relative to the top-left of
810 //the window
811 ClientToScreen(&point);
812 GetClientRect(&rect);
813 ClientToScreen(&rect);
814 point.x -= rect.left;
815 point.y -= treelist.top;
817 //same for the window coordinates - make them relative to 0,0
818 OffsetRect(&treelist, -treelist.left, -treelist.top);
820 if (point.x < treelist.left + REPOBROWSER_CTRL_MIN_WIDTH)
821 point.x = treelist.left + REPOBROWSER_CTRL_MIN_WIDTH;
822 if (point.x > treelist.right - REPOBROWSER_CTRL_MIN_WIDTH)
823 point.x = treelist.right - REPOBROWSER_CTRL_MIN_WIDTH;
825 if ((nFlags & MK_LBUTTON) && (point.x != oldx))
827 CDC * pDC = GetDC();
829 if (pDC)
831 DrawXorBar(pDC, oldx + 2, treelistclient.top, 4, treelistclient.bottom - treelistclient.top - 2);
832 DrawXorBar(pDC, point.x + 2, treelistclient.top, 4, treelistclient.bottom - treelistclient.top - 2);
834 ReleaseDC(pDC);
837 oldx = point.x;
838 oldy = point.y;
841 CStandAloneDialogTmpl<CResizableDialog>::OnMouseMove(nFlags, point);
844 void CRepositoryBrowser::OnLButtonDown(UINT nFlags, CPoint point)
846 RECT rect, tree, list, treelist, treelistclient;
848 // create an union of the tree and list control rectangle
849 GetDlgItem(IDC_REPOLIST)->GetWindowRect(&list);
850 GetDlgItem(IDC_REPOTREE)->GetWindowRect(&tree);
851 UnionRect(&treelist, &tree, &list);
852 treelistclient = treelist;
853 ScreenToClient(&treelistclient);
855 //convert the mouse coordinates relative to the top-left of
856 //the window
857 ClientToScreen(&point);
858 GetClientRect(&rect);
859 ClientToScreen(&rect);
860 point.x -= rect.left;
861 point.y -= treelist.top;
863 //same for the window coordinates - make them relative to 0,0
864 OffsetRect(&treelist, -treelist.left, -treelist.top);
866 if (point.x < treelist.left + REPOBROWSER_CTRL_MIN_WIDTH)
867 return CStandAloneDialogTmpl < CResizableDialog>::OnLButtonDown(nFlags, point);
868 if (point.x > treelist.right - 3)
869 return CStandAloneDialogTmpl < CResizableDialog>::OnLButtonDown(nFlags, point);
870 if (point.x > treelist.right - REPOBROWSER_CTRL_MIN_WIDTH)
871 point.x = treelist.right - REPOBROWSER_CTRL_MIN_WIDTH;
873 if ((point.y < treelist.top + 3) || (point.y > treelist.bottom - 3))
874 return CStandAloneDialogTmpl<CResizableDialog>::OnLButtonDown(nFlags, point);
876 bDragMode = true;
878 SetCapture();
880 CDC * pDC = GetDC();
881 DrawXorBar(pDC, point.x + 2, treelistclient.top, 4, treelistclient.bottom - treelistclient.top - 2);
882 ReleaseDC(pDC);
884 oldx = point.x;
885 oldy = point.y;
887 CStandAloneDialogTmpl<CResizableDialog>::OnLButtonDown(nFlags, point);
890 void CRepositoryBrowser::OnLButtonUp(UINT nFlags, CPoint point)
892 if (bDragMode == FALSE)
893 return;
895 HandleDividerMove(point, true);
897 bDragMode = false;
898 ReleaseCapture();
900 CStandAloneDialogTmpl<CResizableDialog>::OnLButtonUp(nFlags, point);
903 void CRepositoryBrowser::OnCaptureChanged(CWnd *pWnd)
905 bDragMode = false;
907 __super::OnCaptureChanged(pWnd);
910 void CRepositoryBrowser::DrawXorBar(CDC * pDC, int x1, int y1, int width, int height)
912 static WORD _dotPatternBmp[8] =
914 0x0055, 0x00aa, 0x0055, 0x00aa,
915 0x0055, 0x00aa, 0x0055, 0x00aa
918 HBITMAP hbm;
919 HBRUSH hbr, hbrushOld;
921 hbm = CreateBitmap(8, 8, 1, 1, _dotPatternBmp);
922 hbr = CreatePatternBrush(hbm);
924 pDC->SetBrushOrg(x1, y1);
925 hbrushOld = (HBRUSH)pDC->SelectObject(hbr);
927 PatBlt(pDC->GetSafeHdc(), x1, y1, width, height, PATINVERT);
929 pDC->SelectObject(hbrushOld);
931 DeleteObject(hbr);
932 DeleteObject(hbm);
935 BOOL CRepositoryBrowser::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
937 if (pWnd == this)
939 RECT rect;
940 POINT pt;
941 GetClientRect(&rect);
942 GetCursorPos(&pt);
943 ScreenToClient(&pt);
944 if (PtInRect(&rect, pt))
946 ClientToScreen(&pt);
947 // are we right of the tree control?
948 GetDlgItem(IDC_REPOTREE)->GetWindowRect(&rect);
949 if ((pt.x > rect.right) && (pt.y >= rect.top + 3) && (pt.y <= rect.bottom - 3))
951 // but left of the list control?
952 GetDlgItem(IDC_REPOLIST)->GetWindowRect(&rect);
953 if (pt.x < rect.left)
955 HCURSOR hCur = LoadCursor(NULL, MAKEINTRESOURCE(IDC_SIZEWE));
956 SetCursor(hCur);
957 return TRUE;
962 return CStandAloneDialogTmpl<CResizableDialog>::OnSetCursor(pWnd, nHitTest, message);
965 void CRepositoryBrowser::FileSaveAs(const CString path)
967 CTGitPath gitPath(path);
969 CString filename;
970 filename.Format(_T("%s-%s%s"), gitPath.GetBaseFilename(), CGitHash(m_sRevision).ToString().Left(g_Git.GetShortHASHLength()), gitPath.GetFileExtension());
971 CFileDialog dlg(FALSE, NULL, filename, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, NULL);
973 CString cmd, out;
974 INT_PTR ret = dlg.DoModal();
975 SetCurrentDirectory(g_Git.m_CurrentDir);
976 if (ret == IDOK)
978 filename = dlg.GetPathName();
979 if (g_Git.GetOneFile(m_sRevision, gitPath, filename))
981 out.Format(IDS_STATUSLIST_CHECKOUTFILEFAILED, gitPath.GetGitPathString(), m_sRevision, filename);
982 MessageBox(out, _T("TortoiseGit"), MB_OK);
983 return;
988 void CRepositoryBrowser::OpenFile(const CString path, eOpenType mode)
990 CTGitPath gitPath(path);
992 CString temppath;
993 CString file;
994 GetTempPath(temppath);
995 file.Format(_T("%s%s_%s%s"), temppath, gitPath.GetBaseFilename(), CGitHash(m_sRevision).ToString().Left(g_Git.GetShortHASHLength()), gitPath.GetFileExtension());
997 CString out;
998 if(g_Git.GetOneFile(m_sRevision, gitPath, file))
1000 out.Format(IDS_STATUSLIST_CHECKOUTFILEFAILED, gitPath.GetGitPathString(), m_sRevision, file);
1001 MessageBox(out, _T("TortoiseGit"), MB_OK);
1002 return;
1005 if (mode == ALTERNATIVEEDITOR)
1007 CAppUtils::LaunchAlternativeEditor(file);
1008 return;
1010 else if (mode == OPEN)
1012 int ret = HINSTANCE_ERROR;
1013 ret = (int)ShellExecute(this->m_hWnd, NULL, file, NULL, NULL, SW_SHOW);
1015 if (ret > HINSTANCE_ERROR)
1016 return;
1019 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ") + file;
1020 CAppUtils::LaunchApplication(cmd, NULL, false);
1022 bool CRepositoryBrowser::RevertItemToVersion(const CString &path)
1024 CString cmd, out;
1025 cmd.Format(_T("git.exe checkout %s -- \"%s\""), m_sRevision, path);
1026 if (g_Git.Run(cmd, &out, CP_UTF8))
1028 if (MessageBox(out, _T("TortoiseGit"), MB_ICONEXCLAMATION | MB_OKCANCEL) == IDCANCEL)
1029 return false;
1032 return true;
1035 void CRepositoryBrowser::CopyHashToClipboard(TShadowFilesTreeList &selectedLeafs)
1037 if (!selectedLeafs.empty())
1039 CString sClipdata;
1040 bool first = true;
1041 for (int i = 0; i < selectedLeafs.size(); i++)
1043 if (!first)
1044 sClipdata += _T("\r\n");
1045 sClipdata += selectedLeafs[i]->m_hash;
1046 first = false;
1048 CStringUtils::WriteAsciiStringToClipboard(sClipdata, GetSafeHwnd());