Keep the font size of 8 for the explorer property page
[TortoiseGit.git] / src / TortoiseProc / GitRefCompareList.cpp
blob5147902f2e8dbc49e1ba10626df882a7077a7156
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2013-2018 - TortoiseGit
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 // GitRefCompareList.cpp : implementation file
21 #include "stdafx.h"
22 #include "resource.h"
23 #include "Git.h"
24 #include "GitRefCompareList.h"
25 #include "registry.h"
26 #include "UnicodeUtils.h"
27 #include "IconMenu.h"
28 #include "AppUtils.h"
29 #include "../TortoiseShell/resource.h"
30 #include "LoglistCommonResource.h"
32 IMPLEMENT_DYNAMIC(CGitRefCompareList, CHintCtrl<CListCtrl>)
34 BEGIN_MESSAGE_MAP(CGitRefCompareList, CHintCtrl<CListCtrl>)
35 ON_WM_CONTEXTMENU()
36 ON_NOTIFY(HDN_ITEMCLICKA, 0, OnHdnItemclick)
37 ON_NOTIFY(HDN_ITEMCLICKW, 0, OnHdnItemclick)
38 END_MESSAGE_MAP()
40 BOOL CGitRefCompareList::m_bSortLogical = FALSE;
42 enum IDGITRCL
44 IDGITRCL_OLDLOG = 1,
45 IDGITRCL_NEWLOG,
46 IDGITRCL_COMPARE,
47 IDGITRCL_REFLOG,
50 enum IDGITRCLH
52 IDGITRCLH_HIDEUNCHANGED = 1,
55 CGitRefCompareList::CGitRefCompareList()
56 : CHintCtrl<CListCtrl>()
57 , colRef(0)
58 , colRefType(0)
59 , colChange(0)
60 , colOldHash(0)
61 , colOldMessage(0)
62 , colNewHash(0)
63 , colNewMessage(0)
64 , m_bAscending(false)
65 , m_nSortedColumn(-1)
67 m_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER);
68 if (m_bSortLogical)
69 m_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_LOCAL_MACHINE);
70 m_bHideUnchanged = CRegDWORD(L"Software\\TortoiseGit\\RefCompareHideUnchanged", FALSE);
73 void CGitRefCompareList::Init()
75 int index = 0;
76 colRef = InsertColumn(index++, CString(MAKEINTRESOURCE(IDS_REF)));
77 colRefType = InsertColumn(index++, CString(MAKEINTRESOURCE(IDS_REF)));
78 colChange = InsertColumn(index++, CString(MAKEINTRESOURCE(IDS_CHANGETYPE)));
79 colOldHash = InsertColumn(index++, CString(MAKEINTRESOURCE(IDS_OLDHASH)));
80 colOldMessage = InsertColumn(index++, CString(MAKEINTRESOURCE(IDS_OLDMESSAGE)));
81 colNewHash = InsertColumn(index++, CString(MAKEINTRESOURCE(IDS_NEWHASH)));
82 colNewMessage = InsertColumn(index++,CString(MAKEINTRESOURCE(IDS_NEWMESSAGE)));
83 for (int i = 0; i < index; ++i)
84 SetColumnWidth(i, LVSCW_AUTOSIZE_USEHEADER);
85 SetColumnWidth(colRef, 130);
87 CImageList *imagelist = new CImageList();
88 imagelist->Create(IDB_BITMAP_REFTYPE, 16, 3, RGB(255, 255, 255));
89 SetImageList(imagelist, LVSIL_SMALL);
91 SetWindowTheme(m_hWnd, L"Explorer", nullptr);
94 int CGitRefCompareList::AddEntry(git_repository* repo, const CString& ref, const CGitHash* oldHash, const CGitHash* newHash)
96 RefEntry entry;
97 entry.fullName = ref;
98 entry.shortName = CGit::GetShortName(ref, &entry.refType);
99 if (oldHash)
100 entry.oldHash = oldHash->ToString().Left(g_Git.GetShortHASHLength());
101 if (newHash)
102 entry.newHash = newHash->ToString().Left(g_Git.GetShortHASHLength());
104 CAutoCommit oldCommit;
105 if (oldHash)
107 if (!git_commit_lookup(oldCommit.GetPointer(), repo, *oldHash))
108 entry.oldMessage = GetCommitMessage(oldCommit);
111 CAutoCommit newCommit;
112 if (newHash)
114 if (!git_commit_lookup(newCommit.GetPointer(), repo, *newHash))
115 entry.newMessage = GetCommitMessage(newCommit);
118 if (oldHash && newHash)
120 if (*oldHash == *newHash)
122 entry.change.LoadString(IDS_SAME);
123 entry.changeType = ChangeType::Same;
125 else
127 size_t ahead = 0, behind = 0;
128 if (!git_graph_ahead_behind(&ahead, &behind, repo, *newHash, *oldHash))
130 CString change;
131 if (ahead > 0 && behind == 0)
133 entry.change.Format(IDS_FORWARDN, ahead);
134 entry.changeType = ChangeType::FastForward;
136 else if (ahead == 0 && behind > 0)
138 entry.change.Format(IDS_REWINDN, behind);
139 entry.changeType = ChangeType::Rewind;
141 else
143 git_time_t oldTime = git_commit_committer(oldCommit)->when.time;
144 git_time_t newTime = git_commit_committer(newCommit)->when.time;
145 if (oldTime < newTime)
147 entry.change.LoadString(IDS_SUBMODULEDIFF_NEWERTIME);
148 entry.changeType = ChangeType::NewerTime;
150 else if (oldTime > newTime)
152 entry.change.LoadString(IDS_SUBMODULEDIFF_OLDERTIME);
153 entry.changeType = ChangeType::OlderTime;
155 else
157 entry.change.LoadString(IDS_SUBMODULEDIFF_SAMETIME);
158 entry.changeType = ChangeType::SameTime;
164 else if (oldHash)
166 entry.change.LoadString(IDS_DELETED);
167 entry.changeType = ChangeType::Deleted;
169 else if (newHash)
171 entry.change.LoadString(IDS_NEW);
172 entry.changeType = ChangeType::New;
175 m_RefList.push_back(entry);
176 return (int)m_RefList.size() - 1;
179 inline static bool StringComparePredicate(bool sortLogical, const CString& e1, const CString& e2)
181 if (sortLogical)
182 return StrCmpLogicalW(e1, e2) < 0;
183 return e1.Compare(e2) < 0;
186 static CString RefTypeString(CGit::REF_TYPE reftype)
188 CString type;
189 switch (reftype)
191 case CGit::REF_TYPE::LOCAL_BRANCH:
192 type.LoadString(IDS_PROC_BRANCH);
193 break;
194 case CGit::REF_TYPE::REMOTE_BRANCH:
195 type.LoadString(IDS_PROC_REMOTEBRANCH);
196 break;
197 case CGit::REF_TYPE::ANNOTATED_TAG:
198 case CGit::REF_TYPE::TAG:
199 type.LoadString(IDS_PROC_TAG);
200 break;
202 return type;
205 void CGitRefCompareList::Show()
208 CString pleaseWait;
209 pleaseWait.LoadString(IDS_PROGRESSWAIT);
210 ShowText(pleaseWait, true);
212 SetRedraw(false);
213 DeleteAllItems();
215 if (m_nSortedColumn >= 0)
217 auto predicate = [](bool sortLogical, int sortColumn, const RefEntry& e1, const RefEntry& e2)
219 switch (sortColumn)
221 case 0:
222 return StringComparePredicate(sortLogical, e1.shortName, e2.shortName);
223 break;
224 case 1:
225 return StringComparePredicate(false, RefTypeString(e1.refType), RefTypeString(e2.refType));
226 break;
227 case 2:
228 return StringComparePredicate(false, e1.change, e2.change);
229 break;
230 case 3:
231 return e1.oldHash.Compare(e2.oldHash) < 0;
232 break;
233 case 4:
234 return StringComparePredicate(sortLogical, e1.oldMessage, e2.oldMessage);
235 break;
236 case 5:
237 return e1.newHash.Compare(e2.newHash) < 0;
238 break;
239 case 6:
240 return StringComparePredicate(sortLogical, e1.newMessage, e2.newMessage);
241 break;
243 return false;
246 if (m_bAscending)
247 std::stable_sort(m_RefList.begin(), m_RefList.end(), std::bind(predicate, m_bSortLogical, m_nSortedColumn, std::placeholders::_1, std::placeholders::_2));
248 else
249 std::stable_sort(m_RefList.begin(), m_RefList.end(), std::bind(predicate, m_bSortLogical, m_nSortedColumn, std::placeholders::_2, std::placeholders::_1));
251 else
252 std::sort(m_RefList.begin(), m_RefList.end(), SortPredicate);
254 int index = 0;
255 int listIdx = -1;
256 for (const auto& entry : m_RefList)
258 ++listIdx;
259 if (entry.changeType == ChangeType::Same && m_bHideUnchanged)
260 continue;
262 int nImage = -1;
263 if (entry.refType == CGit::REF_TYPE::LOCAL_BRANCH)
264 nImage = 1;
265 else if (entry.refType == CGit::REF_TYPE::REMOTE_BRANCH)
266 nImage = 2;
267 else if (entry.refType == CGit::REF_TYPE::ANNOTATED_TAG || entry.refType == CGit::REF_TYPE::TAG)
268 nImage = 0;
269 InsertItem(index, entry.shortName, nImage);
270 SetItemText(index, colRefType, RefTypeString(entry.refType));
271 SetItemText(index, colChange, entry.change);
272 SetItemText(index, colOldHash, entry.oldHash);
273 SetItemText(index, colOldMessage, entry.oldMessage);
274 SetItemText(index, colNewHash, entry.newHash);
275 SetItemText(index, colNewMessage, entry.newMessage);
276 SetItemData(index, listIdx);
277 ++index;
280 auto pHeader = GetHeaderCtrl();
281 HDITEM HeaderItem = { 0 };
282 HeaderItem.mask = HDI_FORMAT;
283 for (int i = 0; i < pHeader->GetItemCount(); ++i)
285 pHeader->GetItem(i, &HeaderItem);
286 HeaderItem.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
287 pHeader->SetItem(i, &HeaderItem);
289 if (m_nSortedColumn >= 0)
291 pHeader->GetItem(m_nSortedColumn, &HeaderItem);
292 HeaderItem.fmt |= (m_bAscending ? HDF_SORTUP : HDF_SORTDOWN);
293 pHeader->SetItem(m_nSortedColumn, &HeaderItem);
295 SetRedraw(true);
297 if (!index)
299 CString empty;
300 empty.LoadString(IDS_COMPAREREV_NODIFF);
301 ShowText(empty, true);
303 else
304 ShowText(L"", true);
307 void CGitRefCompareList::Clear()
309 m_RefList.clear();
310 DeleteAllItems();
313 void CGitRefCompareList::OnHdnItemclick(NMHDR *pNMHDR, LRESULT *pResult)
315 auto phdr = reinterpret_cast<LPNMHEADER>(pNMHDR);
316 *pResult = 0;
318 if (m_RefList.empty())
319 return;
321 if (m_nSortedColumn == phdr->iItem)
322 m_bAscending = !m_bAscending;
323 else
324 m_bAscending = TRUE;
325 m_nSortedColumn = phdr->iItem;
327 Show();
330 void CGitRefCompareList::OnContextMenu(CWnd *pWnd, CPoint point)
332 if (pWnd == this)
334 OnContextMenuList(pWnd, point);
336 else if (pWnd == GetHeaderCtrl())
338 OnContextMenuHeader(pWnd, point);
342 void CGitRefCompareList::OnContextMenuList(CWnd * /*pWnd*/, CPoint point)
344 int selIndex = GetSelectionMark();
345 if (selIndex < 0)
346 return;
348 int index = (int)GetItemData(selIndex);
349 if (index < 0 || (size_t)index >= m_RefList.size())
350 return;
352 CString refName = m_RefList[index].fullName;
353 CString oldHash = m_RefList[index].oldHash;
354 CString newHash = m_RefList[index].newHash;
355 CIconMenu popup;
356 popup.CreatePopupMenu();
357 CString logStr;
358 if (!oldHash.IsEmpty())
360 logStr.Format(IDS_SHOWLOG_OF, (LPCTSTR)oldHash);
361 popup.AppendMenuIcon(IDGITRCL_OLDLOG, logStr, IDI_LOG);
363 if (!newHash.IsEmpty() && oldHash != newHash)
365 logStr.Format(IDS_SHOWLOG_OF, (LPCTSTR)newHash);
366 popup.AppendMenuIcon(IDGITRCL_NEWLOG, logStr, IDI_LOG);
368 if (!oldHash.IsEmpty() && !newHash.IsEmpty() && oldHash != newHash)
369 popup.AppendMenuIcon(IDGITRCL_COMPARE, IDS_LOG_POPUP_COMPAREWITHPREVIOUS, IDI_DIFF);
370 popup.AppendMenuIcon(IDGITRCL_REFLOG, IDS_MENUREFLOG, IDI_LOG);
372 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this);
373 AfxGetApp()->DoWaitCursor(1);
374 switch (cmd)
376 case IDGITRCL_OLDLOG:
377 case IDGITRCL_NEWLOG:
379 CString sCmd;
380 sCmd.Format(L"/command:log /path:\"%s\" /endrev:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir, cmd == IDGITRCL_OLDLOG ? (LPCTSTR)oldHash : (LPCTSTR)newHash);
381 CAppUtils::RunTortoiseGitProc(sCmd);
382 break;
384 case IDGITRCL_COMPARE:
386 CString sCmd;
387 sCmd.Format(L"/command:showcompare /path:\"%s\" /revision1:\"%s\" /revision2:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)oldHash, (LPCTSTR)newHash);
388 if (!!(GetAsyncKeyState(VK_SHIFT) & 0x8000))
389 sCmd += L" /alternative";
390 CAppUtils::RunTortoiseGitProc(sCmd);
391 break;
393 case IDGITRCL_REFLOG:
395 CString sCmd;
396 sCmd.Format(L"/command:reflog /path:\"%s\" /ref:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)refName);
397 CAppUtils::RunTortoiseGitProc(sCmd);
398 break;
401 AfxGetApp()->DoWaitCursor(-1);
404 static void AppendMenuChecked(CMenu &menu, UINT nTextID, UINT_PTR nItemID, BOOL checked = FALSE, BOOL enabled = TRUE)
406 CString text;
407 text.LoadString(nTextID);
408 menu.AppendMenu(MF_STRING | (enabled ? MF_ENABLED : MF_DISABLED) | (checked ? MF_CHECKED : MF_UNCHECKED), nItemID, text);
411 void CGitRefCompareList::OnContextMenuHeader(CWnd * /*pWnd*/, CPoint point)
413 CMenu popup;
414 if (popup.CreatePopupMenu())
416 AppendMenuChecked(popup, IDS_HIDEUNCHANGED, IDGITRCLH_HIDEUNCHANGED, m_bHideUnchanged);
418 int selection = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this);
419 switch (selection)
421 case IDGITRCLH_HIDEUNCHANGED:
422 m_bHideUnchanged = !m_bHideUnchanged;
423 Show();
424 break;
429 CString CGitRefCompareList::GetCommitMessage(git_commit *commit)
431 int encode = CP_UTF8;
432 const char *encodingString = git_commit_message_encoding(commit);
433 if (encodingString != nullptr)
434 encode = CUnicodeUtils::GetCPCode(CUnicodeUtils::GetUnicode(encodingString));
436 CString message = CUnicodeUtils::GetUnicode(git_commit_message(commit), encode);
437 int start = 0;
438 message = message.Tokenize(L"\n", start);
439 return message;
442 bool CGitRefCompareList::SortPredicate(const RefEntry &e1, const RefEntry &e2)
444 if (e1.changeType < e2.changeType)
445 return true;
446 if (e1.changeType > e2.changeType)
447 return false;
448 if (m_bSortLogical)
449 return StrCmpLogicalW(e1.fullName, e2.fullName) < 0;
450 return e1.fullName.Compare(e2.fullName) < 0;
453 ULONG CGitRefCompareList::GetGestureStatus(CPoint /*ptTouch*/)
455 return 0;