Fixed issue #3834: Log window hangs on pasting wrong SHA-1 into commit log list when...
[TortoiseGit.git] / src / TortoiseProc / LogDlg.cpp
blob283b282ee65a268bba003cf1478d3091f8af947b
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2009, 2015 - TortoiseSVN
4 // Copyright (C) 2008-2021 - TortoiseGit
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 #include "stdafx.h"
21 #include "TortoiseProc.h"
22 #include "StatGraphDlg.h"
23 #include "LogDlg.h"
24 #include "MessageBox.h"
25 #include "AppUtils.h"
26 #include "StringUtils.h"
27 #include "UnicodeUtils.h"
28 #include "TempFile.h"
29 #include "IconMenu.h"
30 #include "BrowseRefsDlg.h"
31 #include "SmartHandle.h"
32 #include "LogOrdering.h"
33 #include "ClipboardHelper.h"
34 #include "DPIAware.h"
35 #include "LogDlgFileFilter.h"
37 #define MIN_CTRL_HEIGHT (CDPIAware::Instance().ScaleY(20))
38 #define MIN_SPLITTER_HEIGHT (CDPIAware::Instance().ScaleY(10))
40 #define WM_TGIT_REFRESH_SELECTION (WM_APP + 1)
42 IMPLEMENT_DYNAMIC(CLogDlg, CResizableStandAloneDialog)
43 CLogDlg::CLogDlg(CWnd* pParent /*=nullptr*/)
44 : CResizableStandAloneDialog(CLogDlg::IDD, pParent)
45 , m_wParam(0)
46 , m_nSortColumn(0)
47 , m_bFollowRenames(false)
48 , m_bSelect(false)
49 , m_bSelectionMustBeSingle(true)
50 , m_bShowTags(true)
51 , m_bShowLocalBranches(true)
52 , m_bShowRemoteBranches(true)
53 , m_bShowOtherRefs(true)
54 , m_bNoMerges(false)
55 , m_iHidePaths(0)
56 , m_bFirstParent(false)
57 , m_bWholeProject(FALSE)
58 , m_iCompressedGraph(0)
59 , m_bShowWC(true)
61 , m_bSelectionMustBeContinuous(false)
63 , m_pNotifyWindow(nullptr)
65 , m_bAscending(FALSE)
66 , m_hAccel(nullptr)
67 , m_bNavigatingWithSelect(false)
69 m_bFilterWithRegex = !!CRegDWORD(L"Software\\TortoiseGit\\UseRegexFilter", FALSE);
70 m_bFilterCaseSensitively = !!CRegDWORD(L"Software\\TortoiseGit\\FilterCaseSensitively", FALSE);
71 m_bAsteriskLogPrefix = !!CRegDWORD(L"Software\\TortoiseGit\\AsteriskLogPrefix", TRUE);
72 m_SelectedFilters = CRegDWORD(L"Software\\TortoiseGit\\SelectedLogFilters", LOGFILTER_ALL);
74 CString str = g_Git.m_CurrentDir;
75 str.Replace(L':', L'_');
77 m_regbAllBranch = CRegDWORD(L"Software\\TortoiseGit\\LogDialog\\AllBranch\\" + str, FALSE);
78 m_AllBranchType = static_cast<AllBranchType>(static_cast<DWORD>(m_regbAllBranch));
79 switch (m_AllBranchType)
81 case AllBranchType::None:
82 m_bAllBranch = FALSE;
83 break;
84 case AllBranchType::AllBranches:
85 m_bAllBranch = TRUE;
86 break;
87 default:
88 m_bAllBranch = BST_INDETERMINATE;
91 m_regShowWholeProject = CRegDWORD(L"Software\\TortoiseGit\\TortoiseProc\\ShowWholeProject\\" + str, FALSE);
92 m_bWholeProject = m_regShowWholeProject;
93 m_regbShowTags = CRegDWORD(L"Software\\TortoiseGit\\LogDialog\\ShowTags\\" + str, TRUE);
94 m_bShowTags = !!m_regbShowTags;
95 m_regbShowLocalBranches = CRegDWORD(L"Software\\TortoiseGit\\LogDialog\\ShowLocalBranches\\" + str, TRUE);
96 m_bShowLocalBranches = !!m_regbShowLocalBranches;
97 m_regbShowRemoteBranches = CRegDWORD(L"Software\\TortoiseGit\\LogDialog\\ShowRemoteBranches\\" + str, TRUE);
98 m_bShowRemoteBranches = !!m_regbShowRemoteBranches;
99 m_regbShowOtherRefs = CRegDWORD(L"Software\\TortoiseGit\\LogDialog\\ShowOtherRefs\\" + str, TRUE);
100 m_bShowOtherRefs = !!m_regbShowOtherRefs;
101 m_regbFullHistory = CRegDWORD(L"Software\\TortoiseGit\\LogDialog\\FullHistory", FALSE);
102 m_bFullHistory = !!m_regbFullHistory;
104 m_regAddBeforeCommit = CRegDWORD(L"Software\\TortoiseGit\\AddBeforeCommit", TRUE);
105 m_bShowUnversioned = !!m_regAddBeforeCommit;
107 m_bShowGravatar = !!CRegDWORD(L"Software\\TortoiseGit\\EnableGravatar", FALSE);
108 m_regbShowGravatar = CRegDWORD(L"Software\\TortoiseGit\\LogDialog\\ShowGravatar\\" + str, m_bShowGravatar);
109 m_bShowGravatar = !!m_regbShowGravatar;
110 m_bShowDescribe = !!CRegDWORD(L"Software\\TortoiseGit\\ShowDescribe");
111 m_bShowBranchRevNo = !!CRegDWORD(L"Software\\TortoiseGit\\ShowBranchRevisionNumber", FALSE);
114 CLogDlg::~CLogDlg()
116 m_regbAllBranch = static_cast<DWORD>(m_AllBranchType);
117 m_regbShowTags = m_bShowTags;
118 m_regbShowLocalBranches = m_bShowLocalBranches;
119 m_regbShowRemoteBranches = m_bShowRemoteBranches;
120 m_regbShowOtherRefs = m_bShowOtherRefs;
121 m_regbShowGravatar = m_bShowGravatar;
122 m_regAddBeforeCommit = m_bShowUnversioned;
125 void CLogDlg::DoDataExchange(CDataExchange* pDX)
127 CResizableStandAloneDialog::DoDataExchange(pDX);
128 DDX_Control(pDX, IDC_LOGLIST, m_LogList);
129 DDX_Control(pDX, IDC_LOGMSG, m_ChangedFileListCtrl);
130 DDX_Control(pDX, IDC_PROGRESS, m_LogProgress);
131 DDX_Control(pDX, IDC_SPLITTERTOP, m_wndSplitter1);
132 DDX_Control(pDX, IDC_SPLITTERBOTTOM, m_wndSplitter2);
133 DDX_Text(pDX, IDC_SEARCHEDIT, m_sFilterText);
134 DDX_Control(pDX, IDC_DATEFROM, m_DateFrom);
135 DDX_Control(pDX, IDC_DATETO, m_DateTo);
136 DDX_Control(pDX, IDC_LOG_JUMPTYPE, m_JumpType);
137 DDX_Control(pDX, IDC_LOG_JUMPUP, m_JumpUp);
138 DDX_Control(pDX, IDC_LOG_JUMPDOWN, m_JumpDown);
139 DDX_Text(pDX, IDC_LOGINFO, m_sLogInfo);
140 DDX_Check(pDX, IDC_LOG_ALLBRANCH,m_bAllBranch);
141 DDX_Check(pDX, IDC_WHOLE_PROJECT, m_bWholeProject);
142 DDX_Control(pDX, IDC_WALKBEHAVIOUR, m_ctrlWalkBehavior);
143 DDX_Control(pDX, IDC_VIEW, m_ctrlView);
144 DDX_Control(pDX, IDC_SEARCHEDIT, m_cFilter);
145 DDX_Control(pDX, IDC_STATIC_REF, m_staticRef);
146 DDX_Control(pDX, IDC_PIC_AUTHOR, m_gravatar);
147 DDX_Control(pDX, IDC_FILTER, m_cFileFilter);
150 BEGIN_MESSAGE_MAP(CLogDlg, CResizableStandAloneDialog)
151 ON_WM_CONTEXTMENU()
152 ON_WM_SETCURSOR()
153 ON_NOTIFY(LVN_ITEMCHANGED, IDC_LOGLIST, OnLvnItemchangedLoglist)
154 ON_NOTIFY(LVN_ITEMCHANGED, IDC_LOGMSG, OnLvnItemchangedLogmsg)
155 ON_NOTIFY(NM_CUSTOMDRAW, IDC_LOGMSG, OnNMCustomdrawChangedFileList)
156 ON_NOTIFY(EN_LINK, IDC_MSGVIEW, OnEnLinkMsgview)
157 ON_EN_VSCROLL(IDC_MSGVIEW, OnEnscrollMsgview)
158 ON_EN_HSCROLL(IDC_MSGVIEW, OnEnscrollMsgview)
159 ON_BN_CLICKED(IDC_STATBUTTON, OnBnClickedStatbutton)
161 ON_MESSAGE(WM_TGIT_REFRESH_SELECTION, OnRefreshSelection)
162 ON_REGISTERED_MESSAGE(CFilterEdit::WM_FILTEREDIT_INFOCLICKED, OnClickedInfoIcon)
163 ON_REGISTERED_MESSAGE(CFilterEdit::WM_FILTEREDIT_CANCELCLICKED, OnClickedCancelFilter)
165 ON_MESSAGE(MSG_LOAD_PERCENTAGE,OnLogListLoading)
167 ON_EN_CHANGE(IDC_SEARCHEDIT, OnEnChangeSearchedit)
168 ON_WM_TIMER()
169 ON_NOTIFY(DTN_DATETIMECHANGE, IDC_DATETO, OnDtnDatetimechangeDateto)
170 ON_NOTIFY(DTN_DATETIMECHANGE, IDC_DATEFROM, OnDtnDatetimechangeDatefrom)
171 ON_CBN_SELCHANGE(IDC_LOG_JUMPTYPE, &CLogDlg::OnCbnSelchangeJumpType)
172 ON_COMMAND(IDC_LOG_JUMPUP, &CLogDlg::OnBnClickedJumpUp)
173 ON_COMMAND(IDC_LOG_JUMPDOWN, &CLogDlg::OnBnClickedJumpDown)
174 ON_COMMAND(ID_GO_UP, &CLogDlg::OnBnClickedJumpUp)
175 ON_COMMAND(ID_GO_DOWN, &CLogDlg::OnBnClickedJumpDown)
176 ON_COMMAND(ID_EXITCLEARFILTER, OnExitClearFilter)
177 ON_BN_CLICKED(IDC_WALKBEHAVIOUR, OnBnClickedWalkBehaviour)
178 ON_BN_CLICKED(IDC_VIEW, OnBnClickedView)
179 ON_BN_CLICKED(IDC_WHOLE_PROJECT, OnBnClickShowWholeProject)
180 ON_NOTIFY(LVN_COLUMNCLICK,IDC_LOGLIST, OnLvnColumnclick)
181 ON_COMMAND(MSG_FETCHED_DIFF, OnBnClickedHidepaths)
182 ON_BN_CLICKED(IDC_LOG_ALLBRANCH, OnBnClickedAllBranch)
183 ON_EN_CHANGE(IDC_FILTER, OnEnChangeFileFilter)
185 ON_NOTIFY(DTN_DROPDOWN, IDC_DATEFROM, &CLogDlg::OnDtnDropdownDatefrom)
186 ON_WM_SIZE()
187 ON_BN_CLICKED(IDC_REFRESH, &CLogDlg::OnBnClickedRefresh)
188 ON_STN_CLICKED(IDC_STATIC_REF, &CLogDlg::OnBnClickedBrowseRef)
189 ON_COMMAND(ID_LOGDLG_REFRESH, &CLogDlg::OnBnClickedRefresh)
190 ON_COMMAND(ID_GO_BACKWARD_SELECT, &CLogDlg::GoBackAndSelect)
191 ON_COMMAND(ID_GO_FORWARD_SELECT, &CLogDlg::GoForwardAndSelect)
192 ON_COMMAND(ID_GO_BACKWARD, &CLogDlg::GoBack)
193 ON_COMMAND(ID_GO_FORWARD, &CLogDlg::GoForward)
194 ON_COMMAND(ID_SELECT_SEARCHFIELD, &CLogDlg::OnSelectSearchField)
195 ON_COMMAND(ID_LOGDLG_FIND, &CLogDlg::OnFind)
196 ON_COMMAND(ID_LOGDLG_FOCUSFILTER, &CLogDlg::OnFocusFilter)
197 ON_COMMAND(ID_EDIT_COPY, &CLogDlg::OnEditCopy)
198 ON_MESSAGE(MSG_REFLOG_CHANGED, OnRefLogChanged)
199 ON_REGISTERED_MESSAGE(TaskBarButtonCreated, OnTaskbarBtnCreated)
201 ON_REGISTERED_MESSAGE(CGitStatusListCtrl::GITSLNM_ITEMCHANGED, &CLogDlg::OnFileListCtrlItemChanged)
202 ON_REGISTERED_MESSAGE(CGitStatusListCtrl::GITSLNM_NEEDSREFRESH, OnGitStatusListCtrlNeedsRefresh)
203 ON_REGISTERED_MESSAGE(CGitLogListBase::LOGLIST_RESET_WCREV, OnResetWcRev)
204 ON_WM_MOVE()
205 ON_WM_MOVING()
206 ON_WM_SIZING()
207 ON_WM_CTLCOLOR()
208 ON_WM_PAINT()
209 ON_WM_SYSCOLORCHANGE()
210 END_MESSAGE_MAP()
212 enum JumpType
214 JumpType_AuthorEmail,
215 JumpType_CommitterEmail,
216 JumpType_MergePoint,
217 JumpType_Parent1,
218 JumpType_Parent2,
219 JumpType_Tag,
220 JumpType_TagFF,
221 JumpType_Branch,
222 JumpType_BranchFF,
223 JumpType_History,
226 LRESULT CLogDlg::OnResetWcRev(WPARAM, LPARAM)
228 if (m_LogList.m_hasWC && m_ChangedFileListCtrl.m_CurrentVersion.IsEmpty())
229 m_ChangedFileListCtrl.Clear();
231 return 0;
234 LRESULT CLogDlg::OnGitStatusListCtrlNeedsRefresh(WPARAM, LPARAM)
236 if (m_LogList.m_hasWC && m_LogList.GetSelectedCount() == 1)
238 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
239 size_t selIndex = m_LogList.GetNextSelectedItem(pos);
240 GitRevLoglist* pLogEntry = m_LogList.m_arShownList.SafeGetAt(selIndex);
241 if (pLogEntry && pLogEntry->m_CommitHash.IsEmpty())
242 m_ChangedFileListCtrl.StoreScrollPos();
244 m_LogList.ResetWcRev(true);
245 m_LogList.Invalidate();
246 return 0;
249 void CLogDlg::SetParams(const CTGitPath& orgPath, const CTGitPath& path, CString hightlightRevision, CString range, DWORD limit, int limitScale/*=-1*/)
251 m_orgPath = orgPath;
252 m_path = path;
253 m_hightlightRevision = hightlightRevision;
255 if (range == GIT_REV_ZERO)
256 range = L"HEAD";
258 if (!(range.IsEmpty() || range == L"HEAD"))
260 m_bAllBranch = BST_UNCHECKED;
261 m_AllBranchType = AllBranchType::None;
264 SetRange(range);
266 if (limitScale == CFilterData::SHOW_NO_LIMIT || (!range.IsEmpty() && m_LogList.m_Filter.m_NumberOfLogsScale != CFilterData::SHOW_LAST_N_COMMITS))
267 m_LogList.m_Filter.m_NumberOfLogsScale = CFilterData::SHOW_NO_LIMIT;
268 if (limitScale >= CFilterData::SHOW_LAST_N_COMMITS && limit > 0)
270 // limitation from command line argument, so override the filter.
271 m_LogList.m_Filter.m_NumberOfLogs = limit;
272 m_LogList.m_Filter.m_NumberOfLogsScale = limitScale;
275 if (::IsWindow(m_hWnd))
276 UpdateData(FALSE);
279 void CLogDlg::SetFilter(const CString& findstr, LONG findtype, bool findregex)
281 m_LogList.m_LogFilter = std::make_shared<CLogDlgFilter>(findstr, m_bFilterWithRegex, static_cast<DWORD>(findtype), m_bFilterCaseSensitively);
282 m_sFilterText = findstr;
283 m_bFilterWithRegex = findregex;
286 BOOL CLogDlg::OnInitDialog()
288 CString temp;
289 CResizableStandAloneDialog::OnInitDialog();
290 CAppUtils::MarkWindowAsUnpinnable(m_hWnd);
292 // Let the TaskbarButtonCreated message through the UIPI filter. If we don't
293 // do this, Explorer would be unable to send that message to our window if we
294 // were running elevated. It's OK to make the call all the time, since if we're
295 // not elevated, this is a no-op.
296 CHANGEFILTERSTRUCT cfs = { sizeof(CHANGEFILTERSTRUCT) };
297 typedef BOOL STDAPICALLTYPE ChangeWindowMessageFilterExDFN(HWND hWnd, UINT message, DWORD action, PCHANGEFILTERSTRUCT pChangeFilterStruct);
298 CAutoLibrary hUser = AtlLoadSystemLibraryUsingFullPath(L"user32.dll");
299 if (hUser)
301 auto pfnChangeWindowMessageFilterEx = reinterpret_cast<ChangeWindowMessageFilterExDFN*>(GetProcAddress(hUser, "ChangeWindowMessageFilterEx"));
302 if (pfnChangeWindowMessageFilterEx)
303 pfnChangeWindowMessageFilterEx(m_hWnd, TaskBarButtonCreated, MSGFLT_ALLOW, &cfs);
305 m_pTaskbarList.Release();
306 if (FAILED(m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList)))
307 m_pTaskbarList = nullptr;
309 m_hAccel = LoadAccelerators(AfxGetResourceHandle(),MAKEINTRESOURCE(IDR_ACC_LOGDLG));
311 // use the state of the "stop on copy/rename" option from the last time
312 UpdateData(FALSE);
314 // set the font to use in the log message view, configured in the settings dialog
315 CAppUtils::CreateFontForLogs(m_logFont);
316 SetupLogMessageViewControl();
318 // "unrelated paths" should be in gray color
319 m_iHidePaths = 2;
321 // set up the columns
322 m_LogList.DeleteAllItems();
324 m_LogList.m_Path=m_path;
325 m_LogList.m_hasWC = !GitAdminDir::IsBareRepo(g_Git.m_CurrentDir);
326 m_LogList.m_bShowWC = !!CRegDWORD(L"Software\\TortoiseGit\\LogIncludeWorkingTreeChanges", TRUE) && m_LogList.m_hasWC && m_bShowWC;
327 m_LogList.InsertGitColumn();
329 if (m_bWholeProject)
330 m_LogList.m_Path.Reset();
332 m_ChangedFileListCtrl.Init(GITSLC_COLEXT | GITSLC_COLSTATUS | GITSLC_COLADD | GITSLC_COLDEL, L"LogDlg", (GITSLC_POPALL ^ (GITSLC_POPRESTORE | GITSLC_POPCHANGELISTS)), false, m_LogList.m_hasWC, GITSLC_COLEXT | GITSLC_COLSTATUS | GITSLC_COLADD | GITSLC_COLDEL);
334 GetDlgItem(IDC_LOGLIST)->UpdateData(FALSE);
336 SetDlgTitle();
338 CheckRegexpTooltip();
340 SetSplitterRange();
342 // the filter control has a 'cancel' button (the red 'X'), we need to load its bitmap
343 m_cFilter.SetCancelBitmaps(IDI_CANCELNORMAL, IDI_CANCELPRESSED, 14, 14);
344 m_cFilter.SetInfoIcon(IDI_LOGFILTER, 19, 19);
345 m_cFilter.SetValidator(this);
347 AdjustControlSize(IDC_LOG_ALLBRANCH);
348 AdjustControlSize(IDC_WHOLE_PROJECT);
350 GetClientRect(m_DlgOrigRect);
351 m_LogList.GetClientRect(m_LogListOrigRect);
352 GetDlgItem(IDC_MSGVIEW)->GetClientRect(m_MsgViewOrigRect);
353 m_ChangedFileListCtrl.GetClientRect(m_ChgOrigRect);
355 m_Brush.CreateSolidBrush(RGB(174, 200, 255));
357 m_DateFrom.SendMessage(DTM_SETMCSTYLE, 0, MCS_WEEKNUMBERS|MCS_NOTODAY|MCS_NOTRAILINGDATES|MCS_NOSELCHANGEONNAV);
358 m_DateTo.SendMessage(DTM_SETMCSTYLE, 0, MCS_WEEKNUMBERS | MCS_NOTRAILINGDATES | MCS_NOSELCHANGEONNAV);
359 COleDateTime minDateUTC((time_t)0); // negative TZ: 1969-12-31
360 COleDateTime minDate(minDateUTC.GetYear(), minDateUTC.GetMonth(), minDateUTC.GetDay(), 0, 0, 0), maxDate(2100, 1, 2, 23, 59, 59);
361 m_DateFrom.SetRange(&minDate, &maxDate);
362 m_DateTo.SetRange(&minDate, &maxDate);
364 m_staticRef.SetURL(CString());
366 AddMainAnchors();
367 SetFilterCueText();
369 m_LogList.m_ShowMask &= ~CGit::LOG_INFO_LOCAL_BRANCHES;
370 if (m_AllBranchType == AllBranchType::AllBranches)
371 m_LogList.m_ShowMask|=CGit::LOG_INFO_ALL_BRANCH;
372 else if (m_AllBranchType == AllBranchType::AllLocalBranches)
373 m_LogList.m_ShowMask |= CGit::LOG_INFO_LOCAL_BRANCHES;
374 else if (m_AllBranchType == AllBranchType::AllBasicRefs)
375 m_LogList.m_ShowMask |= CGit::LOG_INFO_BASIC_REFS;
376 else
377 m_LogList.m_ShowMask&=~CGit::LOG_INFO_ALL_BRANCH;
379 if (m_bFullHistory)
380 m_LogList.m_ShowMask |= CGit::LOG_INFO_FULL_HISTORY;
382 HandleShowLabels(m_bShowTags, LOGLIST_SHOWTAGS);
383 HandleShowLabels(m_bShowLocalBranches, LOGLIST_SHOWLOCALBRANCHES);
384 HandleShowLabels(m_bShowRemoteBranches, LOGLIST_SHOWREMOTEBRANCHES);
385 HandleShowLabels(m_bShowOtherRefs, LOGLIST_SHOWOTHERREFS);
387 // SetPromptParentWindow(m_hWnd);
388 m_JumpType.AddString(CString(MAKEINTRESOURCE(IDS_PROC_LOG_AUTHOREMAIL)));
389 m_JumpType.AddString(CString(MAKEINTRESOURCE(IDS_PROC_LOG_COMMITTEREMAIL)));
390 m_JumpType.AddString(CString(MAKEINTRESOURCE(IDS_PROC_LOG_MERGEPOINT)));
391 m_JumpType.AddString(CString(MAKEINTRESOURCE(IDS_PROC_LOG_PARENT1)));
392 m_JumpType.AddString(CString(MAKEINTRESOURCE(IDS_PROC_LOG_PARENT2)));
393 m_JumpType.AddString(CString(MAKEINTRESOURCE(IDS_PROC_TAG)));
394 m_JumpType.AddString(CString(MAKEINTRESOURCE(IDS_PROC_TAG_FF)));
395 m_JumpType.AddString(CString(MAKEINTRESOURCE(IDS_PROC_BRANCH)));
396 m_JumpType.AddString(CString(MAKEINTRESOURCE(IDS_PROC_BRANCH_FF)));
397 m_JumpType.AddString(CString(MAKEINTRESOURCE(IDS_PROC_SELECTION_HISTORY)));
398 m_JumpType.SetCurSel(0);
399 int iconWidth = GetSystemMetrics(SM_CXSMICON);
400 int iconHeight = GetSystemMetrics(SM_CYSMICON);
401 m_JumpUp.SetIcon(CCommonAppUtils::LoadIconEx(IDI_JUMPUP, iconWidth, iconHeight));
402 m_JumpDown.SetIcon(CCommonAppUtils::LoadIconEx(IDI_JUMPDOWN, iconWidth, iconHeight));
404 if (GetExplorerHWND())
405 CenterWindow(CWnd::FromHandle(GetExplorerHWND()));
406 EnableSaveRestore(L"LogDlg");
408 DWORD yPos1 = CDPIAware::Instance().ScaleY(CRegDWORD(L"Software\\TortoiseGit\\TortoiseProc\\ResizableState\\LogDlgSizer1"));
409 DWORD yPos2 = CDPIAware::Instance().ScaleY(CRegDWORD(L"Software\\TortoiseGit\\TortoiseProc\\ResizableState\\LogDlgSizer2"));
410 RECT rcDlg, rcLogList, rcChgMsg;
411 GetClientRect(&rcDlg);
412 m_LogList.GetWindowRect(&rcLogList);
413 ScreenToClient(&rcLogList);
414 m_ChangedFileListCtrl.GetWindowRect(&rcChgMsg);
415 ScreenToClient(&rcChgMsg);
416 if (yPos1 && (static_cast<LONG>(yPos1) < rcDlg.bottom - CDPIAware::Instance().ScaleY(185)))
418 RECT rectSplitter;
419 m_wndSplitter1.GetWindowRect(&rectSplitter);
420 ScreenToClient(&rectSplitter);
421 int delta = yPos1 - rectSplitter.top;
423 if ((rcLogList.bottom + delta > rcLogList.top) && (rcLogList.bottom + delta < rcChgMsg.bottom - CDPIAware::Instance().ScaleY(30)))
425 m_wndSplitter1.SetWindowPos(nullptr, rectSplitter.left, yPos1, 0, 0, SWP_NOSIZE);
426 DoSizeV1(delta);
429 if (yPos2 && (static_cast<LONG>(yPos2) < rcDlg.bottom - CDPIAware::Instance().ScaleY(153)))
431 RECT rectSplitter;
432 m_wndSplitter2.GetWindowRect(&rectSplitter);
433 ScreenToClient(&rectSplitter);
434 int delta = yPos2 - rectSplitter.top;
436 if ((rcChgMsg.top + delta < rcChgMsg.bottom) && (rcChgMsg.top + delta > rcLogList.top + CDPIAware::Instance().ScaleY(30)))
438 m_wndSplitter2.SetWindowPos(nullptr, rectSplitter.left, yPos2, 0, 0, SWP_NOSIZE);
439 DoSizeV2(delta);
443 SetSplitterRange();
444 Invalidate();
446 if (m_bSelect)
448 // the dialog is used to select revisions
449 // enable the OK button if appropriate
450 EnableOKButton();
452 else
454 // the dialog is used to just view log messages
455 // hide the OK button and set text on Cancel button to OK
456 GetDlgItemText(IDOK, temp);
457 SetDlgItemText(IDCANCEL, temp);
458 GetDlgItem(IDOK)->ShowWindow(SW_HIDE);
461 // first start a thread to obtain the log messages without
462 // blocking the dialog
463 //m_tTo = 0;
464 //m_tFrom = static_cast<DWORD>(-1);
466 // scroll to user selected or current revision
467 if (m_hightlightRevision.IsEmpty() || g_Git.GetHash(m_LogList.m_lastSelectedHash, m_hightlightRevision))
469 if (g_Git.GetHash(m_LogList.m_lastSelectedHash, L"HEAD"))
470 MessageBox(g_Git.GetGitLastErr(L"Could not get HEAD hash."), L"TortoiseGit", MB_ICONERROR);
473 if (g_Git.GetConfigValueBool(L"tgit.logshowpatch"))
474 TogglePatchView();
476 m_LogList.FetchLogAsync(this);
477 ShowGravatar();
478 m_gravatar.Init();
480 m_cFileFilter.SetCancelBitmaps(IDI_CANCELNORMAL, IDI_CANCELPRESSED, 14, 14);
481 m_cFileFilter.SetInfoIcon(IDI_FILTEREDIT, 19, 19);
482 temp.LoadString(IDS_FILEDIFF_FILTERCUE);
483 temp = L" " + temp;
484 m_cFileFilter.SetCueBanner(temp);
486 GetDlgItem(IDC_LOGLIST)->SetFocus();
488 m_History.SetMaxHistoryItems(CRegDWORD(L"Software\\TortoiseGit\\MaxRefHistoryItems", 5));
489 CString reg;
490 reg.Format(L"Software\\TortoiseGit\\History\\log-refs\\%s", static_cast<LPCWSTR>(g_Git.m_CurrentDir));
491 reg.Replace(L':', L'_');
492 m_History.Load(reg, L"ref");
494 reg.Format(L"Software\\TortoiseGit\\History\\LogDlg_Limits\\%s\\FromDate", static_cast<LPCWSTR>(g_Git.m_CurrentDir));
495 reg.Replace(L':', L'_');
496 m_regLastSelectedFromDate = CRegString(reg);
498 m_ctrlWalkBehavior.m_bAlwaysShowArrow = true;
499 m_ctrlView.m_bAlwaysShowArrow = true;
501 ShowStartRef();
502 return FALSE;
505 HBRUSH CLogDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
507 HBRUSH hbr = __super::OnCtlColor(pDC, pWnd, nCtlColor);
509 if ((m_LogList.m_Filter.m_NumberOfLogsScale >= CFilterData::SHOW_LAST_N_COMMITS || m_LogList.m_Filter.m_NumberOfLogsScale == CFilterData::SHOW_LAST_SEL_DATE && m_LogList.m_Filter.m_From != -1) && pWnd->GetDlgCtrlID() == IDC_FROMLABEL)
511 pDC->SetBkMode(TRANSPARENT);
512 return m_Brush;
515 return hbr;
518 void CLogDlg::OnPaint()
520 CPaintDC dc(this);
522 if (!(m_LogList.m_Filter.m_NumberOfLogsScale >= CFilterData::SHOW_LAST_N_COMMITS || m_LogList.m_Filter.m_NumberOfLogsScale == CFilterData::SHOW_LAST_SEL_DATE && m_LogList.m_Filter.m_From != -1))
523 return;
525 CWnd* pWnd = GetDlgItem(IDC_FROMLABEL);
526 if (!pWnd)
527 return;
529 CRect rect;
530 pWnd->GetWindowRect(&rect);
531 ScreenToClient(rect);
532 rect.left -= 5;
533 rect.top -= 3;
534 rect.bottom += 3;
536 dc.FillRect(rect, &m_Brush);
539 LRESULT CLogDlg::OnLogListLoading(WPARAM wParam, LPARAM /*lParam*/)
541 int cur = static_cast<int>(wParam);
543 if( cur == GITLOG_START )
545 CString temp;
546 temp.LoadString(IDS_PROGRESSWAIT);
548 this->m_LogList.ShowText(temp, true);
550 // We use a progress bar while getting the logs
551 m_LogProgress.SetRange32(0, 100);
552 m_LogProgress.SetPos(0);
553 if (m_pTaskbarList)
555 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
556 m_pTaskbarList->SetProgressValue(m_hWnd, 0, 100);
559 GetDlgItem(IDC_WALKBEHAVIOUR)->ShowWindow(SW_HIDE);
560 GetDlgItem(IDC_VIEW)->ShowWindow(SW_HIDE);
561 GetDlgItem(IDC_STATBUTTON)->ShowWindow(SW_HIDE);
562 GetDlgItem(IDC_PROGRESS)->ShowWindow(TRUE);
564 DialogEnableWindow(IDC_WALKBEHAVIOUR, FALSE);
565 DialogEnableWindow(IDC_STATBUTTON, FALSE);
566 //DialogEnableWindow(IDC_REFRESH, FALSE);
567 DialogEnableWindow(IDC_VIEW, FALSE);
570 else if( cur == GITLOG_END)
572 if(this->m_LogList.HasText())
574 this->m_LogList.ClearText();
576 UpdateLogInfoLabel();
578 if (m_pTaskbarList)
579 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NOPROGRESS);
581 DialogEnableWindow(IDC_WHOLE_PROJECT, !m_bFollowRenames && !m_path.IsEmpty());
583 DialogEnableWindow(IDC_STATBUTTON, !(m_LogList.m_arShownList.empty() || m_LogList.m_arShownList.size() == 1 && m_LogList.m_bShowWC));
584 DialogEnableWindow(IDC_REFRESH, TRUE);
585 DialogEnableWindow(IDC_VIEW, TRUE);
586 DialogEnableWindow(IDC_WALKBEHAVIOUR, TRUE);
587 // PostMessage(WM_TIMER, LOGFILTER_TIMER);
588 GetDlgItem(IDC_WALKBEHAVIOUR)->ShowWindow(SW_SHOW);
589 GetDlgItem(IDC_VIEW)->ShowWindow(SW_SHOW);
590 GetDlgItem(IDC_STATBUTTON)->ShowWindow(SW_SHOW);
591 GetDlgItem(IDC_PROGRESS)->ShowWindow(FALSE);
592 //CTime time=m_LogList.GetOldestTime();
593 CTime begin,end;
594 m_LogList.GetTimeRange(begin,end);
596 if (m_LogList.m_Filter.m_From > 0 && m_LogList.m_Filter.m_NumberOfLogsScale >= CFilterData::SHOW_LAST_SEL_DATE)
597 begin = m_LogList.m_Filter.m_From;
598 m_DateFrom.SetTime(&begin);
599 if (m_LogList.m_Filter.m_To != -1)
600 end = m_LogList.m_Filter.m_To;
601 m_DateTo.SetTime(&end);
602 Invalidate();
604 else
606 if(this->m_LogList.HasText())
608 this->m_LogList.ClearText();
609 this->m_LogList.Invalidate();
611 UpdateLogInfoLabel();
612 m_LogProgress.SetPos(cur);
613 if (m_pTaskbarList)
615 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
616 m_pTaskbarList->SetProgressValue(m_hWnd, cur, 100);
619 return 0;
621 void CLogDlg::SetDlgTitle()
623 if (m_sTitle.IsEmpty())
624 GetWindowText(m_sTitle);
626 if (m_LogList.m_Path.IsEmpty() || m_orgPath.GetWinPathString().IsEmpty())
627 CAppUtils::SetWindowTitle(m_hWnd, g_Git.m_CurrentDir, m_sTitle);
628 else
629 CAppUtils::SetWindowTitle(m_hWnd, m_orgPath.GetWinPathString(), m_sTitle);
632 void CLogDlg::CheckRegexpTooltip()
634 CWnd *pWnd = GetDlgItem(IDC_SEARCHEDIT);
635 // Since tooltip describes regexp features, show it only if regexps are enabled.
636 if (m_bFilterWithRegex)
637 m_tooltips.AddTool(pWnd, IDS_LOG_FILTER_REGEX_TT);
638 else
639 m_tooltips.DelTool(pWnd);
642 void CLogDlg::EnableOKButton()
644 if (m_bSelect)
646 // the dialog is used to select revisions
647 if (m_bSelectionMustBeSingle)
649 // enable OK button if only a single revision is selected
650 DialogEnableWindow(IDOK, (m_LogList.GetSelectedCount()==1));
652 else if (m_bSelectionMustBeContinuous)
653 DialogEnableWindow(IDOK, (m_LogList.GetSelectedCount()!=0)&&(m_LogList.IsSelectionContinuous()));
654 else
655 DialogEnableWindow(IDOK, m_LogList.GetSelectedCount()!=0);
657 else
658 DialogEnableWindow(IDOK, TRUE);
661 bool LookLikeGitHash(const CString& msg, int &pos)
663 static int shortHashLength = std::min(GIT_HASH_SIZE * 2, std::max(3, static_cast<int>(CRegDWORD(L"Software\\TortoiseGit\\ShortHashLengthForHyperLinkInLogMessage", g_Git.GetShortHASHLength()))));
664 int c = 0;
665 for (; pos < msg.GetLength(); ++pos)
667 if (msg[pos] >= '0' && msg[pos] <= '9' || msg[pos] >= 'a' && msg[pos] <= 'f')
668 c++;
669 else
670 return c >= shortHashLength && c <= GIT_HASH_SIZE * 2 && msg[pos] != '@';
672 return c >= shortHashLength && c <= GIT_HASH_SIZE * 2;
675 std::vector<CHARRANGE> FindGitHashPositions(const CString& msg, int offset)
677 std::vector<CHARRANGE> result;
678 offset = offset < 0 ? 0 : offset;
679 int old = offset;
680 while (offset < msg.GetLength())
682 old = offset;
683 wchar_t e = msg[offset];
684 if (e == '\n')
686 ++offset;
687 if (msg.Mid(offset, 11) == L"git-svn-id:"
688 || msg.Mid(offset, 14) == L"Signed-off-by:"
689 || msg.Mid(offset, 10) == L"Change-Id:"
692 offset += 10;
693 while (offset < msg.GetLength())
695 if (msg[++offset] == '\n')
696 break;
698 continue;
701 else if (e >= 'A' && e <= 'Z' || e >= 'h' && e <= 'z')
705 e = msg[++offset];
706 } while (offset < msg.GetLength() && (e >= 'A' && e <= 'Z' || e >= 'a' && e <= 'z' || e >= '0' && e <= '9'));
708 else if (e >= 'a' && e <= 'g' || e >= '0' && e <= '9')
710 if (e == 'g')
712 ++old;
713 ++offset;
715 if (LookLikeGitHash(msg, offset))
717 wchar_t d = offset < msg.GetLength() ? msg[offset] : '\0';
718 if (!((d >= 'A' && d <= 'Z') || (d >= 'a' && d <= 'z') || (d >= '0' && d <= '9')))
720 CHARRANGE range = { old, offset };
721 result.push_back(range);
724 ++offset;
726 else
727 ++offset;
730 return result;
733 BOOL FindGitHash(const CString& msg, int offset, CWnd *pWnd)
735 std::vector<CHARRANGE> positions = FindGitHashPositions(msg, offset);
736 CAppUtils::SetCharFormat(pWnd, CFM_LINK, CFE_LINK, positions);
738 return positions.empty() ? FALSE : TRUE;
741 static int DescribeCommit(const CGitHash& hash, CString& result)
743 CAutoRepository repo(g_Git.GetGitRepository());
744 if (!repo)
745 return -1;
746 CAutoObject commit;
747 if (git_object_lookup(commit.GetPointer(), repo, hash, GIT_OBJECT_COMMIT))
748 return -1;
750 CAutoDescribeResult describe;
751 git_describe_options describe_options = GIT_DESCRIBE_OPTIONS_INIT;
752 describe_options.describe_strategy = CRegDWORD(L"Software\\TortoiseGit\\DescribeStrategy", GIT_DESCRIBE_DEFAULT);
753 describe_options.only_follow_first_parent = CRegDWORD(L"Software\\TortoiseGit\\DescribeOnlyFollowFirstParent", 0);
754 if (git_describe_commit(describe.GetPointer(), static_cast<git_object*>(commit), &describe_options))
755 return -1;
757 CAutoBuf describe_buf;
758 git_describe_format_options format_options = GIT_DESCRIBE_FORMAT_OPTIONS_INIT;
759 format_options.abbreviated_size = CRegDWORD(L"Software\\TortoiseGit\\DescribeAbbreviatedSize", GIT_DESCRIBE_DEFAULT_ABBREVIATED_SIZE);
760 format_options.always_use_long_format = CRegDWORD(L"Software\\TortoiseGit\\DescribeAlwaysLong");
761 if (git_describe_format(describe_buf, describe, &format_options))
762 return -1;
764 result = CUnicodeUtils::GetUnicode(describe_buf->ptr);
765 return 0;
768 namespace
770 bool IsAllWhitespace(const CString& text, long first, long last)
772 for (; first < last; ++first)
774 wchar_t c = text[first];
775 if (c > L' ')
776 return false;
778 if (c != L' ' && c != L'\t' && c != L'\r' && c != L'\n')
779 return false;
782 return true;
785 void ReduceRanges(std::vector<CHARRANGE>& ranges, const CString& text)
787 if (ranges.size() < 2)
788 return;
790 auto begin = ranges.begin();
791 auto end = ranges.end();
793 auto target = begin;
794 for (auto source = begin + 1; source != end; ++source)
795 if (IsAllWhitespace(text, target->cpMax, source->cpMin))
796 target->cpMax = source->cpMax;
797 else
798 *(++target) = *source;
800 ranges.erase(++target, end);
802 } // namespace
804 void CLogDlg::FillLogMessageCtrl(bool bShow /* = true*/)
806 // we fill here the log message rich edit control,
807 // and also populate the changed files list control
808 // according to the selected revision(s).
810 auto pMsgView = static_cast<CRichEditCtrl*>(GetDlgItem(IDC_MSGVIEW));
811 // empty the log message view
812 pMsgView->SetWindowText(L" ");
813 FillPatchView(true);
814 // empty the changed files list
815 m_ChangedFileListCtrl.SetRedraw(FALSE);
816 // InterlockedExchange(&m_bNoDispUpdates, TRUE);
817 m_ChangedFileListCtrl.Clear();
819 // if we're not here to really show a selected revision, just
820 // get out of here after clearing the views, which is what is intended
821 // if that flag is not set.
822 if (!bShow)
824 // force a redraw
825 m_ChangedFileListCtrl.Invalidate();
826 // InterlockedExchange(&m_bNoDispUpdates, FALSE);
827 m_ChangedFileListCtrl.SetRedraw(TRUE);
828 m_gravatar.LoadGravatar();
829 return;
832 // depending on how many revisions are selected, we have to do different
833 // tasks.
834 int selCount = m_LogList.GetSelectedCount();
835 if (selCount == 0)
837 // if nothing is selected, we have nothing more to do
838 // InterlockedExchange(&m_bNoDispUpdates, FALSE);
839 m_ChangedFileListCtrl.SetRedraw(TRUE);
840 m_gravatar.LoadGravatar();
841 return;
843 else if (selCount == 1)
845 // if one revision is selected, we have to fill the log message view
846 // with the corresponding log message, and also fill the changed files
847 // list fully.
848 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
849 size_t selIndex = m_LogList.GetNextSelectedItem(pos);
850 if (selIndex >= m_LogList.m_arShownList.size())
852 // InterlockedExchange(&m_bNoDispUpdates, FALSE);
853 m_ChangedFileListCtrl.SetRedraw(TRUE);
854 return;
856 GitRevLoglist* pLogEntry = m_LogList.m_arShownList.SafeGetAt(selIndex);
859 m_gravatar.LoadGravatar(pLogEntry->GetAuthorEmail());
861 CString out_describe;
862 if (m_bShowDescribe)
864 CString result;
865 if (!DescribeCommit(pLogEntry->m_CommitHash, result))
866 out_describe = L"Describe: " + result + L"\n";
869 CString out_counter;
870 if (m_bShowBranchRevNo && !pLogEntry->m_CommitHash.IsEmpty())
872 bool isFirstParentCommit = !pLogEntry->m_Lanes.empty() && Lanes::isActive(pLogEntry->m_Lanes[0]);
874 if (isFirstParentCommit)
876 CString rev_counter, rev_err;
877 if (g_Git.Run(L"git.exe rev-list --count --first-parent " + pLogEntry->m_CommitHash.ToString(), &rev_counter, &rev_err, CP_UTF8))
878 CMessageBox::Show(GetSafeHwnd(), L"Could not get rev count\n" + rev_counter + L'\n' + rev_err, L"TortoiseGit", MB_ICONERROR);
879 else
880 out_counter = L", " + CString(MAKEINTRESOURCE(IDS_REV_COUNTER)) + L": " + rev_counter.Trim();
884 std::vector<CHARRANGE> hightlightRanges;
885 std::vector<CHARRANGE> boldRanges;
886 auto filter = m_LogList.m_LogFilter;
888 // set the log message text
889 CString msg = CString(MAKEINTRESOURCE(IDS_HASH)) + L": ";
890 int offset = msg.GetLength();
891 msg += pLogEntry->m_CommitHash.ToString();
892 if ((m_SelectedFilters & LOGFILTER_REVS) && filter->IsFilterActive())
893 filter->GetMatchRanges(hightlightRanges, msg.Mid(offset), offset);
894 msg += out_counter + L"\n" + out_describe + L"\n";
896 if (m_bAsteriskLogPrefix)
897 msg += L"* ";
898 offset = msg.GetLength();
899 msg += pLogEntry->GetSubject();
900 msg.Remove(L'\r');
901 boldRanges.push_back({ offset, msg.GetLength() });
902 if ((m_SelectedFilters & (LOGFILTER_SUBJECT | LOGFILTER_MESSAGES)) && filter->IsFilterActive())
903 filter->GetMatchRanges(hightlightRanges, msg.Mid(offset), offset);
905 msg += L'\n';
906 offset = msg.GetLength();
907 msg += pLogEntry->GetBody();
908 msg.Remove(L'\r');
909 if ((m_SelectedFilters & LOGFILTER_MESSAGES) && filter->IsFilterActive())
910 filter->GetMatchRanges(hightlightRanges, msg.Mid(offset), offset);
912 if (!pLogEntry->m_Notes.IsEmpty())
914 msg += L'\n';
915 if (m_bAsteriskLogPrefix)
916 msg += L"* ";
917 offset = msg.GetLength();
918 msg += CString(MAKEINTRESOURCE(IDS_NOTES)) + L":\n";
919 boldRanges.push_back({ offset, msg.GetLength() });
920 msg += pLogEntry->m_Notes;
921 msg.Remove(L'\r');
922 if ((m_SelectedFilters & LOGFILTER_NOTES) && filter->IsFilterActive())
923 filter->GetMatchRanges(hightlightRanges, msg.Mid(offset), offset);
926 CString tagInfo = m_LogList.GetTagInfo(pLogEntry);
927 if (!tagInfo.IsEmpty())
929 msg += L'\n';
930 if (m_bAsteriskLogPrefix)
931 msg += L"* ";
932 offset = msg.GetLength();
933 msg += CString(MAKEINTRESOURCE(IDS_PROC_LOG_TAGINFO)) + L":\n";
934 boldRanges.push_back({ offset, msg.GetLength() });
935 msg += tagInfo;
936 msg.Remove(L'\r');
937 if ((m_SelectedFilters & LOGFILTER_ANNOTATEDTAG) && filter->IsFilterActive())
938 filter->GetMatchRanges(hightlightRanges, msg.Mid(offset), offset);
941 pMsgView->SetWindowText(msg);
943 CAppUtils::SetCharFormat(pMsgView, CFM_BOLD, CFE_BOLD, boldRanges);
944 if (!hightlightRanges.empty())
946 ReduceRanges(hightlightRanges, msg);
947 CAppUtils::SetCharFormat(pMsgView, CFM_COLOR, m_Colors.GetColor(CColors::FilterMatch), hightlightRanges);
950 // turn bug ID's into links if the bugtraq: properties have been set
951 // and we can find a match of those in the log message
952 int findHashStart = msg.Find(L'\n');
953 if (!out_describe.IsEmpty())
954 findHashStart = msg.Find(L'\n', findHashStart + 1);
955 FindGitHash(msg, findHashStart, pMsgView);
956 m_LogList.m_ProjectProperties.FindBugID(msg, pMsgView);
957 CAppUtils::StyleURLs(msg, pMsgView);
958 if (static_cast<DWORD>(CRegStdDWORD(L"Software\\TortoiseGit\\StyleCommitMessages", TRUE)) == TRUE)
959 CAppUtils::FormatTextInRichEditControl(pMsgView);
961 auto files = pLogEntry->GetFiles(&m_LogList); // either load the diff from disk cache and sets m_IsDiffFiles (then we safe a reload) or it enqueues it in the AsyncDiffThread
962 if (!pLogEntry->m_IsDiffFiles)
964 m_ChangedFileListCtrl.SetBusyString(CString(MAKEINTRESOURCE(IDS_PROC_LOG_FETCHINGFILES)));
965 m_ChangedFileListCtrl.SetBusy(TRUE);
966 m_ChangedFileListCtrl.SetRedraw(TRUE);
967 return;
970 CString matchpath=this->m_path.GetGitPathString();
972 int count = files.GetCount();
973 if (!m_bWholeProject && !matchpath.IsEmpty() && m_iHidePaths)
975 if (m_path.IsDirectory() && !CStringUtils::EndsWith(matchpath, L'/'))
976 matchpath.AppendChar(L'/');
977 int matchPathLen = matchpath.GetLength();
978 bool somethingHidden = false;
979 for (int i = 0; i < count; ++i)
981 const_cast<CTGitPath&>(files[i]).m_Action &= ~(CTGitPath::LOGACTIONS_HIDE | CTGitPath::LOGACTIONS_GRAY);
983 bool bothAreDirectory = m_path.IsDirectory() && files[i].IsDirectory() && files[i].GetGitPathString().GetLength() == matchPathLen - 1; // submodules don't end with slash, but we must also not match a submodule in a fodler with an equal prefix
984 if ((bothAreDirectory && wcsncmp(files[i].GetGitPathString(), matchpath, matchPathLen - 1) || !bothAreDirectory && wcsncmp(files[i].GetGitPathString(), matchpath, matchPathLen)) && ((files[i].m_Action & (CTGitPath::LOGACTIONS_REPLACED | CTGitPath::LOGACTIONS_COPY)) == 0 || (bothAreDirectory && wcsncmp(files[i].GetGitOldPathString(), matchpath, matchPathLen - 1) || !bothAreDirectory && wcsncmp(files[i].GetGitOldPathString(), matchpath, matchPathLen))))
986 somethingHidden = true;
987 if (m_iHidePaths == 1)
988 const_cast<CTGitPath&>(files[i]).m_Action |= CTGitPath::LOGACTIONS_HIDE;
989 else if (m_iHidePaths == 2)
990 const_cast<CTGitPath&>(files[i]).m_Action |= CTGitPath::LOGACTIONS_GRAY;
993 if (somethingHidden)
994 pLogEntry->GetAction(&m_LogList) |= CTGitPath::LOGACTIONS_HIDE;
995 else
996 pLogEntry->GetAction(&m_LogList) &= ~CTGitPath::LOGACTIONS_HIDE;
998 else if (pLogEntry->GetAction(&m_LogList) & CTGitPath::LOGACTIONS_HIDE)
1000 pLogEntry->GetAction(&m_LogList) &= ~CTGitPath::LOGACTIONS_HIDE;
1001 for (int i = 0 ; i < count; ++i)
1002 const_cast<CTGitPath&>(files[i]).m_Action &= ~(CTGitPath::LOGACTIONS_HIDE | CTGitPath::LOGACTIONS_GRAY);
1005 CString fileFilterText;
1006 m_cFileFilter.GetWindowText(fileFilterText);
1007 if (!fileFilterText.IsEmpty())
1009 CLogDlgFileFilter fileFilter(fileFilterText, false, 0, false);
1010 bool somethingHidden = false;
1011 for (int i = 0; i < count; ++i)
1013 if (!fileFilter(files[i]))
1015 const_cast<CTGitPath&>(files[i]).m_Action |= CTGitPath::LOGACTIONS_HIDE;
1016 somethingHidden = true;
1019 if (somethingHidden)
1020 pLogEntry->GetAction(&m_LogList) |= CTGitPath::LOGACTIONS_HIDE;
1023 m_ChangedFileListCtrl.UpdateWithGitPathList(const_cast<CTGitPathList&>(files.m_files));
1024 m_ChangedFileListCtrl.m_CurrentVersion = pLogEntry->m_CommitHash;
1025 if (pLogEntry->m_CommitHash.IsEmpty() && m_bShowUnversioned)
1027 m_ChangedFileListCtrl.InsertUnRevListFromPreCalculatedList(pLogEntry->GetUnRevFiles());
1028 m_ChangedFileListCtrl.Show(GITSLC_SHOWVERSIONED | GITSLC_SHOWUNVERSIONED);
1030 else
1031 m_ChangedFileListCtrl.Show(GITSLC_SHOWVERSIONED);
1033 m_ChangedFileListCtrl.SetBusy(FALSE);
1035 m_ChangedFileListCtrl.SetRedraw(TRUE);
1036 return;
1040 else
1042 // more than one revision is selected:
1043 // the log message view must be emptied
1044 // the changed files list contains all the changed paths from all
1045 // selected revisions, with 'doubles' removed
1046 m_gravatar.LoadGravatar();
1049 // redraw the views
1050 // InterlockedExchange(&m_bNoDispUpdates, FALSE);
1052 // sort according to the settings
1053 if (m_nSortColumnPathList > 0)
1054 SetSortArrow(&m_ChangedFileListCtrl, m_nSortColumnPathList, m_bAscendingPathList);
1055 else
1056 SetSortArrow(&m_ChangedFileListCtrl, -1, false);
1057 m_ChangedFileListCtrl.SetRedraw(TRUE);
1060 void CLogDlg::FillPatchView(bool onlySetTimer)
1062 if (!::IsWindow(this->m_patchViewdlg.m_hWnd))
1063 return;
1065 KillTimer(LOG_FILLPATCHVTIMER);
1066 if (onlySetTimer)
1068 SetTimer(LOG_FILLPATCHVTIMER, 100, nullptr);
1069 return;
1072 POSITION posLogList = m_LogList.GetFirstSelectedItemPosition();
1073 if (posLogList == nullptr)
1075 m_patchViewdlg.ClearView();
1076 return; // nothing is selected, get out of here
1079 GitRev* pLogEntry = m_LogList.m_arShownList.SafeGetAt(m_LogList.GetNextSelectedItem(posLogList));
1080 if (pLogEntry == nullptr || m_LogList.GetNextSelectedItem(posLogList) != -1)
1082 m_patchViewdlg.ClearView();
1083 return;
1086 auto locker(m_ChangedFileListCtrl.AcquireReadWeakLock(50));
1087 if (!locker.IsAcquired())
1089 SetTimer(LOG_FILLPATCHVTIMER, 100, nullptr);
1090 return;
1092 POSITION pos = m_ChangedFileListCtrl.GetFirstSelectedItemPosition();
1093 CString out;
1095 if (pos == nullptr)
1097 int diffContext = g_Git.GetConfigValueInt32(L"diff.context", -1);
1098 CStringA outA;
1099 CString rev1 = pLogEntry->m_CommitHash.IsEmpty() ? CString("HEAD") : (pLogEntry->m_CommitHash.ToString() + L"~1");
1100 CString rev2 = pLogEntry->m_CommitHash.IsEmpty() ? CString(GIT_REV_ZERO) : pLogEntry->m_CommitHash.ToString();
1101 g_Git.GetUnifiedDiff(CTGitPath(), rev1, rev2, &outA, false, false, diffContext);
1102 out = CUnicodeUtils::GetUnicode(outA);
1104 else
1106 while (pos)
1108 int nSelect = m_ChangedFileListCtrl.GetNextSelectedItem(pos);
1109 auto p = m_ChangedFileListCtrl.GetListEntry(nSelect);
1110 if (p && !(p->m_Action&CTGitPath::LOGACTIONS_UNVER))
1112 CString cmd;
1113 if (pLogEntry->m_CommitHash.IsEmpty())
1114 cmd.Format(L"git.exe diff HEAD -- \"%s\"", static_cast<LPCWSTR>(p->GetGitPathString()));
1115 else
1116 cmd.Format(L"git.exe diff %s^%d..%s -- \"%s\"", static_cast<LPCWSTR>(pLogEntry->m_CommitHash.ToString()), p->m_ParentNo + 1, static_cast<LPCWSTR>(pLogEntry->m_CommitHash.ToString()), static_cast<LPCWSTR>(p->GetGitPathString()));
1117 g_Git.Run(cmd, &out, CP_UTF8);
1122 m_patchViewdlg.SetText(out);
1125 void CLogDlg::TogglePatchView()
1127 m_patchViewdlg.m_ParentDlg = this;
1128 if (!IsWindow(m_patchViewdlg.m_hWnd))
1130 if (g_Git.GetConfigValueBool(L"tgit.logshowpatch") == FALSE)
1131 g_Git.SetConfigValue(L"tgit.logshowpatch", L"true");
1132 m_patchViewdlg.Create(IDD_PATCH_VIEW, this);
1133 m_patchViewdlg.ShowAndAlignToParent();
1135 FillPatchView();
1137 else
1139 g_Git.SetConfigValue(L"tgit.logshowpatch", L"false");
1140 m_patchViewdlg.ShowWindow(SW_HIDE);
1141 m_patchViewdlg.DestroyWindow();
1145 LRESULT CLogDlg::OnFileListCtrlItemChanged(WPARAM /*wparam*/, LPARAM /*lparam*/)
1147 FillPatchView(true);
1148 return 0;
1151 void CLogDlg::OnMoving(UINT fwSide, LPRECT pRect)
1153 __super::OnMoving(fwSide, pRect);
1155 m_patchViewdlg.ParentOnMoving(m_hWnd, pRect);
1158 void CLogDlg::OnSizing(UINT fwSide, LPRECT pRect)
1160 __super::OnSizing(fwSide, pRect);
1162 m_patchViewdlg.ParentOnSizing(m_hWnd, pRect);
1165 void CLogDlg::OnSelectSearchField()
1167 m_cFilter.SetSel(0, -1, FALSE);
1168 m_cFilter.SetFocus();
1171 void CLogDlg::GoBack()
1173 GoBackForward(false, false);
1176 void CLogDlg::GoForward()
1178 GoBackForward(false, true);
1181 void CLogDlg::GoBackAndSelect()
1183 GoBackForward(true, false);
1186 void CLogDlg::GoForwardAndSelect()
1188 GoBackForward(true, true);
1191 void CLogDlg::GoBackForward(bool select, bool bForward)
1193 m_LogList.m_highlight.Empty();
1194 CGitHash gotoHash;
1195 if (bForward ? m_LogList.m_selectionHistory.GoForward(gotoHash) : m_LogList.m_selectionHistory.GoBack(gotoHash))
1197 int i;
1198 for (i = 0; i < static_cast<int>(m_LogList.m_arShownList.size()); ++i)
1200 GitRev* rev = m_LogList.m_arShownList.SafeGetAt(i);
1201 if (!rev) continue;
1202 if (rev->m_CommitHash == gotoHash)
1204 m_LogList.EnsureVisible(i, FALSE);
1205 if (select)
1207 m_LogList.m_highlight.Empty();
1208 m_LogList.SetItemState(m_LogList.GetSelectionMark(), 0, LVIS_SELECTED | LVIS_FOCUSED);
1209 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
1210 while (pos)
1212 int index = m_LogList.GetNextSelectedItem(pos);
1213 if (index >= 0)
1214 m_LogList.SetItemState(index, 0, LVIS_SELECTED);
1216 m_bNavigatingWithSelect = true;
1217 m_LogList.SetItemState(i, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
1218 m_LogList.SetSelectionMark(i);
1219 m_bNavigatingWithSelect = false;
1221 else
1222 m_LogList.m_highlight = gotoHash;
1223 m_LogList.Invalidate();
1224 return;
1227 if (i == static_cast<int>(m_LogList.m_arShownList.size()))
1229 CString msg;
1230 msg.Format(IDS_LOG_NOT_VISIBLE, static_cast<LPCWSTR>(gotoHash.ToString()));
1231 MessageBox(msg, L"TortoiseGit", MB_OK | MB_ICONINFORMATION);
1232 return;
1235 PlaySound(reinterpret_cast<LPCWSTR>(SND_ALIAS_SYSTEMASTERISK), nullptr, SND_ASYNC | SND_ALIAS_ID);
1238 void CLogDlg::OnBnClickedRefresh()
1240 Refresh (true);
1243 void CLogDlg::Refresh (bool clearfilter /*autoGoOnline*/)
1245 if (m_bSelect)
1246 DialogEnableWindow(IDOK, FALSE);
1247 m_LogList.m_LogFilter = std::make_shared<CLogDlgFilter>(m_sFilterText, m_bFilterWithRegex, static_cast<DWORD>(m_SelectedFilters), m_bFilterCaseSensitively);
1248 m_LogList.Refresh(clearfilter);
1249 if (clearfilter)
1250 m_sFilterText.Empty();
1251 EnableOKButton();
1252 ShowStartRef();
1253 FillLogMessageCtrl(false);
1256 void CLogDlg::SaveSplitterPos()
1258 if (!IsIconic())
1260 CRegDWORD regPos1(L"Software\\TortoiseGit\\TortoiseProc\\ResizableState\\LogDlgSizer1");
1261 CRegDWORD regPos2(L"Software\\TortoiseGit\\TortoiseProc\\ResizableState\\LogDlgSizer2");
1262 RECT rectSplitter;
1263 m_wndSplitter1.GetWindowRect(&rectSplitter);
1264 ScreenToClient(&rectSplitter);
1265 regPos1 = CDPIAware::Instance().UnscaleY(rectSplitter.top);
1266 m_wndSplitter2.GetWindowRect(&rectSplitter);
1267 ScreenToClient(&rectSplitter);
1268 regPos2 = CDPIAware::Instance().UnscaleY(rectSplitter.top);
1272 void CLogDlg::OnCancel()
1274 this->ShowWindow(SW_HIDE);
1276 // canceling means stopping the working thread if it's still running.
1277 m_LogList.SafeTerminateAsyncDiffThread();
1278 if (this->IsThreadRunning())
1280 m_LogList.SafeTerminateThread();
1282 UpdateData();
1284 SaveSplitterPos();
1285 __super::OnCancel();
1288 void CLogDlg::CopyChangedSelectionToClipBoard()
1290 POSITION posLogList = m_LogList.GetFirstSelectedItemPosition();
1291 if (posLogList == nullptr)
1292 return; // nothing is selected, get out of here
1294 CString sPaths;
1296 // CGitRev* pLogEntry = reinterpret_cast<CGitRev* >(m_LogList.m_arShownList.SafeGetAt(m_LogList.GetNextSelectedItem(pos)));
1297 // if (posLogList)
1299 auto locker(m_ChangedFileListCtrl.AcquireReadLock());
1300 POSITION pos = m_ChangedFileListCtrl.GetFirstSelectedItemPosition();
1301 while (pos)
1303 int nItem = m_ChangedFileListCtrl.GetNextSelectedItem(pos);
1304 auto path = m_ChangedFileListCtrl.GetListEntry(nItem);
1305 if (!path)
1306 continue;
1307 sPaths += path->GetGitPathString();
1308 sPaths += L"\r\n";
1311 #if 0
1312 else
1314 // only one revision is selected in the log dialog top pane
1315 // but multiple items could be selected in the changed items list
1316 POSITION pos = m_ChangedFileListCtrl.GetFirstSelectedItemPosition();
1317 while (pos)
1319 int nItem = m_ChangedFileListCtrl.GetNextSelectedItem(pos);
1320 LogChangedPath * changedlogpath = pLogEntry->pArChangedPaths->SafeGetAt(nItem);
1322 if ((m_cHidePaths.GetState() & 0x0003)==BST_CHECKED)
1324 // some items are hidden! So find out which item the user really selected
1325 INT_PTR selRealIndex = -1;
1326 for (INT_PTR hiddenindex=0; hiddenindex<pLogEntry->pArChangedPaths->GetCount(); ++hiddenindex)
1328 if (pLogEntry->pArChangedPaths->SafeGetAt(hiddenindex)->sPath.Left(m_sRelativeRoot.GetLength()).Compare(m_sRelativeRoot)==0)
1329 ++selRealIndex;
1330 if (selRealIndex == nItem)
1332 changedlogpath = pLogEntry->pArChangedPaths->SafeGetAt(hiddenindex);
1333 break;
1337 if (changedlogpath)
1339 sPaths += changedlogpath->sPath;
1340 sPaths += L"\r\n";
1344 #endif
1345 sPaths.Trim();
1346 CStringUtils::WriteAsciiStringToClipboard(sPaths, GetSafeHwnd());
1349 void CLogDlg::OnContextMenu(CWnd* pWnd, CPoint point)
1351 // we have 4 separate context menus:
1352 // one for the branch label in the upper left
1353 // one for FROM date control
1354 // one shown on the log message list control,
1355 // one shown in the changed-files list control
1356 if (pWnd == GetDlgItem(IDC_STATIC_REF))
1358 CIconMenu popup;
1359 if (!popup.CreatePopupMenu())
1360 return;
1362 int cnt = 0;
1363 popup.AppendMenuIcon(++cnt, IDS_MENUREFBROWSE);
1364 popup.SetDefaultItem(cnt);
1365 CString head = L"HEAD";
1366 CString curBranch = g_Git.GetCurrentBranch();
1367 if (!curBranch.IsEmpty())
1368 head.AppendFormat(L" -> \"%s\"", static_cast<LPCWSTR>(curBranch));
1369 popup.AppendMenuIcon(++cnt, head);
1370 CGitHash fetchHead;
1371 g_Git.GetHash(fetchHead, g_Git.FixBranchName(L"FETCH_HEAD"));
1372 popup.AppendMenuIcon(++cnt, L"FETCH_HEAD");
1373 popup.EnableMenuItem(cnt, fetchHead.IsEmpty());
1374 popup.AppendMenuIcon(++cnt, IDS_ALL);
1375 popup.EnableMenuItem(cnt, m_bFollowRenames);
1376 popup.AppendMenuIcon(++cnt, IDS_PROC_LOG_SELECT_BASIC_REFS);
1377 popup.EnableMenuItem(cnt, m_bFollowRenames);
1378 popup.AppendMenuIcon(++cnt, IDS_PROC_LOG_SELECT_LOCAL_BRANCHES);
1379 popup.EnableMenuItem(cnt, m_bFollowRenames);
1380 int offset = ++cnt;
1381 if (m_History.GetCount() > 0)
1383 popup.AppendMenu(MF_SEPARATOR, 0);
1384 for (size_t i = 0; i < m_History.GetCount(); ++i)
1386 CString entry = m_History.GetEntry(i);
1387 if (entry.GetLength() > 150)
1388 entry = entry.Left(150) + L"...";
1389 popup.AppendMenuIcon(cnt++, entry);
1393 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this);
1394 if (cmd == 0)
1395 return;
1396 else if (cmd == 1)
1398 OnBnClickedBrowseRef();
1399 return;
1402 m_LogList.m_ShowMask &= ~(CGit::LOG_INFO_ALL_BRANCH | CGit::LOG_INFO_BASIC_REFS | CGit::LOG_INFO_LOCAL_BRANCHES);
1403 m_bAllBranch = BST_UNCHECKED;
1404 m_AllBranchType = AllBranchType::None;
1405 if (cmd == 2)
1407 SetRange(g_Git.GetCurrentBranch(true));
1409 else if (cmd == 3)
1411 SetRange(fetchHead.ToString());
1413 else if (cmd == 4)
1415 m_bAllBranch = BST_CHECKED;
1416 m_AllBranchType = AllBranchType::AllBranches;
1417 m_LogList.m_ShowMask |= CGit::LOG_INFO_ALL_BRANCH;
1419 else if (cmd == 5)
1421 m_bAllBranch = BST_INDETERMINATE;
1422 m_AllBranchType = AllBranchType::AllBasicRefs;
1423 m_LogList.m_ShowMask |= CGit::LOG_INFO_BASIC_REFS;
1425 else if (cmd == 6)
1427 m_bAllBranch = BST_INDETERMINATE;
1428 m_AllBranchType = AllBranchType::AllLocalBranches;
1429 m_LogList.m_ShowMask |= CGit::LOG_INFO_LOCAL_BRANCHES;
1431 else if (cmd >= offset)
1433 SetRange(m_History.GetEntry(cmd - offset));
1434 m_History.AddEntry(m_LogList.m_sRange);
1435 m_History.Save();
1438 UpdateData(FALSE);
1440 OnRefresh();
1441 FillLogMessageCtrl(false);
1443 return;
1446 if (pWnd == GetDlgItem(IDC_DATEFROM))
1448 CIconMenu popup;
1449 if (!popup.CreatePopupMenu())
1450 return;
1452 int cnt = 0;
1454 popup.AppendMenuIcon(++cnt, IDS_NO_LIMIT);
1456 DWORD scale = CRegDWORD(L"Software\\TortoiseGit\\LogDialog\\NumberOfLogsScale", CFilterData::SHOW_NO_LIMIT);
1457 CString strScale;
1458 switch (scale)
1460 case CFilterData::SHOW_LAST_N_COMMITS:
1461 strScale.LoadString(IDS_LAST_N_COMMITS);
1462 break;
1463 case CFilterData::SHOW_LAST_N_YEARS:
1464 strScale.LoadString(IDS_LAST_N_YEARS);
1465 break;
1466 case CFilterData::SHOW_LAST_N_MONTHS:
1467 strScale.LoadString(IDS_LAST_N_MONTHS);
1468 break;
1469 case CFilterData::SHOW_LAST_N_WEEKS:
1470 strScale.LoadString(IDS_LAST_N_WEEKS);
1471 break;
1473 if (!strScale.IsEmpty() && m_LogList.m_Filter.m_NumberOfLogs > 0)
1475 popup.AppendMenu(MF_SEPARATOR, 0);
1476 CString number;
1477 number.Format(L"%ld", m_LogList.m_Filter.m_NumberOfLogs);
1478 CString item;
1479 item.Format(strScale, static_cast<LPCWSTR>(number));
1480 popup.AppendMenuIcon(++cnt, item);
1483 popup.AppendMenu(MF_SEPARATOR);
1484 popup.AppendMenuIcon(++cnt, IDS_CONFIGUREDEFAULT);
1486 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this);
1487 if (cmd <= 0)
1488 return;
1489 else if (cmd == 1)
1491 m_LogList.m_Filter.m_NumberOfLogsScale = CFilterData::SHOW_NO_LIMIT;
1492 // reset last selected date
1493 m_regLastSelectedFromDate.removeValue();
1495 else if (cmd == cnt) // last entry, must be before cmd >= 2
1497 CAppUtils::RunTortoiseGitProc(L"/command:settings /page:dialog");
1498 return;
1500 else if (cmd == 2)
1501 m_LogList.m_Filter.m_NumberOfLogsScale = scale;
1503 UpdateData(FALSE);
1504 OnRefresh();
1505 FillLogMessageCtrl(false);
1507 return;
1510 int selCount = m_LogList.GetSelectedCount();
1511 if ((selCount == 1)&&(pWnd == GetDlgItem(IDC_MSGVIEW)))
1513 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
1514 int selIndex = -1;
1515 if (pos)
1516 selIndex = m_LogList.GetNextSelectedItem(pos);
1518 GitRevLoglist* pRev = nullptr;
1519 if (selIndex >= 0)
1520 pRev = m_LogList.m_arShownList.SafeGetAt(selIndex);
1522 if ((point.x == -1) && (point.y == -1))
1524 CRect rect;
1525 GetDlgItem(IDC_MSGVIEW)->GetClientRect(&rect);
1526 ClientToScreen(&rect);
1527 point = rect.CenterPoint();
1529 CString sMenuItemText;
1530 CIconMenu popup;
1531 if (popup.CreatePopupMenu())
1533 long start = -1, end = -1;
1534 auto pEdit = static_cast<CRichEditCtrl*>(GetDlgItem(IDC_MSGVIEW));
1535 pEdit->GetSel(start, end);
1536 // add the 'default' entries
1537 popup.AppendMenuIcon(WM_COPY, IDS_SCIEDIT_COPY, IDI_COPYCLIP);
1538 if (start >= end)
1539 popup.EnableMenuItem(WM_COPY, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
1540 popup.AppendMenu(MF_SEPARATOR);
1541 sMenuItemText.LoadString(IDS_STATUSLIST_CONTEXT_COPYEXT);
1542 popup.AppendMenuIcon(EM_SETSEL, sMenuItemText, IDI_COPYCLIP);
1543 if (pRev && !pRev->m_CommitHash.IsEmpty())
1545 popup.AppendMenu(MF_SEPARATOR, NULL);
1546 sMenuItemText.LoadString(IDS_EDIT_NOTES);
1547 popup.AppendMenuIcon(CGitLogList::ID_EDITNOTE, sMenuItemText, IDI_EDIT);
1550 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this);
1551 switch (cmd)
1553 case 0:
1554 break; // no command selected
1555 case EM_SETSEL:
1557 pEdit->SetRedraw(FALSE);
1558 int oldLine = pEdit->GetFirstVisibleLine();
1559 pEdit->SetSel(0, -1);
1560 pEdit->Copy();
1561 pEdit->SetSel(start, end);
1562 int newLine = pEdit->GetFirstVisibleLine();
1563 pEdit->LineScroll(oldLine - newLine);
1564 pEdit->SetRedraw(TRUE);
1565 pEdit->RedrawWindow();
1567 break;
1568 case WM_COPY:
1569 ::SendMessage(GetDlgItem(IDC_MSGVIEW)->GetSafeHwnd(), cmd, 0, -1);
1570 break;
1571 case CGitLogList::ID_EDITNOTE:
1572 CAppUtils::EditNote(GetSafeHwnd(), pRev, &m_LogList.m_ProjectProperties);
1573 this->FillLogMessageCtrl(true);
1574 break;
1580 void CLogDlg::OnOK()
1582 // since the log dialog is also used to select revisions for other
1583 // dialogs, we have to do some work before closing this dialog
1584 if (GetFocus() != GetDlgItem(IDOK))
1585 return; // if the "OK" button doesn't have the focus, do nothing: this prevents closing the dialog when pressing enter
1587 m_LogList.SafeTerminateAsyncDiffThread();
1588 if (this->IsThreadRunning())
1590 m_LogList.SafeTerminateThread();
1592 UpdateData();
1593 // get the selected row(s)
1594 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
1595 while (pos)
1597 int selIndex = m_LogList.GetNextSelectedItem(pos);
1598 GitRev* pLogEntry = m_LogList.m_arShownList.SafeGetAt(selIndex);
1599 if (!pLogEntry)
1600 continue;
1601 m_sSelectedHash.push_back(pLogEntry->m_CommitHash);
1603 UpdateData(FALSE);
1604 SaveSplitterPos();
1605 __super::OnOK();
1607 #if 0
1608 if (!GetDlgItem(IDOK)->IsWindowVisible() && GetFocus() != GetDlgItem(IDCANCEL))
1609 return; // the Cancel button works as the OK button. But if the cancel button has not the focus, do nothing.
1611 CString temp;
1612 CString buttontext;
1613 GetDlgItemText(IDOK, buttontext);
1614 temp.LoadString(IDS_MSGBOX_CANCEL);
1615 if (temp.Compare(buttontext) != 0)
1616 __super::OnOK(); // only exit if the button text matches, and that will match only if the thread isn't running anymore
1617 m_selectedRevs.Clear();
1618 m_selectedRevsOneRange.Clear();
1619 if (m_pNotifyWindow)
1621 int selIndex = m_LogList.GetSelectionMark();
1622 if (selIndex >= 0)
1624 PLOGENTRYDATA pLogEntry = nullptr;
1625 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
1626 pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(m_LogList.GetNextSelectedItem(pos)));
1627 m_selectedRevs.AddRevision(pLogEntry->Rev);
1628 git_revnum_t lowerRev = pLogEntry->Rev;
1629 git_revnum_t higherRev = lowerRev;
1630 while (pos)
1632 pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(m_LogList.GetNextSelectedItem(pos)));
1633 git_revnum_t rev = pLogEntry->Rev;
1634 m_selectedRevs.AddRevision(pLogEntry->Rev);
1635 if (lowerRev > rev)
1636 lowerRev = rev;
1637 if (higherRev < rev)
1638 higherRev = rev;
1640 if (m_sFilterText.IsEmpty() && m_nSortColumn == 0 && IsSelectionContinuous())
1642 m_selectedRevsOneRange.AddRevRange(lowerRev, higherRev);
1644 BOOL bSentMessage = FALSE;
1645 if (m_LogList.GetSelectedCount() == 1)
1647 // if only one revision is selected, check if the path/url with which the dialog was started
1648 // was directly affected in that revision. If it was, then check if our path was copied from somewhere.
1649 // if it was copied, use the copy from revision as lowerRev
1650 if ((pLogEntry)&&(pLogEntry->pArChangedPaths)&&(lowerRev == higherRev))
1652 CString sUrl = m_path.GetGitPathString();
1653 if (!m_path.IsUrl())
1655 sUrl = GetURLFromPath(m_path);
1657 sUrl = sUrl.Mid(m_sRepositoryRoot.GetLength());
1658 for (int cp = 0; cp < pLogEntry->pArChangedPaths->GetCount(); ++cp)
1660 LogChangedPath * pData = pLogEntry->pArChangedPaths->SafeGetAt(cp);
1661 if (pData)
1663 if (sUrl.Compare(pData->sPath) == 0)
1665 if (!pData->sCopyFromPath.IsEmpty())
1667 lowerRev = pData->lCopyFromRev;
1668 m_pNotifyWindow->SendMessage(WM_REVSELECTED, m_wParam & (MERGE_REVSELECTSTART), lowerRev);
1669 m_pNotifyWindow->SendMessage(WM_REVSELECTED, m_wParam & (MERGE_REVSELECTEND), higherRev);
1670 m_pNotifyWindow->SendMessage(WM_REVLIST, m_selectedRevs.GetCount(), reinterpret_cast<LPARAM>(&m_selectedRevs));
1671 bSentMessage = TRUE;
1678 if ( !bSentMessage )
1680 m_pNotifyWindow->SendMessage(WM_REVSELECTED, m_wParam & (MERGE_REVSELECTSTART | MERGE_REVSELECTMINUSONE), lowerRev);
1681 m_pNotifyWindow->SendMessage(WM_REVSELECTED, m_wParam & (MERGE_REVSELECTEND | MERGE_REVSELECTMINUSONE), higherRev);
1682 m_pNotifyWindow->SendMessage(WM_REVLIST, m_selectedRevs.GetCount(), reinterpret_cast<LPARAM>(&m_selectedRevs));
1683 if (m_selectedRevsOneRange.GetCount())
1684 m_pNotifyWindow->SendMessage(WM_REVLISTONERANGE, 0, reinterpret_cast<LPARAM>(&m_selectedRevsOneRange));
1688 UpdateData();
1689 CRegDWORD reg(L"Software\\TortoiseGit\\ShowAllEntry");
1690 SaveSplitterPos();
1691 #endif
1694 void CLogDlg::OnPasteGitHash()
1696 if (!IsClipboardFormatAvailable(CF_TEXT))
1697 return;
1699 CClipboardHelper clipboardHelper;
1700 if (!clipboardHelper.Open(GetSafeHwnd()))
1701 return;
1703 HGLOBAL hClipboardData = GetClipboardData(CF_TEXT);
1704 if (!hClipboardData)
1705 return;
1707 auto pStr = static_cast<char*>(GlobalLock(hClipboardData));
1708 CString str(pStr);
1709 GlobalUnlock(hClipboardData);
1710 if (str.IsEmpty())
1711 return;
1713 int pos = 0;
1714 if (LookLikeGitHash(str, pos))
1715 JumpToGitHash(str);
1718 void CLogDlg::JumpToGitHash(CString hash)
1720 int prefixLen = hash.GetLength();
1721 while (hash.GetLength() < 2 * GIT_HASH_SIZE)
1722 hash += L'0';
1723 CGitHash prefixHash = CGitHash::FromHexStrTry(hash);
1724 // start searching downwards, because it's unlikely that a hash is a forward reference
1725 int currentPos = m_LogList.GetSelectionMark();
1726 int cnt = static_cast<int>(m_LogList.m_arShownList.size());
1727 if (!cnt || currentPos < 0)
1728 return;
1729 for (int i = currentPos + 1; i != currentPos; ++i)
1731 if (i >= cnt)
1733 i = -1;
1734 continue;
1737 GitRev* rev = m_LogList.m_arShownList.SafeGetAt(i);
1738 if (!rev) continue;
1739 if (!rev->m_CommitHash.MatchesPrefix(prefixHash, hash, prefixLen))
1740 continue;
1742 m_LogList.SetItemState(m_LogList.GetSelectionMark(), 0, LVIS_SELECTED | LVIS_FOCUSED);
1743 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
1744 while (pos)
1746 int index = m_LogList.GetNextSelectedItem(pos);
1747 if (index >= 0)
1748 m_LogList.SetItemState(index, 0, LVIS_SELECTED);
1750 m_LogList.EnsureVisible(i, FALSE);
1751 m_LogList.SetSelectionMark(i);
1752 m_LogList.SetItemState(i, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
1753 // refresh of selection needs to be queued instead of done immediately, to ensure hyperlinks in target selection are created
1754 PostMessage(WM_TGIT_REFRESH_SELECTION, 0, 0);
1755 return;
1758 FlashWindowEx(FLASHW_ALL, 3, 100);
1759 CMessageBox::ShowCheck(GetSafeHwnd(), IDS_PROC_LOG_JUMPNOTFOUND, IDS_APPNAME, 1, IDI_INFORMATION, IDS_OKBUTTON, 0, 0, L"NoJumpNotFoundWarning", IDS_MSGBOX_DONOTSHOWAGAIN);
1762 BOOL CLogDlg::PreTranslateMessage(MSG* pMsg)
1764 // Skip Ctrl-C when copying text out of the log message or search filter
1765 bool bSkipAccelerator = (pMsg->message == WM_KEYDOWN && (pMsg->wParam == 'C' || pMsg->wParam == VK_INSERT) && (GetFocus() == GetDlgItem(IDC_MSGVIEW) || GetFocus() == GetDlgItem(IDC_SEARCHEDIT) || GetFocus() == GetDlgItem(IDC_FILTER)) && GetKeyState(VK_CONTROL) & 0x8000);
1766 if (pMsg->message == WM_KEYDOWN && pMsg->wParam=='\r')
1768 if (GetFocus()==GetDlgItem(IDC_LOGLIST))
1770 if (CRegDWORD(L"Software\\TortoiseGit\\DiffByDoubleClickInLog", FALSE))
1772 m_LogList.DiffSelectedRevWithPrevious();
1773 return TRUE;
1776 if (GetFocus() == GetDlgItem(IDC_SEARCHEDIT))
1778 KillTimer(LOGFILTER_TIMER);
1779 Refresh(false);
1781 if (GetFocus() == GetDlgItem(IDC_FILTER))
1783 KillTimer(FILEFILTER_TIMER);
1784 FillLogMessageCtrl();
1785 return TRUE;
1788 else if (pMsg->message == WM_KEYDOWN && ((pMsg->wParam == 'V' && GetKeyState(VK_CONTROL) < 0) || (pMsg->wParam == VK_INSERT && GetKeyState(VK_SHIFT) < 0 && GetKeyState(VK_CONTROL) >= 0)) && GetKeyState(VK_MENU) >= 0)
1790 if (GetFocus() != GetDlgItem(IDC_SEARCHEDIT) && GetFocus() != GetDlgItem(IDC_FILTER))
1792 OnPasteGitHash();
1793 return TRUE;
1796 else if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_ESCAPE && GetFocus() == GetDlgItem(IDC_FILTER) && m_cFileFilter.GetWindowTextLength())
1798 m_cFileFilter.SetWindowText(L"");
1799 KillTimer(FILEFILTER_TIMER);
1800 FillLogMessageCtrl();
1801 return TRUE;
1803 else if (pMsg->message == WM_XBUTTONUP)
1805 bool select = (pMsg->wParam & MK_SHIFT) == 0;
1806 if (HIWORD(pMsg->wParam) & XBUTTON1)
1807 GoBackForward(select, false);
1808 if (HIWORD(pMsg->wParam) & XBUTTON2)
1809 GoBackForward(select, true);
1810 if (HIWORD(pMsg->wParam) & (XBUTTON1 | XBUTTON2))
1811 return TRUE;
1813 if (m_hAccel && !bSkipAccelerator)
1815 int ret = TranslateAccelerator(m_hWnd, m_hAccel, pMsg);
1816 if (ret)
1817 return TRUE;
1820 return __super::PreTranslateMessage(pMsg);
1824 BOOL CLogDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
1826 //if (this->IsThreadRunning())
1827 if(m_LogList.m_bNoDispUpdates)
1829 // only show the wait cursor over the list control
1830 if ((pWnd)&&
1831 ((pWnd == GetDlgItem(IDC_LOGLIST))||
1832 (pWnd == GetDlgItem(IDC_MSGVIEW))||
1833 (pWnd == GetDlgItem(IDC_LOGMSG))))
1835 HCURSOR hCur = LoadCursor(nullptr, IDC_WAIT);
1836 SetCursor(hCur);
1837 return TRUE;
1840 if (pWnd && (pWnd == GetDlgItem(IDC_MSGVIEW) || pWnd == GetDlgItem(IDC_SEARCHEDIT) || pWnd == GetDlgItem(IDC_FILTER) || pWnd == GetDlgItem(IDC_LOGINFO)))
1841 return CResizableStandAloneDialog::OnSetCursor(pWnd, nHitTest, message);
1843 HCURSOR hCur = LoadCursor(nullptr, IDC_ARROW);
1844 SetCursor(hCur);
1845 return CResizableStandAloneDialog::OnSetCursor(pWnd, nHitTest, message);
1848 void CLogDlg::OnLvnItemchangedLoglist(NMHDR *pNMHDR, LRESULT *pResult)
1850 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1851 *pResult = 0;
1852 //if (this->IsThreadRunning())
1853 if(m_LogList.m_bNoDispUpdates)
1854 return;
1855 if (pNMLV->iItem >= 0)
1857 if (!m_LogList.m_highlight.IsEmpty())
1859 m_LogList.m_highlight.Empty();
1860 m_LogList.Invalidate();
1862 this->m_LogList.m_nSearchIndex = pNMLV->iItem;
1863 GitRev* pLogEntry = m_LogList.m_arShownList.SafeGetAt(pNMLV->iItem);
1864 if (pLogEntry == nullptr)
1865 return;
1866 m_LogList.m_lastSelectedHash = pLogEntry->m_CommitHash;
1867 if (pNMLV->iSubItem != 0)
1868 return;
1869 if (pNMLV->iItem == static_cast<int>(m_LogList.m_arShownList.size()))
1871 // remove the selected state
1872 if (pNMLV->uChanged & LVIF_STATE)
1874 m_LogList.SetItemState(pNMLV->iItem, 0, LVIS_SELECTED);
1875 FillLogMessageCtrl();
1876 UpdateData(FALSE);
1877 UpdateLogInfoLabel();
1879 return;
1881 if (pNMLV->uChanged & LVIF_STATE)
1883 if ((pNMLV->uNewState & LVIS_SELECTED) && !m_bNavigatingWithSelect)
1885 m_LogList.m_selectionHistory.Add(m_LogList.m_lastSelectedHash);
1886 m_LogList.m_lastSelectedHash = pLogEntry->m_CommitHash;
1888 FillLogMessageCtrl();
1889 UpdateData(FALSE);
1892 else
1894 m_LogList.m_lastSelectedHash.Empty();
1895 FillLogMessageCtrl();
1896 UpdateData(FALSE);
1898 EnableOKButton();
1899 if (pNMLV->iItem < 0)
1900 return;
1901 UpdateLogInfoLabel();
1904 void CLogDlg::OnLvnItemchangedLogmsg(NMHDR * /*pNMHDR*/, LRESULT * /*pResult*/)
1906 if (m_ChangedFileListCtrl.IsBusy())
1907 return;
1908 UpdateLogInfoLabel();
1911 void CLogDlg::OnEnLinkMsgview(NMHDR *pNMHDR, LRESULT *pResult)
1913 // similar code in ProgressDlg.cpp and SyncDlg.cpp
1914 ENLINK *pEnLink = reinterpret_cast<ENLINK *>(pNMHDR);
1915 if ((pEnLink->msg == WM_LBUTTONUP) || (pEnLink->msg == WM_SETCURSOR))
1917 auto pEdit = reinterpret_cast<CRichEditCtrl*>(GetDlgItem(IDC_MSGVIEW));
1918 CHARRANGE selRange;
1919 pEdit->GetSel(selRange);
1920 bool hasSelection = (selRange.cpMax != selRange.cpMin);
1922 CString url, msg;
1923 GetDlgItemText(IDC_MSGVIEW, msg);
1924 msg.Replace(L"\r\n", L"\n");
1925 url = msg.Mid(pEnLink->chrg.cpMin, pEnLink->chrg.cpMax-pEnLink->chrg.cpMin);
1926 auto findResult = m_LogList.m_ProjectProperties.FindBugIDPositions(msg);
1927 if (std::any_of(findResult.cbegin(), findResult.cend(),
1928 [=] (const CHARRANGE &cr) -> bool { return cr.cpMin == pEnLink->chrg.cpMin && cr.cpMax == pEnLink->chrg.cpMax; }
1931 url = m_LogList.m_ProjectProperties.GetBugIDUrl(url);
1932 url = GetAbsoluteUrlFromRelativeUrl(url);
1934 // check if it's an email address
1935 auto atpos = url.Find(L'@');
1936 if ((atpos > 0) && (url.ReverseFind(L'.') > atpos) && !::PathIsURL(url))
1937 url = L"mailto:" + url;
1938 if (::PathIsURL(url))
1940 if (pEnLink->msg == WM_LBUTTONUP)
1942 if (!hasSelection)
1943 ShellExecute(GetSafeHwnd(), L"open", url, nullptr, nullptr, SW_SHOWDEFAULT);
1945 else
1947 static RECT prevRect = { 0 };
1948 CWnd * pMsgView = GetDlgItem(IDC_MSGVIEW);
1949 if (pMsgView)
1951 RECT rc;
1952 POINTL pt;
1953 pMsgView->SendMessage(EM_POSFROMCHAR, reinterpret_cast<WPARAM>(&pt), pEnLink->chrg.cpMin);
1954 rc.left = pt.x;
1955 rc.top = pt.y;
1956 pMsgView->SendMessage(EM_POSFROMCHAR, reinterpret_cast<WPARAM>(&pt), pEnLink->chrg.cpMax);
1957 rc.right = pt.x;
1958 rc.bottom = pt.y + 12;
1959 if ((prevRect.left != rc.left) || (prevRect.top != rc.top))
1961 m_tooltips.DelTool(pMsgView, 1);
1962 m_tooltips.AddTool(pMsgView, url, &rc, 1);
1963 prevRect = rc;
1968 else if(pEnLink->msg == WM_LBUTTONUP && !hasSelection)
1970 int pos = 0;
1971 if (LookLikeGitHash(url, pos))
1972 JumpToGitHash(url);
1975 *pResult = 0;
1978 void CLogDlg::OnBnClickedStatbutton()
1980 if (this->IsThreadRunning())
1981 return;
1983 if (m_LogList.m_arShownList.empty() || m_LogList.m_arShownList.size() == 1 && m_LogList.m_bShowWC)
1984 return; // nothing or just the working copy changes are shown, so no statistics.
1986 CStatGraphDlg dlg;
1987 dlg.m_ShowList.reserve(m_LogList.m_arShownList.size());
1988 for (size_t i = m_LogList.m_bShowWC ? 1 : 0; i < m_LogList.m_arShownList.size(); ++i)
1989 dlg.m_ShowList.emplace_back(m_LogList.m_arShownList.SafeGetAt(i));
1991 dlg.m_path = m_orgPath;
1992 dlg.DoModal();
1995 void CLogDlg::MoveToSameTop(CWnd *pWndRef, CWnd *pWndTarget)
1997 CRect rcWndPicAuthor, rcWndMsgView;
1998 pWndRef->GetWindowRect(rcWndMsgView);
1999 ScreenToClient(rcWndMsgView);
2000 pWndTarget->GetWindowRect(rcWndPicAuthor);
2001 ScreenToClient(rcWndPicAuthor);
2002 int diff = rcWndMsgView.top - rcWndPicAuthor.top;
2003 rcWndPicAuthor.top += diff;
2004 rcWndPicAuthor.bottom += diff;
2005 pWndTarget->MoveWindow(rcWndPicAuthor);
2008 void CLogDlg::DoSizeV1(int delta)
2010 RemoveMainAnchors();
2012 // first, reduce the middle section to a minimum.
2013 // if that is not sufficient, minimize the lower section
2015 CRect changeListViewRect;
2016 m_ChangedFileListCtrl.GetClientRect(changeListViewRect);
2017 CRect messageViewRect;
2018 GetDlgItem(IDC_MSGVIEW)->GetClientRect(messageViewRect);
2020 int messageViewDelta = max(-delta, CDPIAware::Instance().ScaleY(20) - messageViewRect.Height());
2021 int changeFileListDelta = -delta - messageViewDelta;
2023 // set new sizes & positions
2024 auto hdwp = BeginDeferWindowPos(5);
2025 hdwp = CSplitterControl::ChangeRect(hdwp, &m_LogList, 0, 0, 0, delta);
2026 hdwp = CSplitterControl::ChangeRect(hdwp, GetDlgItem(IDC_MSGVIEW), 0, delta, 0, delta + messageViewDelta);
2027 MoveToSameTop(GetDlgItem(IDC_MSGVIEW), GetDlgItem(IDC_PIC_AUTHOR));
2028 hdwp = CSplitterControl::ChangeRect(hdwp, GetDlgItem(IDC_SPLITTERBOTTOM), 0, -changeFileListDelta, 0, -changeFileListDelta);
2029 hdwp = CSplitterControl::ChangeRect(hdwp, &m_ChangedFileListCtrl, 0, -changeFileListDelta, 0, 0);
2030 EndDeferWindowPos(hdwp);
2032 AddMainAnchors();
2033 ArrangeLayout();
2034 SetSplitterRange();
2035 m_LogList.Invalidate();
2036 m_ChangedFileListCtrl.Invalidate();
2037 GetDlgItem(IDC_MSGVIEW)->Invalidate();
2038 m_gravatar.Invalidate();
2041 void CLogDlg::DoSizeV2(int delta)
2043 RemoveMainAnchors();
2045 // first, reduce the middle section to a minimum.
2046 // if that is not sufficient, minimize the top section
2048 CRect logViewRect;
2049 m_LogList.GetClientRect(logViewRect);
2050 CRect messageViewRect;
2051 GetDlgItem(IDC_MSGVIEW)->GetClientRect(messageViewRect);
2053 int messageViewDelta = max(delta, CDPIAware::Instance().ScaleY(20) - messageViewRect.Height());
2054 int logListDelta = delta - messageViewDelta;
2056 // set new sizes & positions
2057 auto hdwp = BeginDeferWindowPos(5);
2058 hdwp = CSplitterControl::ChangeRect(hdwp, &m_LogList, 0, 0, 0, logListDelta);
2059 hdwp = CSplitterControl::ChangeRect(hdwp, GetDlgItem(IDC_SPLITTERTOP), 0, logListDelta, 0, logListDelta);
2060 hdwp = CSplitterControl::ChangeRect(hdwp, GetDlgItem(IDC_MSGVIEW), 0, logListDelta, 0, logListDelta + messageViewDelta);
2061 MoveToSameTop(GetDlgItem(IDC_MSGVIEW), GetDlgItem(IDC_PIC_AUTHOR));
2062 hdwp = CSplitterControl::ChangeRect(hdwp, &m_ChangedFileListCtrl, 0, delta, 0, 0);
2063 EndDeferWindowPos(hdwp);
2065 AddMainAnchors();
2066 ArrangeLayout();
2067 SetSplitterRange();
2068 GetDlgItem(IDC_MSGVIEW)->Invalidate();
2069 m_ChangedFileListCtrl.Invalidate();
2070 m_gravatar.Invalidate();
2073 void CLogDlg::AddMainAnchors()
2075 AddAnchor(IDC_STATIC_REF, TOP_LEFT);
2076 AddAnchor(IDC_FROMLABEL, TOP_LEFT);
2077 AddAnchor(IDC_DATEFROM, TOP_LEFT);
2078 AddAnchor(IDC_TOLABEL, TOP_LEFT);
2079 AddAnchor(IDC_DATETO, TOP_LEFT);
2081 AddAnchor(IDC_SEARCHEDIT, TOP_LEFT, TOP_RIGHT);
2082 AddAnchor(IDC_LOG_JUMPTYPE, TOP_RIGHT);
2083 AddAnchor(IDC_LOG_JUMPUP, TOP_RIGHT);
2084 AddAnchor(IDC_LOG_JUMPDOWN, TOP_RIGHT);
2086 AddAnchor(IDC_LOGLIST, TOP_LEFT, MIDDLE_RIGHT);
2087 AddAnchor(IDC_SPLITTERTOP, MIDDLE_LEFT, MIDDLE_RIGHT);
2088 AddAnchor(IDC_MSGVIEW, MIDDLE_LEFT, MIDDLE_RIGHT); // keep in sync with ShowGravatar()
2089 AddAnchor(IDC_PIC_AUTHOR, MIDDLE_RIGHT);
2090 AddAnchor(IDC_SPLITTERBOTTOM, MIDDLE_LEFT, MIDDLE_RIGHT);
2091 AddAnchor(IDC_LOGMSG, MIDDLE_LEFT, BOTTOM_RIGHT);
2093 AddAnchor(IDC_LOGINFO, BOTTOM_LEFT, BOTTOM_RIGHT);
2094 AddAnchor(IDC_WALKBEHAVIOUR, BOTTOM_LEFT);
2095 AddAnchor(IDC_VIEW, BOTTOM_LEFT);
2096 AddAnchor(IDC_LOG_ALLBRANCH, BOTTOM_LEFT);
2097 AddAnchor(IDC_FILTER, BOTTOM_LEFT, BOTTOM_RIGHT);
2098 AddAnchor(IDC_WHOLE_PROJECT, BOTTOM_LEFT);
2099 AddAnchor(IDC_REFRESH, BOTTOM_LEFT);
2100 AddAnchor(IDC_STATBUTTON, BOTTOM_LEFT);
2101 AddAnchor(IDC_PROGRESS, BOTTOM_LEFT, BOTTOM_RIGHT);
2102 AddAnchor(IDOK, BOTTOM_RIGHT);
2103 AddAnchor(IDCANCEL, BOTTOM_RIGHT);
2104 AddAnchor(IDHELP, BOTTOM_RIGHT);
2107 void CLogDlg::RemoveMainAnchors()
2109 RemoveAnchor(IDC_STATIC_REF);
2110 RemoveAnchor(IDC_FROMLABEL);
2111 RemoveAnchor(IDC_DATEFROM);
2112 RemoveAnchor(IDC_TOLABEL);
2113 RemoveAnchor(IDC_DATETO);
2115 RemoveAnchor(IDC_SEARCHEDIT);
2116 RemoveAnchor(IDC_LOG_JUMPTYPE);
2117 RemoveAnchor(IDC_LOG_JUMPUP);
2118 RemoveAnchor(IDC_LOG_JUMPDOWN);
2120 RemoveAnchor(IDC_LOGLIST);
2121 RemoveAnchor(IDC_SPLITTERTOP);
2122 RemoveAnchor(IDC_MSGVIEW);
2123 RemoveAnchor(IDC_PIC_AUTHOR);
2124 RemoveAnchor(IDC_SPLITTERBOTTOM);
2125 RemoveAnchor(IDC_LOGMSG);
2127 RemoveAnchor(IDC_LOGINFO);
2128 RemoveAnchor(IDC_WALKBEHAVIOUR);
2129 RemoveAnchor(IDC_VIEW);
2130 RemoveAnchor(IDC_LOG_ALLBRANCH);
2131 RemoveAnchor(IDC_FILTER);
2132 RemoveAnchor(IDC_WHOLE_PROJECT);
2133 RemoveAnchor(IDC_REFRESH);
2134 RemoveAnchor(IDC_STATBUTTON);
2135 RemoveAnchor(IDC_PROGRESS);
2136 RemoveAnchor(IDOK);
2137 RemoveAnchor(IDCANCEL);
2138 RemoveAnchor(IDHELP);
2141 LRESULT CLogDlg::DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam)
2143 switch (message) {
2144 case WM_NOTIFY:
2145 if (wParam == IDC_SPLITTERTOP)
2147 auto pHdr = reinterpret_cast<SPC_NMHDR*>(lParam);
2148 DoSizeV1(pHdr->delta);
2150 else if (wParam == IDC_SPLITTERBOTTOM)
2152 auto pHdr = reinterpret_cast<SPC_NMHDR*>(lParam);
2153 DoSizeV2(pHdr->delta);
2155 break;
2158 return CResizableDialog::DefWindowProc(message, wParam, lParam);
2161 void CLogDlg::SetSplitterRange()
2163 if ((m_LogList)&&(m_ChangedFileListCtrl))
2165 CRect rcTop;
2166 m_LogList.GetWindowRect(rcTop);
2167 ScreenToClient(rcTop);
2169 CRect rcBottom;
2170 m_ChangedFileListCtrl.GetWindowRect(rcBottom);
2171 ScreenToClient(rcBottom);
2173 m_wndSplitter1.SetRange(rcTop.top + MIN_CTRL_HEIGHT, rcBottom.bottom - (2 * MIN_CTRL_HEIGHT + MIN_SPLITTER_HEIGHT));
2174 m_wndSplitter2.SetRange(rcTop.top + (2 * MIN_CTRL_HEIGHT + MIN_SPLITTER_HEIGHT), rcBottom.bottom - MIN_CTRL_HEIGHT);
2178 void CLogDlg::OnEnscrollMsgview()
2180 m_tooltips.DelTool(GetDlgItem(IDC_MSGVIEW), 1);
2183 LRESULT CLogDlg::OnClickedInfoIcon(WPARAM wParam, LPARAM lParam)
2185 if (reinterpret_cast<HWND>(wParam) == m_cFileFilter.GetSafeHwnd())
2186 return 0;
2188 // FIXME: x64 version would get this function called with unexpected parameters.
2189 if (!lParam)
2190 return 0;
2192 auto rect = reinterpret_cast<LPRECT>(lParam);
2193 CPoint point;
2194 CString temp;
2195 point = CPoint(rect->left, rect->bottom);
2196 #define LOGMENUFLAGS(x) (MF_STRING | MF_ENABLED | ((m_SelectedFilters & x) ? MF_CHECKED : MF_UNCHECKED))
2197 CMenu popup;
2198 if (popup.CreatePopupMenu())
2200 temp.LoadString(IDS_LOG_FILTER_SUBJECT);
2201 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_SUBJECT), LOGFILTER_SUBJECT, temp);
2203 temp.LoadString(IDS_LOG_FILTER_MESSAGES);
2204 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_MESSAGES), LOGFILTER_MESSAGES, temp);
2206 temp.LoadString(IDS_LOG_FILTER_PATHS);
2207 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_PATHS), LOGFILTER_PATHS, temp);
2209 temp.LoadString(IDS_LOG_FILTER_AUTHORS);
2210 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_AUTHORS), LOGFILTER_AUTHORS, temp);
2212 temp.LoadString(IDS_LOG_FILTER_EMAILS);
2213 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_EMAILS), LOGFILTER_EMAILS, temp);
2215 temp.LoadString(IDS_LOG_FILTER_REVS);
2216 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REVS), LOGFILTER_REVS, temp);
2218 temp.LoadString(IDS_LOG_FILTER_REFNAME);
2219 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REFNAME), LOGFILTER_REFNAME, temp);
2221 temp.LoadString(IDS_PROC_LOG_TAGINFO);
2222 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_ANNOTATEDTAG), LOGFILTER_ANNOTATEDTAG, temp);
2224 temp.LoadString(IDS_NOTES);
2225 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_NOTES), LOGFILTER_NOTES, temp);
2227 if (m_LogList.m_bShowBugtraqColumn == TRUE) {
2228 temp.LoadString(IDS_LOG_FILTER_BUGIDS);
2229 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_BUGID), LOGFILTER_BUGID, temp);
2232 popup.AppendMenu(MF_SEPARATOR, NULL);
2234 temp.LoadString(IDS_LOG_FILTER_TOGGLE);
2235 popup.AppendMenu(MF_STRING | MF_ENABLED, LOGFILTER_TOGGLE, temp);
2237 temp.LoadString(IDS_ALL);
2238 popup.AppendMenu(MF_STRING | MF_ENABLED, LOGFILTER_ALL, temp);
2240 popup.AppendMenu(MF_SEPARATOR, NULL);
2242 temp.LoadString(IDS_LOG_FILTER_REGEX);
2243 popup.AppendMenu(MF_STRING | MF_ENABLED | (m_bFilterWithRegex ? MF_CHECKED : MF_UNCHECKED), LOGFILTER_REGEX, temp);
2245 temp.LoadString(IDS_LOG_FILTER_CASESENSITIVE);
2246 popup.AppendMenu(MF_STRING | MF_ENABLED | (m_bFilterCaseSensitively ? MF_CHECKED : MF_UNCHECKED), LOGFILTER_CASE, temp);
2248 m_tooltips.Pop();
2249 int selection = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this);
2250 if (selection != 0)
2252 if (selection == LOGFILTER_REGEX)
2254 m_bFilterWithRegex = !m_bFilterWithRegex;
2255 CRegDWORD b(L"Software\\TortoiseGit\\UseRegexFilter", FALSE);
2256 b = m_bFilterWithRegex;
2257 SetFilterCueText();
2258 CheckRegexpTooltip();
2259 m_cFilter.ValidateAndRedraw();
2261 else if (selection == LOGFILTER_CASE)
2263 m_bFilterCaseSensitively = !m_bFilterCaseSensitively;
2264 CRegDWORD b(L"Software\\TortoiseGit\\FilterCaseSensitively", FALSE);
2265 b = m_bFilterCaseSensitively;
2267 else if (selection == LOGFILTER_TOGGLE)
2269 m_SelectedFilters = (~m_SelectedFilters) & LOGFILTER_ALL;
2270 SetFilterCueText();
2272 else if (selection == LOGFILTER_ALL)
2274 m_SelectedFilters = selection;
2275 SetFilterCueText();
2277 else
2279 m_SelectedFilters ^= selection;
2280 SetFilterCueText();
2282 CRegDWORD(L"Software\\TortoiseGit\\SelectedLogFilters") = m_SelectedFilters;
2283 // Reload only if a search text is entered
2284 if (m_LogList.m_LogFilter->IsFilterActive())
2285 SetTimer(LOGFILTER_TIMER, 1000, nullptr);
2288 return 0L;
2291 LRESULT CLogDlg::OnClickedCancelFilter(WPARAM wParam, LPARAM /*lParam*/)
2293 if (reinterpret_cast<HWND>(wParam) == m_cFileFilter.GetSafeHwnd())
2295 KillTimer(FILEFILTER_TIMER);
2297 FillLogMessageCtrl();
2298 return 0L;
2301 KillTimer(LOGFILTER_TIMER);
2303 m_sFilterText.Empty();
2304 UpdateData(FALSE);
2305 theApp.DoWaitCursor(1);
2306 FillLogMessageCtrl(false);
2308 Refresh(true);
2310 theApp.DoWaitCursor(-1);
2311 GetDlgItem(IDC_SEARCHEDIT)->ShowWindow(SW_HIDE);
2312 GetDlgItem(IDC_SEARCHEDIT)->ShowWindow(SW_SHOW);
2313 GetDlgItem(IDC_SEARCHEDIT)->SetFocus();
2314 UpdateLogInfoLabel();
2316 return 0L;
2320 void CLogDlg::SetFilterCueText()
2322 CString temp(MAKEINTRESOURCE(IDS_LOG_FILTER_BY));
2323 temp += L' ';
2325 if (m_SelectedFilters & LOGFILTER_SUBJECT)
2327 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_SUBJECT));
2330 if (m_SelectedFilters & LOGFILTER_MESSAGES)
2332 if (!CStringUtils::EndsWith(temp, L' '))
2333 temp += L", ";
2334 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_MESSAGES));
2337 if (m_SelectedFilters & LOGFILTER_PATHS)
2339 if (!CStringUtils::EndsWith(temp, L' '))
2340 temp += L", ";
2341 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_PATHS));
2344 if (m_SelectedFilters & LOGFILTER_AUTHORS)
2346 if (!CStringUtils::EndsWith(temp, L' '))
2347 temp += L", ";
2348 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_AUTHORS));
2351 if (m_SelectedFilters & LOGFILTER_EMAILS)
2353 if (!CStringUtils::EndsWith(temp, L' '))
2354 temp += L", ";
2355 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_EMAILS));
2358 if (m_SelectedFilters & LOGFILTER_REVS)
2360 if (!CStringUtils::EndsWith(temp, L' '))
2361 temp += L", ";
2362 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REVS));
2365 if (m_SelectedFilters & LOGFILTER_REFNAME)
2367 if (!CStringUtils::EndsWith(temp, L' '))
2368 temp += L", ";
2369 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REFNAME));
2372 if (m_SelectedFilters & LOGFILTER_NOTES)
2374 if (!CStringUtils::EndsWith(temp, L' '))
2375 temp += L", ";
2376 temp += CString(MAKEINTRESOURCE(IDS_NOTES));
2379 if (m_LogList.m_bShowBugtraqColumn && m_SelectedFilters & LOGFILTER_BUGID)
2381 if (!CStringUtils::EndsWith(temp, L' '))
2382 temp += L", ";
2383 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_BUGIDS));
2386 // to make the cue banner text appear more to the right of the edit control
2387 temp = L" " + temp;
2388 m_cFilter.SetCueBanner(temp.TrimRight());
2391 bool CLogDlg::Validate(LPCWSTR string)
2393 if (!m_bFilterWithRegex)
2394 return true;
2395 std::vector<std::wregex> pats;
2396 return m_LogList.m_LogFilter->ValidateRegexp(string, pats);
2399 void CLogDlg::OnEnChangeFileFilter()
2401 SetTimer(FILEFILTER_TIMER, 1000, nullptr);
2404 void CLogDlg::OnTimer(UINT_PTR nIDEvent)
2406 if (nIDEvent == LOGFTIME_TIMER)
2408 KillTimer(LOGFTIME_TIMER);
2409 Refresh(false);
2411 else if (nIDEvent == LOG_FILLPATCHVTIMER)
2412 FillPatchView();
2413 else if (nIDEvent == LOGFILTER_TIMER)
2415 KillTimer(LOGFILTER_TIMER);
2416 Refresh(false);
2418 #if 0
2419 /* we will use git built-in grep to filter log */
2420 if (this->IsThreadRunning())
2422 // thread still running! So just restart the timer.
2423 SetTimer(LOGFILTER_TIMER, 1000, nullptr);
2424 return;
2426 CWnd * focusWnd = GetFocus();
2427 bool bSetFocusToFilterControl = ((focusWnd != GetDlgItem(IDC_DATEFROM))&&(focusWnd != GetDlgItem(IDC_DATETO))
2428 && (focusWnd != GetDlgItem(IDC_LOGLIST)));
2429 if (m_LogList.m_sFilterText.IsEmpty())
2431 DialogEnableWindow(IDC_STATBUTTON, !(((this->IsThreadRunning())||(m_LogList.m_arShownList.IsEmpty()))));
2432 // do not return here!
2433 // we also need to run the filter if the filter text is empty:
2434 // 1. to clear an existing filter
2435 // 2. to rebuild the m_arShownList after sorting
2437 theApp.DoWaitCursor(1);
2438 CStoreSelection storeselection(this);
2439 KillTimer(LOGFILTER_TIMER);
2440 FillLogMessageCtrl(false);
2442 // now start filter the log list
2443 m_LogList.StartFilter();
2445 if ( m_LogList.GetItemCount()==1 )
2447 m_LogList.SetSelectionMark(0);
2448 m_LogList.SetItemState(0, LVIS_SELECTED, LVIS_SELECTED);
2450 theApp.DoWaitCursor(-1);
2451 GetDlgItem(IDC_SEARCHEDIT)->ShowWindow(SW_HIDE);
2452 GetDlgItem(IDC_SEARCHEDIT)->ShowWindow(SW_SHOW);
2453 if (bSetFocusToFilterControl)
2454 GetDlgItem(IDC_SEARCHEDIT)->SetFocus();
2455 UpdateLogInfoLabel();
2456 #endif
2457 } // if (nIDEvent == LOGFILTER_TIMER)
2458 else if (nIDEvent == LOG_HEADER_ORDER_TIMER)
2460 KillTimer(LOG_HEADER_ORDER_TIMER);
2461 CLogOrdering orderDlg;
2462 if (orderDlg.DoModal() == IDOK)
2463 Refresh();
2465 else if (nIDEvent == FILEFILTER_TIMER)
2467 KillTimer(FILEFILTER_TIMER);
2468 FillLogMessageCtrl();
2470 DialogEnableWindow(IDC_STATBUTTON, !((IsThreadRunning() || (m_LogList.m_arShownList.empty() || m_LogList.m_arShownList.size() == 1 && m_LogList.m_bShowWC))));
2471 __super::OnTimer(nIDEvent);
2474 void CLogDlg::OnDtnDatetimechangeDateto(NMHDR * /*pNMHDR*/, LRESULT *pResult)
2478 COleDateTime _time;
2479 m_DateTo.GetTime(_time);
2481 COleDateTime fromTime;
2482 m_DateFrom.GetTime(fromTime);
2483 if (_time < fromTime)
2485 _time = fromTime;
2486 m_DateTo.SetTime(_time);
2489 CTime time = (_time < COleDateTime((time_t)0)) ? CTime(0) : CTime(_time.GetYear(), _time.GetMonth(), _time.GetDay(), 23, 59, 59);
2490 if (time.GetTime() != m_LogList.m_Filter.m_To)
2492 m_LogList.m_Filter.m_To = time.GetTime();
2493 SetTimer(LOGFTIME_TIMER, 10, nullptr);
2496 catch (...)
2498 CMessageBox::Show(GetSafeHwnd(), L"Invalidate Parameter", L"TortoiseGit", MB_OK | MB_ICONERROR);
2501 *pResult = 0;
2504 void CLogDlg::OnDtnDatetimechangeDatefrom(NMHDR * /*pNMHDR*/, LRESULT *pResult)
2508 COleDateTime _time;
2509 m_DateFrom.GetTime(_time);
2511 COleDateTime toTime;
2512 m_DateTo.GetTime(toTime);
2513 if (_time > toTime)
2515 _time = toTime;
2516 m_DateFrom.SetTime(_time);
2519 CTime time = (_time < COleDateTime((time_t)0)) ? CTime(0) : CTime(_time.GetYear(), _time.GetMonth(), _time.GetDay(), 0, 0, 0);
2520 if (time.GetTime() != m_LogList.m_Filter.m_From)
2522 m_LogList.m_Filter.m_From = time.GetTime();
2523 m_LogList.m_Filter.m_NumberOfLogsScale = CFilterData::SHOW_LAST_SEL_DATE;
2525 if (CFilterData::SHOW_LAST_SEL_DATE == static_cast<DWORD>(CRegDWORD(L"Software\\TortoiseGit\\LogDialog\\NumberOfLogsScale", CFilterData::SHOW_NO_LIMIT)))
2526 m_regLastSelectedFromDate = time.Format(L"%Y-%m-%d");
2528 SetTimer(LOGFTIME_TIMER, 10, nullptr);
2531 catch (...)
2533 CMessageBox::Show(GetSafeHwnd(), L"Invalidate Parameter", L"TortoiseGit", MB_OK | MB_ICONERROR);
2536 *pResult = 0;
2539 void CLogDlg::OnCbnSelchangeJumpType()
2541 // reserved for future use
2544 void CLogDlg::OnBnClickedJumpUp()
2546 int sel = m_JumpType.GetCurSel();
2547 if (sel < 0) return;
2548 JumpType jumpType = static_cast<JumpType>(sel);
2550 if (jumpType == JumpType_History)
2552 GoBack();
2553 return;
2556 CString strValue;
2557 CGitHash hashValue;
2558 int index = -1;
2559 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
2560 if (pos)
2562 index = m_LogList.GetNextSelectedItem(pos);
2563 if (index == 0) return;
2565 GitRev* data = m_LogList.m_arShownList.SafeGetAt(index);
2566 if (jumpType == JumpType_AuthorEmail)
2567 strValue = data->GetAuthorEmail();
2568 else if (jumpType == JumpType_CommitterEmail)
2569 strValue = data->GetCommitterEmail();
2570 else if (jumpType == JumpType_Parent1)
2571 hashValue = data->m_CommitHash;
2572 else if (jumpType == JumpType_Parent2)
2573 hashValue = data->m_CommitHash;
2574 else if (jumpType == JumpType_TagFF)
2575 hashValue = data->m_CommitHash;
2576 else if (jumpType == JumpType_BranchFF)
2577 hashValue = data->m_CommitHash;
2579 m_LogList.SetItemState(index, 0, LVIS_SELECTED);
2581 else
2582 return;
2584 while (pos)
2586 index = m_LogList.GetNextSelectedItem(pos);
2587 m_LogList.SetItemState(index, 0, LVIS_SELECTED);
2589 m_LogList.SetSelectionMark(-1);
2591 auto hashMapSharedPtr = m_LogList.m_HashMap;
2592 const auto& hashMap = *hashMapSharedPtr;
2594 for (int i = index - 1; i >= 0; i--)
2596 bool found = false;
2597 GitRev* data = m_LogList.m_arShownList.SafeGetAt(i);
2598 if (jumpType == JumpType_AuthorEmail)
2599 found = strValue == data->GetAuthorEmail();
2600 else if (jumpType == JumpType_CommitterEmail)
2601 found = strValue == data->GetCommitterEmail();
2602 else if (jumpType == JumpType_MergePoint)
2603 found = data->ParentsCount() > 1;
2604 else if (jumpType == JumpType_Parent1)
2606 if (!data->m_ParentHash.empty())
2607 found = data->m_ParentHash[0] == hashValue;
2609 else if (jumpType == JumpType_Parent2)
2611 if (data->m_ParentHash.size() > 1)
2612 found = data->m_ParentHash[1] == hashValue;
2614 else if (jumpType == JumpType_Tag || jumpType == JumpType_TagFF)
2616 if (auto refList = hashMap.find(data->m_CommitHash); refList != hashMap.cend())
2617 found = any_of((*refList).second, [](const auto& ref) { return CStringUtils::StartsWith(ref, L"refs/tags/"); });
2619 if (found && jumpType == JumpType_TagFF)
2620 found = g_Git.IsFastForward(hashValue.ToString(), data->m_CommitHash.ToString());
2622 else if (jumpType == JumpType_Branch || jumpType == JumpType_BranchFF)
2624 if (auto refList = hashMap.find(data->m_CommitHash); refList != hashMap.cend())
2625 found = any_of((*refList).second, [](const auto& ref) { return CStringUtils::StartsWith(ref, L"refs/heads/") || CStringUtils::StartsWith(ref, L"refs/remotes/"); });
2627 if (found && jumpType == JumpType_BranchFF)
2628 found = g_Git.IsFastForward(hashValue.ToString(), data->m_CommitHash.ToString());
2631 if (found)
2633 m_LogList.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
2634 m_LogList.EnsureVisible(i, FALSE);
2635 m_LogList.SetSelectionMark(i);
2636 return;
2640 CMessageBox::ShowCheck(GetSafeHwnd(), IDS_PROC_LOG_JUMPNOTFOUND, IDS_APPNAME, 1, IDI_INFORMATION, IDS_OKBUTTON, 0, 0, L"NoJumpNotFoundWarning", IDS_MSGBOX_DONOTSHOWAGAIN);
2643 void CLogDlg::OnBnClickedJumpDown()
2645 int jumpType = m_JumpType.GetCurSel();
2646 if (jumpType < 0) return;
2648 if (jumpType == JumpType_History)
2650 GoForward();
2651 return;
2654 CString strValue;
2655 CGitHash hashValue;
2656 int index = -1;
2657 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
2658 if (pos)
2660 index = m_LogList.GetNextSelectedItem(pos);
2661 if (index == 0) return;
2663 GitRev* data = m_LogList.m_arShownList.SafeGetAt(index);
2664 if (jumpType == JumpType_AuthorEmail)
2665 strValue = data->GetAuthorEmail();
2666 else if (jumpType == JumpType_CommitterEmail)
2667 strValue = data->GetCommitterEmail();
2668 else if (jumpType == JumpType_Parent1)
2670 if (!data->m_ParentHash.empty())
2671 hashValue = data->m_ParentHash.at(0);
2672 else
2673 return;
2675 else if (jumpType == JumpType_Parent2)
2677 if (data->m_ParentHash.size() > 1)
2678 hashValue = data->m_ParentHash.at(1);
2679 else
2680 return;
2682 else if (jumpType == JumpType_TagFF)
2683 hashValue = data->m_CommitHash;
2684 else if (jumpType == JumpType_BranchFF)
2685 hashValue = data->m_CommitHash;
2687 m_LogList.SetItemState(index, 0, LVIS_SELECTED);
2689 else
2690 return;
2692 while (pos)
2694 index = m_LogList.GetNextSelectedItem(pos);
2695 m_LogList.SetItemState(index, 0, LVIS_SELECTED);
2697 m_LogList.SetSelectionMark(-1);
2699 auto hashMapSharedPtr = m_LogList.m_HashMap;
2700 const auto& hashMap = *hashMapSharedPtr;
2702 for (int i = index + 1; i < m_LogList.GetItemCount(); ++i)
2704 bool found = false;
2705 GitRev* data = m_LogList.m_arShownList.SafeGetAt(i);
2706 if (jumpType == JumpType_AuthorEmail)
2707 found = strValue == data->GetAuthorEmail();
2708 else if (jumpType == JumpType_CommitterEmail)
2709 found = strValue == data->GetCommitterEmail();
2710 else if (jumpType == JumpType_MergePoint)
2711 found = data->ParentsCount() > 1;
2712 else if (jumpType == JumpType_Parent1)
2713 found = data->m_CommitHash == hashValue;
2714 else if (jumpType == JumpType_Parent2)
2715 found = data->m_CommitHash == hashValue;
2716 else if (jumpType == JumpType_Tag || jumpType == JumpType_TagFF)
2718 if (auto refList = hashMap.find(data->m_CommitHash); refList != hashMap.cend())
2719 found = any_of((*refList).second, [](const auto& ref) { return CStringUtils::StartsWith(ref, L"refs/tags/"); });
2721 if (found && jumpType == JumpType_TagFF)
2722 found = g_Git.IsFastForward(data->m_CommitHash.ToString(), hashValue.ToString());
2724 else if (jumpType == JumpType_Branch || jumpType == JumpType_BranchFF)
2726 if (auto refList = hashMap.find(data->m_CommitHash); refList != hashMap.cend())
2727 found = any_of((*refList).second, [](const auto& ref) { return CStringUtils::StartsWith(ref, L"refs/heads/") || CStringUtils::StartsWith(ref, L"refs/remotes/"); });
2729 if (found && jumpType == JumpType_BranchFF)
2730 found = g_Git.IsFastForward(data->m_CommitHash.ToString(), hashValue.ToString());
2733 if (found)
2735 m_LogList.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
2736 m_LogList.EnsureVisible(i, FALSE);
2737 m_LogList.SetSelectionMark(i);
2738 return;
2742 CMessageBox::ShowCheck(GetSafeHwnd(), IDS_PROC_LOG_JUMPNOTFOUND, IDS_APPNAME, 1, IDI_INFORMATION, IDS_OKBUTTON, 0, 0, L"NoJumpNotFoundWarning", IDS_MSGBOX_DONOTSHOWAGAIN);
2745 void CLogDlg::SortByColumn(int /*nSortColumn*/, bool /*bAscending*/)
2747 #if 0
2748 switch(nSortColumn)
2750 case 0: // Revision
2752 if(bAscending)
2753 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::AscRevSort());
2754 else
2755 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::DescRevSort());
2757 break;
2758 case 1: // action
2760 if(bAscending)
2761 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::AscActionSort());
2762 else
2763 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::DescActionSort());
2765 break;
2766 case 2: // Author
2768 if(bAscending)
2769 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::AscAuthorSort());
2770 else
2771 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::DescAuthorSort());
2773 break;
2774 case 3: // Date
2776 if(bAscending)
2777 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::AscDateSort());
2778 else
2779 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::DescDateSort());
2781 break;
2782 case 4: // Message or bug id
2783 if (m_bShowBugtraqColumn)
2785 if(bAscending)
2786 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::AscBugIDSort());
2787 else
2788 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::DescBugIDSort());
2789 break;
2791 [[fallthrough]];
2792 case 5: // Message
2794 if(bAscending)
2795 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::AscMessageSort());
2796 else
2797 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::DescMessageSort());
2799 break;
2800 default:
2801 ATLASSERT(0);
2802 break;
2804 #endif
2807 void CLogDlg::OnLvnColumnclick(NMHDR *pNMHDR, LRESULT *pResult)
2809 if (this->IsThreadRunning())
2810 return; //no sorting while the arrays are filled
2811 #if 0
2812 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
2813 const int nColumn = pNMLV->iSubItem;
2814 m_bAscending = nColumn == m_nSortColumn ? !m_bAscending : TRUE;
2815 m_nSortColumn = nColumn;
2816 SortByColumn(m_nSortColumn, m_bAscending);
2817 SetSortArrow(&m_LogList, m_nSortColumn, !!m_bAscending);
2818 SortShownListArray();
2819 m_LogList.Invalidate();
2820 UpdateLogInfoLabel();
2821 #else
2822 UNREFERENCED_PARAMETER(pNMHDR);
2823 #endif
2824 *pResult = 0;
2826 SetTimer(LOG_HEADER_ORDER_TIMER, 10, nullptr);
2829 void CLogDlg::SortShownListArray()
2831 // make sure the shown list still matches the filter after sorting.
2832 OnTimer(LOGFILTER_TIMER);
2833 // clear the selection states
2834 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
2835 while (pos)
2837 m_LogList.SetItemState(m_LogList.GetNextSelectedItem(pos), 0, LVIS_SELECTED);
2839 m_LogList.SetSelectionMark(-1);
2842 void CLogDlg::SetSortArrow(CListCtrl * control, int nColumn, bool bAscending)
2844 if (!control)
2845 return;
2846 // set the sort arrow
2847 CHeaderCtrl * pHeader = control->GetHeaderCtrl();
2848 HDITEM HeaderItem = {0};
2849 HeaderItem.mask = HDI_FORMAT;
2850 for (int i=0; i<pHeader->GetItemCount(); ++i)
2852 pHeader->GetItem(i, &HeaderItem);
2853 HeaderItem.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
2854 pHeader->SetItem(i, &HeaderItem);
2856 if (nColumn >= 0)
2858 pHeader->GetItem(nColumn, &HeaderItem);
2859 HeaderItem.fmt |= (bAscending ? HDF_SORTUP : HDF_SORTDOWN);
2860 pHeader->SetItem(nColumn, &HeaderItem);
2864 int CLogDlg::m_nSortColumnPathList = 0;
2865 bool CLogDlg::m_bAscendingPathList = false;
2867 void CLogDlg::OnBnClickedHidepaths()
2869 FillLogMessageCtrl();
2870 m_ChangedFileListCtrl.Invalidate();
2871 UpdateLogInfoLabel();
2874 void CLogDlg::UpdateLogInfoLabel()
2876 CGitHash rev1 ;
2877 CGitHash rev2 ;
2878 long selectedrevs = 0;
2879 long selectedfiles = 0;
2880 int count = static_cast<int>(m_LogList.m_arShownList.size());
2881 int start = 0;
2882 if (count >= 1)
2884 auto pRev = m_LogList.m_arShownList.SafeGetAt(0);
2885 if (pRev)
2887 rev1 = m_LogList.m_arShownList.SafeGetAt(0)->m_CommitHash;
2888 if (m_LogList.m_bShowWC && rev1.IsEmpty() && count > 1)
2890 start = 1;
2891 pRev = m_LogList.m_arShownList.SafeGetAt(start);
2892 if (pRev)
2893 rev1 = pRev->m_CommitHash;
2896 pRev = m_LogList.m_arShownList.SafeGetAt(count - 1);
2897 if (pRev)
2898 rev2 = pRev->m_CommitHash;
2899 selectedrevs = m_LogList.GetSelectedCount();
2900 if (selectedrevs)
2901 selectedfiles = m_ChangedFileListCtrl.GetSelectedCount();
2903 CString sTemp;
2904 sTemp.FormatMessage(IDS_PROC_LOG_STATS,
2905 count - start,
2906 static_cast<LPCWSTR>(rev2.ToString(g_Git.GetShortHASHLength())), static_cast<LPCWSTR>(rev1.ToString(g_Git.GetShortHASHLength())), selectedrevs, selectedfiles);
2908 if(selectedrevs == 1)
2910 CString str=m_ChangedFileListCtrl.GetStatisticsString(true);
2911 str.Replace(L'\n', L' ');
2912 sTemp += L"; " + str;
2914 m_sLogInfo = sTemp;
2916 UpdateData(FALSE);
2919 void CLogDlg::OnDtnDropdownDatefrom(NMHDR * /*pNMHDR*/, LRESULT *pResult)
2921 // the date control should not show the "today" button
2922 CMonthCalCtrl * pCtrl = m_DateFrom.GetMonthCalCtrl();
2923 if (pCtrl)
2924 pCtrl->ModifyStyle(0, MCS_NOTODAY);
2925 *pResult = 0;
2928 void CLogDlg::OnSize(UINT nType, int cx, int cy)
2930 __super::OnSize(nType, cx, cy);
2931 if ((m_LogList) && (m_ChangedFileListCtrl) && (nType == 0) && (cx >0) && (cy > 0))
2933 // correct the splitter positions if they're out of bounds
2934 CRect rcTop;
2935 m_LogList.GetWindowRect(rcTop);
2936 ScreenToClient(rcTop);
2938 CRect rcMiddle;
2939 GetDlgItem(IDC_MSGVIEW)->GetWindowRect(rcMiddle);
2940 ScreenToClient(rcMiddle);
2942 CRect rcBottom;
2943 m_ChangedFileListCtrl.GetWindowRect(rcBottom);
2944 ScreenToClient(rcBottom);
2946 CRect rcBottomLimit;
2947 GetDlgItem(IDC_LOGINFO)->GetWindowRect(rcBottomLimit);
2948 ScreenToClient(rcBottomLimit);
2950 auto minCtrlHeight = MIN_CTRL_HEIGHT;
2952 // the IDC_LOGINFO and the changed file list control
2953 // have a space of 3 dlg units between them (check in the dlg resource editor)
2954 CRect dlgUnitRect(0, 0, 3, 3);
2955 MapDialogRect(&dlgUnitRect);
2957 if ((rcTop.Height() < minCtrlHeight) ||
2958 (rcMiddle.Height() < minCtrlHeight) ||
2959 (rcBottom.Height() < minCtrlHeight) ||
2960 (rcBottom.bottom > rcBottomLimit.top - dlgUnitRect.bottom))
2962 // controls sizes and splitters need adjusting
2963 RemoveMainAnchors();
2965 auto hdwp = BeginDeferWindowPos(5);
2966 auto hdwpflags = SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOREDRAW | SWP_NOZORDER;
2967 auto splitterHeight = MIN_SPLITTER_HEIGHT;
2969 if ((rcBottom.bottom > rcBottomLimit.top - dlgUnitRect.bottom) || (rcBottom.Height() < minCtrlHeight))
2971 // the bottom of the changed files list control is
2972 // below the point it should get, so move it upwards.
2973 // or the control is too small and needs extending.
2974 rcBottom.bottom = rcBottomLimit.top + dlgUnitRect.bottom;
2975 rcBottom.top = min(rcBottom.top, rcBottom.bottom - minCtrlHeight);
2976 DeferWindowPos(hdwp, m_ChangedFileListCtrl.GetSafeHwnd(), nullptr, rcBottom.left, rcBottom.top, rcBottom.Width(), rcBottom.Height(), hdwpflags);
2977 if (rcBottom.top < rcMiddle.bottom + splitterHeight)
2979 // we also need to move splitter2 and rcMiddle.bottom upwards
2980 CRect rcSplitter2;
2981 m_wndSplitter2.GetWindowRect(rcSplitter2);
2982 ScreenToClient(rcSplitter2);
2983 rcSplitter2.top = rcBottom.top - splitterHeight;
2984 rcSplitter2.bottom = rcBottom.top;
2985 DeferWindowPos(hdwp, m_wndSplitter2.GetSafeHwnd(), nullptr, rcSplitter2.left, rcSplitter2.top, rcSplitter2.Width(), rcSplitter2.Height(), hdwpflags);
2986 rcMiddle.bottom = rcSplitter2.top;
2987 if (rcMiddle.Height() < minCtrlHeight)
2989 // now the message view is too small, we have to
2990 // move splitter1 upwards and resize the top view
2991 CRect rcSplitter1;
2992 m_wndSplitter1.GetWindowRect(rcSplitter1);
2993 ScreenToClient(rcSplitter1);
2994 rcMiddle.top = min(rcMiddle.top, rcMiddle.bottom - minCtrlHeight);
2995 rcSplitter1.top = rcMiddle.top - splitterHeight;
2996 rcSplitter1.bottom = rcMiddle.top;
2997 DeferWindowPos(hdwp, m_wndSplitter1.GetSafeHwnd(), nullptr, rcSplitter1.left, rcSplitter1.top, rcSplitter1.Width(), rcSplitter1.Height(), hdwpflags);
2998 rcTop.bottom = rcSplitter1.top;
2999 DeferWindowPos(hdwp, m_LogList.GetSafeHwnd(), nullptr, rcTop.left, rcTop.top, rcTop.Width(), rcTop.Height(), hdwpflags);
3001 rcMiddle.top = min(rcMiddle.top, rcMiddle.bottom - minCtrlHeight);
3002 DeferWindowPos(hdwp, GetDlgItem(IDC_MSGVIEW)->GetSafeHwnd(), nullptr, rcMiddle.left, rcMiddle.top, rcMiddle.Width(), rcMiddle.Height(), hdwpflags);
3005 if (rcTop.Height() < minCtrlHeight)
3007 // the log list view is too small. Extend its height down and move splitter1 down.
3008 rcTop.bottom = rcTop.top + minCtrlHeight;
3009 DeferWindowPos(hdwp, m_LogList.GetSafeHwnd(), nullptr, rcTop.left, rcTop.top, rcTop.Width(), rcTop.Height(), hdwpflags);
3010 CRect rcSplitter1;
3011 m_wndSplitter1.GetWindowRect(rcSplitter1);
3012 ScreenToClient(rcSplitter1);
3013 rcSplitter1.top = rcTop.bottom;
3014 rcSplitter1.bottom = rcSplitter1.top + splitterHeight;
3015 DeferWindowPos(hdwp, m_wndSplitter1.GetSafeHwnd(), nullptr, rcSplitter1.left, rcSplitter1.top, rcSplitter1.Width(), rcSplitter1.Height(), hdwpflags);
3016 // since splitter1 moves down, also adjust the message view
3017 rcMiddle.top = rcSplitter1.bottom;
3018 DeferWindowPos(hdwp, GetDlgItem(IDC_MSGVIEW)->GetSafeHwnd(), nullptr, rcMiddle.left, rcMiddle.top, rcMiddle.Width(), rcMiddle.Height(), hdwpflags);
3020 if (rcMiddle.Height() < minCtrlHeight)
3022 // the message view is too small. Extend its height down and move splitter2 down;
3023 rcMiddle.bottom = rcMiddle.top + minCtrlHeight;
3024 DeferWindowPos(hdwp, GetDlgItem(IDC_MSGVIEW)->GetSafeHwnd(), nullptr, rcMiddle.left, rcMiddle.top, rcMiddle.Width(), rcMiddle.Height(), hdwpflags);
3025 CRect rcSplitter2;
3026 m_wndSplitter2.GetWindowRect(rcSplitter2);
3027 ScreenToClient(rcSplitter2);
3028 rcSplitter2.top = rcMiddle.bottom;
3029 rcSplitter2.bottom = rcSplitter2.top + splitterHeight;
3030 DeferWindowPos(hdwp, m_wndSplitter2.GetSafeHwnd(), nullptr, rcSplitter2.left, rcSplitter2.top, rcSplitter2.Width(), rcSplitter2.Height(), hdwpflags);
3031 // since splitter2 moves down, also adjust the changed files list control
3032 rcBottom.top = rcSplitter2.bottom;
3033 DeferWindowPos(hdwp, m_ChangedFileListCtrl.GetSafeHwnd(), nullptr, rcBottom.left, rcBottom.top, rcBottom.Width(), rcBottom.Height(), hdwpflags);
3035 EndDeferWindowPos(hdwp);
3037 AddMainAnchors();
3038 ArrangeLayout();
3041 m_wndSplitter1.SetRange(rcTop.top + MIN_CTRL_HEIGHT, rcBottom.bottom - (2 * MIN_CTRL_HEIGHT + MIN_SPLITTER_HEIGHT));
3042 m_wndSplitter2.SetRange(rcTop.top + (2 * MIN_CTRL_HEIGHT + MIN_SPLITTER_HEIGHT), rcBottom.bottom - MIN_CTRL_HEIGHT);
3044 m_LogList.Invalidate();
3045 m_ChangedFileListCtrl.Invalidate();
3046 GetDlgItem(IDC_MSGVIEW)->Invalidate();
3050 void CLogDlg::OnRefresh()
3053 this->m_LogProgress.SetPos(0);
3055 Refresh (false);
3059 void CLogDlg::OnFocusFilter()
3061 GetDlgItem(IDC_SEARCHEDIT)->SetFocus();
3064 void CLogDlg::OnEditCopy()
3066 if (GetFocus() == &m_ChangedFileListCtrl)
3067 CopyChangedSelectionToClipBoard();
3068 else
3069 m_LogList.CopySelectionToClipBoard();
3072 CString CLogDlg::GetAbsoluteUrlFromRelativeUrl(const CString& url)
3074 // is the URL a relative one?
3075 if (CStringUtils::StartsWith(url, L"^/"))
3077 // URL is relative to the repository root
3078 CString url1 = m_sRepositoryRoot + url.Mid(1);
3079 wchar_t buf[INTERNET_MAX_URL_LENGTH] = { 0 };
3080 DWORD len = url.GetLength();
3081 if (UrlCanonicalize(static_cast<LPCWSTR>(url1), buf, &len, 0) == S_OK)
3082 return CString(buf, len);
3083 return url1;
3085 else if (url[0] == '/')
3087 // URL is relative to the server's hostname
3088 CString sHost;
3089 // find the server's hostname
3090 int schemepos = m_sRepositoryRoot.Find(L"//");
3091 if (schemepos >= 0)
3093 sHost = m_sRepositoryRoot.Left(m_sRepositoryRoot.Find(L'/', schemepos + 3));
3094 CString url1 = sHost + url;
3095 wchar_t buf[INTERNET_MAX_URL_LENGTH] = { 0 };
3096 DWORD len = url.GetLength();
3097 if (UrlCanonicalize(static_cast<LPCWSTR>(url), buf, &len, 0) == S_OK)
3098 return CString(buf, len);
3099 return url1;
3102 return url;
3105 void CLogDlg::ShowGravatar()
3107 m_gravatar.EnableGravatar(m_bShowGravatar);
3108 RemoveAnchor(IDC_MSGVIEW);
3109 if (m_gravatar.IsGravatarEnabled())
3111 RECT rect, rect2;
3112 GetDlgItem(IDC_MSGVIEW)->GetWindowRect(&rect);
3113 ScreenToClient(&rect);
3114 m_gravatar.GetWindowRect(&rect2);
3115 ScreenToClient(&rect2);
3116 rect.right = rect2.left;
3117 GetDlgItem(IDC_MSGVIEW)->MoveWindow(&rect);
3118 m_gravatar.ShowWindow(SW_SHOW);
3120 else
3122 RECT rect, rect2;
3123 GetDlgItem(IDC_MSGVIEW)->GetWindowRect(&rect);
3124 ScreenToClient(&rect);
3125 m_gravatar.GetWindowRect(&rect2);
3126 ScreenToClient(&rect2);
3127 rect.right = rect2.right;
3128 GetDlgItem(IDC_MSGVIEW)->MoveWindow(&rect);
3129 m_gravatar.ShowWindow(SW_HIDE);
3131 AddAnchor(IDC_MSGVIEW, MIDDLE_LEFT, MIDDLE_RIGHT); // keep in sync with AddMainAnchors
3135 void CLogDlg::OnEnChangeSearchedit()
3137 UpdateData();
3138 if (m_sFilterText.IsEmpty())
3140 // clear the filter, i.e. make all entries appear
3141 theApp.DoWaitCursor(1);
3142 KillTimer(LOGFILTER_TIMER);
3143 FillLogMessageCtrl(false);
3145 Refresh(true);
3147 theApp.DoWaitCursor(-1);
3148 GetDlgItem(IDC_SEARCHEDIT)->ShowWindow(SW_HIDE);
3149 GetDlgItem(IDC_SEARCHEDIT)->ShowWindow(SW_SHOW);
3150 GetDlgItem(IDC_SEARCHEDIT)->SetFocus();
3151 DialogEnableWindow(IDC_STATBUTTON, !(this->IsThreadRunning() || m_LogList.m_arShownList.empty()));
3152 return;
3154 if (Validate(m_sFilterText))
3155 SetTimer(LOGFILTER_TIMER, 1000, nullptr);
3156 else
3157 KillTimer(LOGFILTER_TIMER);
3160 void CLogDlg::OnBnClickedAllBranch()
3162 // m_bAllBranch is not auto-toggled by MFC, we have to handle it manually (in order to prevent the indeterminate state)
3164 m_LogList.m_ShowMask &=~ (CGit::LOG_INFO_LOCAL_BRANCHES | CGit::LOG_INFO_BASIC_REFS | CGit::LOG_INFO_ALL_BRANCH);
3166 if (m_bAllBranch)
3168 m_bAllBranch = BST_UNCHECKED;
3169 m_AllBranchType = AllBranchType::None;
3170 m_ChangedFileListCtrl.m_sDisplayedBranch = m_LogList.GetRange();
3172 else
3174 m_bAllBranch = BST_CHECKED;
3175 m_AllBranchType = AllBranchType::AllBranches;
3176 m_LogList.m_ShowMask|=CGit::LOG_INFO_ALL_BRANCH;
3177 m_ChangedFileListCtrl.m_sDisplayedBranch.Empty();
3180 // need to save value here, so that log dialogs started from now on also have AllBranch activated
3181 m_regbAllBranch = static_cast<DWORD>(m_AllBranchType);
3183 UpdateData(FALSE);
3185 OnRefresh();
3187 FillLogMessageCtrl(false);
3190 void CLogDlg::OnBnClickedFollowRenames()
3192 if(m_bFollowRenames)
3194 m_LogList.m_ShowMask |= CGit::LOG_INFO_FOLLOW;
3195 m_LogList.m_ShowMask &=~ CGit::LOG_INFO_LOCAL_BRANCHES;
3196 m_LogList.m_ShowMask &= ~CGit::LOG_INFO_BASIC_REFS;
3197 if (m_bAllBranch)
3199 m_bAllBranch = FALSE;
3200 m_AllBranchType = AllBranchType::None;
3201 m_LogList.m_ShowMask &=~ CGit::LOG_INFO_ALL_BRANCH;
3204 else
3205 m_LogList.m_ShowMask &= ~CGit::LOG_INFO_FOLLOW;
3207 DialogEnableWindow(IDC_LOG_ALLBRANCH, !m_bFollowRenames);
3208 DialogEnableWindow(IDC_WHOLE_PROJECT, !m_bFollowRenames && !m_path.IsEmpty());
3210 OnRefresh();
3212 FillLogMessageCtrl(false);
3215 void CLogDlg::HandleShowLabels(bool var, int flag)
3217 if (var)
3218 m_LogList.m_ShowRefMask |= flag;
3219 else
3220 m_LogList.m_ShowRefMask &= ~flag;
3222 if ((m_LogList.m_ShowFilter & CGitLogListBase::FILTERSHOW_REFS) && !(m_LogList.m_ShowFilter & CGitLogListBase::FILTERSHOW_ANYCOMMIT))
3224 // Remove commits where labels are not shown.
3225 OnRefresh();
3226 FillLogMessageCtrl(false);
3228 else
3230 // Just redraw
3231 m_LogList.Invalidate();
3235 void CLogDlg::OnBnClickedCompressedGraph()
3237 UpdateData();
3239 if (m_iCompressedGraph == 2)
3240 m_LogList.m_ShowFilter = CGitLogListBase::FILTERSHOW_REFS;
3241 else if (m_iCompressedGraph == 1)
3242 m_LogList.m_ShowFilter = static_cast<CGitLogListBase::FilterShow>(CGitLogListBase::FILTERSHOW_REFS | CGitLogListBase::FILTERSHOW_MERGEPOINTS);
3243 else
3244 m_LogList.m_ShowFilter = CGitLogListBase::FILTERSHOW_ALL;
3246 OnRefresh();
3247 FillLogMessageCtrl(false);
3250 void CLogDlg::OnBnClickedBrowseRef()
3252 CString newRef = CBrowseRefsDlg::PickRef(false, m_LogList.GetRange(), gPickRef_All, true);
3253 if(newRef.IsEmpty())
3254 return;
3256 m_History.AddEntry(newRef);
3257 m_History.Save();
3259 SetRange(newRef);
3261 m_LogList.m_ShowMask &= ~(CGit::LOG_INFO_ALL_BRANCH | CGit::LOG_INFO_BASIC_REFS | CGit::LOG_INFO_LOCAL_BRANCHES);
3262 m_bAllBranch = BST_UNCHECKED;
3263 m_AllBranchType = AllBranchType::None;
3264 UpdateData(FALSE);
3266 OnRefresh();
3267 FillLogMessageCtrl(false);
3270 void CLogDlg::ShowStartRef()
3272 //Show ref name on top
3273 if(!::IsWindow(m_hWnd))
3274 return;
3275 if (m_LogList.m_ShowMask & (CGit::LOG_INFO_ALL_BRANCH | CGit::LOG_INFO_BASIC_REFS | CGit::LOG_INFO_LOCAL_BRANCHES))
3277 switch (m_LogList.m_ShowMask & (CGit::LOG_INFO_ALL_BRANCH | CGit::LOG_INFO_BASIC_REFS | CGit::LOG_INFO_LOCAL_BRANCHES))
3279 case CGit::LOG_INFO_ALL_BRANCH:
3280 m_staticRef.SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_LOG_ALLBRANCHES)));
3281 break;
3283 case CGit::LOG_INFO_BASIC_REFS:
3284 m_staticRef.SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_LOG_BASIC_REFS)));
3285 break;
3287 case CGit::LOG_INFO_LOCAL_BRANCHES:
3288 m_staticRef.SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_LOG_LOCAL_BRANCHES)));
3289 break;
3292 m_staticRef.Invalidate(TRUE);
3293 m_tooltips.DelTool(GetDlgItem(IDC_STATIC_REF));
3294 return;
3297 CString showStartRef = m_LogList.GetRange();
3298 if (showStartRef.IsEmpty() || showStartRef == L"HEAD")
3300 showStartRef.Empty();
3301 //Ref name is HEAD
3302 if (g_Git.Run(L"git.exe symbolic-ref HEAD", &showStartRef, nullptr, CP_UTF8))
3303 showStartRef.LoadString(IDS_PROC_LOG_NOBRANCH);
3304 showStartRef.Trim(L"\r\n\t ");
3308 showStartRef = g_Git.StripRefName(showStartRef);
3310 m_staticRef.SetWindowText(showStartRef);
3311 CWnd *pWnd = GetDlgItem(IDC_STATIC_REF);
3312 m_tooltips.AddTool(pWnd, showStartRef);
3313 m_staticRef.Invalidate(TRUE);
3316 void CLogDlg::SetRange(const CString& range)
3318 m_LogList.SetRange(range);
3319 m_ChangedFileListCtrl.m_sDisplayedBranch = range;
3321 ShowStartRef();
3324 static void AppendMenuChecked(CMenu &menu, UINT nTextID, UINT_PTR nItemID, BOOL checked = FALSE, BOOL enabled = TRUE)
3326 CString text;
3327 text.LoadString(nTextID);
3328 menu.AppendMenu(MF_STRING | (enabled ? MF_ENABLED : MF_DISABLED) | (checked ? MF_CHECKED : MF_UNCHECKED), nItemID, text);
3331 #define WALKBEHAVIOUR_FIRSTPARENT 1
3332 #define WALKBEHAVIOUR_FOLLOWRENAMES 2
3333 #define WALKBEHAVIOUR_COMPRESSEDGRAPH 3
3334 #define WALKBEHAVIOUR_LABELEDCOMMITS 4
3335 #define WALKBEHAVIOUR_NOMERGES 5
3336 #define WALKBEHAVIOUR_FULLHISTORY 6
3338 void CLogDlg::OnBnClickedWalkBehaviour()
3340 CMenu popup;
3341 if (popup.CreatePopupMenu())
3343 m_ctrlWalkBehavior.SetCheck(BST_CHECKED);
3344 AppendMenuChecked(popup, IDS_WALKBEHAVIOUR_FIRSTPARENT, WALKBEHAVIOUR_FIRSTPARENT, m_bFirstParent);
3345 AppendMenuChecked(popup, IDS_WALKBEHAVIOUR_NOMERGES, WALKBEHAVIOUR_NOMERGES, m_bNoMerges);
3346 AppendMenuChecked(popup, IDS_WALKBEHAVIOUR_FOLLOWRENAMES, WALKBEHAVIOUR_FOLLOWRENAMES, m_bFollowRenames, !(m_path.IsEmpty() || m_path.IsDirectory()));
3347 AppendMenuChecked(popup, IDS_WALKBEHAVIOUR_FULLHISTORY, WALKBEHAVIOUR_FULLHISTORY, m_bFullHistory);
3348 popup.AppendMenu(MF_SEPARATOR, NULL);
3349 AppendMenuChecked(popup, IDS_WALKBEHAVIOUR_COMPRESSED, WALKBEHAVIOUR_COMPRESSEDGRAPH, m_iCompressedGraph == 1);
3350 AppendMenuChecked(popup, IDS_WALKBEHAVIOUR_LABELEDCOMMITS, WALKBEHAVIOUR_LABELEDCOMMITS, m_iCompressedGraph == 2);
3352 m_tooltips.Pop();
3353 RECT rect;
3354 GetDlgItem(IDC_WALKBEHAVIOUR)->GetWindowRect(&rect);
3355 TPMPARAMS params;
3356 params.cbSize = sizeof(TPMPARAMS);
3357 params.rcExclude = rect;
3358 int selection = popup.TrackPopupMenuEx(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY | TPM_VERTICAL, rect.left, rect.top, this, &params);
3359 switch (selection)
3361 case WALKBEHAVIOUR_FIRSTPARENT:
3362 m_bFirstParent = !m_bFirstParent;
3363 OnBnClickedFirstParent();
3364 break;
3365 case WALKBEHAVIOUR_NOMERGES:
3366 m_bNoMerges = !m_bNoMerges;
3367 OnBnClickedFirstParent(); // OnBnClickedFirstParent handles both cases: m_bFirstParent and m_bNoMerges
3368 break;
3369 case WALKBEHAVIOUR_FULLHISTORY:
3370 m_regbFullHistory = m_bFullHistory = !m_bFullHistory;
3371 if (m_bFullHistory)
3372 m_LogList.m_ShowMask |= CGit::LOG_INFO_FULL_HISTORY;
3373 else
3374 m_LogList.m_ShowMask &= ~CGit::LOG_INFO_FULL_HISTORY;
3375 OnRefresh();
3376 break;
3377 case WALKBEHAVIOUR_FOLLOWRENAMES:
3378 m_bFollowRenames = !m_bFollowRenames;
3379 OnBnClickedFollowRenames();
3380 break;
3381 case WALKBEHAVIOUR_COMPRESSEDGRAPH:
3382 m_iCompressedGraph = (m_iCompressedGraph == 1 ? 0 : 1);
3383 OnBnClickedCompressedGraph();
3384 break;
3385 case WALKBEHAVIOUR_LABELEDCOMMITS:
3386 m_iCompressedGraph = (m_iCompressedGraph == 2 ? 0 : 2);
3387 OnBnClickedCompressedGraph();
3388 break;
3389 default:
3390 break;
3392 m_ctrlWalkBehavior.SetCheck((m_bFirstParent || m_bNoMerges || m_bFollowRenames || m_iCompressedGraph) ? BST_CHECKED : BST_UNCHECKED);
3396 #define VIEW_HIDEPATHS 1
3397 #define VIEW_GRAYPATHS 2
3398 #define VIEW_SHOWTAGS 3
3399 #define VIEW_SHOWLOCALBRANCHES 4
3400 #define VIEW_SHOWREMOTEBRANCHES 5
3401 #define VIEW_SHOWGRAVATAR 6
3402 #define VIEW_SHOWPATCH 7
3403 #define VIEW_SHOWWCUNVERSIONED 8
3404 #define VIEW_SHOWOTHERREFS 9
3406 void CLogDlg::OnBnClickedView()
3408 CMenu popup;
3409 if (popup.CreatePopupMenu())
3411 m_ctrlView.SetCheck(BST_CHECKED);
3412 AppendMenuChecked(popup, IDS_SHOWFILES_HIDEPATHS, VIEW_HIDEPATHS, m_iHidePaths == 1);
3413 AppendMenuChecked(popup, IDS_SHOWFILES_GRAYPATHS, VIEW_GRAYPATHS, m_iHidePaths == 2);
3414 popup.AppendMenu(MF_SEPARATOR, NULL);
3415 AppendMenuChecked(popup, IDS_PROC_LOG_SHOWUNVERSIONED, VIEW_SHOWWCUNVERSIONED, m_bShowUnversioned == BST_CHECKED);
3416 popup.AppendMenu(MF_SEPARATOR, NULL);
3417 CMenu showLabelsMenu;
3418 if (showLabelsMenu.CreatePopupMenu())
3420 AppendMenuChecked(showLabelsMenu, IDS_VIEW_SHOWTAGLABELS, VIEW_SHOWTAGS, m_bShowTags);
3421 AppendMenuChecked(showLabelsMenu, IDS_VIEW_SHOWLOCALBRANCHLABELS, VIEW_SHOWLOCALBRANCHES, m_bShowLocalBranches);
3422 AppendMenuChecked(showLabelsMenu, IDS_VIEW_SHOWREMOTEBRANCHLABELS, VIEW_SHOWREMOTEBRANCHES, m_bShowRemoteBranches);
3423 AppendMenuChecked(showLabelsMenu, IDS_VIEW_SHOWROTHERLABELS, VIEW_SHOWOTHERREFS, m_bShowOtherRefs);
3424 popup.AppendMenu(MF_STRING | MF_POPUP, reinterpret_cast<UINT_PTR>(showLabelsMenu.m_hMenu), static_cast<CString>(MAKEINTRESOURCE(IDS_VIEW_LABELS)));
3426 popup.AppendMenu(MF_SEPARATOR, NULL);
3427 AppendMenuChecked(popup, IDS_VIEW_SHOWGRAVATAR, VIEW_SHOWGRAVATAR, m_bShowGravatar);
3428 AppendMenuChecked(popup, IDS_MENU_VIEWPATCH, VIEW_SHOWPATCH, IsWindow(this->m_patchViewdlg.m_hWnd));
3430 m_tooltips.Pop();
3431 RECT rect;
3432 GetDlgItem(IDC_VIEW)->GetWindowRect(&rect);
3433 TPMPARAMS params;
3434 params.cbSize = sizeof(TPMPARAMS);
3435 params.rcExclude = rect;
3436 int selection = popup.TrackPopupMenuEx(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY | TPM_VERTICAL, rect.left, rect.top, this, &params);
3437 switch (selection)
3439 case VIEW_HIDEPATHS:
3440 if (m_iHidePaths == 1)
3441 m_iHidePaths = 0;
3442 else
3443 m_iHidePaths = 1;
3444 OnBnClickedHidepaths();
3445 break;
3446 case VIEW_GRAYPATHS:
3447 if (m_iHidePaths == 2)
3448 m_iHidePaths = 0;
3449 else
3450 m_iHidePaths = 2;
3451 OnBnClickedHidepaths();
3452 break;
3453 case VIEW_SHOWTAGS:
3454 m_bShowTags = !m_bShowTags;
3455 HandleShowLabels(m_bShowTags, LOGLIST_SHOWTAGS);
3456 break;
3457 case VIEW_SHOWLOCALBRANCHES:
3458 m_bShowLocalBranches = !m_bShowLocalBranches;
3459 HandleShowLabels(m_bShowLocalBranches, LOGLIST_SHOWLOCALBRANCHES);
3460 break;
3461 case VIEW_SHOWREMOTEBRANCHES:
3462 m_bShowRemoteBranches = !m_bShowRemoteBranches;
3463 HandleShowLabels(m_bShowRemoteBranches, LOGLIST_SHOWREMOTEBRANCHES);
3464 break;
3465 case VIEW_SHOWOTHERREFS:
3466 m_bShowOtherRefs = !m_bShowOtherRefs;
3467 HandleShowLabels(m_bShowOtherRefs, LOGLIST_SHOWOTHERREFS);
3468 break;
3469 case VIEW_SHOWGRAVATAR:
3471 m_bShowGravatar = !m_bShowGravatar;
3472 ShowGravatar();
3473 m_gravatar.Init();
3474 CString email;
3475 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
3476 if (pos)
3478 int selIndex = m_LogList.GetNextSelectedItem(pos);
3479 int moreSel = m_LogList.GetNextSelectedItem(pos);
3480 if (moreSel < 0)
3482 GitRev* pLogEntry = m_LogList.m_arShownList.SafeGetAt(selIndex);
3483 if (pLogEntry)
3484 email = pLogEntry->GetAuthorEmail();
3487 m_gravatar.LoadGravatar(email);
3488 break;
3490 case VIEW_SHOWPATCH:
3491 TogglePatchView();
3492 break;
3493 case VIEW_SHOWWCUNVERSIONED:
3494 m_bShowUnversioned = !m_bShowUnversioned;
3495 FillLogMessageCtrl();
3496 m_ChangedFileListCtrl.Invalidate();
3497 break;
3498 default:
3499 break;
3501 m_ctrlView.SetCheck(BST_UNCHECKED);
3505 void CLogDlg::OnBnClickedFirstParent()
3507 if(this->m_bFirstParent)
3508 m_LogList.m_ShowMask|=CGit::LOG_INFO_FIRST_PARENT;
3509 else
3510 m_LogList.m_ShowMask&=~CGit::LOG_INFO_FIRST_PARENT;
3512 if (m_bNoMerges)
3513 m_LogList.m_ShowMask |= CGit::LOG_INFO_NO_MERGE;
3514 else
3515 m_LogList.m_ShowMask &= ~CGit::LOG_INFO_NO_MERGE;
3517 OnRefresh();
3519 FillLogMessageCtrl(false);
3522 void CLogDlg::OnBnClickShowWholeProject()
3524 this->UpdateData();
3526 if(this->m_bWholeProject)
3528 m_LogList.m_Path.Reset();
3529 SetDlgTitle();
3531 else
3532 m_LogList.m_Path=m_path;
3534 m_regShowWholeProject = m_bWholeProject;
3536 SetDlgTitle();
3538 OnRefresh();
3540 FillLogMessageCtrl(false);
3543 LRESULT CLogDlg::OnRefLogChanged(WPARAM /*wParam*/, LPARAM /*lParam*/)
3545 ShowStartRef();
3546 return 0;
3549 LRESULT CLogDlg::OnTaskbarBtnCreated(WPARAM wParam, LPARAM lParam)
3551 m_pTaskbarList.Release();
3552 m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList);
3553 return __super::OnTaskbarButtonCreated(wParam, lParam);
3556 LRESULT CLogDlg::OnRefreshSelection(WPARAM /*wParam*/, LPARAM /*lParam*/)
3558 // it's enough to deselect, then select again one item of the whole selection
3559 int selMark = m_LogList.GetSelectionMark();
3560 if (selMark >= 0)
3562 m_LogList.SetSelectionMark(selMark);
3563 m_LogList.SetItemState(selMark, 0, LVIS_SELECTED);
3564 m_LogList.SetItemState(selMark, LVIS_SELECTED, LVIS_SELECTED);
3566 return 0;
3569 void CLogDlg::OnExitClearFilter()
3571 if (m_LogList.m_LogFilter->IsFilterActive())
3573 OnClickedCancelFilter(NULL, NULL);
3574 return;
3576 SendMessage(WM_CLOSE);
3579 void CLogDlg::OnNMCustomdrawChangedFileList(NMHDR* pNMHDR, LRESULT* pResult)
3581 NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>(pNMHDR);
3582 if (pLVCD->nmcd.dwDrawStage != (CDDS_ITEMPREPAINT | CDDS_ITEM | CDDS_SUBITEM))
3583 return;
3585 if (pLVCD->iSubItem > 2)
3586 return;
3588 auto filter(m_LogList.m_LogFilter);
3589 if ((m_SelectedFilters & LOGFILTER_PATHS) && (filter->IsFilterActive()))
3590 *pResult = CGitLogListBase::DrawListItemWithMatches(filter.get(), m_ChangedFileListCtrl, pLVCD, m_Colors);
3593 void CLogDlg::OnSysColorChange()
3595 __super::OnSysColorChange();
3596 SetupLogMessageViewControl();
3597 CMFCVisualManager::GetInstance()->RedrawAll();
3598 FillLogMessageCtrl();
3601 void CLogDlg::SetupLogMessageViewControl()
3603 auto pWnd = GetDlgItem(IDC_MSGVIEW);
3604 // set the font to use in the log message view, configured in the settings dialog
3605 pWnd->SetFont(&m_logFont);
3606 // make the log message rich edit control send a message when the mouse pointer is over a link
3607 pWnd->SendMessage(EM_SETEVENTMASK, NULL, ENM_LINK | ENM_SCROLL);
3609 CHARFORMAT2 format = { 0 };
3610 format.cbSize = sizeof(CHARFORMAT2);
3611 format.dwMask = CFM_COLOR | CFM_BACKCOLOR;
3612 if (CTheme::Instance().IsDarkTheme())
3614 format.crTextColor = CTheme::darkTextColor;
3615 format.crBackColor = CTheme::darkBkColor;
3617 else
3619 format.crTextColor = ::GetSysColor(COLOR_WINDOWTEXT);
3620 format.crBackColor = ::GetSysColor(COLOR_WINDOW);
3622 pWnd->SendMessage(EM_SETCHARFORMAT, SCF_ALL, reinterpret_cast<LPARAM>(&format));
3623 pWnd->SendMessage(EM_SETBKGNDCOLOR, 0, format.crBackColor);