Don't import ogdf namespace
[TortoiseGit.git] / src / TortoiseProc / GitLogListBase.cpp
blobdbc7512247b3d1af27eb0fccc663a0e3d5b7524d
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2018 - TortoiseGit
4 // Copyright (C) 2005-2007 Marco Costalba
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 // GitLogList.cpp : implementation file
22 #include "stdafx.h"
23 #include "GitLogListBase.h"
24 #include "IconMenu.h"
25 #include "GitProgressDlg.h"
26 #include "ProgressDlg.h"
27 #include "MessageBox.h"
28 #include "LoglistUtils.h"
29 #include "StringUtils.h"
30 #include "UnicodeUtils.h"
31 #include "../TortoiseShell/Resource.h"
32 #include "CommonAppUtils.h"
33 #include "DPIAware.h"
35 const UINT CGitLogListBase::m_FindDialogMessage = RegisterWindowMessage(FINDMSGSTRING);
36 const UINT CGitLogListBase::m_ScrollToMessage = RegisterWindowMessage(L"TORTOISEGIT_LOG_SCROLLTO");
37 const UINT CGitLogListBase::m_ScrollToRef = RegisterWindowMessage(L"TORTOISEGIT_LOG_SCROLLTOREF");
38 const UINT CGitLogListBase::m_RebaseActionMessage = RegisterWindowMessage(L"TORTOISEGIT_LOG_REBASEACTION");
39 const UINT CGitLogListBase::LOGLIST_RESET_WCREV = RegisterWindowMessage(L"TORTOISEGIT_LOG_RESET_WCREV");
41 IMPLEMENT_DYNAMIC(CGitLogListBase, CHintCtrl<CResizableColumnsListCtrl<CListCtrl>>)
43 CGitLogListBase::CGitLogListBase() : CHintCtrl<CResizableColumnsListCtrl<CListCtrl>>()
44 ,m_regMaxBugIDColWidth(L"Software\\TortoiseGit\\MaxBugIDColWidth", 200)
45 ,m_nSearchIndex(0)
46 ,m_bNoDispUpdates(FALSE)
47 , m_bThreadRunning(FALSE)
48 , m_ShowFilter(FILTERSHOW_ALL)
49 , m_bShowWC(false)
50 , m_logEntries(&m_LogCache)
51 , m_pFindDialog(nullptr)
52 , m_dwDefaultColumns(0)
53 , m_arShownList(&m_critSec)
54 , m_hasWC(true)
55 , m_bNoHightlightHead(FALSE)
56 , m_ShowRefMask(LOGLIST_SHOWALLREFS)
57 , m_bFullCommitMessageOnLogLine(false)
58 , m_OldTopIndex(-1)
59 , m_AsyncThreadRunning(FALSE)
60 , m_AsyncThreadExit(FALSE)
61 , m_bIsCherryPick(false)
62 , m_pMailmap(nullptr)
63 , m_bShowBugtraqColumn(false)
64 , m_IsIDReplaceAction(FALSE)
65 , m_ShowMask(0)
66 , m_LoadingThread(nullptr)
67 , m_bExitThread(FALSE)
68 , m_IsOldFirst(FALSE)
69 , m_IsRebaseReplaceGraph(FALSE)
70 , m_ContextMenuMask(0xFFFFFFFFFFFFFFFF)
71 , m_bDragndropEnabled(false)
72 , m_bDragging(FALSE)
73 , m_nDropIndex(-1)
74 , m_nDropMarkerLast(-1)
75 , m_nDropMarkerLastHot(-1)
76 , m_LogFilter(std::make_shared<CLogDlgFilter>())
78 // use the default GUI font, create a copy of it and
79 // change the copy to BOLD (leave the rest of the font
80 // the same)
82 this->m_critSec.Init();
83 ResetWcRev(false);
85 int cx = GetSystemMetrics(SM_CXSMICON);
86 int cy = GetSystemMetrics(SM_CYSMICON);
87 m_hModifiedIcon = CCommonAppUtils::LoadIconEx(IDI_ACTIONMODIFIED, cx, cy);
88 m_hReplacedIcon = CCommonAppUtils::LoadIconEx(IDI_ACTIONREPLACED, cx, cy);
89 m_hConflictedIcon = CCommonAppUtils::LoadIconEx(IDI_ACTIONCONFLICTED, cx, cy);
90 m_hAddedIcon = CCommonAppUtils::LoadIconEx(IDI_ACTIONADDED, cx, cy);
91 m_hDeletedIcon = CCommonAppUtils::LoadIconEx(IDI_ACTIONDELETED, cx, cy);
92 m_hFetchIcon = CCommonAppUtils::LoadIconEx(IDI_ACTIONFETCHING, cx, cy);
94 m_Filter.m_NumberOfLogsScale = (DWORD)CRegDWORD(L"Software\\TortoiseGit\\LogDialog\\NumberOfLogsScale", CFilterData::SHOW_NO_LIMIT);
95 if (m_Filter.m_NumberOfLogsScale == CFilterData::SHOW_LAST_SEL_DATE)
97 CString key;
98 key.Format(L"Software\\TortoiseGit\\History\\LogDlg_Limits\\%s\\FromDate", (LPCTSTR)g_Git.m_CurrentDir);
99 key.Replace(L':', L'_');
100 CString lastSelFromDate = CRegString(key);
101 if (lastSelFromDate.GetLength() == 10)
103 CTime time = CTime(_wtoi((LPCTSTR)lastSelFromDate.Mid(0, 4)), _wtoi((LPCTSTR)lastSelFromDate.Mid(5, 2)), _wtoi((LPCTSTR)lastSelFromDate.Mid(8, 2)), 0, 0, 0);
104 m_Filter.m_From = (DWORD)time.GetTime();
107 m_Filter.m_NumberOfLogs = (DWORD)CRegDWORD(L"Software\\TortoiseGit\\LogDialog\\NumberOfLogs", 1);
109 for (int i = 0; i < Lanes::COLORS_NUM; ++i)
111 m_LineColors[i] = m_Colors.GetColor((CColors::Colors)(CColors::BranchLine1+i));
113 // get short/long datetime setting from registry
114 DWORD RegUseShortDateFormat = CRegDWORD(L"Software\\TortoiseGit\\LogDateFormat", TRUE);
115 if ( RegUseShortDateFormat )
117 m_DateFormat = DATE_SHORTDATE;
119 else
121 m_DateFormat = DATE_LONGDATE;
123 // get relative time display setting from registry
124 DWORD regRelativeTimes = CRegDWORD(L"Software\\TortoiseGit\\RelativeTimes", FALSE);
125 m_bRelativeTimes = (regRelativeTimes != 0);
127 m_ContextMenuMask &= ~GetContextMenuBit(ID_REBASE_PICK);
128 m_ContextMenuMask &= ~GetContextMenuBit(ID_REBASE_SQUASH);
129 m_ContextMenuMask &= ~GetContextMenuBit(ID_REBASE_EDIT);
130 m_ContextMenuMask &= ~GetContextMenuBit(ID_REBASE_SKIP);
131 m_ContextMenuMask &= ~GetContextMenuBit(ID_LOG);
132 m_ContextMenuMask &= ~GetContextMenuBit(ID_BLAME);
133 m_ContextMenuMask &= ~GetContextMenuBit(ID_BLAMEPREVIOUS);
135 m_ColumnRegKey = L"log";
137 m_bTagsBranchesOnRightSide = !!CRegDWORD(L"Software\\TortoiseGit\\DrawTagsBranchesOnRightSide", FALSE);
138 m_bSymbolizeRefNames = !!CRegDWORD(L"Software\\TortoiseGit\\SymbolizeRefNames", FALSE);
139 m_bIncludeBoundaryCommits = !!CRegDWORD(L"Software\\TortoiseGit\\LogIncludeBoundaryCommits", FALSE);
140 m_bFullCommitMessageOnLogLine = !!CRegDWORD(L"Software\\TortoiseGit\\FullCommitMessageOnLogLine", FALSE);
142 m_LineWidth = max(1, CRegDWORD(L"Software\\TortoiseGit\\TortoiseProc\\Graph\\LogLineWidth", 2));
143 m_NodeSize = max(1, CRegDWORD(L"Software\\TortoiseGit\\TortoiseProc\\Graph\\LogNodeSize", 10));
145 if (CRegDWORD(L"Software\\TortoiseGit\\LogDialog\\UseMailmap", FALSE) == TRUE)
146 git_read_mailmap(&m_pMailmap);
148 m_AsyncDiffEvent = ::CreateEvent(nullptr, FALSE, TRUE, nullptr);
149 m_AsynDiffListLock.Init();
150 StartAsyncDiffThread();
153 HWND CGitLogListBase::GetParentHWND()
155 auto owner = GetSafeOwner();
156 if (!owner)
157 return GetSafeHwnd();
158 return owner->GetSafeHwnd();
161 int CGitLogListBase::AsyncDiffThread()
163 while(!m_AsyncThreadExit)
165 ::WaitForSingleObject(m_AsyncDiffEvent, INFINITE);
167 GitRevLoglist* pRev = nullptr;
168 while(!m_AsyncThreadExit && !m_AsynDiffList.empty())
170 m_AsynDiffListLock.Lock();
171 pRev = m_AsynDiffList.back();
172 m_AsynDiffList.pop_back();
173 m_AsynDiffListLock.Unlock();
175 if( pRev->m_CommitHash.IsEmpty() )
177 if(pRev->m_IsDiffFiles)
178 continue;
180 CTGitPathList& files = pRev->GetFiles(this);
181 files.Clear();
182 pRev->m_ParentHash.clear();
183 pRev->m_ParentHash.push_back(m_HeadHash);
184 g_Git.RefreshGitIndex();
185 g_Git.GetWorkingTreeChanges(files, false, nullptr, true); // filtering is done in LogDlg.cpp
186 auto& action = pRev->GetAction(this);
187 action = 0;
188 for (int j = 0; j < files.GetCount(); ++j)
189 action |= files[j].m_Action;
191 CString err;
192 if (pRev->GetUnRevFiles().FillUnRev(CTGitPath::LOGACTIONS_UNVER, nullptr, &err))
194 MessageBox(L"Failed to get UnRev file list\n" + err, L"TortoiseGit", MB_OK | MB_ICONERROR);
195 InterlockedExchange(&m_AsyncThreadRunning, FALSE);
196 return -1;
199 InterlockedExchange(&pRev->m_IsDiffFiles, TRUE);
200 InterlockedExchange(&pRev->m_IsFull, TRUE);
202 CString body = L"\n";
203 body.AppendFormat(IDS_FILESCHANGES, files.GetCount());
204 pRev->GetBody() = body;
205 ::PostMessage(m_hWnd,MSG_LOADED,(WPARAM)0,0);
206 this->GetParent()->PostMessage(WM_COMMAND, MSG_FETCHED_DIFF, 0);
209 if (!pRev->CheckAndDiff())
210 { // fetch change file list
211 for (int i = GetTopIndex(); !m_AsyncThreadExit && i <= GetTopIndex() + GetCountPerPage(); ++i)
213 if (i < (int)m_arShownList.size())
215 GitRevLoglist* data = m_arShownList.SafeGetAt(i);
216 if(data->m_CommitHash == pRev->m_CommitHash)
218 ::PostMessage(m_hWnd,MSG_LOADED,(WPARAM)i,0);
219 break;
224 if(!m_AsyncThreadExit && GetSelectedCount() == 1)
226 POSITION pos = GetFirstSelectedItemPosition();
227 int nItem = GetNextSelectedItem(pos);
229 if(nItem>=0)
231 GitRevLoglist* data = m_arShownList.SafeGetAt(nItem);
232 if(data)
233 if(data->m_CommitHash == pRev->m_CommitHash)
234 this->GetParent()->PostMessage(WM_COMMAND, MSG_FETCHED_DIFF, 0);
240 InterlockedExchange(&m_AsyncThreadRunning, FALSE);
241 return 0;
243 void CGitLogListBase::hideFromContextMenu(unsigned __int64 hideMask, bool exclusivelyShow)
245 if (exclusivelyShow)
246 m_ContextMenuMask &= hideMask;
247 else
248 m_ContextMenuMask &= ~hideMask;
251 CGitLogListBase::~CGitLogListBase()
253 InterlockedExchange(&m_bNoDispUpdates, TRUE);
254 this->m_arShownList.SafeRemoveAll();
256 DestroyIcon(m_hModifiedIcon);
257 DestroyIcon(m_hReplacedIcon);
258 DestroyIcon(m_hConflictedIcon);
259 DestroyIcon(m_hAddedIcon);
260 DestroyIcon(m_hDeletedIcon);
261 m_logEntries.ClearAll();
263 git_free_mailmap(m_pMailmap);
265 SafeTerminateThread();
266 SafeTerminateAsyncDiffThread();
268 if(m_AsyncDiffEvent)
269 CloseHandle(m_AsyncDiffEvent);
273 BEGIN_MESSAGE_MAP(CGitLogListBase, CHintCtrl<CResizableColumnsListCtrl<CListCtrl>>)
274 ON_REGISTERED_MESSAGE(m_FindDialogMessage, OnFindDialogMessage)
275 ON_REGISTERED_MESSAGE(m_ScrollToMessage, OnScrollToMessage)
276 ON_REGISTERED_MESSAGE(m_ScrollToRef, OnScrollToRef)
277 ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnNMCustomdrawLoglist)
278 ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnLvnGetdispinfoLoglist)
279 ON_WM_CONTEXTMENU()
280 ON_NOTIFY_REFLECT(NM_DBLCLK, OnNMDblclkLoglist)
281 ON_NOTIFY_REFLECT(LVN_ODFINDITEM,OnLvnOdfinditemLoglist)
282 ON_WM_CREATE()
283 ON_WM_DESTROY()
284 ON_MESSAGE(MSG_LOADED,OnLoad)
285 ON_WM_MEASUREITEM()
286 ON_WM_MEASUREITEM_REFLECT()
287 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, &OnToolTipText)
288 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, &OnToolTipText)
289 ON_WM_MOUSEMOVE()
290 ON_WM_LBUTTONUP()
291 ON_NOTIFY_REFLECT(LVN_BEGINDRAG, OnBeginDrag)
292 END_MESSAGE_MAP()
294 void CGitLogListBase::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)
296 //if (m_nRowHeight>0)
297 lpMeasureItemStruct->itemHeight = CDPIAware::Instance().ScaleY(50);
300 int CGitLogListBase:: OnCreate(LPCREATESTRUCT lpCreateStruct)
302 PreSubclassWindow();
303 return __super::OnCreate(lpCreateStruct);
306 void CGitLogListBase::SetStyle()
308 SetExtendedStyle(LVS_EX_INFOTIP | LVS_EX_DOUBLEBUFFER | LVS_EX_SUBITEMIMAGES | LVS_EX_FULLROWSELECT);
311 void CGitLogListBase::PreSubclassWindow()
313 SetStyle();
314 // load the icons for the action columns
315 // m_Theme.Open(m_hWnd, L"ListView");
316 SetWindowTheme(m_hWnd, L"Explorer", nullptr);
317 __super::PreSubclassWindow();
320 CString CGitLogListBase::GetRebaseActionName(int action)
322 if (action & LOGACTIONS_REBASE_EDIT)
323 return MAKEINTRESOURCE(IDS_PATHACTIONS_EDIT);
324 if (action & LOGACTIONS_REBASE_SQUASH)
325 return MAKEINTRESOURCE(IDS_PATHACTIONS_SQUASH);
326 if (action & LOGACTIONS_REBASE_PICK)
327 return MAKEINTRESOURCE(IDS_PATHACTIONS_PICK);
328 if (action & LOGACTIONS_REBASE_SKIP)
329 return MAKEINTRESOURCE(IDS_PATHACTIONS_SKIP);
331 return MAKEINTRESOURCE(IDS_PATHACTIONS_UNKNOWN);
334 void CGitLogListBase::InsertGitColumn()
336 CString temp;
338 Init();
340 // use the default font, create a copy of it and
341 // change the copy to BOLD (leave the rest of the font
342 // the same)
343 LOGFONT lf = { 0 };
344 GetFont()->GetLogFont(&lf);
345 lf.lfWeight = FW_BOLD;
346 m_boldFont.CreateFontIndirect(&lf);
347 lf.lfWeight = FW_DONTCARE;
348 lf.lfItalic = TRUE;
349 m_FontItalics.CreateFontIndirect(&lf);
350 lf.lfWeight = FW_BOLD;
351 m_boldItalicsFont.CreateFontIndirect(&lf);
353 // only load properties if we have a repository
354 if (GitAdminDir::IsWorkingTreeOrBareRepo(g_Git.m_CurrentDir))
355 UpdateProjectProperties();
357 static UINT normal[] =
359 IDS_LOG_GRAPH,
360 IDS_LOG_REBASE,
361 IDS_LOG_ID,
362 IDS_LOG_HASH,
363 IDS_LOG_ACTIONS,
364 IDS_LOG_MESSAGE,
365 IDS_LOG_AUTHOR,
366 IDS_LOG_DATE,
367 IDS_LOG_EMAIL,
368 IDS_LOG_COMMIT_NAME,
369 IDS_LOG_COMMIT_EMAIL,
370 IDS_LOG_COMMIT_DATE,
371 IDS_LOG_BUGIDS,
372 IDS_LOG_SVNREV,
375 auto iconItemBorder = CDPIAware::Instance().ScaleX(ICONITEMBORDER);
376 auto columnWidth = CDPIAware::Instance().ScaleX(ICONITEMBORDER + 16 * 4);
377 static int with[] =
379 columnWidth,
380 columnWidth,
381 columnWidth,
382 columnWidth,
383 2 * iconItemBorder + GetSystemMetrics(SM_CXSMICON) * 5,
384 CDPIAware::Instance().ScaleX(LOGLIST_MESSAGE_MIN),
385 columnWidth,
386 columnWidth,
387 columnWidth,
388 columnWidth,
389 columnWidth,
390 columnWidth,
391 columnWidth,
392 columnWidth,
394 m_dwDefaultColumns = GIT_LOG_GRAPH|GIT_LOG_ACTIONS|GIT_LOG_MESSAGE|GIT_LOG_AUTHOR|GIT_LOG_DATE;
396 DWORD hideColumns = 0;
397 if(this->m_IsRebaseReplaceGraph)
399 hideColumns |= GIT_LOG_GRAPH;
400 m_dwDefaultColumns |= GIT_LOG_REBASE;
402 else
403 hideColumns |= GIT_LOG_REBASE;
405 if(this->m_IsIDReplaceAction)
407 hideColumns |= GIT_LOG_ACTIONS;
408 m_dwDefaultColumns |= GIT_LOG_ID;
409 m_dwDefaultColumns |= GIT_LOG_HASH;
411 else
412 hideColumns |= GIT_LOG_ID;
413 if(this->m_bShowBugtraqColumn)
414 m_dwDefaultColumns |= GIT_LOGLIST_BUG;
415 else
416 hideColumns |= GIT_LOGLIST_BUG;
417 if (CTGitPath(g_Git.m_CurrentDir).HasGitSVNDir())
418 m_dwDefaultColumns |= GIT_LOGLIST_SVNREV;
419 else
420 hideColumns |= GIT_LOGLIST_SVNREV;
421 SetRedraw(false);
423 m_ColumnManager.SetNames(normal, _countof(normal));
424 m_ColumnManager.ReadSettings(m_dwDefaultColumns, hideColumns, m_ColumnRegKey + L"loglist", _countof(normal), with);
425 m_ColumnManager.SetRightAlign(LOGLIST_ID);
427 if (!(hideColumns & GIT_LOG_ACTIONS))
429 // Configure fake a imagelist for LogList with 1px width and height = GetSystemMetrics(SM_CYSMICON)
430 // to set the minimum item height: we draw icons in the actions column, but on High-DPI the
431 // display's font height may be less than small icon height.
432 ASSERT((GetStyle() & LVS_SHAREIMAGELISTS) == 0);
433 HIMAGELIST hImageList = ImageList_Create(1, GetSystemMetrics(SM_CYSMICON), 0, 1, 0);
434 ListView_SetImageList(GetSafeHwnd(), hImageList, LVSIL_SMALL);
437 SetRedraw(true);
440 void CGitLogListBase::FillBackGround(HDC hdc, DWORD_PTR Index, CRect &rect)
442 LVITEM rItem = { 0 };
443 rItem.mask = LVIF_STATE;
444 rItem.iItem = (int)Index;
445 rItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
446 GetItem(&rItem);
448 GitRevLoglist* pLogEntry = m_arShownList.SafeGetAt(Index);
449 HBRUSH brush = nullptr;
451 if (!(rItem.state & LVIS_SELECTED))
453 int action = pLogEntry->GetRebaseAction();
454 if (action & LOGACTIONS_REBASE_SQUASH)
455 brush = ::CreateSolidBrush(RGB(156,156,156));
456 else if (action & LOGACTIONS_REBASE_EDIT)
457 brush = ::CreateSolidBrush(RGB(200,200,128));
459 else if (!IsAppThemed())
461 if (rItem.state & LVIS_SELECTED)
463 if (::GetFocus() == m_hWnd)
464 brush = ::CreateSolidBrush(::GetSysColor(COLOR_HIGHLIGHT));
465 else
466 brush = ::CreateSolidBrush(::GetSysColor(COLOR_BTNFACE));
469 if (brush)
471 ::FillRect(hdc, &rect, brush);
472 ::DeleteObject(brush);
476 void DrawTrackingRoundRect(HDC hdc, CRect rect, HBRUSH brush, COLORREF darkColor)
478 POINT point = { 4, 4 };
479 CRect rt2 = rect;
480 rt2.DeflateRect(1, 1);
481 rt2.OffsetRect(2, 2);
483 HPEN nullPen = ::CreatePen(PS_NULL, 0, 0);
484 HPEN oldpen = (HPEN)::SelectObject(hdc, nullPen);
485 HBRUSH darkBrush = (HBRUSH)::CreateSolidBrush(darkColor);
486 HBRUSH oldbrush = (HBRUSH)::SelectObject(hdc, darkBrush);
487 ::RoundRect(hdc, rt2.left, rt2.top, rt2.right, rt2.bottom, point.x, point.y);
489 ::SelectObject(hdc, brush);
490 rt2.OffsetRect(-2, -2);
491 ::RoundRect(hdc, rt2.left, rt2.top, rt2.right, rt2.bottom, point.x, point.y);
492 ::SelectObject(hdc, oldbrush);
493 ::SelectObject(hdc, oldpen);
494 ::DeleteObject(nullPen);
495 ::DeleteObject(darkBrush);
498 void DrawUpstream(HDC hdc, CRect rect, COLORREF color, int bold)
500 HPEN pen = ::CreatePen(PS_SOLID, bold, color);
501 HPEN oldpen = (HPEN)::SelectObject(hdc, pen);
502 ::MoveToEx(hdc, rect.left + 2 + bold, rect.top + 2 - bold, nullptr);
503 ::LineTo(hdc, rect.left + 2 + bold, rect.bottom + 1 - bold);
504 ::MoveToEx(hdc, rect.left + 3, rect.top + 1, nullptr);
505 ::LineTo(hdc, rect.left, rect.top + 4);
506 ::MoveToEx(hdc, rect.left + 2 + bold, rect.top + 1, nullptr);
507 ::LineTo(hdc, rect.right + 1 + bold, rect.top + 4);
508 ::MoveToEx(hdc, rect.left + 1, rect.top + 2 + bold, nullptr);
509 ::LineTo(hdc, rect.right + 1 + bold, rect.top + 2 + bold);
510 ::SelectObject(hdc, oldpen);
511 ::DeleteObject(pen);
514 void CGitLogListBase::DrawTagBranchMessage(NMLVCUSTOMDRAW* pLVCD, CRect& rect, INT_PTR index, std::vector<REFLABEL>& refList)
516 GitRevLoglist* data = m_arShownList.SafeGetAt(index);
517 CRect rt=rect;
518 LVITEM rItem = { 0 };
519 rItem.mask = LVIF_STATE;
520 rItem.iItem = (int)index;
521 rItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
522 GetItem(&rItem);
524 CDC W_Dc;
525 W_Dc.Attach(pLVCD->nmcd.hdc);
527 HTHEME hTheme = nullptr;
528 if (IsAppThemed())
529 hTheme = OpenThemeData(m_hWnd, L"Explorer::ListView;ListView");
531 SIZE oneSpaceSize;
532 if (m_bTagsBranchesOnRightSide)
534 HFONT oldFont = (HFONT)SelectObject(pLVCD->nmcd.hdc, (HFONT)GetStockObject(DEFAULT_GUI_FONT));
535 GetTextExtentPoint32(pLVCD->nmcd.hdc, L" ", 1, &oneSpaceSize);
536 SelectObject(pLVCD->nmcd.hdc, oldFont);
537 rt.left += oneSpaceSize.cx * 2;
539 else
541 GetTextExtentPoint32(pLVCD->nmcd.hdc, L" ", 1, &oneSpaceSize);
542 DrawTagBranch(pLVCD->nmcd.hdc, W_Dc, hTheme, rect, rt, rItem, data, refList);
543 rt.left += oneSpaceSize.cx;
546 CString msg = MessageDisplayStr(data);
547 int action = data->GetRebaseAction();
548 bool skip = !!(action & (LOGACTIONS_REBASE_DONE | LOGACTIONS_REBASE_SKIP));
549 std::vector<CHARRANGE> ranges;
550 auto filter = m_LogFilter;
551 if ((filter->GetSelectedFilters() & (LOGFILTER_SUBJECT | (m_bFullCommitMessageOnLogLine ? LOGFILTER_MESSAGES : 0))) && filter->IsFilterActive())
552 filter->GetMatchRanges(ranges, msg, 0);
553 if (hTheme)
555 int txtState = LISS_NORMAL;
556 if (rItem.state & LVIS_SELECTED)
557 txtState = LISS_SELECTED;
559 if (!ranges.empty())
560 DrawListItemWithMatchesRect(pLVCD, ranges, rt, msg, hTheme, txtState);
561 else
563 DTTOPTS opts = { 0 };
564 opts.dwSize = sizeof(opts);
565 opts.crText = skip ? RGB(128, 128, 128) : ::GetSysColor(COLOR_WINDOWTEXT);
566 opts.dwFlags = DTT_TEXTCOLOR;
567 DrawThemeTextEx(hTheme, pLVCD->nmcd.hdc, LVP_LISTITEM, txtState, msg, -1, DT_NOPREFIX | DT_LEFT | DT_SINGLELINE | DT_VCENTER | DT_END_ELLIPSIS, &rt, &opts);
570 else
572 if ((rItem.state & LVIS_SELECTED) && ::GetFocus() == m_hWnd)
573 pLVCD->clrText = skip ? RGB(128, 128, 128) : ::GetSysColor(COLOR_HIGHLIGHTTEXT);
574 else
575 pLVCD->clrText = skip ? RGB(128, 128, 128) : ::GetSysColor(COLOR_WINDOWTEXT);
576 if (!ranges.empty())
577 DrawListItemWithMatchesRect(pLVCD, ranges, rt, msg);
578 else
580 COLORREF clrOld = ::SetTextColor(pLVCD->nmcd.hdc, pLVCD->clrText);
581 ::DrawText(pLVCD->nmcd.hdc, msg, msg.GetLength(), &rt, DT_NOPREFIX | DT_LEFT | DT_SINGLELINE | DT_VCENTER | DT_END_ELLIPSIS);
582 ::SetTextColor(pLVCD->nmcd.hdc, clrOld);
586 if (m_bTagsBranchesOnRightSide)
588 SIZE size;
589 GetTextExtentPoint32(pLVCD->nmcd.hdc, msg, msg.GetLength(), &size);
591 rt.left += oneSpaceSize.cx + size.cx;
593 DrawTagBranch(pLVCD->nmcd.hdc, W_Dc, hTheme, rect, rt, rItem, data, refList);
596 if (hTheme)
597 CloseThemeData(hTheme);
599 W_Dc.Detach();
602 void CGitLogListBase::DrawTagBranch(HDC hdc, CDC& W_Dc, HTHEME hTheme, CRect& rect, CRect& rt, LVITEM& rItem, GitRevLoglist* data, std::vector<REFLABEL>& refList)
604 for (unsigned int i = 0; i < refList.size(); ++i)
606 CString shortname = !refList[i].simplifiedName.IsEmpty() ? refList[i].simplifiedName : refList[i].name;
607 HBRUSH brush = 0;
608 COLORREF colRef = refList[i].color;
609 bool singleRemote = refList[i].singleRemote;
610 bool hasTracking = refList[i].hasTracking;
611 CGit::REF_TYPE refType = refList[i].refType;
613 //When row selected, ajust label color
614 if (!IsAppThemed())
616 if (rItem.state & LVIS_SELECTED)
617 colRef = CColors::MixColors(colRef, ::GetSysColor(COLOR_HIGHLIGHT), 150);
620 brush = ::CreateSolidBrush(colRef);
622 if (!shortname.IsEmpty() && (rt.left < rect.right))
624 SIZE size = { 0 };
625 GetTextExtentPoint32(hdc, shortname, shortname.GetLength(), &size);
627 rt.SetRect(rt.left, rt.top, rt.left + size.cx, rt.bottom);
628 rt.right += 8;
630 int textpos = DT_CENTER;
632 if (rt.right > rect.right)
634 rt.right = rect.right;
635 textpos = 0;
638 CRect textRect = rt;
640 if (singleRemote)
642 rt.right += 5;
643 textRect.OffsetRect(5, 0);
646 if (hasTracking)
647 DrawTrackingRoundRect(hdc, rt, brush, m_Colors.Darken(colRef, 100));
648 else
650 //Fill interior of ref label
651 ::FillRect(hdc, &rt, brush);
654 //Draw edge of label
655 CRect rectEdge = rt;
657 if (!hasTracking)
659 W_Dc.Draw3dRect(rectEdge, m_Colors.Lighten(colRef, 100), m_Colors.Darken(colRef, 100));
660 rectEdge.DeflateRect(1, 1);
661 W_Dc.Draw3dRect(rectEdge, m_Colors.Lighten(colRef, 50), m_Colors.Darken(colRef, 50));
664 if (refType == CGit::REF_TYPE::ANNOTATED_TAG)
666 rt.right += 8;
667 POINT trianglept[3] = { { rt.right - 8, rt.top }, { rt.right, (rt.top + rt.bottom) / 2 }, { rt.right - 8, rt.bottom } };
668 HRGN hrgn = ::CreatePolygonRgn(trianglept, 3, ALTERNATE);
669 ::FillRgn(hdc, hrgn, brush);
670 ::DeleteObject(hrgn);
671 ::MoveToEx(hdc, trianglept[0].x - 1, trianglept[0].y, nullptr);
672 HPEN pen;
673 HPEN oldpen = (HPEN)SelectObject(hdc, pen = ::CreatePen(PS_SOLID, 2, m_Colors.Lighten(colRef, 50)));
674 ::LineTo(hdc, trianglept[1].x - 1, trianglept[1].y - 1);
675 ::DeleteObject(pen);
676 SelectObject(hdc, pen = ::CreatePen(PS_SOLID, 2, m_Colors.Darken(colRef, 50)));
677 ::LineTo(hdc, trianglept[2].x - 1, trianglept[2].y - 1);
678 ::DeleteObject(pen);
679 SelectObject(hdc, pen = ::CreatePen(PS_SOLID, 2, colRef));
680 ::MoveToEx(hdc, trianglept[0].x - 1, trianglept[2].y - 3, nullptr);
681 ::LineTo(hdc, trianglept[0].x - 1, trianglept[0].y);
682 ::DeleteObject(pen);
683 SelectObject(hdc, oldpen);
686 //Draw text inside label
687 bool customColor = (colRef & 0xff) * 30 + ((colRef >> 8) & 0xff) * 59 + ((colRef >> 16) & 0xff) * 11 <= 12800; // check if dark background
688 if (!customColor && IsAppThemed())
690 int txtState = LISS_NORMAL;
691 if (rItem.state & LVIS_SELECTED)
692 txtState = LISS_SELECTED;
694 DTTOPTS opts = { 0 };
695 opts.dwSize = sizeof(opts);
696 opts.crText = ::GetSysColor(COLOR_WINDOWTEXT);
697 opts.dwFlags = DTT_TEXTCOLOR;
698 DrawThemeTextEx(hTheme, hdc, LVP_LISTITEM, txtState, shortname, -1, textpos | DT_SINGLELINE | DT_VCENTER, &textRect, &opts);
700 else
702 W_Dc.SetBkMode(TRANSPARENT);
703 if (customColor || (rItem.state & LVIS_SELECTED))
705 COLORREF clrNew = ::GetSysColor(COLOR_HIGHLIGHTTEXT);
706 COLORREF clrOld = ::SetTextColor(hdc,clrNew);
707 ::DrawText(hdc, shortname, shortname.GetLength(), &textRect, textpos | DT_SINGLELINE | DT_VCENTER);
708 ::SetTextColor(hdc,clrOld);
710 else
712 COLORREF clrOld = ::SetTextColor(hdc, ::GetSysColor(COLOR_WINDOWTEXT));
713 ::DrawText(hdc, shortname, shortname.GetLength(), &textRect, textpos | DT_SINGLELINE | DT_VCENTER);
714 ::SetTextColor(hdc, clrOld);
718 if (singleRemote)
720 COLORREF color = ::GetSysColor(customColor ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT);
721 int bold = data->m_CommitHash == m_HeadHash ? 2 : 1;
722 CRect newRect;
723 newRect.SetRect(rt.left + 2, rt.top + 4, rt.left + 6, rt.bottom - 4);
724 DrawUpstream(hdc, newRect, color, bold);
727 if (!refList[i].fullName.IsEmpty())
728 m_RefLabelPosMap[refList[i].fullName] = rt;
730 rt.left = rt.right + 1;
732 if (brush)
733 ::DeleteObject(brush);
735 rt.right = rect.right;
738 static COLORREF blend(const COLORREF& col1, const COLORREF& col2, int amount = 128) {
739 // Returns ((256 - amount)*col1 + amount*col2) / 256;
740 return RGB(((256 - amount)*GetRValue(col1) + amount*GetRValue(col2) ) / 256,
741 ((256 - amount)*GetGValue(col1) + amount*GetGValue(col2) ) / 256,
742 ((256 - amount)*GetBValue(col1) + amount*GetBValue(col2) ) / 256);
745 Gdiplus::Color GetGdiColor(COLORREF col)
747 return Gdiplus::Color(GetRValue(col),GetGValue(col),GetBValue(col));
749 void CGitLogListBase::paintGraphLane(HDC hdc, int laneHeight,int type, int x1, int x2,
750 const COLORREF& col,const COLORREF& activeColor, int top
753 int h = laneHeight / 2;
754 int m = (x1 + x2) / 2;
755 int r = (x2 - x1) * m_NodeSize / 30;
756 int d = 2 * r;
758 #define P_CENTER m , h+top
759 #define P_0 x2, h+top
760 #define P_90 m , 0+top-1
761 #define P_180 x1, h+top
762 #define P_270 m , 2 * h+top +1
763 #define R_CENTER m - r, h - r+top, d, d
766 #define DELTA_UR_B 2*(x1 - m), 2*h +top
767 #define DELTA_UR_E 0*16, 90*16 +top // -,
769 #define DELTA_DR_B 2*(x1 - m), 2*-h +top
770 #define DELTA_DR_E 270*16, 90*16 +top // -'
772 #define DELTA_UL_B 2*(x2 - m), 2*h +top
773 #define DELTA_UL_E 90*16, 90*16 +top // ,-
775 #define DELTA_DL_B 2*(x2 - m),2*-h +top
776 #define DELTA_DL_E 180*16, 90*16 // '-
778 #define CENTER_UR x1, 2*h, 225
779 #define CENTER_DR x1, 0 , 135
780 #define CENTER_UL x2, 2*h, 315
781 #define CENTER_DL x2, 0 , 45
784 Gdiplus::Graphics graphics( hdc );
786 graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
788 // arc
789 switch (type) {
790 case Lanes::JOIN:
791 case Lanes::JOIN_R:
792 case Lanes::HEAD:
793 case Lanes::HEAD_R:
795 Gdiplus::LinearGradientBrush gradient(
796 Gdiplus::Point(x1-2, h+top-2),
797 Gdiplus::Point(P_270),
798 GetGdiColor(activeColor),GetGdiColor(col));
801 Gdiplus::Pen mypen(&gradient, (Gdiplus::REAL)m_LineWidth);
802 //Gdiplus::Pen mypen(Gdiplus::Color(0,0,0),2);
804 //graphics.DrawRectangle(&mypen,x1-(x2-x1)/2,top+h, x2-x1,laneHeight);
805 graphics.DrawArc(&mypen,x1-(x2-x1)/2-1,top+h-1, x2-x1,laneHeight,270,90);
806 //graphics.DrawLine(&mypen,x1-1,h+top,P_270);
808 break;
810 case Lanes::JOIN_L:
812 Gdiplus::LinearGradientBrush gradient(
813 Gdiplus::Point(P_270),
814 Gdiplus::Point(x2+1, h+top-1),
815 GetGdiColor(col),GetGdiColor(activeColor));
818 Gdiplus::Pen mypen(&gradient, (Gdiplus::REAL)m_LineWidth);
819 //Gdiplus::Pen mypen(Gdiplus::Color(0,0,0),2);
821 //graphics.DrawRectangle(&mypen,x1-(x2-x1)/2,top+h, x2-x1,laneHeight);
822 graphics.DrawArc(&mypen,x1+(x2-x1)/2,top+h-1, x2-x1,laneHeight,180,90);
823 //graphics.DrawLine(&mypen,x1-1,h+top,P_270);
826 break;
828 case Lanes::TAIL:
829 case Lanes::TAIL_R:
831 Gdiplus::LinearGradientBrush gradient(
832 Gdiplus::Point(x1-2, h+top-2),
833 Gdiplus::Point(P_90),
834 GetGdiColor(activeColor),GetGdiColor(col));
836 Gdiplus::Pen mypen(&gradient, (Gdiplus::REAL)m_LineWidth);
838 graphics.DrawArc(&mypen,x1-(x2-x1)/2-1,top-h-1, x2-x1,laneHeight,0,90);
840 #if 0
841 QConicalGradient gradient(CENTER_DR);
842 gradient.setColorAt(0.375, activeCol);
843 gradient.setColorAt(0.625, col);
844 myPen.setBrush(gradient);
845 p->setPen(myPen);
846 p->drawArc(P_CENTER, DELTA_DR);
847 #endif
848 break;
850 default:
851 break;
855 //static QPen myPen(Qt::black, 2); // fast path here
856 CPen pen;
857 pen.CreatePen(PS_SOLID,2,col);
858 //myPen.setColor(col);
859 HPEN oldpen=(HPEN)::SelectObject(hdc,(HPEN)pen);
861 Gdiplus::Pen myPen(GetGdiColor(col), (Gdiplus::REAL)m_LineWidth);
863 graphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
865 //p->setPen(myPen);
867 // vertical line
868 switch (type) {
869 case Lanes::ACTIVE:
870 case Lanes::NOT_ACTIVE:
871 case Lanes::MERGE_FORK:
872 case Lanes::MERGE_FORK_R:
873 case Lanes::MERGE_FORK_L:
874 case Lanes::JOIN:
875 case Lanes::JOIN_R:
876 case Lanes::JOIN_L:
877 case Lanes::CROSS:
878 //DrawLine(hdc,P_90,P_270);
879 graphics.DrawLine(&myPen,P_90,P_270);
880 //p->drawLine(P_90, P_270);
881 break;
882 case Lanes::HEAD_L:
883 case Lanes::BRANCH:
884 //DrawLine(hdc,P_CENTER,P_270);
885 graphics.DrawLine(&myPen,P_CENTER,P_270);
886 //p->drawLine(P_CENTER, P_270);
887 break;
888 case Lanes::TAIL_L:
889 case Lanes::INITIAL:
890 case Lanes::MERGE_FORK_L_INITIAL:
891 case Lanes::BOUNDARY:
892 case Lanes::BOUNDARY_C:
893 case Lanes::BOUNDARY_R:
894 case Lanes::BOUNDARY_L:
895 //DrawLine(hdc,P_90, P_CENTER);
896 graphics.DrawLine(&myPen,P_90,P_CENTER);
897 //p->drawLine(P_90, P_CENTER);
898 break;
899 default:
900 break;
903 myPen.SetColor(GetGdiColor(activeColor));
905 // horizontal line
906 switch (type) {
907 case Lanes::MERGE_FORK:
908 case Lanes::JOIN:
909 case Lanes::HEAD:
910 case Lanes::TAIL:
911 case Lanes::CROSS:
912 case Lanes::CROSS_EMPTY:
913 case Lanes::BOUNDARY_C:
914 //DrawLine(hdc,P_180,P_0);
915 graphics.DrawLine(&myPen,P_180,P_0);
916 //p->drawLine(P_180, P_0);
917 break;
918 case Lanes::MERGE_FORK_R:
919 case Lanes::BOUNDARY_R:
920 //DrawLine(hdc,P_180,P_CENTER);
921 graphics.DrawLine(&myPen,P_180,P_CENTER);
922 //p->drawLine(P_180, P_CENTER);
923 break;
924 case Lanes::MERGE_FORK_L:
925 case Lanes::MERGE_FORK_L_INITIAL:
926 case Lanes::HEAD_L:
927 case Lanes::TAIL_L:
928 case Lanes::BOUNDARY_L:
929 //DrawLine(hdc,P_CENTER,P_0);
930 graphics.DrawLine(&myPen,P_CENTER,P_0);
931 //p->drawLine(P_CENTER, P_0);
932 break;
933 default:
934 break;
937 graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
939 CBrush brush;
940 brush.CreateSolidBrush(col);
941 HBRUSH oldbrush=(HBRUSH)::SelectObject(hdc,(HBRUSH)brush);
943 Gdiplus::SolidBrush myBrush(GetGdiColor(col));
944 // center symbol, e.g. rect or ellipse
945 switch (type) {
946 case Lanes::ACTIVE:
947 case Lanes::INITIAL:
948 case Lanes::BRANCH:
950 //p->setPen(Qt::NoPen);
951 //p->setBrush(col);
952 graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
953 graphics.FillEllipse(&myBrush, R_CENTER);
954 //p->drawEllipse(R_CENTER);
955 break;
956 case Lanes::MERGE_FORK:
957 case Lanes::MERGE_FORK_R:
958 case Lanes::MERGE_FORK_L:
959 case Lanes::MERGE_FORK_L_INITIAL:
960 //p->setPen(Qt::NoPen);
961 //p->setBrush(col);
962 //p->drawRect(R_CENTER);
963 graphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
964 graphics.FillRectangle(&myBrush, R_CENTER);
965 break;
966 case Lanes::UNAPPLIED:
967 // Red minus sign
968 //p->setPen(Qt::NoPen);
969 //p->setBrush(Qt::red);
970 //p->drawRect(m - r, h - 1, d, 2);
971 graphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
972 graphics.FillRectangle(&myBrush,m-r,h-1,d,2);
973 break;
974 case Lanes::APPLIED:
975 // Green plus sign
976 //p->setPen(Qt::NoPen);
977 //p->setBrush(DARK_GREEN);
978 //p->drawRect(m - r, h - 1, d, 2);
979 //p->drawRect(m - 1, h - r, 2, d);
980 graphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
981 graphics.FillRectangle(&myBrush,m-r,h-1,d,2);
982 graphics.FillRectangle(&myBrush,m-1,h-r,2,d);
983 break;
984 case Lanes::BOUNDARY:
985 //p->setBrush(back);
986 //p->drawEllipse(R_CENTER);
987 graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
988 graphics.DrawEllipse(&myPen, R_CENTER);
989 break;
990 case Lanes::BOUNDARY_C:
991 case Lanes::BOUNDARY_R:
992 case Lanes::BOUNDARY_L:
993 //p->setBrush(back);
994 //p->drawRect(R_CENTER);
995 graphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
996 graphics.FillRectangle(&myBrush,R_CENTER);
997 break;
998 default:
999 break;
1002 ::SelectObject(hdc,oldpen);
1003 ::SelectObject(hdc,oldbrush);
1004 #undef P_CENTER
1005 #undef P_0
1006 #undef P_90
1007 #undef P_180
1008 #undef P_270
1009 #undef R_CENTER
1012 void CGitLogListBase::DrawGraph(HDC hdc,CRect &rect,INT_PTR index)
1014 // TODO: unfinished
1015 // return;
1016 GitRevLoglist* data = m_arShownList.SafeGetAt(index);
1017 if(data->m_CommitHash.IsEmpty())
1018 return;
1020 CRect rt=rect;
1021 LVITEM rItem = { 0 };
1022 rItem.mask = LVIF_STATE;
1023 rItem.iItem = (int)index;
1024 rItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
1025 GetItem(&rItem);
1027 // p->translate(QPoint(opt.rect.left(), opt.rect.top()));
1029 if (data->m_Lanes.empty())
1030 m_logEntries.setLane(data->m_CommitHash);
1032 std::vector<int>& lanes=data->m_Lanes;
1033 size_t laneNum = lanes.size();
1034 UINT activeLane = 0;
1035 for (UINT i = 0; i < laneNum; ++i)
1036 if (Lanes::isMerge(lanes[i])) {
1037 activeLane = i;
1038 break;
1041 int x2 = 0;
1042 int maxWidth = rect.Width();
1043 int lw = 3 * rect.Height() / 4; //laneWidth()
1045 COLORREF activeColor = m_LineColors[activeLane % Lanes::COLORS_NUM];
1046 //if (opt.state & QStyle::State_Selected)
1047 // activeColor = blend(activeColor, opt.palette.highlightedText().color(), 208);
1049 for (unsigned int i = 0; i < laneNum && x2 < maxWidth; ++i)
1051 int x1 = x2;
1052 x2 += lw;
1054 int ln = lanes[i];
1055 if (ln == Lanes::EMPTY)
1056 continue;
1058 COLORREF color = i == activeLane ? activeColor : m_LineColors[i % Lanes::COLORS_NUM];
1059 paintGraphLane(hdc, rect.Height(),ln, x1+rect.left, x2+rect.left, color,activeColor, rect.top);
1062 #if 0
1063 for (UINT i = 0; i < laneNum && x2 < maxWidth; ++i) {
1064 int x1 = x2;
1065 x2 += lw;
1067 int ln = lanes[i];
1068 if (ln == Lanes::EMPTY)
1069 continue;
1071 UINT col = ( Lanes:: isHead(ln) ||Lanes:: isTail(ln) || Lanes::isJoin(ln)
1072 || ln ==Lanes:: CROSS_EMPTY) ? activeLane : i;
1074 if (ln == Lanes::CROSS)
1076 paintGraphLane(hdc, rect.Height(),Lanes::NOT_ACTIVE, x1, x2, m_LineColors[col % Lanes::COLORS_NUM],rect.top);
1077 paintGraphLane(hdc, rect.Height(),Lanes::CROSS, x1, x2, m_LineColors[activeLane % Lanes::COLORS_NUM],rect.top);
1079 else
1080 paintGraphLane(hdc, rect.Height(),ln, x1, x2, m_LineColors[col % Lanes::COLORS_NUM],rect.top);
1082 #endif
1085 void CGitLogListBase::OnNMCustomdrawLoglist(NMHDR *pNMHDR, LRESULT *pResult)
1087 NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>( pNMHDR );
1088 // Take the default processing unless we set this to something else below.
1089 *pResult = CDRF_DODEFAULT;
1091 if (m_bNoDispUpdates)
1092 return;
1094 switch (pLVCD->nmcd.dwDrawStage)
1096 case CDDS_PREPAINT:
1098 *pResult = CDRF_NOTIFYITEMDRAW;
1099 return;
1101 break;
1102 case CDDS_ITEMPREPAINT:
1104 // This is the prepaint stage for an item. Here's where we set the
1105 // item's text color.
1107 // Tell Windows to send draw notifications for each subitem.
1108 *pResult = CDRF_NOTIFYSUBITEMDRAW;
1110 COLORREF crText = GetSysColor(COLOR_WINDOWTEXT);
1112 if (m_arShownList.size() > pLVCD->nmcd.dwItemSpec)
1114 GitRevLoglist* data = m_arShownList.SafeGetAt(pLVCD->nmcd.dwItemSpec);
1115 if (data)
1117 HGDIOBJ hGdiObj = nullptr;
1118 int action = data->GetRebaseAction();
1119 if (action & (LOGACTIONS_REBASE_DONE | LOGACTIONS_REBASE_SKIP))
1120 crText = RGB(128,128,128);
1122 if (action & LOGACTIONS_REBASE_SQUASH)
1123 pLVCD->clrTextBk = RGB(156,156,156);
1124 else if (action & LOGACTIONS_REBASE_EDIT)
1125 pLVCD->clrTextBk = RGB(200,200,128);
1126 else
1127 pLVCD->clrTextBk = ::GetSysColor(COLOR_WINDOW);
1129 if (action & LOGACTIONS_REBASE_CURRENT)
1130 hGdiObj = m_boldFont.GetSafeHandle();
1132 BOOL isHeadHash = data->m_CommitHash == m_HeadHash && m_bNoHightlightHead == FALSE;
1133 BOOL isHighlight = data->m_CommitHash == m_highlight && !m_highlight.IsEmpty();
1134 if (isHeadHash && isHighlight)
1135 hGdiObj = m_boldItalicsFont.GetSafeHandle();
1136 else if (isHeadHash)
1137 hGdiObj = m_boldFont.GetSafeHandle();
1138 else if (isHighlight)
1139 hGdiObj = m_FontItalics.GetSafeHandle();
1141 if (hGdiObj)
1143 SelectObject(pLVCD->nmcd.hdc, hGdiObj);
1144 *pResult = CDRF_NOTIFYSUBITEMDRAW | CDRF_NEWFONT;
1147 // if ((data->childStackDepth)||(m_mergedRevs.find(data->Rev) != m_mergedRevs.end()))
1148 // crText = GetSysColor(COLOR_GRAYTEXT);
1150 if (data->m_CommitHash.IsEmpty())
1152 //crText = GetSysColor(RGB(200,200,0));
1153 //SelectObject(pLVCD->nmcd.hdc, m_boldFont);
1154 // We changed the font, so we're returning CDRF_NEWFONT. This
1155 // tells the control to recalculate the extent of the text.
1156 *pResult = CDRF_NOTIFYSUBITEMDRAW | CDRF_NEWFONT;
1160 // Store the color back in the NMLVCUSTOMDRAW struct.
1161 pLVCD->clrText = crText;
1162 return;
1164 break;
1166 case CDDS_ITEMPREPAINT | CDDS_ITEM | CDDS_SUBITEM:
1168 switch (pLVCD->iSubItem)
1170 case LOGLIST_GRAPH:
1171 if ((m_ShowFilter & FILTERSHOW_MERGEPOINTS) && !m_LogFilter->IsFilterActive())
1173 if (m_arShownList.size() > pLVCD->nmcd.dwItemSpec && !this->m_IsRebaseReplaceGraph)
1175 CRect rect;
1176 GetSubItemRect((int)pLVCD->nmcd.dwItemSpec, pLVCD->iSubItem, LVIR_LABEL, rect);
1178 //TRACE(L"A Graphic left %d right %d\r\n", rect.left, rect.right);
1179 FillBackGround(pLVCD->nmcd.hdc, pLVCD->nmcd.dwItemSpec,rect);
1181 GitRevLoglist* data = m_arShownList.SafeGetAt(pLVCD->nmcd.dwItemSpec);
1182 if( !data ->m_CommitHash.IsEmpty())
1183 DrawGraph(pLVCD->nmcd.hdc,rect,pLVCD->nmcd.dwItemSpec);
1185 *pResult = CDRF_SKIPDEFAULT;
1186 return;
1189 break;
1191 case LOGLIST_MESSAGE:
1192 // If the top index of list is changed, the position map of reference label is outdated.
1193 if (m_OldTopIndex != GetTopIndex())
1195 m_OldTopIndex = GetTopIndex();
1196 m_RefLabelPosMap.clear();
1199 if (m_arShownList.size() > pLVCD->nmcd.dwItemSpec)
1201 GitRevLoglist* data = m_arShownList.SafeGetAt(pLVCD->nmcd.dwItemSpec);
1203 if ((m_HashMap.find(data->m_CommitHash) != m_HashMap.cend() || (!m_superProjectHash.IsEmpty() && data->m_CommitHash == m_superProjectHash)) && !(data->GetRebaseAction() & LOGACTIONS_REBASE_DONE))
1205 CRect rect;
1206 GetSubItemRect((int)pLVCD->nmcd.dwItemSpec, pLVCD->iSubItem, LVIR_BOUNDS, rect);
1208 // BEGIN: extended redraw, HACK for issue #1618 and #2014
1209 // not in FillBackGround method, because this only affected the message subitem
1210 if (0 != pLVCD->iStateId) // don't know why, but this helps against loosing the focus rect
1211 return;
1213 int index = (int)pLVCD->nmcd.dwItemSpec;
1214 int state = GetItemState(index, LVIS_SELECTED);
1215 int txtState = LISS_NORMAL;
1216 if (IsAppThemed() && GetHotItem() == (int)index)
1218 if (state & LVIS_SELECTED)
1219 txtState = LISS_HOTSELECTED;
1220 else
1221 txtState = LISS_HOT;
1223 else if (state & LVIS_SELECTED)
1225 if (::GetFocus() == m_hWnd)
1226 txtState = LISS_SELECTED;
1227 else
1228 txtState = LISS_SELECTEDNOTFOCUS;
1231 HTHEME hTheme = nullptr;
1232 if (IsAppThemed())
1234 hTheme = OpenThemeData(m_hWnd, L"Explorer::ListView;ListView");
1236 // make sure the column separator/border is not overpainted
1237 int borderWidth = 0;
1238 GetThemeMetric(hTheme, pLVCD->nmcd.hdc, LVP_LISTITEM, LISS_NORMAL, TMT_BORDERSIZE, &borderWidth);
1239 InflateRect(&rect, -(2 * borderWidth), 0);
1242 if (hTheme && IsThemeBackgroundPartiallyTransparent(hTheme, LVP_LISTDETAIL, txtState))
1243 DrawThemeParentBackground(m_hWnd, pLVCD->nmcd.hdc, &rect);
1244 else
1246 HBRUSH brush = ::CreateSolidBrush(pLVCD->clrTextBk);
1247 ::FillRect(pLVCD->nmcd.hdc, rect, brush);
1248 ::DeleteObject(brush);
1250 if (hTheme && txtState != LISS_NORMAL)
1252 CRect rt;
1253 // get rect of whole line
1254 GetItemRect(index, rt, LVIR_BOUNDS);
1255 CRect rect2 = rect;
1257 // calculate background for rect of whole line, but limit redrawing to SubItem rect
1258 DrawThemeBackground(hTheme, pLVCD->nmcd.hdc, LVP_LISTITEM, txtState, rt, rect2);
1260 CloseThemeData(hTheme);
1262 // END: extended redraw
1264 FillBackGround(pLVCD->nmcd.hdc, pLVCD->nmcd.dwItemSpec, rect);
1266 std::vector<REFLABEL> refsToShow;
1267 STRING_VECTOR remoteTrackingList;
1268 const STRING_VECTOR& refList = m_HashMap[data->m_CommitHash];
1269 for (unsigned int i = 0; i < refList.size(); ++i)
1271 CString str = refList[i];
1273 REFLABEL refLabel;
1274 refLabel.color = RGB(255, 255, 255);
1275 refLabel.singleRemote = false;
1276 refLabel.hasTracking = false;
1277 refLabel.sameName = false;
1278 refLabel.name = CGit::GetShortName(str, &refLabel.refType);
1279 refLabel.fullName = str;
1281 switch (refLabel.refType)
1283 case CGit::REF_TYPE::LOCAL_BRANCH:
1285 if (!(m_ShowRefMask & LOGLIST_SHOWLOCALBRANCHES))
1286 continue;
1287 if (refLabel.name == m_CurrentBranch)
1288 refLabel.color = m_Colors.GetColor(CColors::CurrentBranch);
1289 else
1290 refLabel.color = m_Colors.GetColor(CColors::LocalBranch);
1292 std::pair<CString, CString> trackingEntry = m_TrackingMap[refLabel.name];
1293 CString pullRemote = trackingEntry.first;
1294 CString pullBranch = trackingEntry.second;
1295 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty())
1297 CString defaultUpstream;
1298 defaultUpstream.Format(L"refs/remotes/%s/%s", (LPCTSTR)pullRemote, (LPCTSTR)pullBranch);
1299 refLabel.hasTracking = true;
1300 if (m_ShowRefMask & LOGLIST_SHOWREMOTEBRANCHES)
1302 bool found = false;
1303 for (size_t j = i + 1; j < refList.size(); ++j)
1305 if (refList[j] == defaultUpstream)
1307 found = true;
1308 break;
1312 if (found)
1314 bool sameName = pullBranch == refLabel.name;
1315 refsToShow.push_back(refLabel);
1316 CGit::GetShortName(defaultUpstream, refLabel.name, L"refs/remotes/");
1317 refLabel.color = m_Colors.GetColor(CColors::RemoteBranch);
1318 if (m_bSymbolizeRefNames)
1320 if (!m_SingleRemote.IsEmpty() && m_SingleRemote == pullRemote)
1322 refLabel.simplifiedName = L'/';
1323 if (sameName)
1324 refLabel.simplifiedName += L'≡';
1325 else
1326 refLabel.simplifiedName += pullBranch;
1327 refLabel.singleRemote = true;
1329 else if (sameName)
1330 refLabel.simplifiedName = pullRemote + L"/≡";
1331 refLabel.sameName = sameName;
1333 refLabel.fullName = defaultUpstream;
1334 refsToShow.push_back(refLabel);
1335 remoteTrackingList.push_back(defaultUpstream);
1336 continue;
1340 break;
1342 case CGit::REF_TYPE::REMOTE_BRANCH:
1344 if (!(m_ShowRefMask & LOGLIST_SHOWREMOTEBRANCHES))
1345 continue;
1346 bool found = false;
1347 for (size_t j = 0; j < remoteTrackingList.size(); ++j)
1349 if (remoteTrackingList[j] == str)
1351 found = true;
1352 break;
1355 if (found)
1356 continue;
1358 refLabel.color = m_Colors.GetColor(CColors::RemoteBranch);
1359 if (m_bSymbolizeRefNames)
1361 if (!m_SingleRemote.IsEmpty() && CStringUtils::StartsWith(refLabel.name, m_SingleRemote + L"/"))
1363 refLabel.simplifiedName = L'/' + refLabel.name.Mid(m_SingleRemote.GetLength() + 1);
1364 refLabel.singleRemote = true;
1367 break;
1369 case CGit::REF_TYPE::ANNOTATED_TAG: // fallthrough
1370 case CGit::REF_TYPE::TAG:
1371 if (!(m_ShowRefMask & LOGLIST_SHOWTAGS))
1372 continue;
1373 refLabel.color = m_Colors.GetColor(CColors::Tag);
1374 break;
1376 case CGit::REF_TYPE::STASH:
1377 if (!(m_ShowRefMask & LOGLIST_SHOWSTASH))
1378 continue;
1379 refLabel.color = m_Colors.GetColor(CColors::Stash);
1380 break;
1382 case CGit::REF_TYPE::BISECT_GOOD: // fallthrough
1383 case CGit::REF_TYPE::BISECT_BAD: // fallthrough
1384 case CGit::REF_TYPE::BISECT_SKIP:
1385 if (!(m_ShowRefMask & LOGLIST_SHOWBISECT))
1386 continue;
1387 refLabel.color = (refLabel.refType == CGit::REF_TYPE::BISECT_GOOD) ? m_Colors.GetColor(CColors::BisectGood) : ((refLabel.refType == CGit::REF_TYPE::BISECT_SKIP) ? m_Colors.GetColor(CColors::BisectSkip) : m_Colors.GetColor(CColors::BisectBad));
1388 break;
1390 case CGit::REF_TYPE::NOTES:
1391 if (!(m_ShowRefMask & LOGLIST_SHOWOTHERREFS))
1392 continue;
1393 refLabel.color = m_Colors.GetColor(CColors::NoteNode);
1394 break;
1396 default:
1397 if (!(m_ShowRefMask & LOGLIST_SHOWOTHERREFS))
1398 continue;
1399 refLabel.color = m_Colors.GetColor(CColors::OtherRef);
1400 break;
1402 refsToShow.push_back(refLabel);
1404 if (!m_superProjectHash.IsEmpty() && data->m_CommitHash == m_superProjectHash)
1406 REFLABEL refLabel;
1407 refLabel.color = RGB(246, 153, 253);
1408 refLabel.singleRemote = false;
1409 refLabel.hasTracking = false;
1410 refLabel.sameName = false;
1411 refLabel.name = L"super-project-pointer";
1412 refLabel.fullName = "";
1413 refsToShow.push_back(refLabel);
1416 if (refsToShow.empty())
1418 *pResult = CDRF_DODEFAULT;
1419 return;
1422 DrawTagBranchMessage(pLVCD, rect, pLVCD->nmcd.dwItemSpec, refsToShow);
1424 *pResult = CDRF_SKIPDEFAULT;
1425 return;
1427 else if (DrawListItemWithMatchesIfEnabled(m_LogFilter, LOGFILTER_SUBJECT | LOGFILTER_MESSAGES, pLVCD, pResult))
1428 return;
1430 break;
1432 case LOGLIST_ACTION:
1434 if (m_IsIDReplaceAction || !m_ColumnManager.IsVisible(LOGLIST_ACTION))
1436 *pResult = CDRF_DODEFAULT;
1437 return;
1439 *pResult = CDRF_DODEFAULT;
1441 if (m_arShownList.size() <= pLVCD->nmcd.dwItemSpec)
1442 return;
1444 int nIcons = 0;
1445 int iconwidth = ::GetSystemMetrics(SM_CXSMICON);
1446 int iconheight = ::GetSystemMetrics(SM_CYSMICON);
1448 GitRevLoglist* pLogEntry = m_arShownList.SafeGetAt(pLVCD->nmcd.dwItemSpec);
1449 CRect rect;
1450 GetSubItemRect((int)pLVCD->nmcd.dwItemSpec, pLVCD->iSubItem, LVIR_BOUNDS, rect);
1451 //TRACE(L"Action left %d right %d\r\n", rect.left, rect.right);
1452 // Get the selected state of the
1453 // item being drawn.
1455 CMemDC myDC(*CDC::FromHandle(pLVCD->nmcd.hdc), rect);
1456 BitBlt(myDC.GetDC(), rect.left, rect.top, rect.Width(), rect.Height(), pLVCD->nmcd.hdc, rect.left, rect.top, SRCCOPY);
1458 // Fill the background if necessary
1459 FillBackGround(myDC.GetDC(), pLVCD->nmcd.dwItemSpec, rect);
1461 // Draw the icon(s) into the compatible DC
1462 int action = pLogEntry->GetAction(this);
1463 auto iconItemBorder = CDPIAware::Instance().ScaleX(ICONITEMBORDER);
1464 if (!pLogEntry->m_IsDiffFiles)
1466 ::DrawIconEx(myDC.GetDC(), rect.left + iconItemBorder, rect.top, m_hFetchIcon, iconwidth, iconheight, 0, nullptr, DI_NORMAL);
1467 *pResult = CDRF_SKIPDEFAULT;
1468 return;
1471 if (action & CTGitPath::LOGACTIONS_MODIFIED)
1472 ::DrawIconEx(myDC.GetDC(), rect.left + iconItemBorder, rect.top, m_hModifiedIcon, iconwidth, iconheight, 0, nullptr, DI_NORMAL);
1473 ++nIcons;
1475 if (action & (CTGitPath::LOGACTIONS_ADDED | CTGitPath::LOGACTIONS_COPY))
1476 ::DrawIconEx(myDC.GetDC(), rect.left + nIcons * iconwidth + iconItemBorder, rect.top, m_hAddedIcon, iconwidth, iconheight, 0, nullptr, DI_NORMAL);
1477 ++nIcons;
1479 if (action & CTGitPath::LOGACTIONS_DELETED)
1480 ::DrawIconEx(myDC.GetDC(), rect.left + nIcons * iconwidth + iconItemBorder, rect.top, m_hDeletedIcon, iconwidth, iconheight, 0, nullptr, DI_NORMAL);
1481 ++nIcons;
1483 if (action & CTGitPath::LOGACTIONS_REPLACED)
1484 ::DrawIconEx(myDC.GetDC(), rect.left + nIcons * iconwidth + iconItemBorder, rect.top, m_hReplacedIcon, iconwidth, iconheight, 0, nullptr, DI_NORMAL);
1485 ++nIcons;
1487 if (action & CTGitPath::LOGACTIONS_UNMERGED)
1488 ::DrawIconEx(myDC.GetDC(), rect.left + nIcons * iconwidth + iconItemBorder, rect.top, m_hConflictedIcon, iconwidth, iconheight, 0, nullptr, DI_NORMAL);
1489 ++nIcons;
1491 *pResult = CDRF_SKIPDEFAULT;
1492 return;
1495 case LOGLIST_HASH:
1496 if (DrawListItemWithMatchesIfEnabled(m_LogFilter, LOGFILTER_REVS, pLVCD, pResult))
1497 return;
1498 break;
1500 case LOGLIST_AUTHOR:
1501 case LOGLIST_COMMIT_NAME:
1502 if (DrawListItemWithMatchesIfEnabled(m_LogFilter, LOGFILTER_AUTHORS, pLVCD, pResult))
1503 return;
1504 break;
1506 case LOGLIST_EMAIL:
1507 case LOGLIST_COMMIT_EMAIL:
1508 if (DrawListItemWithMatchesIfEnabled(m_LogFilter, LOGFILTER_EMAILS, pLVCD, pResult))
1509 return;
1510 break;
1512 case LOGLIST_BUG:
1513 if (DrawListItemWithMatchesIfEnabled(m_LogFilter, LOGFILTER_BUGID, pLVCD, pResult))
1514 return;
1515 break;
1518 break;
1520 *pResult = CDRF_DODEFAULT;
1523 CString FindSVNRev(const CString& msg)
1527 const std::wsregex_iterator end;
1528 std::wstring s = msg;
1529 std::wregex regex1(L"^\\s*git-svn-id:\\s+(.*)\\@(\\d+)\\s([a-f\\d\\-]+)$");
1530 for (std::wsregex_iterator it(s.cbegin(), s.cend(), regex1); it != end; ++it)
1532 const std::wsmatch match = *it;
1533 if (match.size() == 4)
1535 ATLTRACE(L"matched rev: %s\n", std::wstring(match[2]).c_str());
1536 return std::wstring(match[2]).c_str();
1539 std::wregex regex2(L"^\\s*git-svn-id:\\s(\\d+)\\@([a-f\\d\\-]+)$");
1540 for (std::wsregex_iterator it(s.cbegin(), s.cend(), regex2); it != end; ++it)
1542 const std::wsmatch match = *it;
1543 if (match.size() == 3)
1545 ATLTRACE(L"matched rev: %s\n", std::wstring(match[1]).c_str());
1546 return std::wstring(match[1]).c_str();
1550 catch (std::exception&) {}
1552 return L"";
1555 CString CGitLogListBase::MessageDisplayStr(GitRev* pLogEntry)
1557 if (!m_bFullCommitMessageOnLogLine || pLogEntry->GetBody().IsEmpty())
1558 return pLogEntry->GetSubject();
1560 CString txt(pLogEntry->GetSubject());
1561 txt += L' ';
1562 txt += pLogEntry->GetBody();
1564 // Deal with CRLF
1565 txt.Replace(L'\n', L' ');
1566 txt.Replace(L'\r', L' ');
1568 return txt;
1571 // CGitLogListBase message handlers
1573 static const char* GetMailmapMapping(GIT_MAILMAP mailmap, const CString& email, const CString& name, bool returnEmail)
1575 struct payload_struct { const CString* name; const char* authorName; };
1576 payload_struct payload = { &name, nullptr };
1577 const char* author1 = nullptr;
1578 const char* email1 = nullptr;
1579 git_lookup_mailmap(mailmap, &email1, &author1, CUnicodeUtils::GetUTF8(email), &payload,
1580 [](void* payload) -> const char* { return reinterpret_cast<payload_struct*>(payload)->authorName = _strdup(CUnicodeUtils::GetUTF8(*reinterpret_cast<payload_struct*>(payload)->name)); });
1581 free((void *)payload.authorName);
1582 if (returnEmail)
1583 return email1;
1584 return author1;
1587 static void CopyMailmapProcessedData(GIT_MAILMAP mailmap, LV_ITEM* pItem, const CString& email, const CString& name, bool returnEmail)
1589 if (mailmap)
1591 const char* translated = GetMailmapMapping(mailmap, email, name, returnEmail);
1592 if (translated)
1594 lstrcpyn(pItem->pszText, CUnicodeUtils::GetUnicode(translated), pItem->cchTextMax - 1);
1595 return;
1598 if (returnEmail)
1599 lstrcpyn(pItem->pszText, (LPCTSTR)email, pItem->cchTextMax - 1);
1600 else
1601 lstrcpyn(pItem->pszText, (LPCTSTR)name, pItem->cchTextMax - 1);
1604 void CGitLogListBase::OnLvnGetdispinfoLoglist(NMHDR *pNMHDR, LRESULT *pResult)
1606 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1608 // Create a pointer to the item
1609 LV_ITEM* pItem = &(pDispInfo)->item;
1611 // Do the list need text information?
1612 if (!(pItem->mask & LVIF_TEXT))
1613 return;
1615 // By default, clear text buffer.
1616 lstrcpyn(pItem->pszText, L"", pItem->cchTextMax - 1);
1618 bool bOutOfRange = pItem->iItem >= (int)m_arShownList.size();
1620 *pResult = 0;
1621 if (m_bNoDispUpdates || bOutOfRange)
1622 return;
1624 // Which item number?
1625 GitRevLoglist* pLogEntry = m_arShownList.SafeGetAt(pItem->iItem);
1627 CString temp;
1628 if(m_IsOldFirst)
1629 temp.Format(L"%d", pItem->iItem + 1);
1630 else
1631 temp.Format(L"%zu", m_arShownList.size() - pItem->iItem);
1633 if (!pLogEntry)
1634 return;
1636 // Which column?
1637 switch (pItem->iSubItem)
1639 case LOGLIST_GRAPH: //Graphic
1640 break;
1641 case LOGLIST_REBASE:
1642 if (m_IsRebaseReplaceGraph)
1643 lstrcpyn(pItem->pszText, GetRebaseActionName(pLogEntry->GetRebaseAction() & LOGACTIONS_REBASE_MODE_MASK), pItem->cchTextMax - 1);
1644 break;
1645 case LOGLIST_ACTION: //action -- no text in the column
1646 break;
1647 case LOGLIST_HASH:
1648 lstrcpyn(pItem->pszText, pLogEntry->m_CommitHash.ToString(), pItem->cchTextMax - 1);
1649 break;
1650 case LOGLIST_ID:
1651 if (this->m_IsIDReplaceAction)
1652 lstrcpyn(pItem->pszText, temp, pItem->cchTextMax - 1);
1653 break;
1654 case LOGLIST_MESSAGE: //Message
1655 lstrcpyn(pItem->pszText, (LPCTSTR)MessageDisplayStr(pLogEntry), pItem->cchTextMax - 1);
1656 break;
1657 case LOGLIST_AUTHOR: //Author
1658 CopyMailmapProcessedData(m_pMailmap, pItem, pLogEntry->GetAuthorEmail(), pLogEntry->GetAuthorName(), false);
1659 break;
1660 case LOGLIST_DATE: //Date
1661 if (!pLogEntry->m_CommitHash.IsEmpty())
1662 lstrcpyn(pItem->pszText,
1663 CLoglistUtils::FormatDateAndTime(pLogEntry->GetAuthorDate(), m_DateFormat, true, m_bRelativeTimes),
1664 pItem->cchTextMax - 1);
1665 break;
1667 case LOGLIST_EMAIL:
1668 CopyMailmapProcessedData(m_pMailmap, pItem, pLogEntry->GetAuthorEmail(), pLogEntry->GetAuthorName(), true);
1669 break;
1671 case LOGLIST_COMMIT_NAME: //Commit
1672 CopyMailmapProcessedData(m_pMailmap, pItem, pLogEntry->GetCommitterEmail(), pLogEntry->GetCommitterName(), false);
1673 break;
1675 case LOGLIST_COMMIT_EMAIL: //Commit Email
1676 CopyMailmapProcessedData(m_pMailmap, pItem, pLogEntry->GetCommitterEmail(), pLogEntry->GetCommitterName(), true);
1677 break;
1679 case LOGLIST_COMMIT_DATE: //Commit Date
1680 if (!pLogEntry->m_CommitHash.IsEmpty())
1681 lstrcpyn(pItem->pszText,
1682 CLoglistUtils::FormatDateAndTime(pLogEntry->GetCommitterDate(), m_DateFormat, true, m_bRelativeTimes),
1683 pItem->cchTextMax - 1);
1684 break;
1685 case LOGLIST_BUG: //Bug ID
1686 lstrcpyn(pItem->pszText, (LPCTSTR)this->m_ProjectProperties.FindBugID(pLogEntry->GetSubjectBody()), pItem->cchTextMax - 1);
1687 break;
1688 case LOGLIST_SVNREV: //SVN revision
1689 lstrcpyn(pItem->pszText, (LPCTSTR)FindSVNRev(pLogEntry->GetSubjectBody()), pItem->cchTextMax - 1);
1690 break;
1692 default:
1693 ASSERT(false);
1697 bool CGitLogListBase::IsOnStash(int index)
1699 GitRevLoglist* rev = m_arShownList.SafeGetAt(index);
1700 if (IsStash(rev))
1701 return true;
1702 if (index > 0)
1704 GitRevLoglist* preRev = m_arShownList.SafeGetAt(index - 1);
1705 if (IsStash(preRev))
1706 return preRev->m_ParentHash.size() == 2 && preRev->m_ParentHash[1] == rev->m_CommitHash;
1708 return false;
1711 bool CGitLogListBase::IsStash(const GitRev * pSelLogEntry)
1713 auto refList = m_HashMap.find(pSelLogEntry->m_CommitHash);
1714 if (refList == m_HashMap.cend())
1715 return false;
1716 return find_if((*refList).second, [](const auto& ref) { return ref == L"refs/stash"; }) != (*refList).second.cend();
1719 bool CGitLogListBase::IsBisect(const GitRev * pSelLogEntry)
1721 auto refList = m_HashMap.find(pSelLogEntry->m_CommitHash);
1722 if (refList == m_HashMap.cend())
1723 return false;
1724 return find_if((*refList).second, [](const auto& ref) { return CStringUtils::StartsWith(ref, L"refs/bisect/"); }) != (*refList).second.cend();
1727 void CGitLogListBase::GetParentHashes(GitRev *pRev, GIT_REV_LIST &parentHash)
1729 if (pRev->m_ParentHash.empty())
1731 if (pRev->GetParentFromHash(pRev->m_CommitHash))
1732 MessageBox(pRev->GetLastErr(), L"TortoiseGit", MB_ICONERROR);
1734 parentHash = pRev->m_ParentHash;
1737 void CGitLogListBase::OnContextMenu(CWnd* pWnd, CPoint point)
1739 __super::OnContextMenu(pWnd, point);
1741 if (pWnd != this)
1742 return;
1744 int selIndex = GetSelectionMark();
1745 if (selIndex < 0)
1746 return; // nothing selected, nothing to do with a context menu
1748 // if the context menu is invoked through the keyboard, we have to use
1749 // a calculated position on where to anchor the menu on
1750 if ((point.x == -1) && (point.y == -1))
1752 CRect rect;
1753 GetItemRect(selIndex, &rect, LVIR_LABEL);
1754 ClientToScreen(&rect);
1755 point = rect.CenterPoint();
1757 m_nSearchIndex = selIndex;
1759 bool showExtendedMenu = (GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0;
1761 POSITION pos = GetFirstSelectedItemPosition();
1762 int FirstSelect = GetNextSelectedItem(pos);
1763 if (FirstSelect < 0)
1764 return;
1766 GitRevLoglist* pSelLogEntry = m_arShownList.SafeGetAt(FirstSelect);
1767 if (pSelLogEntry == nullptr)
1768 return;
1770 int LastSelect = -1;
1771 UINT selectedCount = 1;
1772 while (pos)
1774 LastSelect = GetNextSelectedItem(pos);
1775 ++selectedCount;
1778 ASSERT(GetSelectedCount() == selectedCount);
1780 //entry is selected, now show the popup menu
1781 CIconMenu popup;
1782 CIconMenu subbranchmenu, submenu, gnudiffmenu, diffmenu, blamemenu, revertmenu;
1784 if (popup.CreatePopupMenu())
1786 CGitHash headHash;
1787 if (g_Git.GetHash(headHash, L"HEAD"))
1789 MessageBox(g_Git.GetGitLastErr(L"Could not get HEAD hash."), L"TortoiseGit", MB_ICONERROR);
1790 return;
1792 bool isHeadCommit = (pSelLogEntry->m_CommitHash == headHash);
1793 CString currentBranch = L"refs/heads/" + g_Git.GetCurrentBranch();
1794 CTGitPath workingTree(g_Git.m_CurrentDir);
1795 bool isMergeActive = workingTree.IsMergeActive();
1796 bool isBisectActive = workingTree.IsBisectActive();
1797 bool isStash = IsOnStash(FirstSelect);
1798 GIT_REV_LIST parentHash;
1799 GetParentHashes(pSelLogEntry, parentHash);
1801 if (m_ContextMenuMask & GetContextMenuBit(ID_REBASE_PICK) && !(pSelLogEntry->GetRebaseAction() & (LOGACTIONS_REBASE_CURRENT | LOGACTIONS_REBASE_DONE)))
1802 popup.AppendMenuIcon(ID_REBASE_PICK, IDS_REBASE_PICK, IDI_PICK);
1804 if (m_ContextMenuMask & GetContextMenuBit(ID_REBASE_SQUASH) && !(pSelLogEntry->GetRebaseAction() & (LOGACTIONS_REBASE_CURRENT | LOGACTIONS_REBASE_DONE)) && FirstSelect != GetItemCount() - 1 && LastSelect != GetItemCount() - 1 && (m_bIsCherryPick || pSelLogEntry->ParentsCount() == 1))
1805 popup.AppendMenuIcon(ID_REBASE_SQUASH, IDS_REBASE_SQUASH, IDI_SQUASH);
1807 if (m_ContextMenuMask & GetContextMenuBit(ID_REBASE_EDIT) && !(pSelLogEntry->GetRebaseAction() & (LOGACTIONS_REBASE_CURRENT | LOGACTIONS_REBASE_DONE)))
1808 popup.AppendMenuIcon(ID_REBASE_EDIT, IDS_REBASE_EDIT, IDI_EDIT);
1810 if (m_ContextMenuMask & GetContextMenuBit(ID_REBASE_SKIP) && !(pSelLogEntry->GetRebaseAction() & (LOGACTIONS_REBASE_CURRENT | LOGACTIONS_REBASE_DONE)))
1811 popup.AppendMenuIcon(ID_REBASE_SKIP, IDS_REBASE_SKIP, IDI_SKIP);
1813 if (m_ContextMenuMask & (GetContextMenuBit(ID_REBASE_SKIP) | GetContextMenuBit(ID_REBASE_EDIT) | GetContextMenuBit(ID_REBASE_SQUASH) | GetContextMenuBit(ID_REBASE_PICK)) && !(pSelLogEntry->GetRebaseAction() & (LOGACTIONS_REBASE_CURRENT | LOGACTIONS_REBASE_DONE)))
1814 popup.AppendMenu(MF_SEPARATOR, NULL);
1816 if (selectedCount == 1)
1819 bool requiresSeparator = false;
1820 if( !pSelLogEntry->m_CommitHash.IsEmpty())
1822 if(m_ContextMenuMask&GetContextMenuBit(ID_COMPARE) && m_hasWC) // compare revision with WC
1824 popup.AppendMenuIcon(ID_COMPARE, IDS_LOG_POPUP_COMPARE, IDI_DIFF);
1825 requiresSeparator = true;
1828 else
1830 if(m_ContextMenuMask&GetContextMenuBit(ID_COMMIT))
1832 popup.AppendMenuIcon(ID_COMMIT, IDS_LOG_POPUP_COMMIT, IDI_COMMIT);
1833 requiresSeparator = true;
1835 if (isMergeActive && (m_ContextMenuMask & GetContextMenuBit(ID_MERGE_ABORT)))
1837 popup.AppendMenuIcon(ID_MERGE_ABORT, IDS_MENUMERGEABORT, IDI_MERGEABORT);
1838 requiresSeparator = true;
1842 if (m_ContextMenuMask & GetContextMenuBit(ID_BLAMEPREVIOUS))
1844 if (parentHash.size() == 1)
1846 popup.AppendMenuIcon(ID_BLAMEPREVIOUS, IDS_LOG_POPUP_BLAMEPREVIOUS, IDI_BLAME);
1847 requiresSeparator = true;
1849 else if (parentHash.size() > 1)
1851 blamemenu.CreatePopupMenu();
1852 popup.AppendMenuIcon(ID_BLAMEPREVIOUS, IDS_LOG_POPUP_BLAMEPREVIOUS, IDI_BLAME, blamemenu.m_hMenu);
1853 for (size_t i = 0; i < parentHash.size(); ++i)
1855 CString str;
1856 str.Format(IDS_PARENT, i + 1);
1857 blamemenu.AppendMenuIcon(ID_BLAMEPREVIOUS +((i + 1) << 16), str);
1859 requiresSeparator = true;
1863 if(m_ContextMenuMask&GetContextMenuBit(ID_GNUDIFF1) && m_hasWC) // compare with WC, unified
1865 if (parentHash.size() == 1)
1867 popup.AppendMenuIcon(ID_GNUDIFF1, IDS_LOG_POPUP_GNUDIFF, IDI_DIFF);
1868 requiresSeparator = true;
1870 else if (parentHash.size() > 1)
1872 gnudiffmenu.CreatePopupMenu();
1873 popup.AppendMenuIcon(ID_GNUDIFF1,IDS_LOG_POPUP_GNUDIFF_PARENT, IDI_DIFF, gnudiffmenu.m_hMenu);
1875 gnudiffmenu.AppendMenuIcon((UINT_PTR)(ID_GNUDIFF1 + (0xFFFF << 16)), CString(MAKEINTRESOURCE(IDS_ALLPARENTS)));
1876 gnudiffmenu.AppendMenuIcon((UINT_PTR)(ID_GNUDIFF1 + (0xFFFE << 16)), CString(MAKEINTRESOURCE(IDS_ONLYMERGEDFILES)));
1877 gnudiffmenu.AppendMenuIcon((UINT_PTR)(ID_GNUDIFF1 + (0xFFFD << 16)), CString(MAKEINTRESOURCE(IDS_DIFFWITHMERGE)));
1879 for (size_t i = 0; i < parentHash.size(); ++i)
1881 CString str;
1882 str.Format(IDS_PARENT, i + 1);
1883 gnudiffmenu.AppendMenuIcon(ID_GNUDIFF1+((i+1)<<16),str);
1885 requiresSeparator = true;
1889 if(m_ContextMenuMask&GetContextMenuBit(ID_COMPAREWITHPREVIOUS))
1891 if (parentHash.size() == 1)
1893 popup.AppendMenuIcon(ID_COMPAREWITHPREVIOUS, IDS_LOG_POPUP_COMPAREWITHPREVIOUS, IDI_DIFF);
1894 if (CRegDWORD(L"Software\\TortoiseGit\\DiffByDoubleClickInLog", FALSE))
1895 popup.SetDefaultItem(ID_COMPAREWITHPREVIOUS, FALSE);
1896 requiresSeparator = true;
1898 else if (parentHash.size() > 1)
1900 diffmenu.CreatePopupMenu();
1901 popup.AppendMenuIcon(ID_COMPAREWITHPREVIOUS, IDS_LOG_POPUP_COMPAREWITHPREVIOUS, IDI_DIFF, diffmenu.m_hMenu);
1902 for (size_t i = 0; i < parentHash.size(); ++i)
1904 CString str;
1905 str.Format(IDS_PARENT, i + 1);
1906 diffmenu.AppendMenuIcon(ID_COMPAREWITHPREVIOUS +((i+1)<<16),str);
1907 if (i == 0 && CRegDWORD(L"Software\\TortoiseGit\\DiffByDoubleClickInLog", FALSE))
1909 popup.SetDefaultItem(ID_COMPAREWITHPREVIOUS, FALSE);
1910 diffmenu.SetDefaultItem((UINT)(ID_COMPAREWITHPREVIOUS + ((i + 1) << 16)), FALSE);
1913 requiresSeparator = true;
1917 if(m_ContextMenuMask&GetContextMenuBit(ID_BLAME))
1919 popup.AppendMenuIcon(ID_BLAME, IDS_LOG_POPUP_BLAME, IDI_BLAME);
1920 requiresSeparator = true;
1923 if (requiresSeparator)
1925 popup.AppendMenu(MF_SEPARATOR, NULL);
1926 requiresSeparator = false;
1929 if (pSelLogEntry->m_CommitHash.IsEmpty() && !isMergeActive)
1931 if(m_ContextMenuMask&GetContextMenuBit(ID_STASH_SAVE))
1933 popup.AppendMenuIcon(ID_STASH_SAVE, IDS_MENUSTASHSAVE, IDI_SHELVE);
1934 requiresSeparator = true;
1938 if ((pSelLogEntry->m_CommitHash.IsEmpty() || isStash) && workingTree.HasStashDir())
1940 if (m_ContextMenuMask&GetContextMenuBit(ID_STASH_POP))
1942 popup.AppendMenuIcon(ID_STASH_POP, IDS_MENUSTASHPOP, IDI_UNSHELVE);
1943 requiresSeparator = true;
1946 if (m_ContextMenuMask&GetContextMenuBit(ID_STASH_LIST))
1948 popup.AppendMenuIcon(ID_STASH_LIST, IDS_MENUSTASHLIST, IDI_LOG);
1949 requiresSeparator = true;
1953 if (requiresSeparator)
1955 popup.AppendMenu(MF_SEPARATOR, NULL);
1956 requiresSeparator = false;
1959 if (isBisectActive)
1961 GitRevLoglist* pFirstEntry = m_arShownList.SafeGetAt(FirstSelect);
1962 if (m_ContextMenuMask&GetContextMenuBit(ID_BISECTGOOD) && !IsBisect(pFirstEntry))
1964 popup.AppendMenuIcon(ID_BISECTGOOD, IDS_MENUBISECTGOOD, IDI_THUMB_UP);
1965 requiresSeparator = true;
1968 if (m_ContextMenuMask&GetContextMenuBit(ID_BISECTBAD) && !IsBisect(pFirstEntry))
1970 popup.AppendMenuIcon(ID_BISECTBAD, IDS_MENUBISECTBAD, IDI_THUMB_DOWN);
1971 requiresSeparator = true;
1973 if (m_ContextMenuMask&GetContextMenuBit(ID_BISECTSKIP) && !IsBisect(pFirstEntry))
1975 popup.AppendMenuIcon(ID_BISECTSKIP, IDS_MENUBISECTSKIP, IDI_BISECT);
1976 requiresSeparator = true;
1980 if (pSelLogEntry->m_CommitHash.IsEmpty() && isBisectActive)
1982 if (m_ContextMenuMask&GetContextMenuBit(ID_BISECTRESET))
1984 popup.AppendMenuIcon(ID_BISECTRESET, IDS_MENUBISECTRESET, IDI_BISECT_RESET);
1985 requiresSeparator = true;
1989 if (requiresSeparator)
1991 popup.AppendMenu(MF_SEPARATOR, NULL);
1992 requiresSeparator = false;
1995 if (pSelLogEntry->m_CommitHash.IsEmpty())
1997 if (m_ContextMenuMask & GetContextMenuBit(ID_PULL) && !isMergeActive)
1998 popup.AppendMenuIcon(ID_PULL, IDS_MENUPULL, IDI_PULL);
2000 if(m_ContextMenuMask&GetContextMenuBit(ID_FETCH))
2001 popup.AppendMenuIcon(ID_FETCH, IDS_MENUFETCH, IDI_UPDATE);
2003 if ((m_ContextMenuMask & GetContextMenuBit(ID_SUBMODULE_UPDATE)) && workingTree.HasSubmodules())
2004 popup.AppendMenuIcon(ID_SUBMODULE_UPDATE, IDS_PROC_SYNC_SUBKODULEUPDATE, IDI_UPDATE);
2006 popup.AppendMenu(MF_SEPARATOR, NULL);
2008 if (m_ContextMenuMask & GetContextMenuBit(ID_CLEANUP))
2009 popup.AppendMenuIcon(ID_CLEANUP, IDS_MENUCLEANUP, IDI_CLEANUP);
2011 popup.AppendMenu(MF_SEPARATOR, NULL);
2015 // if (!m_ProjectProperties.sWebViewerRev.IsEmpty())
2016 // {
2017 // popup.AppendMenuIcon(ID_VIEWREV, IDS_LOG_POPUP_VIEWREV);
2018 // }
2019 // if (!m_ProjectProperties.sWebViewerPathRev.IsEmpty())
2020 // {
2021 // popup.AppendMenuIcon(ID_VIEWPATHREV, IDS_LOG_POPUP_VIEWPATHREV);
2022 // }
2023 // if ((!m_ProjectProperties.sWebViewerPathRev.IsEmpty())||
2024 // (!m_ProjectProperties.sWebViewerRev.IsEmpty()))
2025 // {
2026 // popup.AppendMenu(MF_SEPARATOR, NULL);
2027 // }
2029 CString str;
2030 //if (m_hasWC)
2031 // popup.AppendMenuIcon(ID_REVERTTOREV, IDS_LOG_POPUP_REVERTTOREV, IDI_REVERT);
2033 if(!pSelLogEntry->m_CommitHash.IsEmpty())
2035 if ((m_ContextMenuMask & GetContextMenuBit(ID_LOG)) && selectedCount == 1)
2036 popup.AppendMenuIcon(ID_LOG, IDS_LOG_POPUP_LOG, IDI_LOG);
2038 if (m_ContextMenuMask&GetContextMenuBit(ID_REPOBROWSE))
2039 popup.AppendMenuIcon(ID_REPOBROWSE, IDS_LOG_BROWSEREPO, IDI_REPOBROWSE);
2041 str.Format(IDS_LOG_POPUP_MERGEREV, (LPCTSTR)g_Git.GetCurrentBranch());
2043 if (m_ContextMenuMask&GetContextMenuBit(ID_MERGEREV) && !isHeadCommit && m_hasWC && !isMergeActive && !isStash)
2045 popup.AppendMenuIcon(ID_MERGEREV, str, IDI_MERGE);
2047 size_t index = (size_t)-1;
2048 CGit::REF_TYPE type = CGit::REF_TYPE::UNKNOWN;
2049 if (IsMouseOnRefLabelFromPopupMenu(pSelLogEntry, point, type, nullptr, &index))
2050 popup.SetMenuItemData(ID_MERGEREV, (ULONG_PTR)&m_HashMap[pSelLogEntry->m_CommitHash][index]);
2053 str.Format(IDS_RESET_TO_THIS_FORMAT, (LPCTSTR)g_Git.GetCurrentBranch());
2055 if (m_ContextMenuMask&GetContextMenuBit(ID_RESET) && m_hasWC && !isStash)
2056 popup.AppendMenuIcon(ID_RESET, str, IDI_RESET);
2059 // Add Switch Branch express Menu
2060 if( this->m_HashMap.find(pSelLogEntry->m_CommitHash) != m_HashMap.end()
2061 && (m_ContextMenuMask&GetContextMenuBit(ID_SWITCHBRANCH) && m_hasWC && !isStash)
2064 std::vector<const CString*> branchs;
2065 auto addCheck = [&](const CString& ref)
2067 if (!CStringUtils::StartsWith(ref, L"refs/heads/") || ref == currentBranch)
2068 return;
2069 branchs.push_back(&ref);
2071 size_t index = (size_t)-1;
2072 CGit::REF_TYPE type = CGit::REF_TYPE::UNKNOWN;
2073 if (IsMouseOnRefLabelFromPopupMenu(pSelLogEntry, point, type, nullptr, &index))
2074 addCheck(m_HashMap[pSelLogEntry->m_CommitHash][index]);
2075 else
2076 for_each(m_HashMap[pSelLogEntry->m_CommitHash], addCheck);
2078 CString str2;
2079 str2.LoadString(IDS_SWITCH_BRANCH);
2081 if(branchs.size() == 1)
2083 str2 += L' ';
2084 str2 += L'"' + branchs[0]->Mid((int)wcslen(L"refs/heads/")) + L'"';
2085 popup.AppendMenuIcon(ID_SWITCHBRANCH, str2, IDI_SWITCH);
2087 popup.SetMenuItemData(ID_SWITCHBRANCH,(ULONG_PTR)branchs[0]);
2090 else if(branchs.size() > 1)
2092 subbranchmenu.CreatePopupMenu();
2093 for (size_t i = 0 ; i < branchs.size(); ++i)
2095 if (*branchs[i] != currentBranch)
2097 subbranchmenu.AppendMenuIcon(ID_SWITCHBRANCH + (i << 16), branchs[i]->Mid((int)wcslen(L"refs/heads/")));
2098 subbranchmenu.SetMenuItemData(ID_SWITCHBRANCH+(i<<16), (ULONG_PTR) branchs[i]);
2102 popup.AppendMenuIcon(ID_SWITCHBRANCH, str2, IDI_SWITCH, subbranchmenu.m_hMenu);
2106 if (m_ContextMenuMask&GetContextMenuBit(ID_SWITCHTOREV) && !isHeadCommit && m_hasWC && !isStash)
2108 popup.AppendMenuIcon(ID_SWITCHTOREV, IDS_SWITCH_TO_THIS, IDI_SWITCH);
2109 size_t index = (size_t)-1;
2110 CGit::REF_TYPE type = CGit::REF_TYPE::UNKNOWN;
2111 if (IsMouseOnRefLabelFromPopupMenu(pSelLogEntry, point, type, nullptr, &index))
2112 popup.SetMenuItemData(ID_SWITCHTOREV, (ULONG_PTR)&m_HashMap[pSelLogEntry->m_CommitHash][index]);
2115 if (m_ContextMenuMask&GetContextMenuBit(ID_CREATE_BRANCH) && !isStash)
2117 popup.AppendMenuIcon(ID_CREATE_BRANCH, IDS_CREATE_BRANCH_AT_THIS, IDI_COPY);
2119 size_t index = (size_t)-1;
2120 CGit::REF_TYPE type = CGit::REF_TYPE::REMOTE_BRANCH;
2121 if (IsMouseOnRefLabelFromPopupMenu(pSelLogEntry, point, type, nullptr, &index))
2122 popup.SetMenuItemData(ID_CREATE_BRANCH, (ULONG_PTR)&m_HashMap[pSelLogEntry->m_CommitHash][index]);
2125 if (m_ContextMenuMask&GetContextMenuBit(ID_CREATE_TAG) && !isStash)
2126 popup.AppendMenuIcon(ID_CREATE_TAG,IDS_CREATE_TAG_AT_THIS , IDI_TAG);
2128 str.Format(IDS_REBASE_THIS_FORMAT, (LPCTSTR)g_Git.GetCurrentBranch());
2130 if (pSelLogEntry->m_CommitHash != headHash && m_hasWC && !isMergeActive && !isStash)
2131 if(m_ContextMenuMask&GetContextMenuBit(ID_REBASE_TO_VERSION))
2132 popup.AppendMenuIcon(ID_REBASE_TO_VERSION, str , IDI_REBASE);
2134 if(m_ContextMenuMask&GetContextMenuBit(ID_EXPORT))
2135 popup.AppendMenuIcon(ID_EXPORT,IDS_EXPORT_TO_THIS, IDI_EXPORT);
2137 if (m_ContextMenuMask&GetContextMenuBit(ID_REVERTREV) && m_hasWC && !isMergeActive && !isStash)
2139 if (parentHash.size() == 1)
2140 popup.AppendMenuIcon(ID_REVERTREV, IDS_LOG_POPUP_REVERTREV, IDI_REVERT);
2141 else if (parentHash.size() > 1)
2143 revertmenu.CreatePopupMenu();
2144 popup.AppendMenuIcon(ID_REVERTREV, IDS_LOG_POPUP_REVERTREV, IDI_REVERT, revertmenu.m_hMenu);
2146 for (size_t i = 0; i < parentHash.size(); ++i)
2148 CString str2;
2149 str2.Format(IDS_PARENT, i + 1);
2150 revertmenu.AppendMenuIcon(ID_REVERTREV + ((i + 1) << 16), str2);
2155 if (m_ContextMenuMask&GetContextMenuBit(ID_EDITNOTE) && !isStash)
2156 popup.AppendMenuIcon(ID_EDITNOTE, IDS_EDIT_NOTES, IDI_EDIT);
2158 popup.AppendMenu(MF_SEPARATOR, NULL);
2162 if(!pSelLogEntry->m_Ref.IsEmpty())
2164 popup.AppendMenuIcon(ID_REFLOG_DEL, IDS_REFLOG_DEL, IDI_DELETE);
2165 if (selectedCount == 1 && CStringUtils::StartsWith(pSelLogEntry->m_Ref, L"refs/stash"))
2166 popup.AppendMenuIcon(ID_REFLOG_STASH_APPLY, IDS_MENUSTASHAPPLY, IDI_UNSHELVE);
2167 if (selectedCount <= 2)
2168 popup.AppendMenu(MF_SEPARATOR, NULL);
2171 if (selectedCount >= 2)
2173 bool bAddSeparator = false;
2174 if ((selectedCount == 2) || IsSelectionContinuous())
2176 if (m_ContextMenuMask&GetContextMenuBit(ID_COMPARETWO)) // compare two revisions
2178 popup.AppendMenuIcon(ID_COMPARETWO, IDS_LOG_POPUP_COMPARETWO, IDI_DIFF);
2179 bAddSeparator = true;
2183 if (selectedCount == 2)
2185 if (m_ContextMenuMask&GetContextMenuBit(ID_GNUDIFF2) && m_hasWC) // compare two revisions, unified
2187 popup.AppendMenuIcon(ID_GNUDIFF2, IDS_LOG_POPUP_GNUDIFF, IDI_DIFF);
2188 bAddSeparator = true;
2191 if (!pSelLogEntry->m_CommitHash.IsEmpty())
2193 CString firstSelHash = pSelLogEntry->m_CommitHash.ToString().Left(g_Git.GetShortHASHLength());
2194 GitRevLoglist* pLastEntry = m_arShownList.SafeGetAt(LastSelect);
2195 CString lastSelHash = pLastEntry->m_CommitHash.ToString().Left(g_Git.GetShortHASHLength());
2196 CString menu;
2197 menu.Format(IDS_SHOWLOG_OF, (LPCTSTR)(lastSelHash + L".." + firstSelHash));
2198 popup.AppendMenuIcon(ID_LOG_VIEWRANGE, menu, IDI_LOG);
2199 menu.Format(IDS_SHOWLOG_OF, (LPCTSTR)(lastSelHash + L"..." + firstSelHash));
2200 popup.AppendMenuIcon(ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE, menu, IDI_LOG);
2201 bAddSeparator = true;
2205 if ((m_ContextMenuMask & GetContextMenuBit(ID_COMPARETWOCOMMITCHANGES)) && selectedCount == 2 && !IsSelectionContinuous())
2207 bAddSeparator = true;
2208 popup.AppendMenuIcon(ID_COMPARETWOCOMMITCHANGES, IDS_LOG_POPUP_COMPARECHANGESET, IDI_DIFF);
2211 if (bAddSeparator)
2213 popup.AppendMenu(MF_SEPARATOR, NULL);
2214 bAddSeparator = false;
2217 if (m_ContextMenuMask&GetContextMenuBit(ID_REVERTREV) && m_hasWC && !isMergeActive)
2218 popup.AppendMenuIcon(ID_REVERTREV, IDS_LOG_POPUP_REVERTREVS, IDI_REVERT);
2220 if (bAddSeparator)
2221 popup.AppendMenu(MF_SEPARATOR, NULL);
2224 if (selectedCount > 1 && isBisectActive && (m_ContextMenuMask & GetContextMenuBit(ID_BISECTSKIP)) && !IsBisect(pSelLogEntry))
2226 popup.AppendMenuIcon(ID_BISECTSKIP, IDS_MENUBISECTSKIP, IDI_BISECT);
2227 popup.AppendMenu(MF_SEPARATOR, NULL);
2230 if (!pSelLogEntry->m_CommitHash.IsEmpty())
2232 bool bAddSeparator = false;
2233 if (selectedCount >= 2 && IsSelectionContinuous())
2235 if (m_ContextMenuMask&GetContextMenuBit(ID_COMBINE_COMMIT) && m_hasWC && !isMergeActive)
2237 CString head;
2238 int headindex;
2239 headindex = this->GetHeadIndex();
2240 if(headindex>=0 && LastSelect >= headindex && FirstSelect >= headindex)
2242 head.Format(L"HEAD~%d", FirstSelect - headindex);
2243 CGitHash hashFirst;
2244 int ret = g_Git.GetHash(hashFirst, head);
2245 head.Format(L"HEAD~%d",LastSelect-headindex);
2246 CGitHash hash;
2247 ret = ret || g_Git.GetHash(hash, head);
2248 GitRevLoglist* pFirstEntry = m_arShownList.SafeGetAt(FirstSelect);
2249 GitRevLoglist* pLastEntry = m_arShownList.SafeGetAt(LastSelect);
2250 if (!ret && pFirstEntry->m_CommitHash == hashFirst && pLastEntry->m_CommitHash == hash)
2252 popup.AppendMenuIcon(ID_COMBINE_COMMIT,IDS_COMBINE_TO_ONE,IDI_COMBINE);
2253 bAddSeparator = true;
2258 if (m_ContextMenuMask&GetContextMenuBit(ID_CHERRY_PICK) && !isHeadCommit && m_hasWC && !isMergeActive) {
2259 if (selectedCount >= 2)
2260 popup.AppendMenuIcon(ID_CHERRY_PICK, IDS_CHERRY_PICK_VERSIONS, IDI_PICK);
2261 else
2262 popup.AppendMenuIcon(ID_CHERRY_PICK, IDS_CHERRY_PICK_VERSION, IDI_PICK);
2263 bAddSeparator = true;
2266 if (selectedCount <= 2 || (IsSelectionContinuous() && !isStash))
2267 if(m_ContextMenuMask&GetContextMenuBit(ID_CREATE_PATCH)) {
2268 popup.AppendMenuIcon(ID_CREATE_PATCH, IDS_CREATE_PATCH, IDI_PATCH);
2269 bAddSeparator = true;
2272 if (bAddSeparator)
2273 popup.AppendMenu(MF_SEPARATOR, NULL);
2276 if (m_hasWC && !isMergeActive && !isStash && (m_ContextMenuMask & GetContextMenuBit(ID_BISECTSTART)) && selectedCount == 2 && !m_arShownList.SafeGetAt(FirstSelect)->m_CommitHash.IsEmpty() && !isBisectActive)
2278 popup.AppendMenuIcon(ID_BISECTSTART, IDS_MENUBISECTSTART, IDI_BISECT);
2279 popup.AppendMenu(MF_SEPARATOR, NULL);
2282 if (selectedCount == 1)
2284 bool bAddSeparator = false;
2285 if ((m_ContextMenuMask & GetContextMenuBit(ID_PUSH)) && ((!isStash && m_HashMap.find(pSelLogEntry->m_CommitHash) != m_HashMap.cend()) || showExtendedMenu))
2287 // show the push-option only if the log entry has an associated local branch
2288 bool isLocal = find_if(m_HashMap[pSelLogEntry->m_CommitHash], [](const CString& ref) { return CStringUtils::StartsWith(ref, L"refs/heads/") || CStringUtils::StartsWith(ref, L"refs/tags/"); }) != m_HashMap[pSelLogEntry->m_CommitHash].cend();
2289 if (isLocal || showExtendedMenu)
2291 CString str;
2292 str.LoadString(IDS_MENUPUSH);
2294 CString branch;
2295 size_t index = (size_t)-1;
2296 CGit::REF_TYPE type = CGit::REF_TYPE::UNKNOWN;
2297 if (IsMouseOnRefLabelFromPopupMenu(pSelLogEntry, point, type, &branch, &index))
2298 if (type == CGit::REF_TYPE::LOCAL_BRANCH || type == CGit::REF_TYPE::ANNOTATED_TAG || type == CGit::REF_TYPE::TAG)
2299 str.Insert(str.Find(L'.'), L" \"" + branch + L'"');
2301 popup.AppendMenuIcon(ID_PUSH, str, IDI_PUSH);
2303 if (index != (size_t)-1 && index < m_HashMap[pSelLogEntry->m_CommitHash].size())
2304 popup.SetMenuItemData(ID_PUSH, (ULONG_PTR)&m_HashMap[pSelLogEntry->m_CommitHash][index]);
2306 if (m_ContextMenuMask & GetContextMenuBit(ID_SVNDCOMMIT) && workingTree.HasGitSVNDir())
2307 popup.AppendMenuIcon(ID_SVNDCOMMIT, IDS_MENUSVNDCOMMIT, IDI_COMMIT);
2309 bAddSeparator = true;
2312 if (m_ContextMenuMask & GetContextMenuBit(ID_PULL) && isHeadCommit && !isMergeActive && m_hasWC)
2314 popup.AppendMenuIcon(ID_PULL, IDS_MENUPULL, IDI_PULL);
2315 bAddSeparator = true;
2319 if(m_ContextMenuMask &GetContextMenuBit(ID_DELETE))
2321 if( this->m_HashMap.find(pSelLogEntry->m_CommitHash) != m_HashMap.end() )
2323 std::vector<const CString*> branchs;
2324 auto addCheck = [&](const CString& ref)
2326 if (ref == currentBranch)
2327 return;
2328 branchs.push_back(&ref);
2330 size_t index = (size_t)-1;
2331 CGit::REF_TYPE type = CGit::REF_TYPE::UNKNOWN;
2332 if (IsMouseOnRefLabelFromPopupMenu(pSelLogEntry, point, type, nullptr, &index))
2333 addCheck(m_HashMap[pSelLogEntry->m_CommitHash][index]);
2334 else
2335 for_each(m_HashMap[pSelLogEntry->m_CommitHash], addCheck);
2337 CString str;
2338 if (branchs.size() == 1)
2340 str.LoadString(IDS_DELETE_BRANCHTAG_SHORT);
2341 str += L' ';
2342 str += *branchs[0];
2343 popup.AppendMenuIcon(ID_DELETE, str, IDI_DELETE);
2344 popup.SetMenuItemData(ID_DELETE, (ULONG_PTR)branchs[0]);
2345 bAddSeparator = true;
2347 else if (branchs.size() > 1)
2349 str.LoadString(IDS_DELETE_BRANCHTAG);
2350 submenu.CreatePopupMenu();
2351 for (size_t i = 0; i < branchs.size(); ++i)
2353 submenu.AppendMenuIcon(ID_DELETE + (i << 16), *branchs[i]);
2354 submenu.SetMenuItemData(ID_DELETE + (i << 16), (ULONG_PTR)branchs[i]);
2356 submenu.AppendMenuIcon(ID_DELETE + (branchs.size() << 16), IDS_ALL);
2357 submenu.SetMenuItemData(ID_DELETE + (branchs.size() << 16), (ULONG_PTR)MAKEINTRESOURCE(IDS_ALL));
2359 popup.AppendMenuIcon(ID_DELETE,str, IDI_DELETE, submenu.m_hMenu);
2360 bAddSeparator = true;
2363 } // m_ContextMenuMask &GetContextMenuBit(ID_DELETE)
2364 if (bAddSeparator)
2365 popup.AppendMenu(MF_SEPARATOR, NULL);
2366 } // selectedCount == 1
2368 CIconMenu clipSubMenu;
2369 if (!clipSubMenu.CreatePopupMenu())
2370 return;
2371 if (m_ContextMenuMask & GetContextMenuBit(ID_COPYCLIPBOARD))
2373 clipSubMenu.AppendMenuIcon(ID_COPYCLIPBOARDFULL, IDS_LOG_POPUP_CLIPBOARD_FULL, IDI_COPYCLIP);
2374 clipSubMenu.AppendMenuIcon(ID_COPYCLIPBOARDFULLNOPATHS, IDS_LOG_POPUP_CLIPBOARD_FULLNOPATHS, IDI_COPYCLIP);
2375 clipSubMenu.AppendMenuIcon(ID_COPYCLIPBOARDHASH, IDS_LOG_HASH, IDI_COPYCLIP);
2376 clipSubMenu.AppendMenuIcon(ID_COPYCLIPBOARDAUTHORSFULL, IDS_LOG_POPUP_CLIPBOARD_AUTHORSFULL, IDI_COPYCLIP);
2377 clipSubMenu.AppendMenuIcon(ID_COPYCLIPBOARDAUTHORSNAME, IDS_LOG_POPUP_CLIPBOARD_AUTHORSNAME, IDI_COPYCLIP);
2378 clipSubMenu.AppendMenuIcon(ID_COPYCLIPBOARDAUTHORSEMAIL, IDS_LOG_POPUP_CLIPBOARD_AUTHORSEMAIL, IDI_COPYCLIP);
2379 clipSubMenu.AppendMenuIcon(ID_COPYCLIPBOARDSUBJECTS, IDS_LOG_POPUP_CLIPBOARD_SUBJECTS, IDI_COPYCLIP);
2380 clipSubMenu.AppendMenuIcon(ID_COPYCLIPBOARDMESSAGES, IDS_LOG_POPUP_CLIPBOARD_MSGS, IDI_COPYCLIP);
2381 if (m_HashMap.find(pSelLogEntry->m_CommitHash) != m_HashMap.cend() && selectedCount == 1)
2383 clipSubMenu.AppendMenuIcon(ID_COPYCLIPBOARDBRANCHTAG, IDS_LOG_POPUP_CLIPBOARD_TAGBRANCHES, IDI_COPYCLIP);
2384 size_t index = (size_t)-1;
2385 CGit::REF_TYPE type = CGit::REF_TYPE::UNKNOWN;
2386 if (IsMouseOnRefLabelFromPopupMenu(pSelLogEntry, point, type, nullptr, &index))
2387 clipSubMenu.SetMenuItemData(ID_COPYCLIPBOARDBRANCHTAG, (ULONG_PTR)&m_HashMap[pSelLogEntry->m_CommitHash][index]);
2390 CString temp;
2391 temp.LoadString(IDS_LOG_POPUP_COPYTOCLIPBOARD);
2392 popup.InsertMenu((UINT)-1, MF_BYPOSITION | MF_POPUP, (UINT_PTR)clipSubMenu.m_hMenu, temp);
2395 if(m_ContextMenuMask&GetContextMenuBit(ID_FINDENTRY))
2396 popup.AppendMenuIcon(ID_FINDENTRY, IDS_LOG_POPUP_FIND, IDI_FILTEREDIT);
2398 if (selectedCount == 1 && (m_ContextMenuMask & GetContextMenuBit(ID_SHOWBRANCHES)) && !pSelLogEntry->m_CommitHash.IsEmpty())
2399 popup.AppendMenuIcon(ID_SHOWBRANCHES, IDS_LOG_POPUP_SHOWBRANCHES, IDI_SHOWBRANCHES);
2401 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this);
2402 // DialogEnableWindow(IDOK, FALSE);
2403 // SetPromptApp(&theApp);
2405 this->ContextMenuAction(cmd, FirstSelect, LastSelect, &popup);
2407 // EnableOKButton();
2408 } // if (popup.CreatePopupMenu())
2411 bool CGitLogListBase::IsSelectionContinuous()
2413 if ( GetSelectedCount()==1 )
2415 // if only one revision is selected, the selection is of course
2416 // continuous
2417 return true;
2420 POSITION pos = GetFirstSelectedItemPosition();
2421 bool bContinuous = (m_arShownList.size() == m_logEntries.size());
2422 if (bContinuous)
2424 int itemindex = GetNextSelectedItem(pos);
2425 while (pos)
2427 int nextindex = GetNextSelectedItem(pos);
2428 if (nextindex - itemindex > 1)
2430 bContinuous = false;
2431 break;
2433 itemindex = nextindex;
2436 return bContinuous;
2439 void CGitLogListBase::CopySelectionToClipBoard(int toCopy)
2441 CString sClipdata;
2442 POSITION pos = GetFirstSelectedItemPosition();
2443 if (pos)
2445 CString sRev;
2446 sRev.LoadString(IDS_LOG_REVISION);
2447 CString sAuthor;
2448 sAuthor.LoadString(IDS_LOG_AUTHOR);
2449 CString sDate;
2450 sDate.LoadString(IDS_LOG_DATE);
2451 CString sMessage;
2452 sMessage.LoadString(IDS_LOG_MESSAGE);
2453 CString from(MAKEINTRESOURCE(IDS_STATUSLIST_FROM));
2454 bool first = true;
2455 while (pos)
2457 CString sLogCopyText;
2458 CString sPaths;
2459 GitRevLoglist* pLogEntry = m_arShownList.SafeGetAt(GetNextSelectedItem(pos));
2461 if (toCopy == ID_COPYCLIPBOARDFULL)
2463 sPaths = L"----\r\n";
2464 for (int cpPathIndex = 0; cpPathIndex<pLogEntry->GetFiles(this).GetCount(); ++cpPathIndex)
2466 sPaths += ((CTGitPath&)pLogEntry->GetFiles(this)[cpPathIndex]).GetActionName() + L": " + pLogEntry->GetFiles(this)[cpPathIndex].GetGitPathString();
2467 if (((CTGitPath&)pLogEntry->GetFiles(this)[cpPathIndex]).m_Action & (CTGitPath::LOGACTIONS_REPLACED | CTGitPath::LOGACTIONS_COPY) && !((CTGitPath&)pLogEntry->GetFiles(this)[cpPathIndex]).GetGitOldPathString().IsEmpty())
2469 sPaths += L' ';
2470 sPaths.AppendFormat(from, (LPCTSTR)((CTGitPath&)pLogEntry->GetFiles(this)[cpPathIndex]).GetGitOldPathString());
2472 sPaths += L"\r\n";
2474 sPaths.Trim();
2475 sPaths += L"\r\n";
2478 if (toCopy == ID_COPYCLIPBOARDFULL || toCopy == ID_COPYCLIPBOARDFULLNOPATHS)
2480 CString sNotesTags;
2481 if (!pLogEntry->m_Notes.IsEmpty())
2483 sNotesTags = L"----\n" + CString(MAKEINTRESOURCE(IDS_NOTES));
2484 sNotesTags += L":\n";
2485 sNotesTags += pLogEntry->m_Notes;
2486 sNotesTags.Replace(L"\n", L"\r\n");
2488 CString tagInfo = GetTagInfo(pLogEntry);
2489 if (!tagInfo.IsEmpty())
2491 sNotesTags += L"----\r\n" + CString(MAKEINTRESOURCE(IDS_PROC_LOG_TAGINFO)) + L":\r\n";
2492 tagInfo.Replace(L"\n", L"\r\n");
2493 sNotesTags += tagInfo;
2496 sLogCopyText.Format(L"%s: %s\r\n%s: %s <%s>\r\n%s: %s\r\n%s:\r\n%s\r\n%s%s\r\n",
2497 (LPCTSTR)sRev, (LPCTSTR)pLogEntry->m_CommitHash.ToString(),
2498 (LPCTSTR)sAuthor, (LPCTSTR)pLogEntry->GetAuthorName(), (LPCTSTR)pLogEntry->GetAuthorEmail(),
2499 (LPCTSTR)sDate,
2500 (LPCTSTR)CLoglistUtils::FormatDateAndTime(pLogEntry->GetAuthorDate(), m_DateFormat, true, m_bRelativeTimes),
2501 (LPCTSTR)sMessage, (LPCTSTR)pLogEntry->GetSubjectBody(true),
2502 (LPCTSTR)sNotesTags,
2503 (LPCTSTR)sPaths);
2504 sClipdata += sLogCopyText;
2506 else if (toCopy == ID_COPYCLIPBOARDAUTHORSFULL)
2508 if (!first)
2509 sClipdata += L"\r\n";
2510 sClipdata += pLogEntry->GetAuthorName();
2511 sClipdata += L" <";
2512 sClipdata += pLogEntry->GetAuthorEmail();
2513 sClipdata += L">";
2515 else if (toCopy == ID_COPYCLIPBOARDAUTHORSNAME)
2517 if (!first)
2518 sClipdata += L"\r\n";
2519 sClipdata += pLogEntry->GetAuthorName();
2521 else if (toCopy == ID_COPYCLIPBOARDAUTHORSEMAIL)
2523 if (!first)
2524 sClipdata += L"\r\n";
2525 sClipdata += pLogEntry->GetAuthorEmail();
2528 else if (toCopy == ID_COPYCLIPBOARDMESSAGES)
2530 sClipdata += L"* ";
2531 sClipdata += pLogEntry->GetSubjectBody(true);
2532 sClipdata += L"\r\n\r\n";
2534 else if (toCopy == ID_COPYCLIPBOARDSUBJECTS)
2536 sClipdata += L"* ";
2537 sClipdata += pLogEntry->GetSubject().Trim();
2538 sClipdata += L"\r\n\r\n";
2540 else
2542 if (!first)
2543 sClipdata += L"\r\n";
2544 sClipdata += pLogEntry->m_CommitHash.ToString();
2547 first = false;
2549 CStringUtils::WriteAsciiStringToClipboard(sClipdata, GetSafeHwnd());
2553 void CGitLogListBase::DiffSelectedRevWithPrevious()
2555 if (m_bThreadRunning)
2556 return;
2558 int FirstSelect=-1, LastSelect=-1;
2559 POSITION pos = GetFirstSelectedItemPosition();
2560 FirstSelect = GetNextSelectedItem(pos);
2561 while(pos)
2562 LastSelect = GetNextSelectedItem(pos);
2564 ContextMenuAction(ID_COMPAREWITHPREVIOUS, FirstSelect, LastSelect, nullptr);
2567 void CGitLogListBase::OnLvnOdfinditemLoglist(NMHDR *pNMHDR, LRESULT *pResult)
2569 LPNMLVFINDITEM pFindInfo = reinterpret_cast<LPNMLVFINDITEM>(pNMHDR);
2570 *pResult = -1;
2572 if (pFindInfo->lvfi.flags & LVFI_PARAM)
2573 return;
2574 if (pFindInfo->iStart < 0 || pFindInfo->iStart >= (int)m_arShownList.size())
2575 return;
2576 if (!pFindInfo->lvfi.psz)
2577 return;
2578 #if 0
2579 CString sCmp = pFindInfo->lvfi.psz;
2580 CString sRev;
2581 for (int i=pFindInfo->iStart; i<m_arShownList.GetCount(); ++i)
2583 GitRev * pLogEntry = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(i));
2584 sRev.Format(L"%ld", pLogEntry->Rev);
2585 if (pFindInfo->lvfi.flags & LVFI_PARTIAL)
2587 if (sCmp.Compare(sRev.Left(sCmp.GetLength()))==0)
2589 *pResult = i;
2590 return;
2593 else
2595 if (sCmp.Compare(sRev)==0)
2597 *pResult = i;
2598 return;
2602 if (pFindInfo->lvfi.flags & LVFI_WRAP)
2604 for (int i=0; i<pFindInfo->iStart; ++i)
2606 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(i));
2607 sRev.Format(L"%ld", pLogEntry->Rev);
2608 if (pFindInfo->lvfi.flags & LVFI_PARTIAL)
2610 if (sCmp.Compare(sRev.Left(sCmp.GetLength()))==0)
2612 *pResult = i;
2613 return;
2616 else
2618 if (sCmp.Compare(sRev)==0)
2620 *pResult = i;
2621 return;
2626 #endif
2627 *pResult = -1;
2630 int CGitLogListBase::FillGitLog(CTGitPath *path, CString *range, int info)
2632 ClearText();
2634 this->m_arShownList.SafeRemoveAll();
2636 this->m_logEntries.ClearAll();
2637 if (this->m_logEntries.ParserFromLog(path, 0, info, range))
2638 return -1;
2640 SetItemCountEx((int)m_logEntries.size());
2642 for (unsigned int i = 0; i < m_logEntries.size(); ++i)
2644 if(m_IsOldFirst)
2646 m_logEntries.GetGitRevAt(m_logEntries.size()-i-1).m_IsFull=TRUE;
2647 this->m_arShownList.SafeAdd(&m_logEntries.GetGitRevAt(m_logEntries.size()-i-1));
2649 else
2651 m_logEntries.GetGitRevAt(i).m_IsFull=TRUE;
2652 this->m_arShownList.SafeAdd(&m_logEntries.GetGitRevAt(i));
2656 ReloadHashMap();
2658 if(path)
2659 m_Path=*path;
2660 return 0;
2663 int CGitLogListBase::FillGitLog(std::unordered_set<CGitHash>& hashes)
2665 ClearText();
2667 m_arShownList.SafeRemoveAll();
2669 m_logEntries.ClearAll();
2670 if (m_logEntries.Fill(hashes))
2671 return -1;
2673 SetItemCountEx((int)m_logEntries.size());
2675 for (unsigned int i = 0; i < m_logEntries.size(); ++i)
2677 if (m_IsOldFirst)
2679 m_logEntries.GetGitRevAt(m_logEntries.size() - i - 1).m_IsFull = TRUE;
2680 m_arShownList.SafeAdd(&m_logEntries.GetGitRevAt(m_logEntries.size() - i - 1));
2682 else
2684 m_logEntries.GetGitRevAt(i).m_IsFull = TRUE;
2685 m_arShownList.SafeAdd(&m_logEntries.GetGitRevAt(i));
2689 ReloadHashMap();
2691 return 0;
2694 int CGitLogListBase::BeginFetchLog()
2696 ClearText();
2698 this->m_arShownList.SafeRemoveAll();
2700 this->m_logEntries.ClearAll();
2702 this->m_LogCache.ClearAllParent();
2704 m_LogCache.FetchCacheIndex(g_Git.m_CurrentDir);
2706 CTGitPath *path;
2707 if(this->m_Path.IsEmpty())
2708 path = nullptr;
2709 else
2710 path=&this->m_Path;
2712 int mask;
2713 mask = CGit::LOG_INFO_ONLY_HASH;
2714 if (m_bIncludeBoundaryCommits)
2715 mask |= CGit::LOG_INFO_BOUNDARY;
2716 // if(this->m_bAllBranch)
2717 mask |= m_ShowMask ;
2719 if(m_bShowWC)
2721 this->m_logEntries.insert(m_logEntries.cbegin(), m_wcRev.m_CommitHash);
2722 ResetWcRev();
2723 this->m_LogCache.m_HashMap[m_wcRev.m_CommitHash]=m_wcRev;
2726 if (m_sRange.IsEmpty())
2727 m_sRange = L"HEAD";
2729 // follow does not work for directories
2730 if (!path || path->IsDirectory())
2731 mask &= ~CGit::LOG_INFO_FOLLOW;
2732 // follow does not work with all branches 8at least in TGit)
2733 if (mask & CGit::LOG_INFO_FOLLOW)
2734 mask &= ~(CGit::LOG_INFO_ALL_BRANCH | CGit::LOG_INFO_BASIC_REFS | CGit::LOG_INFO_LOCAL_BRANCHES);
2736 CString cmd = g_Git.GetLogCmd(m_sRange, path, mask, &m_Filter, CRegDWORD(L"Software\\TortoiseGit\\LogOrderBy", CGit::LOG_ORDER_TOPOORDER));
2738 //this->m_logEntries.ParserFromLog();
2739 if(IsInWorkingThread())
2741 PostMessage(LVM_SETITEMCOUNT, (WPARAM) this->m_logEntries.size(),(LPARAM) LVSICF_NOINVALIDATEALL);
2743 else
2745 SetItemCountEx((int)m_logEntries.size());
2750 g_Git.CheckAndInitDll();
2752 catch (char* msg)
2754 CString err(msg);
2755 MessageBox(L"Could not initialize libgit.\nlibgit reports:\n" + err, L"TortoiseGit", MB_ICONERROR);
2756 return -1;
2759 if (!g_Git.CanParseRev(m_sRange))
2761 if (!(mask & CGit::LOG_INFO_ALL_BRANCH) && !(mask & CGit::LOG_INFO_BASIC_REFS) && !(mask & CGit::LOG_INFO_LOCAL_BRANCHES))
2762 return 0;
2764 // if show all branches, pick any ref as dummy entry ref
2765 STRING_VECTOR list;
2766 if (g_Git.GetRefList(list))
2767 MessageBox(g_Git.GetGitLastErr(L"Could not get all refs."), L"TortoiseGit", MB_ICONERROR);
2768 if (list.empty())
2769 return 0;
2771 cmd = g_Git.GetLogCmd(list[0], path, mask, &m_Filter, CRegDWORD(L"Software\\TortoiseGit\\LogOrderBy", CGit::LOG_ORDER_TOPOORDER));
2774 g_Git.m_critGitDllSec.Lock();
2775 try {
2776 if (git_open_log(&m_DllGitLog, CUnicodeUtils::GetMulti(cmd, CP_UTF8)))
2778 g_Git.m_critGitDllSec.Unlock();
2779 return -1;
2782 catch (char* msg)
2784 g_Git.m_critGitDllSec.Unlock();
2785 CString err(msg);
2786 MessageBox(L"Could not open log.\nlibgit reports:\n" + err, L"TortoiseGit", MB_ICONERROR);
2787 return -1;
2789 g_Git.m_critGitDllSec.Unlock();
2791 return 0;
2794 BOOL CGitLogListBase::PreTranslateMessage(MSG* pMsg)
2796 if (pMsg->message == WM_KEYDOWN && pMsg->wParam=='\r')
2798 //if (GetFocus()==GetDlgItem(IDC_LOGLIST))
2800 if (CRegDWORD(L"Software\\TortoiseGit\\DiffByDoubleClickInLog", FALSE))
2802 DiffSelectedRevWithPrevious();
2803 return TRUE;
2806 #if 0
2807 if (GetFocus()==GetDlgItem(IDC_LOGMSG))
2809 DiffSelectedFile();
2810 return TRUE;
2812 #endif
2814 else if (pMsg->message == WM_KEYDOWN && pMsg->wParam == 'A' && GetAsyncKeyState(VK_CONTROL)&0x8000)
2816 // select all entries
2817 for (int i=0; i<GetItemCount(); ++i)
2818 SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
2819 return TRUE;
2822 #if 0
2823 if (m_hAccel && !bSkipAccelerator)
2825 int ret = TranslateAccelerator(m_hWnd, m_hAccel, pMsg);
2826 if (ret)
2827 return TRUE;
2830 #endif
2831 //m_tooltips.RelayEvent(pMsg);
2832 return __super::PreTranslateMessage(pMsg);
2835 void CGitLogListBase::OnNMDblclkLoglist(NMHDR * /*pNMHDR*/, LRESULT *pResult)
2837 // a double click on an entry in the revision list has happened
2838 *pResult = 0;
2840 if (CRegDWORD(L"Software\\TortoiseGit\\DiffByDoubleClickInLog", FALSE))
2841 DiffSelectedRevWithPrevious();
2844 void CGitLogListBase::FetchLogAsync(void* data)
2846 ReloadHashMap();
2847 m_ProcData=data;
2848 StartLoadingThread();
2851 UINT CGitLogListBase::LogThreadEntry(LPVOID pVoid)
2853 return ((CGitLogListBase*)pVoid)->LogThread();
2856 void CGitLogListBase::GetTimeRange(CTime &oldest, CTime &latest)
2858 //CTime time;
2859 oldest=CTime::GetCurrentTime();
2860 latest=CTime(1971,1,2,0,0,0);
2861 for (unsigned int i = 0; i < m_logEntries.size(); ++i)
2863 if(m_logEntries[i].IsEmpty())
2864 continue;
2866 if (m_logEntries.GetGitRevAt(i).GetCommitterDate().GetTime() < oldest.GetTime())
2867 oldest = m_logEntries.GetGitRevAt(i).GetCommitterDate().GetTime();
2869 if (m_logEntries.GetGitRevAt(i).GetCommitterDate().GetTime() > latest.GetTime())
2870 latest = m_logEntries.GetGitRevAt(i).GetCommitterDate().GetTime();
2874 if(latest<oldest)
2875 latest=oldest;
2878 UINT CGitLogListBase::LogThread()
2880 ::PostMessage(this->GetParent()->m_hWnd,MSG_LOAD_PERCENTAGE,(WPARAM) GITLOG_START,0);
2882 ULONGLONG t1,t2;
2884 if(BeginFetchLog())
2886 InterlockedExchange(&m_bThreadRunning, FALSE);
2887 InterlockedExchange(&m_bNoDispUpdates, FALSE);
2889 return 1;
2892 // create a copy we can safely work on in this thread
2893 auto shared_filter(m_LogFilter);
2894 auto& filter = *shared_filter;
2896 TRACE(L"\n===Begin===\n");
2897 //Update work copy item;
2899 if (!m_logEntries.empty())
2901 GitRevLoglist* pRev = &m_logEntries.GetGitRevAt(0);
2903 m_arShownList.SafeAdd(pRev);
2907 InterlockedExchange(&m_bNoDispUpdates, FALSE);
2909 // store commit number of the last selected commit/line before the refresh or -1
2910 int lastSelectedHashNItem = -1;
2911 if (m_lastSelectedHash.IsEmpty())
2912 lastSelectedHashNItem = 0;
2914 int ret = 0;
2916 bool shouldWalk = true;
2917 if (!g_Git.CanParseRev(m_sRange))
2919 // walk revisions if show all branches and there exists any ref
2920 if (!(m_ShowMask & CGit::LOG_INFO_ALL_BRANCH) && !(m_ShowMask & CGit::LOG_INFO_BASIC_REFS) && !(m_ShowMask & CGit::LOG_INFO_LOCAL_BRANCHES))
2921 shouldWalk = false;
2922 else
2924 STRING_VECTOR list;
2925 if (g_Git.GetRefList(list))
2926 MessageBox(g_Git.GetGitLastErr(L"Could not get all refs."), L"TortoiseGit", MB_ICONERROR);
2927 if (list.empty())
2928 shouldWalk = false;
2932 if (shouldWalk)
2934 g_Git.m_critGitDllSec.Lock();
2935 if (!m_DllGitLog)
2937 MessageBox(L"Opening log failed.", L"TortoiseGit", MB_ICONERROR);
2938 g_Git.m_critGitDllSec.Unlock();
2939 InterlockedExchange(&m_bThreadRunning, FALSE);
2940 InterlockedExchange(&m_bNoDispUpdates, FALSE);
2941 return 1;
2943 int total = 0;
2946 [&] {git_get_log_firstcommit(m_DllGitLog);}();
2947 total = git_get_log_estimate_commit_count(m_DllGitLog);
2949 catch (char* msg)
2951 CString err(msg);
2952 MessageBox(L"Could not get first commit.\nlibgit reports:\n" + err, L"TortoiseGit", MB_ICONERROR);
2953 ret = -1;
2955 g_Git.m_critGitDllSec.Unlock();
2957 GIT_COMMIT commit;
2958 t2 = t1 = GetTickCount64();
2959 int oldprecentage = 0;
2960 size_t oldsize = m_logEntries.size();
2961 std::unordered_map<CGitHash, std::unordered_set<CGitHash>> commitChildren;
2962 while (ret== 0 && !m_bExitThread)
2964 g_Git.m_critGitDllSec.Lock();
2967 [&] { ret = git_get_log_nextcommit(this->m_DllGitLog, &commit, m_ShowMask & CGit::LOG_INFO_FOLLOW); } ();
2969 catch (char* msg)
2971 g_Git.m_critGitDllSec.Unlock();
2972 CString err(msg);
2973 MessageBox(L"Could not get next commit.\nlibgit reports:\n" + err, L"TortoiseGit", MB_ICONERROR);
2974 break;
2976 g_Git.m_critGitDllSec.Unlock();
2978 if(ret)
2980 if (ret != -2) // other than end of revision walking
2981 MessageBox((L"Could not get next commit.\nlibgit returns:" + std::to_wstring(ret)).c_str(), L"TortoiseGit", MB_ICONERROR);
2982 break;
2985 if (commit.m_ignore == 1)
2987 git_free_commit(&commit);
2988 continue;
2991 //printf("%s\r\n",commit.GetSubject());
2992 if(m_bExitThread)
2993 break;
2995 CGitHash hash(commit.m_hash);
2997 GitRevLoglist* pRev = m_LogCache.GetCacheData(hash);
2998 pRev->m_GitCommit = commit;
2999 InterlockedExchange(&pRev->m_IsCommitParsed, FALSE);
3001 char* note = nullptr;
3002 g_Git.m_critGitDllSec.Lock();
3005 git_get_notes(commit.m_hash, &note);
3007 catch (char* msg)
3009 g_Git.m_critGitDllSec.Unlock();
3010 CString err(msg);
3011 MessageBox(L"Could not get commit notes.\nlibgit reports:\n" + err, L"TortoiseGit", MB_ICONERROR);
3012 break;
3014 g_Git.m_critGitDllSec.Unlock();
3016 if(note)
3018 pRev->m_Notes = CUnicodeUtils::GetUnicode(note);
3019 free(note);
3020 note = nullptr;
3023 if(!pRev->m_IsDiffFiles)
3025 pRev->m_CallDiffAsync = DiffAsync;
3028 pRev->ParserParentFromCommit(&commit);
3029 if (m_ShowFilter & FILTERSHOW_MERGEPOINTS) // See also ShouldShowFilter()
3031 for (size_t i = 0; i < pRev->m_ParentHash.size(); ++i)
3033 const CGitHash &parentHash = pRev->m_ParentHash[i];
3034 auto it = commitChildren.find(parentHash);
3035 if (it == commitChildren.end())
3036 it = commitChildren.insert(make_pair(parentHash, std::unordered_set<CGitHash>())).first;
3037 it->second.insert(pRev->m_CommitHash);
3041 #ifdef DEBUG
3042 pRev->DbgPrint();
3043 TRACE(L"\n");
3044 #endif
3046 bool visible = filter(pRev, this);
3047 if (visible && !ShouldShowFilter(pRev, commitChildren))
3048 visible = false;
3049 this->m_critSec.Lock();
3050 m_logEntries.append(hash, visible);
3051 if (visible)
3052 m_arShownList.SafeAdd(pRev);
3053 this->m_critSec.Unlock();
3055 if (!visible)
3056 continue;
3058 if (lastSelectedHashNItem == -1 && hash == m_lastSelectedHash)
3059 lastSelectedHashNItem = (int)m_arShownList.size() - 1;
3061 t2 = GetTickCount64();
3063 if (t2 - t1 > 500UL || (m_logEntries.size() - oldsize > 100))
3065 //update UI
3066 int percent = (int)m_logEntries.size() * 100 / total + GITLOG_START + 1;
3067 if(percent > 99)
3068 percent =99;
3069 if(percent < GITLOG_START)
3070 percent = GITLOG_START +1;
3072 oldsize = m_logEntries.size();
3073 PostMessage(LVM_SETITEMCOUNT, (WPARAM) this->m_logEntries.size(),(LPARAM) LVSICF_NOINVALIDATEALL|LVSICF_NOSCROLL);
3075 //if( percent > oldprecentage )
3077 ::PostMessage(this->GetParent()->m_hWnd,MSG_LOAD_PERCENTAGE,(WPARAM) percent,0);
3078 oldprecentage = percent;
3081 if (lastSelectedHashNItem >= 0)
3082 PostMessage(m_ScrollToMessage, lastSelectedHashNItem);
3084 t1 = t2;
3087 g_Git.m_critGitDllSec.Lock();
3088 git_close_log(m_DllGitLog);
3089 g_Git.m_critGitDllSec.Unlock();
3093 if (m_bExitThread)
3095 InterlockedExchange(&m_bThreadRunning, FALSE);
3096 return 0;
3099 //Update UI;
3100 PostMessage(LVM_SETITEMCOUNT, (WPARAM) this->m_logEntries.size(),(LPARAM) LVSICF_NOINVALIDATEALL|LVSICF_NOSCROLL);
3102 if (lastSelectedHashNItem >= 0)
3103 PostMessage(m_ScrollToMessage, lastSelectedHashNItem);
3105 if (this->m_hWnd)
3106 ::PostMessage(this->GetParent()->m_hWnd,MSG_LOAD_PERCENTAGE,(WPARAM) GITLOG_END,0);
3108 InterlockedExchange(&m_bThreadRunning, FALSE);
3110 return 0;
3113 void CGitLogListBase::FetchRemoteList()
3115 STRING_VECTOR remoteList;
3116 m_SingleRemote.Empty();
3117 if (!g_Git.GetRemoteList(remoteList) && remoteList.size() == 1)
3118 m_SingleRemote = remoteList[0];
3121 void CGitLogListBase::FetchTrackingBranchList()
3123 m_TrackingMap.clear();
3124 for (auto it = m_HashMap.cbegin(); it != m_HashMap.cend(); ++it)
3126 for (const auto& ref : it->second)
3128 CString branchName;
3129 if (CGit::GetShortName(ref, branchName, L"refs/heads/"))
3131 CString pullRemote, pullBranch;
3132 g_Git.GetRemoteTrackedBranch(branchName, pullRemote, pullBranch);
3133 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty())
3134 m_TrackingMap[branchName] = std::make_pair(pullRemote, pullBranch);
3140 void CGitLogListBase::Refresh(BOOL IsCleanFilter)
3142 SafeTerminateThread();
3144 this->SetItemCountEx(0);
3145 this->Clear();
3147 ResetWcRev();
3149 ShowGraphColumn((m_ShowMask & CGit::LOG_INFO_FOLLOW) ? false : true);
3151 if (m_pMailmap)
3153 git_free_mailmap(m_pMailmap);
3154 git_read_mailmap(&m_pMailmap);
3157 //Update branch and Tag info
3158 ReloadHashMap();
3159 if (m_pFindDialog)
3160 m_pFindDialog->RefreshList();
3161 //Assume Thread have exited
3162 //if(!m_bThreadRunning)
3164 m_logEntries.clear();
3166 if (IsCleanFilter)
3167 m_LogFilter = std::make_shared<CLogDlgFilter>();
3169 SafeTerminateAsyncDiffThread();
3170 m_AsynDiffListLock.Lock();
3171 m_AsynDiffList.clear();
3172 m_AsynDiffListLock.Unlock();
3173 StartAsyncDiffThread();
3175 StartLoadingThread();
3179 void CGitLogListBase::StartAsyncDiffThread()
3181 if (m_AsyncThreadExit)
3182 return;
3183 if (InterlockedExchange(&m_AsyncThreadRunning, TRUE) != FALSE)
3184 return;
3185 m_DiffingThread = AfxBeginThread(AsyncThread, this, THREAD_PRIORITY_BELOW_NORMAL);
3186 if (!m_DiffingThread)
3188 InterlockedExchange(&m_AsyncThreadRunning, FALSE);
3189 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
3193 void CGitLogListBase::StartLoadingThread()
3195 if (InterlockedExchange(&m_bThreadRunning, TRUE) != FALSE)
3196 return;
3197 InterlockedExchange(&m_bNoDispUpdates, TRUE);
3198 InterlockedExchange(&m_bExitThread, FALSE);
3199 m_LoadingThread = AfxBeginThread(LogThreadEntry, this, THREAD_PRIORITY_LOWEST);
3200 if (!m_LoadingThread)
3202 InterlockedExchange(&m_bThreadRunning, FALSE);
3203 InterlockedExchange(&m_bNoDispUpdates, FALSE);
3204 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
3208 static bool CStringStartsWith(const CString &str, const CString &prefix)
3210 return str.Left(prefix.GetLength()) == prefix;
3212 bool CGitLogListBase::ShouldShowFilter(GitRevLoglist* pRev, const std::unordered_map<CGitHash, std::unordered_set<CGitHash>>& commitChildren)
3214 if (m_ShowFilter & FILTERSHOW_ANYCOMMIT)
3215 return true;
3217 if ((m_ShowFilter & FILTERSHOW_REFS) && m_HashMap.find(pRev->m_CommitHash) != m_HashMap.cend())
3219 // Keep all refs.
3220 const STRING_VECTOR &refList = m_HashMap[pRev->m_CommitHash];
3221 for (size_t i = 0; i < refList.size(); ++i)
3223 const CString &str = refList[i];
3224 if (CStringStartsWith(str, L"refs/heads/"))
3226 if (m_ShowRefMask & LOGLIST_SHOWLOCALBRANCHES)
3227 return true;
3229 else if (CStringStartsWith(str, L"refs/remotes/"))
3231 if (m_ShowRefMask & LOGLIST_SHOWREMOTEBRANCHES)
3232 return true;
3234 else if (CStringStartsWith(str, L"refs/tags/"))
3236 if (m_ShowRefMask & LOGLIST_SHOWTAGS)
3237 return true;
3239 else if (CStringStartsWith(str, L"refs/stash"))
3241 if (m_ShowRefMask & LOGLIST_SHOWSTASH)
3242 return true;
3244 else if (CStringStartsWith(str, L"refs/bisect/"))
3246 if (m_ShowRefMask & LOGLIST_SHOWBISECT)
3247 return true;
3250 // Keep the head too.
3251 if (pRev->m_CommitHash == m_HeadHash)
3252 return true;
3255 if (m_ShowFilter & FILTERSHOW_MERGEPOINTS)
3257 if (pRev->ParentsCount() > 1)
3258 return true;
3259 auto childrenIt = commitChildren.find(pRev->m_CommitHash);
3260 if (childrenIt != commitChildren.end())
3262 const std::unordered_set<CGitHash> &children = childrenIt->second;
3263 if (children.size() > 1)
3264 return true;
3267 return false;
3270 void CGitLogListBase::ShowGraphColumn(bool bShow)
3272 // HACK to hide graph column
3273 if (bShow)
3274 SetColumnWidth(0, m_ColumnManager.GetWidth(0, false));
3275 else
3276 SetColumnWidth(0, 0);
3279 CString CGitLogListBase::GetTagInfo(GitRev* pLogEntry)
3281 if (m_HashMap.find(pLogEntry->m_CommitHash) == m_HashMap.end())
3282 return L"";
3284 CString tagInfo;
3285 const STRING_VECTOR& vector = m_HashMap[pLogEntry->m_CommitHash];
3286 for (auto it = vector.cbegin(); it != vector.cend(); ++it)
3288 if (!CStringUtils::StartsWith((*it), L"refs/tags/"))
3289 continue;
3290 if (!CStringUtils::EndsWith((*it), L"^{}"))
3291 continue;
3293 CString cmd;
3294 cmd.Format(L"git.exe cat-file tag %s", (LPCTSTR)(*it).Left((*it).GetLength() - (int)wcslen(L"^{}")));
3295 CString output;
3296 if (g_Git.Run(cmd, &output, nullptr, CP_UTF8) != 0)
3297 continue;
3299 // parse tag date
3302 // this assumes that in the header of the tag there is no ">" before the "tagger " header entry
3303 int pos1 = output.Find(L'>');
3304 if (pos1 < 0)
3305 break;
3306 ++pos1;
3307 if (output[pos1] == L' ')
3308 ++pos1;
3309 int pos2 = output.Find(L'\n', pos1);
3310 if (pos2 < 0)
3311 break;
3313 CString str = output.Mid(pos1, pos2 - pos1);
3314 wchar_t* pEnd = nullptr;
3315 errno = 0;
3316 auto number = wcstoumax(str.GetBuffer(), &pEnd, 10);
3317 if (str.GetBuffer() == pEnd)
3318 break;
3319 if (errno == ERANGE)
3320 break;
3322 output.Delete(pos1, pos2 - pos1);
3323 output.Insert(pos1, (LPCWSTR)CLoglistUtils::FormatDateAndTime(CTime(number), m_DateFormat, true, m_bRelativeTimes));
3324 } while (0);
3325 output.Trim().AppendChar(L'\n');
3326 tagInfo += output;
3328 return tagInfo;
3331 void CGitLogListBase::Clear()
3333 m_arShownList.SafeRemoveAll();
3334 DeleteAllItems();
3336 m_logEntries.ClearAll();
3339 void CGitLogListBase::OnDestroy()
3341 SafeTerminateThread();
3342 SafeTerminateAsyncDiffThread();
3344 int retry = 0;
3345 while(m_LogCache.SaveCache())
3347 if(retry > 5)
3348 break;
3349 Sleep(1000);
3351 ++retry;
3353 //if(CMessageBox::Show(nullptr, L"Cannot Save Log Cache to Disk. To retry click yes. To give up click no.", L"TortoiseGit",
3354 // MB_YESNO) == IDNO)
3355 // break;
3358 __super::OnDestroy();
3361 LRESULT CGitLogListBase::OnLoad(WPARAM wParam,LPARAM /*lParam*/)
3363 CRect rect;
3364 int i=(int)wParam;
3365 this->GetItemRect(i,&rect,LVIR_BOUNDS);
3366 this->InvalidateRect(rect);
3368 return 0;
3372 * Save column widths to the registry
3374 void CGitLogListBase::SaveColumnWidths()
3376 // HACK that graph column is always shown
3377 SetColumnWidth(0, m_ColumnManager.GetWidth(0, false));
3379 __super::SaveColumnWidths();
3382 int CGitLogListBase::GetHeadIndex()
3384 if(m_HeadHash.IsEmpty())
3385 return -1;
3387 for (size_t i = 0; i < m_arShownList.size(); ++i)
3389 GitRev* pRev = m_arShownList.SafeGetAt(i);
3390 if(pRev)
3392 if (pRev->m_CommitHash == m_HeadHash)
3393 return (int)i;
3396 return -1;
3398 void CGitLogListBase::OnFind()
3400 if (!m_pFindDialog)
3402 m_pFindDialog = new CFindDlg(this);
3403 m_pFindDialog->Create(this);
3407 LRESULT CGitLogListBase::OnScrollToMessage(WPARAM itemToSelect, LPARAM /*lParam*/)
3409 if (GetSelectedCount() != 0)
3410 return 0;
3412 CGitHash theSelectedHash = m_lastSelectedHash;
3413 SetItemState((int)itemToSelect, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
3414 m_lastSelectedHash = theSelectedHash;
3416 int countPerPage = GetCountPerPage();
3417 EnsureVisible(max(0, (int)itemToSelect-countPerPage/2), FALSE);
3418 EnsureVisible(min(GetItemCount(), (int)itemToSelect+countPerPage/2), FALSE);
3419 EnsureVisible((int)itemToSelect, FALSE);
3420 return 0;
3423 LRESULT CGitLogListBase::OnScrollToRef(WPARAM wParam, LPARAM /*lParam*/)
3425 CString* ref = reinterpret_cast<CString*>(wParam);
3426 if (!ref || ref->IsEmpty())
3427 return 1;
3429 bool bShift = (GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0;
3431 CGitHash hash;
3432 if (g_Git.GetHash(hash, *ref + L"^{}")) // add ^{} in order to get the correct SHA-1 (especially for signed tags)
3433 MessageBox(g_Git.GetGitLastErr(L"Could not get hash of ref \"" + *ref + L"^{}\"."), L"TortoiseGit", MB_ICONERROR);
3435 if (hash.IsEmpty())
3436 return 1;
3438 bool bFound = false;
3439 int cnt = (int)m_arShownList.size();
3440 int i;
3441 for (i = 0; i < cnt; ++i)
3443 GitRev* pLogEntry = m_arShownList.SafeGetAt(i);
3444 if (pLogEntry && pLogEntry->m_CommitHash == hash)
3446 bFound = true;
3447 break;
3450 if (!bFound)
3451 return 1;
3453 EnsureVisible(i, FALSE);
3454 if (!bShift)
3456 SetItemState(GetSelectionMark(), 0, LVIS_SELECTED);
3457 SetItemState(i, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
3458 SetSelectionMark(i);
3460 else
3462 GitRev* pLogEntry = m_arShownList.SafeGetAt(i);
3463 if (pLogEntry)
3464 m_highlight = pLogEntry->m_CommitHash;
3466 Invalidate();
3467 UpdateData(FALSE);
3469 return 0;
3472 LRESULT CGitLogListBase::OnFindDialogMessage(WPARAM /*wParam*/, LPARAM /*lParam*/)
3474 ASSERT(m_pFindDialog);
3475 bool bFound = false;
3476 int i=0;
3478 if (m_pFindDialog->IsTerminating())
3480 // invalidate the handle identifying the dialog box.
3481 m_pFindDialog = nullptr;
3482 return 0;
3485 int cnt = (int)m_arShownList.size();
3486 bool bShift = (GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0;
3488 if(m_pFindDialog->IsRef())
3490 CString str;
3491 str=m_pFindDialog->GetFindString();
3493 CGitHash hash;
3495 if(!str.IsEmpty())
3497 if (g_Git.GetHash(hash, str + L"^{}")) // add ^{} in order to get the correct SHA-1 (especially for signed tags)
3498 MessageBox(g_Git.GetGitLastErr(L"Could not get hash of ref \"" + str + L"^{}\"."), L"TortoiseGit", MB_ICONERROR);
3501 if(!hash.IsEmpty())
3503 for (i = 0; i < cnt; ++i)
3505 GitRev* pLogEntry = m_arShownList.SafeGetAt(i);
3506 if(pLogEntry && pLogEntry->m_CommitHash == hash)
3508 bFound = true;
3509 break;
3513 if (!bFound)
3515 m_pFindDialog->FlashWindowEx(FLASHW_ALL, 2, 100);
3516 return 0;
3520 if (m_pFindDialog->FindNext() && !bFound)
3522 //read data from dialog
3523 CLogDlgFilter filter { m_pFindDialog->GetFindString(), m_pFindDialog->Regex(), LOGFILTER_ALL, m_pFindDialog->MatchCase() == TRUE };
3525 for (i = m_nSearchIndex + 1; ; ++i)
3527 if (i >= cnt)
3529 i = 0;
3530 m_pFindDialog->FlashWindowEx(FLASHW_ALL, 2, 100);
3532 if (m_nSearchIndex >= 0)
3534 if (i == m_nSearchIndex)
3536 ::MessageBeep(0xFFFFFFFF);
3537 m_pFindDialog->FlashWindowEx(FLASHW_ALL, 3, 100);
3538 break;
3542 if (filter(m_arShownList.SafeGetAt(i), this))
3544 bFound = true;
3545 break;
3548 } // if(m_pFindDialog->FindNext())
3550 if (bFound)
3552 m_nSearchIndex = i;
3553 EnsureVisible(i, FALSE);
3554 if (!bShift)
3556 SetItemState(GetSelectionMark(), 0, LVIS_SELECTED);
3557 SetItemState(i, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
3558 SetSelectionMark(i);
3560 else
3562 GitRev* pLogEntry = m_arShownList.SafeGetAt(i);
3563 if (pLogEntry)
3564 m_highlight = pLogEntry->m_CommitHash;
3566 Invalidate();
3567 //FillLogMessageCtrl();
3568 UpdateData(FALSE);
3571 return 0;
3574 INT_PTR CGitLogListBase::OnToolHitTest(CPoint point, TOOLINFO * pTI) const
3576 LVHITTESTINFO lvhitTestInfo;
3578 lvhitTestInfo.pt = point;
3580 int nItem = ListView_SubItemHitTest(m_hWnd, &lvhitTestInfo);
3581 int nSubItem = lvhitTestInfo.iSubItem;
3583 UINT nFlags = lvhitTestInfo.flags;
3585 // nFlags is 0 if the SubItemHitTest fails
3586 // Therefore, 0 & <anything> will equal false
3587 if (nFlags & LVHT_ONITEM)
3589 // Get the client area occupied by this control
3590 RECT rcClient;
3591 GetClientRect(&rcClient);
3593 // Fill in the TOOLINFO structure
3594 pTI->hwnd = m_hWnd;
3595 pTI->uId = (UINT)((nItem<<10)+(nSubItem&0x3ff)+1);
3596 pTI->lpszText = LPSTR_TEXTCALLBACK;
3597 pTI->rect = rcClient;
3599 return pTI->uId; // By returning a unique value per listItem,
3600 // we ensure that when the mouse moves over another list item,
3601 // the tooltip will change
3603 else
3605 // Otherwise, we aren't interested, so let the message propagate
3606 return -1;
3610 BOOL CGitLogListBase::OnToolTipText(UINT /*id*/, NMHDR* pNMHDR, LRESULT* pResult)
3612 TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;
3613 TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;
3615 *pResult = 0;
3617 // Ignore messages from the built in tooltip, we are processing them internally
3618 if ((pNMHDR->idFrom == (UINT_PTR)m_hWnd) &&
3619 (((pNMHDR->code == TTN_NEEDTEXTA) && (pTTTA->uFlags & TTF_IDISHWND)) ||
3620 ((pNMHDR->code == TTN_NEEDTEXTW) && (pTTTW->uFlags & TTF_IDISHWND))))
3621 return FALSE;
3623 // Get the mouse position
3624 const MSG* pMessage = GetCurrentMessage();
3626 CPoint pt;
3627 pt = pMessage->pt;
3628 ScreenToClient(&pt);
3630 // Check if the point falls onto a list item
3631 LVHITTESTINFO lvhitTestInfo;
3632 lvhitTestInfo.pt = pt;
3634 int nItem = SubItemHitTest(&lvhitTestInfo);
3636 if (lvhitTestInfo.flags & LVHT_ONITEM)
3638 // Get branch description first
3639 CString strTipText;
3640 if (lvhitTestInfo.iSubItem == LOGLIST_MESSAGE)
3642 CString branch;
3643 CGit::REF_TYPE type = CGit::REF_TYPE::LOCAL_BRANCH;
3644 if (IsMouseOnRefLabel(m_arShownList.SafeGetAt(nItem), lvhitTestInfo.pt, type, &branch))
3646 MAP_STRING_STRING descriptions;
3647 g_Git.GetBranchDescriptions(descriptions);
3648 if (descriptions.find(branch) != descriptions.cend())
3650 strTipText.LoadString(IDS_DESCRIPTION);
3651 strTipText += L":\n";
3652 strTipText += descriptions[branch];
3657 bool followMousePos = false;
3658 if (!strTipText.IsEmpty())
3659 followMousePos = true;
3660 else
3661 strTipText = GetToolTipText(nItem, lvhitTestInfo.iSubItem);
3662 if (strTipText.IsEmpty())
3663 return FALSE;
3665 // we want multiline tooltips
3666 ::SendMessage(pNMHDR->hwndFrom, TTM_SETMAXTIPWIDTH, 0, SHRT_MAX);
3668 wcscpy_s(m_wszTip, strTipText);
3669 // handle Unicode as well as non-Unicode requests
3670 if (pNMHDR->code == TTN_NEEDTEXTA)
3672 pTTTA->hinst = nullptr;
3673 pTTTA->lpszText = m_szTip;
3674 ::WideCharToMultiByte(CP_ACP, 0, m_wszTip, -1, m_szTip, 8192, nullptr, nullptr);
3676 else
3678 pTTTW->hinst = nullptr;
3679 pTTTW->lpszText = m_wszTip;
3682 CRect rect;
3683 GetSubItemRect(nItem, lvhitTestInfo.iSubItem, LVIR_LABEL, rect);
3684 if (followMousePos)
3685 rect.MoveToXY(pt.x, pt.y + 18); // 18: to act like a normal tooltip
3686 ClientToScreen(rect);
3687 ::SetWindowPos(pNMHDR->hwndFrom, HWND_TOP, rect.left, rect.top, 0, 0, SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOOWNERZORDER);
3689 return TRUE; // We found a tool tip,
3690 // tell the framework this message has been handled
3693 return FALSE; // We didn't handle the message,
3694 // let the framework continue propagating the message
3697 CString CGitLogListBase::GetToolTipText(int nItem, int nSubItem)
3699 if (nSubItem == LOGLIST_MESSAGE && !m_bTagsBranchesOnRightSide)
3701 GitRevLoglist* pLogEntry = m_arShownList.SafeGetAt(nItem);
3702 if (pLogEntry == nullptr)
3703 return CString();
3704 if (m_HashMap.find(pLogEntry->m_CommitHash) == m_HashMap.cend() && (m_superProjectHash.IsEmpty() || pLogEntry->m_CommitHash != m_superProjectHash))
3705 return CString();
3706 return pLogEntry->GetSubject();
3708 else if (nSubItem == LOGLIST_DATE && m_bRelativeTimes)
3710 GitRevLoglist* pLogEntry = m_arShownList.SafeGetAt(nItem);
3711 if (pLogEntry == nullptr)
3712 return CString();
3713 return CLoglistUtils::FormatDateAndTime(pLogEntry->GetAuthorDate(), m_DateFormat, true, false);
3715 else if (nSubItem == LOGLIST_COMMIT_DATE && m_bRelativeTimes)
3717 GitRevLoglist* pLogEntry = m_arShownList.SafeGetAt(nItem);
3718 if (pLogEntry == nullptr)
3719 return CString();
3720 return CLoglistUtils::FormatDateAndTime(pLogEntry->GetCommitterDate(), m_DateFormat, true, false);
3722 else if (nSubItem == LOGLIST_ACTION)
3724 GitRevLoglist* pLogEntry = m_arShownList.SafeGetAt(nItem);
3725 if (pLogEntry == nullptr)
3726 return CString();
3728 if (!pLogEntry->m_IsDiffFiles)
3729 return CString(MAKEINTRESOURCE(IDS_PROC_LOG_FETCHINGFILES));
3731 int actions = pLogEntry->GetAction(this);
3732 CString sToolTipText;
3734 CString actionText;
3735 if (actions & CTGitPath::LOGACTIONS_MODIFIED)
3736 actionText += CTGitPath::GetActionName(CTGitPath::LOGACTIONS_MODIFIED);
3738 if (actions & CTGitPath::LOGACTIONS_ADDED)
3740 if (!actionText.IsEmpty())
3741 actionText += L"\r\n";
3742 actionText += CTGitPath::GetActionName(CTGitPath::LOGACTIONS_ADDED);
3745 if (actions & CTGitPath::LOGACTIONS_DELETED)
3747 if (!actionText.IsEmpty())
3748 actionText += L"\r\n";
3749 actionText += CTGitPath::GetActionName(CTGitPath::LOGACTIONS_DELETED);
3752 if (actions & CTGitPath::LOGACTIONS_REPLACED)
3754 if (!actionText.IsEmpty())
3755 actionText += L"\r\n";
3756 actionText += CTGitPath::GetActionName(CTGitPath::LOGACTIONS_REPLACED);
3759 if (actions & CTGitPath::LOGACTIONS_UNMERGED)
3761 if (!actionText.IsEmpty())
3762 actionText += L"\r\n";
3763 actionText += CTGitPath::GetActionName(CTGitPath::LOGACTIONS_UNMERGED);
3766 if (!actionText.IsEmpty())
3768 CString sTitle(MAKEINTRESOURCE(IDS_LOG_ACTIONS));
3769 sToolTipText = sTitle + L":\r\n" + actionText;
3771 return sToolTipText;
3773 return CString();
3776 bool CGitLogListBase::IsMouseOnRefLabelFromPopupMenu(const GitRevLoglist* pLogEntry, const CPoint& point, CGit::REF_TYPE& type, CString* pShortname /*nullptr*/, size_t* pIndex /*nullptr*/)
3778 POINT pt = point;
3779 ScreenToClient(&pt);
3780 return IsMouseOnRefLabel(pLogEntry, pt, type, pShortname, pIndex);
3783 bool CGitLogListBase::IsMouseOnRefLabel(const GitRevLoglist* pLogEntry, const POINT& pt, CGit::REF_TYPE& type, CString* pShortname /*nullptr*/, size_t* pIndex /*nullptr*/)
3785 if (!pLogEntry)
3786 return false;
3788 auto refList = m_HashMap.find(pLogEntry->m_CommitHash);
3789 if (refList == m_HashMap.cend())
3790 return false;
3792 for (size_t i = 0; i < m_HashMap[pLogEntry->m_CommitHash].size(); ++i)
3794 const auto labelpos = m_RefLabelPosMap.find(m_HashMap[pLogEntry->m_CommitHash][i]);
3795 if (labelpos == m_RefLabelPosMap.cend() || !labelpos->second.PtInRect(pt))
3796 continue;
3798 CGit::REF_TYPE foundType;
3799 if (pShortname)
3800 *pShortname = CGit::GetShortName(m_HashMap[pLogEntry->m_CommitHash][i], &foundType);
3801 else
3802 CGit::GetShortName(m_HashMap[pLogEntry->m_CommitHash][i], &foundType);
3803 if (foundType != type && type != CGit::REF_TYPE::UNKNOWN)
3804 return false;
3806 type = foundType;
3807 if (pIndex)
3808 *pIndex = i;
3809 return true;
3811 return false;
3814 void CGitLogListBase::OnBeginDrag(NMHDR* /*pnmhdr*/, LRESULT* pResult)
3816 *pResult = 0;
3818 if (!m_bDragndropEnabled || GetSelectedCount() == 0 || !IsSelectionContinuous())
3819 return;
3821 m_bDragging = TRUE;
3822 m_nDropIndex = -1;
3823 m_nDropMarkerLast = -1;
3824 m_nDropMarkerLastHot = GetHotItem();
3825 SetCapture();
3828 void CGitLogListBase::OnMouseMove(UINT nFlags, CPoint point)
3830 __super::OnMouseMove(nFlags, point);
3832 if (!m_bDragging)
3833 return;
3835 CPoint dropPoint = point;
3836 ClientToScreen(&dropPoint);
3838 if (WindowFromPoint(dropPoint) != this)
3840 SetCursor(LoadCursor(nullptr, IDC_NO));
3841 m_nDropIndex = -1;
3842 DrawDropInsertMarker(m_nDropIndex);
3843 return;
3846 SetCursor(LoadCursor(nullptr, IDC_ARROW));
3847 ScreenToClient(&dropPoint);
3849 dropPoint.y += 10;
3850 m_nDropIndex = HitTest(dropPoint);
3852 if (m_nDropIndex == -1) // might be last item, allow to move past last item
3854 dropPoint.y -= 10;
3855 m_nDropIndex = HitTest(dropPoint);
3856 if (m_nDropIndex != -1)
3857 m_nDropIndex = GetItemCount();
3860 POSITION pos = GetFirstSelectedItemPosition();
3861 int first = GetNextSelectedItem(pos);
3862 int last = first;
3863 while (pos)
3864 last = GetNextSelectedItem(pos);
3865 if (m_nDropIndex == -1 || (m_nDropIndex >= first && m_nDropIndex - 1 <= last))
3867 SetCursor(LoadCursor(nullptr, IDC_NO));
3868 m_nDropIndex = -1;
3871 // handle auto scrolling
3872 int hotItem = GetHotItem();
3873 int topindex = GetTopIndex();
3874 if (hotItem == topindex && hotItem != 0)
3875 EnsureVisible(hotItem - 1, FALSE);
3876 else if (hotItem >= topindex + GetCountPerPage() - 1 && hotItem + 1 < GetItemCount())
3877 EnsureVisible(hotItem + 1, FALSE);
3879 DrawDropInsertMarker(m_nDropIndex);
3882 void CGitLogListBase::OnLButtonUp(UINT nFlags, CPoint point)
3884 if (m_bDragging)
3886 ::ReleaseCapture();
3887 SetCursor(LoadCursor(nullptr, IDC_HAND));
3888 m_bDragging = FALSE;
3890 CRect rect;
3891 GetItemRect(m_nDropMarkerLast, &rect, 0);
3892 rect.bottom = rect.top + 2;
3893 rect.top -= 2;
3894 InvalidateRect(&rect, 0);
3896 CPoint pt(point);
3897 ClientToScreen(&pt);
3898 if (WindowFromPoint(pt) == this && m_nDropIndex != -1)
3899 GetParent()->PostMessage(MSG_COMMITS_REORDERED, m_nDropIndex, 0);
3902 __super::OnLButtonUp(nFlags, point);
3905 void CGitLogListBase::DrawDropInsertMarker(int nIndex)
3907 if (m_nDropMarkerLast != nIndex)
3909 CRect rect;
3910 if (GetItemRect(m_nDropMarkerLast, &rect, 0))
3912 rect.bottom = rect.top + 2;
3913 rect.top -= 2;
3914 InvalidateRect(&rect, 0);
3916 else if (m_nDropMarkerLast == GetItemCount())
3917 DrawDropInsertMarkerLine(m_nDropMarkerLast); // double painting = removal
3918 m_nDropMarkerLast = nIndex;
3920 if (nIndex < 0)
3921 return;
3923 DrawDropInsertMarkerLine(m_nDropMarkerLast);
3925 else if (m_nDropMarkerLastHot != GetHotItem())
3927 m_nDropMarkerLastHot = GetHotItem();
3928 m_nDropMarkerLast = -1;
3932 void CGitLogListBase::DrawDropInsertMarkerLine(int nIndex)
3934 CBrush* pBrush = CDC::GetHalftoneBrush();
3935 CDC* pDC = GetDC();
3937 CRect rect;
3938 if (nIndex < GetItemCount())
3940 GetItemRect(nIndex, &rect, 0);
3941 rect.bottom = rect.top + 2;
3942 rect.top -= 2;
3944 else
3946 GetItemRect(nIndex - 1, &rect, 0);
3947 rect.top = rect.bottom - 2;
3948 rect.bottom += 2;
3951 CBrush* pBrushOld = pDC->SelectObject(pBrush);
3952 pDC->PatBlt(rect.left, rect.top, rect.Width(), rect.Height(), PATINVERT);
3953 pDC->SelectObject(pBrushOld);
3955 ReleaseDC(pDC);
3958 ULONG CGitLogListBase::GetGestureStatus(CPoint /*ptTouch*/)
3960 return 0;
3963 void CGitLogListBase::DrawListItemWithMatchesRect(NMLVCUSTOMDRAW* pLVCD, const std::vector<CHARRANGE>& ranges, CRect rect, const CString& text, HTHEME hTheme /*= nullptr*/, int txtState /*= 0*/)
3965 int drawPos = 0;
3966 COLORREF textColor = pLVCD->clrText;
3967 RECT rc = rect;
3968 if (!hTheme)
3970 ::SetTextColor(pLVCD->nmcd.hdc, textColor);
3971 SetBkMode(pLVCD->nmcd.hdc, TRANSPARENT);
3973 DTTOPTS opts = { 0 };
3974 opts.dwSize = sizeof(opts);
3975 opts.crText = textColor;
3976 opts.dwFlags = DTT_TEXTCOLOR;
3978 for (auto it = ranges.cbegin(); it != ranges.cend(); ++it)
3980 rc = rect;
3981 if (it->cpMin - drawPos)
3983 if (!hTheme)
3985 DrawText(pLVCD->nmcd.hdc, text.Mid(drawPos), it->cpMin - drawPos, &rc, DT_SINGLELINE | DT_VCENTER | DT_NOPREFIX | DT_END_ELLIPSIS);
3986 DrawText(pLVCD->nmcd.hdc, text.Mid(drawPos), it->cpMin - drawPos, &rc, DT_CALCRECT | DT_SINGLELINE | DT_VCENTER | DT_NOPREFIX | DT_END_ELLIPSIS);
3988 else
3990 DrawThemeTextEx(hTheme, pLVCD->nmcd.hdc, LVP_LISTITEM, txtState, text.Mid(drawPos), it->cpMin - drawPos, DT_SINGLELINE | DT_VCENTER | DT_NOPREFIX | DT_END_ELLIPSIS, &rc, &opts);
3991 GetThemeTextExtent(hTheme, pLVCD->nmcd.hdc, LVP_LISTITEM, txtState, text.Mid(drawPos), it->cpMin - drawPos, DT_CALCRECT | DT_SINGLELINE | DT_VCENTER | DT_NOPREFIX | DT_END_ELLIPSIS, &rect, &rc);
3993 rect.left = rc.right;
3995 rc = rect;
3996 drawPos = it->cpMin;
3997 if (it->cpMax - drawPos)
3999 if (!hTheme)
4001 ::SetTextColor(pLVCD->nmcd.hdc, m_Colors.GetColor(CColors::FilterMatch));
4002 DrawText(pLVCD->nmcd.hdc, text.Mid(drawPos), it->cpMax - drawPos, &rc, DT_SINGLELINE | DT_VCENTER | DT_NOPREFIX | DT_END_ELLIPSIS);
4003 DrawText(pLVCD->nmcd.hdc, text.Mid(drawPos), it->cpMax - drawPos, &rc, DT_CALCRECT | DT_SINGLELINE | DT_VCENTER | DT_NOPREFIX | DT_END_ELLIPSIS);
4004 ::SetTextColor(pLVCD->nmcd.hdc, textColor);
4006 else
4008 opts.crText = m_Colors.GetColor(CColors::FilterMatch);
4009 DrawThemeTextEx(hTheme, pLVCD->nmcd.hdc, LVP_LISTITEM, txtState, text.Mid(drawPos), it->cpMax - drawPos, DT_SINGLELINE | DT_VCENTER | DT_NOPREFIX | DT_END_ELLIPSIS, &rc, &opts);
4010 GetThemeTextExtent(hTheme, pLVCD->nmcd.hdc, LVP_LISTITEM, txtState, text.Mid(drawPos), it->cpMax - drawPos, DT_CALCRECT | DT_SINGLELINE | DT_VCENTER | DT_NOPREFIX | DT_END_ELLIPSIS, &rect, &rc);
4011 opts.crText = textColor;
4013 rect.left = rc.right;
4015 rc = rect;
4016 drawPos = it->cpMax;
4018 if (!hTheme)
4019 DrawText(pLVCD->nmcd.hdc, text.Mid(drawPos), -1, &rc, DT_SINGLELINE | DT_VCENTER | DT_NOPREFIX | DT_END_ELLIPSIS);
4020 else
4021 DrawThemeTextEx(hTheme, pLVCD->nmcd.hdc, LVP_LISTITEM, txtState, text.Mid(drawPos), -1, DT_SINGLELINE | DT_VCENTER | DT_NOPREFIX | DT_END_ELLIPSIS, &rc, &opts);
4024 bool CGitLogListBase::DrawListItemWithMatchesIfEnabled(std::shared_ptr<CLogDlgFilter> filter, DWORD selectedFilter, NMLVCUSTOMDRAW* pLVCD, LRESULT* pResult)
4026 if ((filter->GetSelectedFilters() & selectedFilter) && filter->IsFilterActive())
4028 *pResult = DrawListItemWithMatches(filter.get(), *this, pLVCD);
4029 return true;
4031 return false;
4034 LRESULT CGitLogListBase::DrawListItemWithMatches(CLogDlgFilter* filter, CListCtrl& listCtrl, NMLVCUSTOMDRAW* pLVCD)
4036 CString text;
4037 text = (LPCTSTR)listCtrl.GetItemText((int)pLVCD->nmcd.dwItemSpec, pLVCD->iSubItem);
4038 if (text.IsEmpty())
4039 return CDRF_DODEFAULT;
4041 std::vector<CHARRANGE> ranges;
4042 filter->GetMatchRanges(ranges, text, 0);
4043 if (ranges.empty())
4044 return CDRF_DODEFAULT;
4046 // even though we initialize the 'rect' here with nmcd.rc,
4047 // we must not use it but use the rects from GetItemRect()
4048 // and GetSubItemRect(). Because on XP, the nmcd.rc has
4049 // bogus data in it.
4050 CRect rect = pLVCD->nmcd.rc;
4052 // find the margin where the text label starts
4053 CRect labelRC, boundsRC, iconRC;
4054 listCtrl.GetItemRect((int)pLVCD->nmcd.dwItemSpec, &labelRC, LVIR_LABEL);
4055 listCtrl.GetItemRect((int)pLVCD->nmcd.dwItemSpec, &iconRC, LVIR_ICON);
4056 listCtrl.GetItemRect((int)pLVCD->nmcd.dwItemSpec, &boundsRC, LVIR_BOUNDS);
4058 int leftmargin = labelRC.left - boundsRC.left;
4059 if (pLVCD->iSubItem)
4060 leftmargin -= iconRC.Width();
4062 if (pLVCD->iSubItem != 0)
4063 listCtrl.GetSubItemRect((int)pLVCD->nmcd.dwItemSpec, pLVCD->iSubItem, LVIR_BOUNDS, rect);
4065 int borderWidth = 0;
4066 HTHEME hTheme = nullptr;
4067 if (IsAppThemed())
4069 hTheme = OpenThemeData(m_hWnd, L"Explorer::ListView;ListView");
4070 GetThemeMetric(hTheme, pLVCD->nmcd.hdc, LVP_LISTITEM, LISS_NORMAL, TMT_BORDERSIZE, &borderWidth);
4072 else
4073 borderWidth = GetSystemMetrics(SM_CXBORDER);
4075 if (listCtrl.GetExtendedStyle() & LVS_EX_CHECKBOXES)
4077 // I'm not very happy about this fixed margin here
4078 // but I haven't found a way to ask the system what
4079 // the margin really is.
4080 // At least it works on XP/Vista/win7/win8, and even with
4081 // increased font sizes
4082 leftmargin = 4;
4085 LVITEM item = { 0 };
4086 item.iItem = (int)pLVCD->nmcd.dwItemSpec;
4087 item.iSubItem = 0;
4088 item.mask = LVIF_IMAGE | LVIF_STATE;
4089 item.stateMask = (UINT)-1;
4090 listCtrl.GetItem(&item);
4092 // fill background
4093 int txtState = LISS_NORMAL;
4094 if (!hTheme)
4096 HBRUSH brush = nullptr;
4097 if (item.state & LVIS_SELECTED)
4099 if (::GetFocus() == listCtrl.GetSafeHwnd())
4101 brush = ::CreateSolidBrush(::GetSysColor(COLOR_HIGHLIGHT));
4102 pLVCD->clrText = ::GetSysColor(COLOR_HIGHLIGHTTEXT);
4104 else
4106 brush = ::CreateSolidBrush(::GetSysColor(COLOR_BTNFACE));
4107 pLVCD->clrText = ::GetSysColor(COLOR_WINDOWTEXT);
4110 else
4111 brush = ::CreateSolidBrush(::GetSysColor(COLOR_WINDOW));
4112 CRect my;
4113 listCtrl.GetSubItemRect((int)pLVCD->nmcd.dwItemSpec, pLVCD->iSubItem, LVIR_LABEL, my);
4114 ::FillRect(pLVCD->nmcd.hdc, my, brush);
4115 ::DeleteObject(brush);
4117 else
4119 if (listCtrl.GetHotItem() == (int)pLVCD->nmcd.dwItemSpec)
4121 if (item.state & LVIS_SELECTED)
4122 txtState = LISS_HOTSELECTED;
4123 else
4124 txtState = LISS_HOT;
4126 else if (item.state & LVIS_SELECTED)
4128 if (::GetFocus() == listCtrl.GetSafeHwnd())
4129 txtState = LISS_SELECTED;
4130 else
4131 txtState = LISS_SELECTEDNOTFOCUS;
4134 if (IsThemeBackgroundPartiallyTransparent(hTheme, LVP_LISTDETAIL, txtState))
4135 DrawThemeParentBackground(m_hWnd, pLVCD->nmcd.hdc, &rect);
4136 else
4138 HBRUSH brush = ::CreateSolidBrush(::GetSysColor(COLOR_WINDOW));
4139 ::FillRect(pLVCD->nmcd.hdc, rect, brush);
4140 ::DeleteObject(brush);
4142 if (txtState != LISS_NORMAL)
4144 CRect my;
4145 listCtrl.GetSubItemRect((int)pLVCD->nmcd.dwItemSpec, pLVCD->iSubItem, LVIR_LABEL, my);
4146 if (pLVCD->iSubItem == 0)
4148 // also fill the icon part of the line
4149 my.top = 0;
4150 my.left = 0;
4153 // calculate background for rect of whole line, but limit redrawing to SubItem rect
4154 DrawThemeBackground(hTheme, pLVCD->nmcd.hdc, LVP_LISTITEM, txtState, boundsRC, my);
4158 // draw the icon for the first column
4159 if (pLVCD->iSubItem == 0)
4161 rect = boundsRC;
4162 rect.right = rect.left + listCtrl.GetColumnWidth(0);
4163 rect.left = iconRC.left;
4165 if (item.iImage >= 0)
4167 POINT pt;
4168 pt.x = rect.left;
4169 pt.y = rect.top;
4170 CDC dc;
4171 dc.Attach(pLVCD->nmcd.hdc);
4172 int style = ILD_TRANSPARENT;
4173 if (!hTheme)
4175 auto whitebrush = ::CreateSolidBrush(::GetSysColor(COLOR_WINDOW));
4176 ::FillRect(dc, iconRC, whitebrush);
4177 ::DeleteObject(whitebrush);
4178 if (item.state & LVIS_SELECTED)
4180 if (::GetFocus() == listCtrl.GetSafeHwnd())
4181 style = ILD_SELECTED;
4182 else
4183 style = ILD_FOCUS;
4186 listCtrl.GetImageList(LVSIL_SMALL)->Draw(&dc, item.iImage, pt, style);
4187 dc.Detach();
4188 leftmargin -= iconRC.left;
4190 else
4192 RECT irc = boundsRC;
4193 irc.left += borderWidth;
4194 irc.right = iconRC.left;
4196 int state = 0;
4197 if (item.state & LVIS_SELECTED)
4199 if (listCtrl.GetHotItem() == item.iItem)
4200 state = CBS_CHECKEDHOT;
4201 else
4202 state = CBS_CHECKEDNORMAL;
4204 else
4206 if (listCtrl.GetHotItem() == item.iItem)
4207 state = CBS_UNCHECKEDHOT;
4209 if ((state) && (listCtrl.GetExtendedStyle() & LVS_EX_CHECKBOXES))
4211 HTHEME hTheme2 = OpenThemeData(m_hWnd, L"BUTTON");
4212 DrawThemeBackground(hTheme2, pLVCD->nmcd.hdc, BP_CHECKBOX, state, &irc, NULL);
4213 CloseThemeData(hTheme2);
4217 InflateRect(&rect, -(2 * borderWidth), 0);
4219 rect.left += leftmargin;
4220 RECT rc = rect;
4222 // is the column left- or right-aligned? (we don't handle centered (yet))
4223 LVCOLUMN Column;
4224 Column.mask = LVCF_FMT;
4225 listCtrl.GetColumn(pLVCD->iSubItem, &Column);
4226 if (Column.fmt & LVCFMT_RIGHT)
4228 DrawText(pLVCD->nmcd.hdc, text, -1, &rc, DT_CALCRECT | DT_SINGLELINE | DT_VCENTER | DT_NOPREFIX | DT_END_ELLIPSIS);
4229 rect.left = rect.right - (rc.right - rc.left);
4230 if (!hTheme)
4232 rect.left += 2 * borderWidth;
4233 rect.right += 2 * borderWidth;
4237 DrawListItemWithMatchesRect(pLVCD, ranges, rect, text, hTheme, txtState);
4238 if (hTheme)
4239 CloseThemeData(hTheme);
4241 return CDRF_SKIPDEFAULT;