Use CAutoGeneralHandle
[TortoiseGit.git] / src / TortoiseProc / LogDlg.cpp
blobe5f058b7b3725b85e062cff28774553547bdd5b5
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2008 - TortoiseSVN
4 // Copyright (C) 2008-2012 - 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 "cursor.h"
23 #include "InputDlg.h"
24 #include "GITProgressDlg.h"
25 #include "ProgressDlg.h"
26 //#include "RepositoryBrowser.h"
27 //#include "CopyDlg.h"
28 #include "StatGraphDlg.h"
29 #include "Logdlg.h"
30 #include "MessageBox.h"
31 #include "Registry.h"
32 #include "AppUtils.h"
33 #include "PathUtils.h"
34 #include "StringUtils.h"
35 #include "UnicodeUtils.h"
36 #include "TempFile.h"
37 //#include "GitInfo.h"
38 //#include "GitDiff.h"
39 #include "IconMenu.h"
40 //#include "RevisionRangeDlg.h"
41 //#include "BrowseFolder.h"
42 //#include "BlameDlg.h"
43 //#include "Blame.h"
44 //#include "GitHelpers.h"
45 #include "GitStatus.h"
46 //#include "LogDlgHelper.h"
47 //#include "CachedLogInfo.h"
48 //#include "RepositoryInfo.h"
49 //#include "EditPropertiesDlg.h"
50 #include "FileDiffDlg.h"
51 #include "BrowseRefsDlg.h"
52 #include "SmartHandle.h"
54 IMPLEMENT_DYNAMIC(CLogDlg, CResizableStandAloneDialog)
55 CLogDlg::CLogDlg(CWnd* pParent /*=NULL*/)
56 : CResizableStandAloneDialog(CLogDlg::IDD, pParent)
57 , m_logcounter(0)
58 , m_wParam(0)
59 , m_currentChangedArray(NULL)
60 , m_nSortColumn(0)
61 , m_bShowedAll(false)
62 , m_bFollowRenames(FALSE)
63 , m_bSelect(false)
65 , m_bSelectionMustBeContinuous(false)
66 , m_lowestRev(_T(""))
68 , m_sLogInfo(_T(""))
70 , m_bCancelled(FALSE)
71 , m_pNotifyWindow(NULL)
73 , m_bAscending(FALSE)
75 , m_limit(0)
76 , m_childCounter(0)
77 , m_maxChild(0)
78 , m_bIncludeMerges(FALSE)
79 , m_hAccel(NULL)
81 m_bFilterWithRegex = !!CRegDWORD(_T("Software\\TortoiseGit\\UseRegexFilter"), TRUE);
83 CString str;
84 str=g_Git.m_CurrentDir;
85 str.Replace(_T(":"),_T("_"));
86 str=CString(_T("Software\\TortoiseGit\\LogDialog\\AllBranch\\"))+str;
88 m_regbAllBranch=CRegDWORD(str,FALSE);
90 m_bAllBranch=m_regbAllBranch;
92 m_bFirstParent=FALSE;
93 m_bWholeProject=FALSE;
96 CLogDlg::~CLogDlg()
99 m_regbAllBranch=m_bAllBranch;
101 m_CurrentFilteredChangedArray.RemoveAll();
105 void CLogDlg::DoDataExchange(CDataExchange* pDX)
107 CResizableStandAloneDialog::DoDataExchange(pDX);
108 DDX_Control(pDX, IDC_LOGLIST, m_LogList);
109 DDX_Control(pDX, IDC_LOGMSG, m_ChangedFileListCtrl);
110 DDX_Control(pDX, IDC_PROGRESS, m_LogProgress);
111 DDX_Control(pDX, IDC_SPLITTERTOP, m_wndSplitter1);
112 DDX_Control(pDX, IDC_SPLITTERBOTTOM, m_wndSplitter2);
113 DDX_Text(pDX, IDC_SEARCHEDIT, m_LogList.m_sFilterText);
114 DDX_Control(pDX, IDC_DATEFROM, m_DateFrom);
115 DDX_Control(pDX, IDC_DATETO, m_DateTo);
116 DDX_Control(pDX, IDC_HIDEPATHS, m_cHidePaths);
117 DDX_Text(pDX, IDC_LOGINFO, m_sLogInfo);
118 DDX_Check(pDX, IDC_LOG_FIRSTPARENT, m_bFirstParent);
119 DDX_Check(pDX, IDC_LOG_ALLBRANCH,m_bAllBranch);
120 DDX_Check(pDX, IDC_LOG_FOLLOWRENAMES, m_bFollowRenames);
121 DDX_Check(pDX, IDC_SHOWWHOLEPROJECT,m_bWholeProject);
122 DDX_Control(pDX, IDC_SEARCHEDIT, m_cFilter);
123 DDX_Control(pDX, IDC_STATIC_REF, m_staticRef);
126 BEGIN_MESSAGE_MAP(CLogDlg, CResizableStandAloneDialog)
127 //ON_BN_CLICKED(IDC_GETALL, OnBnClickedGetall)
128 //ON_NOTIFY(NM_DBLCLK, IDC_LOGMSG, OnNMDblclkChangedFileList)
129 ON_WM_CONTEXTMENU()
130 ON_WM_SETCURSOR()
131 ON_BN_CLICKED(IDHELP, OnBnClickedHelp)
132 ON_NOTIFY(LVN_ITEMCHANGED, IDC_LOGLIST, OnLvnItemchangedLoglist)
133 ON_NOTIFY(EN_LINK, IDC_MSGVIEW, OnEnLinkMsgview)
134 ON_BN_CLICKED(IDC_STATBUTTON, OnBnClickedStatbutton)
137 ON_MESSAGE(WM_FILTEREDIT_INFOCLICKED, OnClickedInfoIcon)
138 ON_MESSAGE(WM_FILTEREDIT_CANCELCLICKED, OnClickedCancelFilter)
140 ON_MESSAGE(MSG_LOAD_PERCENTAGE,OnLogListLoading)
142 ON_EN_CHANGE(IDC_SEARCHEDIT, OnEnChangeSearchedit)
143 ON_WM_TIMER()
144 ON_NOTIFY(DTN_DATETIMECHANGE, IDC_DATETO, OnDtnDatetimechangeDateto)
145 ON_NOTIFY(DTN_DATETIMECHANGE, IDC_DATEFROM, OnDtnDatetimechangeDatefrom)
146 ON_BN_CLICKED(IDC_SHOWWHOLEPROJECT, OnBnClickShowWholeProject)
147 //ON_NOTIFY(NM_CUSTOMDRAW, IDC_LOGMSG, OnNMCustomdrawChangedFileList)
148 //ON_NOTIFY(LVN_GETDISPINFO, IDC_LOGMSG, OnLvnGetdispinfoChangedFileList)
149 ON_NOTIFY(LVN_COLUMNCLICK,IDC_LOGLIST, OnLvnColumnclick)
150 //ON_NOTIFY(LVN_COLUMNCLICK, IDC_LOGMSG, OnLvnColumnclickChangedFileList)
151 ON_BN_CLICKED(IDC_HIDEPATHS, OnBnClickedHidepaths)
152 ON_COMMAND(MSG_FETCHED_DIFF, OnBnClickedHidepaths)
153 ON_BN_CLICKED(IDC_LOG_ALLBRANCH, OnBnClickedAllBranch)
154 ON_BN_CLICKED(IDC_LOG_FOLLOWRENAMES, OnBnClickedFollowRenames)
156 ON_NOTIFY(DTN_DROPDOWN, IDC_DATEFROM, &CLogDlg::OnDtnDropdownDatefrom)
157 ON_NOTIFY(DTN_DROPDOWN, IDC_DATETO, &CLogDlg::OnDtnDropdownDateto)
158 ON_WM_SIZE()
159 ON_BN_CLICKED(IDC_LOG_FIRSTPARENT, &CLogDlg::OnBnClickedFirstParent)
160 ON_BN_CLICKED(IDC_REFRESH, &CLogDlg::OnBnClickedRefresh)
161 // ON_BN_CLICKED(IDC_BUTTON_BROWSE_REF, &CLogDlg::OnBnClickedBrowseRef)
162 ON_STN_CLICKED(IDC_STATIC_REF, &CLogDlg::OnBnClickedBrowseRef)
163 ON_COMMAND(ID_LOGDLG_REFRESH, &CLogDlg::OnBnClickedRefresh)
164 ON_COMMAND(ID_LOGDLG_FIND, &CLogDlg::OnFind)
165 ON_COMMAND(ID_LOGDLG_FOCUSFILTER, &CLogDlg::OnFocusFilter)
166 ON_COMMAND(ID_EDIT_COPY, &CLogDlg::OnEditCopy)
167 ON_MESSAGE(MSG_REFLOG_CHANGED, OnRefLogChanged)
168 ON_REGISTERED_MESSAGE(WM_TASKBARBTNCREATED, OnTaskbarBtnCreated)
169 END_MESSAGE_MAP()
171 void CLogDlg::SetParams(const CTGitPath& orgPath, const CTGitPath& path, CString hightlightRevision, CString startrev, CString endrev, int limit /* = FALSE */)
173 m_orgPath = orgPath;
174 m_path = path;
175 m_hightlightRevision = hightlightRevision;
177 if (startrev == GIT_REV_ZERO)
178 startrev.Empty();
179 if (endrev == GIT_REV_ZERO)
180 endrev.Empty();
182 this->m_LogList.m_startrev = startrev;
183 m_LogRevision = startrev;
184 this->m_LogList.m_endrev = endrev;
186 if(!endrev.IsEmpty())
187 this->SetStartRef(endrev);
189 m_hasWC = !path.IsUrl();
190 m_limit = limit;
191 if (::IsWindow(m_hWnd))
192 UpdateData(FALSE);
195 void CLogDlg::SetFilter(const CString& findstr, LONG findtype, bool findregex)
197 m_LogList.m_sFilterText = findstr;
198 if (findtype)
199 m_LogList.m_SelectedFilters = findtype;
200 m_LogList.m_bFilterWithRegex = m_bFilterWithRegex = findregex;
203 BOOL CLogDlg::OnInitDialog()
205 CString temp;
206 CResizableStandAloneDialog::OnInitDialog();
207 CAppUtils::MarkWindowAsUnpinnable(m_hWnd);
209 // Let the TaskbarButtonCreated message through the UIPI filter. If we don't
210 // do this, Explorer would be unable to send that message to our window if we
211 // were running elevated. It's OK to make the call all the time, since if we're
212 // not elevated, this is a no-op.
213 CHANGEFILTERSTRUCT cfs = { sizeof(CHANGEFILTERSTRUCT) };
214 typedef BOOL STDAPICALLTYPE ChangeWindowMessageFilterExDFN(HWND hWnd, UINT message, DWORD action, PCHANGEFILTERSTRUCT pChangeFilterStruct);
215 CAutoLibrary hUser = ::LoadLibrary(_T("user32.dll"));
216 if (hUser)
218 ChangeWindowMessageFilterExDFN *pfnChangeWindowMessageFilterEx = (ChangeWindowMessageFilterExDFN*)GetProcAddress(hUser, "ChangeWindowMessageFilterEx");
219 if (pfnChangeWindowMessageFilterEx)
221 pfnChangeWindowMessageFilterEx(m_hWnd, WM_TASKBARBTNCREATED, MSGFLT_ALLOW, &cfs);
224 m_pTaskbarList.Release();
225 m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList);
227 m_hAccel = LoadAccelerators(AfxGetResourceHandle(),MAKEINTRESOURCE(IDR_ACC_LOGDLG));
229 // use the state of the "stop on copy/rename" option from the last time
230 UpdateData(FALSE);
232 // set the font to use in the log message view, configured in the settings dialog
233 CAppUtils::CreateFontForLogs(m_logFont);
234 GetDlgItem(IDC_MSGVIEW)->SetFont(&m_logFont);
235 // automatically detect URLs in the log message and turn them into links
236 GetDlgItem(IDC_MSGVIEW)->SendMessage(EM_AUTOURLDETECT, TRUE, NULL);
237 // make the log message rich edit control send a message when the mouse pointer is over a link
238 GetDlgItem(IDC_MSGVIEW)->SendMessage(EM_SETEVENTMASK, NULL, ENM_LINK);
239 //m_LogList.SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER | LVS_EX_SUBITEMIMAGES);
241 // the "hide unrelated paths" checkbox should be indeterminate
242 m_cHidePaths.SetCheck(BST_INDETERMINATE);
245 //SetWindowTheme(m_LogList.GetSafeHwnd(), L"Explorer", NULL);
246 //SetWindowTheme(m_ChangedFileListCtrl.GetSafeHwnd(), L"Explorer", NULL);
248 // set up the columns
249 m_LogList.DeleteAllItems();
251 m_LogList.m_Path=m_path;
252 m_LogList.m_hasWC = m_LogList.m_bShowWC = !g_GitAdminDir.IsBareRepo(g_Git.m_CurrentDir);
253 m_LogList.InsertGitColumn();
255 m_ChangedFileListCtrl.Init(GITSLC_COLEXT | GITSLC_COLSTATUS |GITSLC_COLADD|GITSLC_COLDEL, _T("LogDlg"), (GITSLC_POPALL ^ (GITSLC_POPCOMMIT|GITSLC_POPIGNORE|GITSLC_POPRESTORE)), false, m_LogList.m_hasWC, GITSLC_COLEXT | GITSLC_COLSTATUS | GITSLC_COLADD| GITSLC_COLDEL);
257 GetDlgItem(IDC_LOGLIST)->UpdateData(FALSE);
259 m_logcounter = 0;
260 m_sMessageBuf.Preallocate(100000);
262 SetDlgTitle();
264 m_tooltips.Create(this);
265 CheckRegexpTooltip();
267 SetSplitterRange();
269 // the filter control has a 'cancel' button (the red 'X'), we need to load its bitmap
270 m_cFilter.SetCancelBitmaps(IDI_CANCELNORMAL, IDI_CANCELPRESSED);
271 m_cFilter.SetInfoIcon(IDI_LOGFILTER);
272 m_cFilter.SetValidator(this);
274 AdjustControlSize(IDC_HIDEPATHS);
275 AdjustControlSize(IDC_LOG_FIRSTPARENT);
276 AdjustControlSize(IDC_LOG_ALLBRANCH);
277 AdjustControlSize(IDC_SHOWWHOLEPROJECT);
278 AdjustControlSize(IDC_LOG_FOLLOWRENAMES);
280 GetClientRect(m_DlgOrigRect);
281 m_LogList.GetClientRect(m_LogListOrigRect);
282 GetDlgItem(IDC_MSGVIEW)->GetClientRect(m_MsgViewOrigRect);
283 m_ChangedFileListCtrl.GetClientRect(m_ChgOrigRect);
285 m_DateFrom.SendMessage(DTM_SETMCSTYLE, 0, MCS_WEEKNUMBERS|MCS_NOTODAY|MCS_NOTRAILINGDATES|MCS_NOSELCHANGEONNAV);
286 m_DateTo.SendMessage(DTM_SETMCSTYLE, 0, MCS_WEEKNUMBERS|MCS_NOTODAY|MCS_NOTRAILINGDATES|MCS_NOSELCHANGEONNAV);
288 m_staticRef.SetURL(CString());
290 // resizable stuff
291 AddAnchor(IDC_STATIC_REF, TOP_LEFT);
292 //AddAnchor(IDC_BUTTON_BROWSE_REF, TOP_LEFT);
293 AddAnchor(IDC_FROMLABEL, TOP_LEFT);
294 AddAnchor(IDC_DATEFROM, TOP_LEFT);
295 AddAnchor(IDC_TOLABEL, TOP_LEFT);
296 AddAnchor(IDC_DATETO, TOP_LEFT);
298 SetFilterCueText();
299 AddAnchor(IDC_SEARCHEDIT, TOP_LEFT, TOP_RIGHT);
301 AddAnchor(IDC_LOGLIST, TOP_LEFT, TOP_RIGHT);
302 AddAnchor(IDC_SPLITTERTOP, TOP_LEFT, TOP_RIGHT);
303 AddAnchor(IDC_MSGVIEW, TOP_LEFT, BOTTOM_RIGHT);
304 AddAnchor(IDC_SPLITTERBOTTOM, BOTTOM_LEFT, BOTTOM_RIGHT);
305 AddAnchor(IDC_LOGMSG, BOTTOM_LEFT, BOTTOM_RIGHT);
307 AddAnchor(IDC_LOGINFO, BOTTOM_LEFT, BOTTOM_RIGHT);
308 AddAnchor(IDC_HIDEPATHS, BOTTOM_LEFT);
309 AddAnchor(IDC_LOG_ALLBRANCH,BOTTOM_LEFT);
310 AddAnchor(IDC_LOG_FOLLOWRENAMES, BOTTOM_LEFT);
311 AddAnchor(IDC_LOG_FIRSTPARENT, BOTTOM_LEFT);
312 //AddAnchor(IDC_GETALL, BOTTOM_LEFT);
313 AddAnchor(IDC_SHOWWHOLEPROJECT, BOTTOM_LEFT);
314 AddAnchor(IDC_REFRESH, BOTTOM_LEFT);
315 AddAnchor(IDC_STATBUTTON, BOTTOM_RIGHT);
316 AddAnchor(IDC_PROGRESS, BOTTOM_LEFT, BOTTOM_RIGHT);
317 AddAnchor(IDOK, BOTTOM_RIGHT);
318 AddAnchor(IDCANCEL, BOTTOM_RIGHT);
319 AddAnchor(IDHELP, BOTTOM_RIGHT);
321 if(this->m_bAllBranch)
322 m_LogList.m_ShowMask|=CGit::LOG_INFO_ALL_BRANCH;
323 else
324 m_LogList.m_ShowMask&=~CGit::LOG_INFO_ALL_BRANCH;
326 // SetPromptParentWindow(m_hWnd);
328 if (hWndExplorer)
329 CenterWindow(CWnd::FromHandle(hWndExplorer));
330 EnableSaveRestore(_T("LogDlg"));
332 DWORD yPos1 = CRegDWORD(_T("Software\\TortoiseGit\\TortoiseProc\\ResizableState\\LogDlgSizer1"));
333 DWORD yPos2 = CRegDWORD(_T("Software\\TortoiseGit\\TortoiseProc\\ResizableState\\LogDlgSizer2"));
334 RECT rcDlg, rcLogList, rcChgMsg;
335 GetClientRect(&rcDlg);
336 m_LogList.GetWindowRect(&rcLogList);
337 ScreenToClient(&rcLogList);
338 m_ChangedFileListCtrl.GetWindowRect(&rcChgMsg);
339 ScreenToClient(&rcChgMsg);
340 if (yPos1)
342 RECT rectSplitter;
343 m_wndSplitter1.GetWindowRect(&rectSplitter);
344 ScreenToClient(&rectSplitter);
345 int delta = yPos1 - rectSplitter.top;
347 if ((rcLogList.bottom + delta > rcLogList.top)&&(rcLogList.bottom + delta < rcChgMsg.bottom - 30))
349 m_wndSplitter1.SetWindowPos(NULL, 0, yPos1, 0, 0, SWP_NOSIZE);
350 DoSizeV1(delta);
353 if (yPos2)
355 RECT rectSplitter;
356 m_wndSplitter2.GetWindowRect(&rectSplitter);
357 ScreenToClient(&rectSplitter);
358 int delta = yPos2 - rectSplitter.top;
360 if ((rcChgMsg.top + delta < rcChgMsg.bottom)&&(rcChgMsg.top + delta > rcLogList.top + 30))
362 m_wndSplitter2.SetWindowPos(NULL, 0, yPos2, 0, 0, SWP_NOSIZE);
363 DoSizeV2(delta);
368 if (m_bSelect)
370 // the dialog is used to select revisions
371 // enable the OK button if appropriate
372 EnableOKButton();
374 else
376 // the dialog is used to just view log messages
377 // hide the OK button and set text on Cancel button to OK
378 GetDlgItemText(IDOK, temp);
379 SetDlgItemText(IDCANCEL, temp);
380 GetDlgItem(IDOK)->ShowWindow(SW_HIDE);
383 m_mergedRevs.clear();
385 // first start a thread to obtain the log messages without
386 // blocking the dialog
387 //m_tTo = 0;
388 //m_tFrom = (DWORD)-1;
390 // scroll to user selected or current revision
391 if (!m_hightlightRevision.IsEmpty() && m_hightlightRevision.GetLength() >= GIT_HASH_SIZE)
392 m_LogList.m_lastSelectedHash = m_hightlightRevision;
393 else if (!m_LogList.m_endrev.IsEmpty() && m_LogList.m_endrev.GetLength() >= GIT_HASH_SIZE)
394 m_LogList.m_lastSelectedHash = m_hightlightRevision;
395 else
399 m_LogList.m_lastSelectedHash = g_Git.GetHash(_T("HEAD"));
401 catch (char* msg)
403 CString err(msg);
404 MessageBox(_T("Could not get HEAD hash.\nlibgit reports:\n") + err, _T("TortoiseGit"), MB_ICONERROR);
408 if (m_path.IsEmpty() || m_path.IsDirectory())
409 DialogEnableWindow(IDC_LOG_FOLLOWRENAMES, FALSE);
411 m_LogList.FetchLogAsync(this);
413 GetDlgItem(IDC_LOGLIST)->SetFocus();
415 ShowStartRef();
416 return FALSE;
419 LRESULT CLogDlg::OnLogListLoading(WPARAM wParam, LPARAM /*lParam*/)
421 int cur=(int)wParam;
423 if( cur == GITLOG_START )
425 CString temp;
426 temp.LoadString(IDS_PROGRESSWAIT);
428 this->m_LogList.ShowText(temp, true);
430 // We use a progress bar while getting the logs
431 m_LogProgress.SetRange32(0, 100);
432 m_LogProgress.SetPos(0);
433 if (m_pTaskbarList)
435 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
436 m_pTaskbarList->SetProgressValue(m_hWnd, 0, 100);
439 GetDlgItem(IDC_PROGRESS)->ShowWindow(TRUE);
441 //DialogEnableWindow(IDC_GETALL, FALSE);
442 //DialogEnableWindow(IDC_SHOWWHOLEPROJECT, FALSE);
443 //DialogEnableWindow(IDC_LOG_FIRSTPARENT, FALSE);
444 DialogEnableWindow(IDC_STATBUTTON, FALSE);
445 //DialogEnableWindow(IDC_REFRESH, FALSE);
446 DialogEnableWindow(IDC_HIDEPATHS,FALSE);
449 else if( cur == GITLOG_END)
451 if(this->m_LogList.HasText())
453 this->m_LogList.ClearText();
455 UpdateLogInfoLabel();
457 if (m_pTaskbarList)
458 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NOPROGRESS);
460 DialogEnableWindow(IDC_SHOWWHOLEPROJECT, !m_bFollowRenames);
462 //DialogEnableWindow(IDC_GETALL, TRUE);
463 DialogEnableWindow(IDC_STATBUTTON, !(m_LogList.m_arShownList.IsEmpty() || m_LogList.m_arShownList.GetCount() == 1 && m_LogList.m_bShowWC));
464 DialogEnableWindow(IDC_REFRESH, TRUE);
465 DialogEnableWindow(IDC_HIDEPATHS,TRUE);
467 // PostMessage(WM_TIMER, LOGFILTER_TIMER);
468 GetDlgItem(IDC_PROGRESS)->ShowWindow(FALSE);
469 //CTime time=m_LogList.GetOldestTime();
470 CTime begin,end;
471 m_LogList.GetTimeRange(begin,end);
473 if(m_LogList.m_From == -1)
474 m_DateFrom.SetTime(&begin);
476 if(m_LogList.m_To == -1)
477 m_DateTo.SetTime(&end);
481 else
483 if(this->m_LogList.HasText())
485 this->m_LogList.ClearText();
486 this->m_LogList.Invalidate();
488 UpdateLogInfoLabel();
489 m_LogProgress.SetPos(cur);
490 if (m_pTaskbarList)
492 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
493 m_pTaskbarList->SetProgressValue(m_hWnd, cur, 100);
496 return 0;
498 void CLogDlg::SetDlgTitle()
500 if (m_sTitle.IsEmpty())
501 GetWindowText(m_sTitle);
503 if (m_LogList.m_Path.IsEmpty() || m_orgPath.GetWinPathString().IsEmpty())
505 CAppUtils::SetWindowTitle(m_hWnd, g_Git.m_CurrentDir, m_sTitle);
507 else
508 CAppUtils::SetWindowTitle(m_hWnd, m_orgPath.GetWinPathString(), m_sTitle);
511 void CLogDlg::CheckRegexpTooltip()
513 CWnd *pWnd = GetDlgItem(IDC_SEARCHEDIT);
514 // Since tooltip describes regexp features, show it only if regexps are enabled.
515 if (m_bFilterWithRegex)
517 m_tooltips.AddTool(pWnd, IDS_LOG_FILTER_REGEX_TT);
519 else
520 m_tooltips.DelTool(pWnd);
523 void CLogDlg::EnableOKButton()
525 if (m_bSelect)
527 // the dialog is used to select revisions
528 if (m_bSelectionMustBeSingle)
530 // enable OK button if only a single revision is selected
531 DialogEnableWindow(IDOK, (m_LogList.GetSelectedCount()==1));
533 else if (m_bSelectionMustBeContinuous)
534 DialogEnableWindow(IDOK, (m_LogList.GetSelectedCount()!=0)&&(m_LogList.IsSelectionContinuous()));
535 else
536 DialogEnableWindow(IDOK, m_LogList.GetSelectedCount()!=0);
538 else
539 DialogEnableWindow(IDOK, TRUE);
542 CString CLogDlg::GetTagInfo(GitRev* pLogEntry)
544 CString cmd;
545 CString output;
547 if(m_LogList.m_HashMap.find(pLogEntry->m_CommitHash) != m_LogList.m_HashMap.end())
549 STRING_VECTOR &vector = m_LogList.m_HashMap[pLogEntry->m_CommitHash];
550 for(int i=0;i<vector.size();i++)
552 if(vector[i].Find(_T("refs/tags/")) == 0 )
554 CString tag= vector[i];
555 int start = vector[i].Find(_T("^{}"));
556 if(start>0)
557 tag=tag.Left(start);
558 else
559 continue;
561 cmd.Format(_T("git.exe cat-file tag %s"), tag);
563 if(g_Git.Run(cmd, &output, NULL, CP_UTF8) == 0 )
564 output+=_T("\n");
569 if(!output.IsEmpty())
571 output = _T("\n*") + CString(MAKEINTRESOURCE(IDS_PROC_LOG_TAGINFO)) + _T("*\n\n") + output;
574 return output;
577 void CLogDlg::FillLogMessageCtrl(bool bShow /* = true*/)
579 // we fill here the log message rich edit control,
580 // and also populate the changed files list control
581 // according to the selected revision(s).
583 CRichEditCtrl * pMsgView = (CRichEditCtrl*)GetDlgItem(IDC_MSGVIEW);
584 // empty the log message view
585 pMsgView->SetWindowText(_T(" "));
586 // empty the changed files list
587 m_ChangedFileListCtrl.SetRedraw(FALSE);
588 // InterlockedExchange(&m_bNoDispUpdates, TRUE);
589 m_currentChangedArray = NULL;
590 //m_ChangedFileListCtrl.SetExtendedStyle ( LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER );
591 m_ChangedFileListCtrl.DeleteAllItems();
593 // if we're not here to really show a selected revision, just
594 // get out of here after clearing the views, which is what is intended
595 // if that flag is not set.
596 if (!bShow)
598 // force a redraw
599 m_ChangedFileListCtrl.Invalidate();
600 // InterlockedExchange(&m_bNoDispUpdates, FALSE);
601 m_ChangedFileListCtrl.SetRedraw(TRUE);
602 return;
605 // depending on how many revisions are selected, we have to do different
606 // tasks.
607 int selCount = m_LogList.GetSelectedCount();
608 if (selCount == 0)
610 // if nothing is selected, we have nothing more to do
611 // InterlockedExchange(&m_bNoDispUpdates, FALSE);
612 m_ChangedFileListCtrl.SetRedraw(TRUE);
613 return;
615 else if (selCount == 1)
617 // if one revision is selected, we have to fill the log message view
618 // with the corresponding log message, and also fill the changed files
619 // list fully.
620 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
621 int selIndex = m_LogList.GetNextSelectedItem(pos);
622 if (selIndex >= m_LogList.m_arShownList.GetCount())
624 // InterlockedExchange(&m_bNoDispUpdates, FALSE);
625 m_ChangedFileListCtrl.SetRedraw(TRUE);
626 return;
628 GitRev* pLogEntry = reinterpret_cast<GitRev *>(m_LogList.m_arShownList.SafeGetAt(selIndex));
631 // set the log message text
632 pMsgView->SetWindowText(CString(MAKEINTRESOURCE(IDS_HASH)) + _T(": ") + pLogEntry->m_CommitHash.ToString() + _T("\r\n\r\n"));
633 // turn bug ID's into links if the bugtraq: properties have been set
634 // and we can find a match of those in the log message
636 pMsgView->SetSel(-1,-1);
637 CHARFORMAT2 format;
638 SecureZeroMemory(&format, sizeof(CHARFORMAT2));
639 format.cbSize = sizeof(CHARFORMAT2);
640 format.dwMask = CFM_BOLD;
641 format.dwEffects = CFE_BOLD;
642 pMsgView->SendMessage(EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&format);
644 CString msg=_T("* ");
645 msg+=pLogEntry->GetSubject();
646 pMsgView->ReplaceSel(msg);
648 pMsgView->SetSel(-1,-1);
649 format.dwEffects = 0;
650 pMsgView->SendMessage(EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&format);
652 msg=_T("\n");
653 msg+=pLogEntry->GetBody();
655 if(!pLogEntry->m_Notes.IsEmpty())
657 msg+= _T("\n*") + CString(MAKEINTRESOURCE(IDS_NOTES)) + _T("* ");
658 msg+= pLogEntry->m_Notes;
659 msg+= _T("\n\n");
662 msg+=GetTagInfo(pLogEntry);
664 pMsgView->ReplaceSel(msg);
666 CString text;
667 pMsgView->GetWindowText(text);
668 // the rich edit control doesn't count the CR char!
669 // to be exact: CRLF is treated as one char.
670 text.Remove('\r');
672 m_LogList.m_ProjectProperties.FindBugID(text, pMsgView);
673 CAppUtils::FormatTextInRichEditControl(pMsgView);
675 int HidePaths=m_cHidePaths.GetState() & 0x0003;
676 CString matchpath=this->m_path.GetGitPathString();
678 int count = pLogEntry->GetFiles(&m_LogList).GetCount();
679 for(int i=0;i<count && (!matchpath.IsEmpty());i++)
681 if( m_bWholeProject )
682 break;
684 ((CTGitPath&)pLogEntry->GetFiles(&m_LogList)[i]).m_Action &= ~(CTGitPath::LOGACTIONS_HIDE|CTGitPath::LOGACTIONS_GRAY);
686 if(pLogEntry->GetFiles(&m_LogList)[i].GetGitPathString().Left(matchpath.GetLength()) != matchpath && pLogEntry->GetFiles(&m_LogList)[i].GetGitOldPathString().Left(matchpath.GetLength()) != matchpath)
688 if(HidePaths==BST_CHECKED)
689 ((CTGitPath&)pLogEntry->GetFiles(&m_LogList)[i]).m_Action |= CTGitPath::LOGACTIONS_HIDE;
690 if(HidePaths==BST_INDETERMINATE)
691 ((CTGitPath&)pLogEntry->GetFiles(&m_LogList)[i]).m_Action |= CTGitPath::LOGACTIONS_GRAY;
695 m_ChangedFileListCtrl.UpdateWithGitPathList(pLogEntry->GetFiles(&m_LogList));
696 m_ChangedFileListCtrl.m_CurrentVersion=pLogEntry->m_CommitHash;
697 m_ChangedFileListCtrl.Show(GITSLC_SHOWVERSIONED);
699 m_ChangedFileListCtrl.SetBusyString(CString(MAKEINTRESOURCE(IDS_PROC_LOG_FETCHINGFILES)));
701 if(!pLogEntry->m_IsDiffFiles)
702 m_ChangedFileListCtrl.SetBusy(TRUE);
703 else
704 m_ChangedFileListCtrl.SetBusy(FALSE);
706 m_ChangedFileListCtrl.SetRedraw(TRUE);
707 return;
711 else
713 // more than one revision is selected:
714 // the log message view must be emptied
715 // the changed files list contains all the changed paths from all
716 // selected revisions, with 'doubles' removed
717 m_currentChangedPathList = GetChangedPathsFromSelectedRevisions(true);
720 // redraw the views
721 // InterlockedExchange(&m_bNoDispUpdates, FALSE);
722 #if 0
723 if (m_currentChangedArray)
725 m_ChangedFileListCtrl.SetItemCountEx(m_currentChangedArray->GetCount());
726 m_ChangedFileListCtrl.RedrawItems(0, m_currentChangedArray->GetCount());
728 else if (m_currentChangedPathList.GetCount())
730 m_ChangedFileListCtrl.SetItemCountEx(m_currentChangedPathList.GetCount());
731 m_ChangedFileListCtrl.RedrawItems(0, m_currentChangedPathList.GetCount());
733 else
735 m_ChangedFileListCtrl.SetItemCountEx(0);
736 m_ChangedFileListCtrl.Invalidate();
738 #endif
739 // sort according to the settings
740 if (m_nSortColumnPathList > 0)
741 SetSortArrow(&m_ChangedFileListCtrl, m_nSortColumnPathList, m_bAscendingPathList);
742 else
743 SetSortArrow(&m_ChangedFileListCtrl, -1, false);
744 m_ChangedFileListCtrl.SetRedraw(TRUE);
748 void CLogDlg::OnBnClickedRefresh()
750 Refresh (true);
753 void CLogDlg::Refresh (bool clearfilter /*autoGoOnline*/)
755 m_limit = 0;
756 m_LogList.Refresh(clearfilter);
757 ShowStartRef();
758 FillLogMessageCtrl(false);
763 BOOL CLogDlg::Cancel()
765 return m_bCancelled;
768 void CLogDlg::SaveSplitterPos()
770 if (!IsIconic())
772 CRegDWORD regPos1 = CRegDWORD(_T("Software\\TortoiseGit\\TortoiseProc\\ResizableState\\LogDlgSizer1"));
773 CRegDWORD regPos2 = CRegDWORD(_T("Software\\TortoiseGit\\TortoiseProc\\ResizableState\\LogDlgSizer2"));
774 RECT rectSplitter;
775 m_wndSplitter1.GetWindowRect(&rectSplitter);
776 ScreenToClient(&rectSplitter);
777 regPos1 = rectSplitter.top;
778 m_wndSplitter2.GetWindowRect(&rectSplitter);
779 ScreenToClient(&rectSplitter);
780 regPos2 = rectSplitter.top;
784 void CLogDlg::OnCancel()
786 this->ShowWindow(SW_HIDE);
788 // canceling means stopping the working thread if it's still running.
789 m_LogList.SafeTerminateAsyncDiffThread();
790 if (this->IsThreadRunning())
792 m_LogList.SafeTerminateThread();
794 UpdateData();
796 SaveSplitterPos();
797 __super::OnCancel();
800 CString CLogDlg::MakeShortMessage(const CString& message)
802 bool bFoundShort = true;
803 CString sShortMessage = m_LogList.m_ProjectProperties.GetLogSummary(message);
804 if (sShortMessage.IsEmpty())
806 bFoundShort = false;
807 sShortMessage = message;
809 // Remove newlines and tabs 'cause those are not shown nicely in the list control
810 sShortMessage.Remove('\r');
811 sShortMessage.Replace(_T('\t'), _T(' '));
813 // Suppose the first empty line separates 'summary' from the rest of the message.
814 int found = sShortMessage.Find(_T("\n\n"));
815 // To avoid too short 'short' messages
816 // (e.g. if the message looks something like "Bugfix:\n\n*done this\n*done that")
817 // only use the empty newline as a separator if it comes after at least 15 chars.
818 if ((!bFoundShort)&&(found >= 15))
820 sShortMessage = sShortMessage.Left(found);
822 sShortMessage.Replace('\n', ' ');
823 return sShortMessage;
826 BOOL CLogDlg::Log(git_revnum_t /*rev*/, const CString& /*author*/, const CString& /*date*/, const CString& /*message*/, LogChangedPathArray * /*cpaths*/, int /*filechanges*/, BOOL /*copies*/, DWORD /*actions*/, BOOL /*haschildren*/)
828 #if 0
829 if (rev == SVN_INVALID_REVNUM)
831 m_childCounter--;
832 return TRUE;
835 // this is the callback function which receives the data for every revision we ask the log for
836 // we store this information here one by one.
837 m_logcounter += 1;
838 if (m_startrev == -1)
839 m_startrev = rev;
840 if (m_limit != 0)
842 m_limitcounter--;
843 m_LogProgress.SetPos(m_limit - m_limitcounter);
845 else if (m_startrev.IsNumber() && m_startrev.IsNumber())
846 m_LogProgress.SetPos((git_revnum_t)m_startrev-rev+(git_revnum_t)m_endrev);
847 __time64_t ttime = time/1000000L;
848 if (m_tTo < (DWORD)ttime)
849 m_tTo = (DWORD)ttime;
850 if (m_tFrom > (DWORD)ttime)
851 m_tFrom = (DWORD)ttime;
852 if ((m_lowestRev > rev)||(m_lowestRev < 0))
853 m_lowestRev = rev;
854 // Add as many characters from the log message to the list control
855 PLOGENTRYDATA pLogItem = new LOGENTRYDATA;
856 pLogItem->bCopies = !!copies;
858 // find out if this item was copied in the revision
859 BOOL copiedself = FALSE;
860 if (copies)
862 for (INT_PTR cpPathIndex = 0; cpPathIndex < cpaths->GetCount(); ++cpPathIndex)
864 LogChangedPath * cpath = cpaths->SafeGetAt(cpPathIndex);
865 if (!cpath->sCopyFromPath.IsEmpty() && (cpath->sPath.Compare(m_sSelfRelativeURL) == 0))
867 // note: this only works if the log is fetched top-to-bottom
868 // but since we do that, it shouldn't be a problem
869 m_sSelfRelativeURL = cpath->sCopyFromPath;
870 copiedself = TRUE;
871 break;
875 pLogItem->bCopiedSelf = copiedself;
876 pLogItem->tmDate = ttime;
877 pLogItem->sAuthor = author;
878 pLogItem->sDate = date;
879 pLogItem->sShortMessage = MakeShortMessage(message);
880 pLogItem->dwFileChanges = filechanges;
881 pLogItem->actions = actions;
882 pLogItem->haschildren = haschildren;
883 pLogItem->childStackDepth = m_childCounter;
884 m_maxChild = max(m_childCounter, m_maxChild);
885 if (haschildren)
886 m_childCounter++;
887 pLogItem->sBugIDs = m_ProjectProperties.FindBugID(message).Trim();
889 // split multi line log entries and concatenate them
890 // again but this time with \r\n as line separators
891 // so that the edit control recognizes them
894 if (message.GetLength()>0)
896 m_sMessageBuf = message;
897 m_sMessageBuf.Replace(_T("\n\r"), _T("\n"));
898 m_sMessageBuf.Replace(_T("\r\n"), _T("\n"));
899 if (m_sMessageBuf.Right(1).Compare(_T("\n"))==0)
900 m_sMessageBuf = m_sMessageBuf.Left(m_sMessageBuf.GetLength()-1);
902 else
903 m_sMessageBuf.Empty();
904 pLogItem->sMessage = m_sMessageBuf;
905 pLogItem->Rev = rev;
907 // move-construct path array
909 pLogItem->pArChangedPaths = new LogChangedPathArray (*cpaths);
910 cpaths->RemoveAll();
912 catch (CException * e)
914 CMessageBox::Show(NULL, IDS_ERR_NOTENOUGHMEMORY, IDS_APPNAME, MB_ICONERROR);
915 e->Delete();
916 m_bCancelled = TRUE;
918 m_logEntries.push_back(pLogItem);
919 m_arShownList.Add(pLogItem);
920 #endif
921 return TRUE;
924 GitRev g_rev;
925 //this is the thread function which calls the subversion function
930 void CLogDlg::CopyChangedSelectionToClipBoard()
933 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
934 if (pos == NULL)
935 return; // nothing is selected, get out of here
937 CString sPaths;
939 // CGitRev* pLogEntry = reinterpret_cast<CGitRev* >(m_LogList.m_arShownList.SafeGetAt(m_LogList.GetNextSelectedItem(pos)));
940 // if (pos)
942 POSITION pos = m_ChangedFileListCtrl.GetFirstSelectedItemPosition();
943 while (pos)
945 int nItem = m_ChangedFileListCtrl.GetNextSelectedItem(pos);
946 CTGitPath *path = (CTGitPath*)m_ChangedFileListCtrl.GetItemData(nItem);
947 if(path)
948 sPaths += path->GetGitPathString();
949 sPaths += _T("\r\n");
952 #if 0
953 else
955 // only one revision is selected in the log dialog top pane
956 // but multiple items could be selected in the changed items list
957 POSITION pos = m_ChangedFileListCtrl.GetFirstSelectedItemPosition();
958 while (pos)
960 int nItem = m_ChangedFileListCtrl.GetNextSelectedItem(pos);
961 LogChangedPath * changedlogpath = pLogEntry->pArChangedPaths->SafeGetAt(nItem);
963 if ((m_cHidePaths.GetState() & 0x0003)==BST_CHECKED)
965 // some items are hidden! So find out which item the user really selected
966 INT_PTR selRealIndex = -1;
967 for (INT_PTR hiddenindex=0; hiddenindex<pLogEntry->pArChangedPaths->GetCount(); ++hiddenindex)
969 if (pLogEntry->pArChangedPaths->SafeGetAt(hiddenindex)->sPath.Left(m_sRelativeRoot.GetLength()).Compare(m_sRelativeRoot)==0)
970 selRealIndex++;
971 if (selRealIndex == nItem)
973 changedlogpath = pLogEntry->pArChangedPaths->SafeGetAt(hiddenindex);
974 break;
978 if (changedlogpath)
980 sPaths += changedlogpath->sPath;
981 sPaths += _T("\r\n");
985 #endif
986 sPaths.Trim();
987 CStringUtils::WriteAsciiStringToClipboard(sPaths, GetSafeHwnd());
991 BOOL CLogDlg::IsDiffPossible(LogChangedPath * /*changedpath*/, git_revnum_t rev)
993 #if 0
994 CString added, deleted;
995 if (changedpath == NULL)
996 return false;
998 if ((rev > 1)&&(changedpath->action != LOGACTIONS_DELETED))
1000 if (changedpath->action == LOGACTIONS_ADDED) // file is added
1002 if (changedpath->lCopyFromRev == 0)
1003 return FALSE; // but file was not added with history
1005 return TRUE;
1007 #endif
1008 return FALSE;
1011 void CLogDlg::OnContextMenu(CWnd* pWnd, CPoint point)
1013 // we have two separate context menus:
1014 // one shown on the log message list control,
1015 // the other shown in the changed-files list control
1016 int selCount = m_LogList.GetSelectedCount();
1017 if (pWnd == &m_LogList)
1019 //ShowContextMenuForRevisions(pWnd, point);
1021 else if (pWnd == &m_ChangedFileListCtrl)
1023 //ShowContextMenuForChangedpaths(pWnd, point);
1025 else if ((selCount == 1)&&(pWnd == GetDlgItem(IDC_MSGVIEW)))
1027 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
1028 int selIndex = -1;
1029 if (pos)
1030 selIndex = m_LogList.GetNextSelectedItem(pos);
1032 GitRev *pRev = ((GitRev*)m_LogList.m_arShownList[selIndex]);
1034 if ((point.x == -1) && (point.y == -1))
1036 CRect rect;
1037 GetDlgItem(IDC_MSGVIEW)->GetClientRect(&rect);
1038 ClientToScreen(&rect);
1039 point = rect.CenterPoint();
1041 CString sMenuItemText;
1042 CIconMenu popup;
1043 if (popup.CreatePopupMenu())
1045 // add the 'default' entries
1046 sMenuItemText.LoadString(IDS_SCIEDIT_COPY);
1047 popup.AppendMenu(MF_STRING | MF_ENABLED, WM_COPY, sMenuItemText);
1048 sMenuItemText.LoadString(IDS_SCIEDIT_SELECTALL);
1049 popup.AppendMenu(MF_STRING | MF_ENABLED, EM_SETSEL, sMenuItemText);
1050 sMenuItemText.LoadString(IDS_EDIT_NOTES);
1051 popup.AppendMenuIcon( CGitLogList::ID_EDITNOTE, sMenuItemText, IDI_EDIT);
1053 //if (selIndex >= 0)
1055 // popup.AppendMenu(MF_SEPARATOR);
1056 // sMenuItemText.LoadString(IDS_LOG_POPUP_EDITLOG);
1057 // popup.AppendMenu(MF_STRING | MF_ENABLED, CGitLogList::ID_EDITAUTHOR, sMenuItemText);
1060 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
1061 switch (cmd)
1063 case 0:
1064 break; // no command selected
1065 case EM_SETSEL:
1066 case WM_COPY:
1067 ::SendMessage(GetDlgItem(IDC_MSGVIEW)->GetSafeHwnd(), cmd, 0, -1);
1068 break;
1069 case CGitLogList::ID_EDITNOTE:
1070 CAppUtils::EditNote(pRev);
1071 this->FillLogMessageCtrl(true);
1072 break;
1078 void CLogDlg::OnOK()
1080 // since the log dialog is also used to select revisions for other
1081 // dialogs, we have to do some work before closing this dialog
1082 if (GetFocus() != GetDlgItem(IDOK))
1083 return; // if the "OK" button doesn't have the focus, do nothing: this prevents closing the dialog when pressing enter
1085 m_LogList.SafeTerminateAsyncDiffThread();
1086 if (this->IsThreadRunning())
1088 m_LogList.SafeTerminateThread();
1090 UpdateData();
1091 // check that one and only one row is selected
1092 if (m_LogList.GetSelectedCount() == 1)
1094 // get the selected row
1095 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
1096 int selIndex = m_LogList.GetNextSelectedItem(pos);
1097 if (selIndex < m_LogList.m_arShownList.GetCount())
1099 // all ok, pick up the revision
1100 GitRev* pLogEntry = reinterpret_cast<GitRev *>(m_LogList.m_arShownList.SafeGetAt(selIndex));
1101 // extract the hash
1102 m_sSelectedHash = pLogEntry->m_CommitHash;
1105 UpdateData(FALSE);
1106 SaveSplitterPos();
1107 __super::OnOK();
1109 #if 0
1110 if (!GetDlgItem(IDOK)->IsWindowVisible() && GetFocus() != GetDlgItem(IDCANCEL))
1111 return; // the Cancel button works as the OK button. But if the cancel button has not the focus, do nothing.
1113 CString temp;
1114 CString buttontext;
1115 GetDlgItemText(IDOK, buttontext);
1116 temp.LoadString(IDS_MSGBOX_CANCEL);
1117 if (temp.Compare(buttontext) != 0)
1118 __super::OnOK(); // only exit if the button text matches, and that will match only if the thread isn't running anymore
1119 m_bCancelled = TRUE;
1120 m_selectedRevs.Clear();
1121 m_selectedRevsOneRange.Clear();
1122 if (m_pNotifyWindow)
1124 int selIndex = m_LogList.GetSelectionMark();
1125 if (selIndex >= 0)
1127 PLOGENTRYDATA pLogEntry = NULL;
1128 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
1129 pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(m_LogList.GetNextSelectedItem(pos)));
1130 m_selectedRevs.AddRevision(pLogEntry->Rev);
1131 git_revnum_t lowerRev = pLogEntry->Rev;
1132 git_revnum_t higherRev = lowerRev;
1133 while (pos)
1135 pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(m_LogList.GetNextSelectedItem(pos)));
1136 git_revnum_t rev = pLogEntry->Rev;
1137 m_selectedRevs.AddRevision(pLogEntry->Rev);
1138 if (lowerRev > rev)
1139 lowerRev = rev;
1140 if (higherRev < rev)
1141 higherRev = rev;
1143 if (m_sFilterText.IsEmpty() && m_nSortColumn == 0 && IsSelectionContinuous())
1145 m_selectedRevsOneRange.AddRevRange(lowerRev, higherRev);
1147 BOOL bSentMessage = FALSE;
1148 if (m_LogList.GetSelectedCount() == 1)
1150 // if only one revision is selected, check if the path/url with which the dialog was started
1151 // was directly affected in that revision. If it was, then check if our path was copied from somewhere.
1152 // if it was copied, use the copy from revision as lowerRev
1153 if ((pLogEntry)&&(pLogEntry->pArChangedPaths)&&(lowerRev == higherRev))
1155 CString sUrl = m_path.GetGitPathString();
1156 if (!m_path.IsUrl())
1158 sUrl = GetURLFromPath(m_path);
1160 sUrl = sUrl.Mid(m_sRepositoryRoot.GetLength());
1161 for (int cp = 0; cp < pLogEntry->pArChangedPaths->GetCount(); ++cp)
1163 LogChangedPath * pData = pLogEntry->pArChangedPaths->SafeGetAt(cp);
1164 if (pData)
1166 if (sUrl.Compare(pData->sPath) == 0)
1168 if (!pData->sCopyFromPath.IsEmpty())
1170 lowerRev = pData->lCopyFromRev;
1171 m_pNotifyWindow->SendMessage(WM_REVSELECTED, m_wParam & (MERGE_REVSELECTSTART), lowerRev);
1172 m_pNotifyWindow->SendMessage(WM_REVSELECTED, m_wParam & (MERGE_REVSELECTEND), higherRev);
1173 m_pNotifyWindow->SendMessage(WM_REVLIST, m_selectedRevs.GetCount(), (LPARAM)&m_selectedRevs);
1174 bSentMessage = TRUE;
1181 if ( !bSentMessage )
1183 m_pNotifyWindow->SendMessage(WM_REVSELECTED, m_wParam & (MERGE_REVSELECTSTART | MERGE_REVSELECTMINUSONE), lowerRev);
1184 m_pNotifyWindow->SendMessage(WM_REVSELECTED, m_wParam & (MERGE_REVSELECTEND | MERGE_REVSELECTMINUSONE), higherRev);
1185 m_pNotifyWindow->SendMessage(WM_REVLIST, m_selectedRevs.GetCount(), (LPARAM)&m_selectedRevs);
1186 if (m_selectedRevsOneRange.GetCount())
1187 m_pNotifyWindow->SendMessage(WM_REVLISTONERANGE, 0, (LPARAM)&m_selectedRevsOneRange);
1191 UpdateData();
1192 CRegDWORD reg = CRegDWORD(_T("Software\\TortoiseGit\\ShowAllEntry"));
1193 reg = m_btnShow.GetCurrentEntry();
1194 SaveSplitterPos();
1195 #endif
1198 void CLogDlg::OnNMDblclkChangedFileList(NMHDR * /*pNMHDR*/, LRESULT *pResult)
1200 // a double click on an entry in the changed-files list has happened
1201 *pResult = 0;
1203 DiffSelectedFile();
1206 void CLogDlg::DiffSelectedFile()
1208 #if 0
1209 if (m_bThreadRunning)
1210 return;
1211 UpdateLogInfoLabel();
1212 INT_PTR selIndex = m_ChangedFileListCtrl.GetSelectionMark();
1213 if (selIndex < 0)
1214 return;
1215 if (m_ChangedFileListCtrl.GetSelectedCount() == 0)
1216 return;
1217 // find out if there's an entry selected in the log list
1218 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
1219 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(m_LogList.GetNextSelectedItem(pos)));
1220 git_revnum_t rev1 = pLogEntry->Rev;
1221 git_revnum_t rev2 = rev1;
1222 if (pos)
1224 while (pos)
1226 // there's at least a second entry selected in the log list: several revisions selected!
1227 pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(m_LogList.GetNextSelectedItem(pos)));
1228 if (pLogEntry)
1230 rev1 = max(rev1,(long)pLogEntry->Rev);
1231 rev2 = min(rev2,(long)pLogEntry->Rev);
1234 rev2--;
1235 // now we have both revisions selected in the log list, so we can do a diff of the selected
1236 // entry in the changed files list with these two revisions.
1237 DoDiffFromLog(selIndex, rev1, rev2, false, false);
1239 else
1241 rev2 = rev1-1;
1242 // nothing or only one revision selected in the log list
1243 LogChangedPath * changedpath = pLogEntry->pArChangedPaths->SafeGetAt(selIndex);
1245 if ((m_cHidePaths.GetState() & 0x0003)==BST_CHECKED)
1247 // some items are hidden! So find out which item the user really clicked on
1248 INT_PTR selRealIndex = -1;
1249 for (INT_PTR hiddenindex=0; hiddenindex<pLogEntry->pArChangedPaths->GetCount(); ++hiddenindex)
1251 if (pLogEntry->pArChangedPaths->SafeGetAt(hiddenindex)->sPath.Left(m_sRelativeRoot.GetLength()).Compare(m_sRelativeRoot)==0)
1252 selRealIndex++;
1253 if (selRealIndex == selIndex)
1255 selIndex = hiddenindex;
1256 changedpath = pLogEntry->pArChangedPaths->SafeGetAt(selIndex);
1257 break;
1262 if (IsDiffPossible(changedpath, rev1))
1264 // diffs with renamed files are possible
1265 if ((changedpath)&&(!changedpath->sCopyFromPath.IsEmpty()))
1266 rev2 = changedpath->lCopyFromRev;
1267 else
1269 // if the path was modified but the parent path was 'added with history'
1270 // then we have to use the copy from revision of the parent path
1271 CTGitPath cpath = CTGitPath(changedpath->sPath);
1272 for (int flist = 0; flist < pLogEntry->pArChangedPaths->GetCount(); ++flist)
1274 CTGitPath p = CTGitPath(pLogEntry->pArChangedPaths->SafeGetAt(flist)->sPath);
1275 if (p.IsAncestorOf(cpath))
1277 if (!pLogEntry->pArChangedPaths->SafeGetAt(flist)->sCopyFromPath.IsEmpty())
1278 rev2 = pLogEntry->pArChangedPaths->SafeGetAt(flist)->lCopyFromRev;
1282 DoDiffFromLog(selIndex, rev1, rev2, false, false);
1284 else
1286 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, CTGitPath(changedpath->sPath));
1287 CTGitPath tempfile2 = CTempFiles::Instance().GetTempFilePath(false, CTGitPath(changedpath->sPath));
1288 GitRev r = rev1;
1289 // deleted files must be opened from the revision before the deletion
1290 if (changedpath->action == LOGACTIONS_DELETED)
1291 r = rev1-1;
1292 m_bCancelled = false;
1294 CProgressDlg progDlg;
1295 progDlg.SetTitle(IDS_APPNAME);
1296 progDlg.SetAnimation(IDR_DOWNLOAD);
1297 CString sInfoLine;
1298 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, (LPCTSTR)(m_sRepositoryRoot + changedpath->sPath), (LPCTSTR)r.ToString());
1299 progDlg.SetLine(1, sInfoLine, true);
1300 SetAndClearProgressInfo(&progDlg);
1301 progDlg.ShowModeless(m_hWnd);
1303 if (!Cat(CTGitPath(m_sRepositoryRoot + changedpath->sPath), r, r, tempfile))
1305 m_bCancelled = false;
1306 if (!Cat(CTGitPath(m_sRepositoryRoot + changedpath->sPath), GitRev::REV_HEAD, r, tempfile))
1308 progDlg.Stop();
1309 SetAndClearProgressInfo((HWND)NULL);
1310 CMessageBox::Show(m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1311 return;
1314 progDlg.Stop();
1315 SetAndClearProgressInfo((HWND)NULL);
1317 CString sName1, sName2;
1318 sName1.Format(_T("%s - Revision %ld"), (LPCTSTR)CPathUtils::GetFileNameFromPath(changedpath->sPath), (git_revnum_t)rev1);
1319 sName2.Format(_T("%s - Revision %ld"), (LPCTSTR)CPathUtils::GetFileNameFromPath(changedpath->sPath), (git_revnum_t)rev1-1);
1320 CAppUtils::DiffFlags flags;
1321 flags.AlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
1322 if (changedpath->action == LOGACTIONS_DELETED)
1323 CAppUtils::StartExtDiff(tempfile, tempfile2, sName2, sName1, flags);
1324 else
1325 CAppUtils::StartExtDiff(tempfile2, tempfile, sName2, sName1, flags);
1328 #endif
1332 void CLogDlg::DoDiffFromLog(INT_PTR selIndex, GitRev* rev1, GitRev* rev2, bool /*blame*/, bool /*unified*/)
1334 DialogEnableWindow(IDOK, FALSE);
1335 // SetPromptApp(&theApp);
1336 theApp.DoWaitCursor(1);
1338 CString temppath;
1339 GetTempPath(temppath);
1341 CString file1;
1342 file1.Format(_T("%s%s_%s%s"),
1343 temppath,
1344 (*m_currentChangedArray)[selIndex].GetBaseFilename(),
1345 rev1->m_CommitHash.ToString().Left(g_Git.GetShortHASHLength()),
1346 (*m_currentChangedArray)[selIndex].GetFileExtension());
1348 CString file2;
1349 file2.Format(_T("%s\\%s_%s%s"),
1350 temppath,
1351 (*m_currentChangedArray)[selIndex].GetBaseFilename(),
1352 rev2->m_CommitHash.ToString().Left(g_Git.GetShortHASHLength()),
1353 (*m_currentChangedArray)[selIndex].GetFileExtension());
1355 CString cmd;
1357 g_Git.GetOneFile(rev1->m_CommitHash.ToString(), (CTGitPath &)(*m_currentChangedArray)[selIndex],file1);
1359 g_Git.GetOneFile(rev2->m_CommitHash.ToString(), (CTGitPath &)(*m_currentChangedArray)[selIndex],file2);
1361 CAppUtils::DiffFlags flags;
1362 CAppUtils::StartExtDiff(file1,file2,_T("A"),_T("B"),flags);
1364 #if 0
1365 //get the filename
1366 CString filepath;
1367 if (Git::PathIsURL(m_path))
1369 filepath = m_path.GetGitPathString();
1371 else
1373 filepath = GetURLFromPath(m_path);
1374 if (filepath.IsEmpty())
1376 theApp.DoWaitCursor(-1);
1377 CString temp;
1378 temp.Format(IDS_ERR_NOURLOFFILE, (LPCTSTR)filepath);
1379 CMessageBox::Show(this->m_hWnd, temp, _T("TortoiseGit"), MB_ICONERROR);
1380 TRACE(_T("could not retrieve the URL of the file!\n"));
1381 EnableOKButton();
1382 theApp.DoWaitCursor(-11);
1383 return; //exit
1386 m_bCancelled = FALSE;
1387 filepath = GetRepositoryRoot(CTGitPath(filepath));
1389 CString firstfile, secondfile;
1390 if (m_LogList.GetSelectedCount()==1)
1392 int s = m_LogList.GetSelectionMark();
1393 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(s));
1394 LogChangedPath * changedpath = pLogEntry->pArChangedPaths->SafeGetAt(selIndex);
1395 firstfile = changedpath->sPath;
1396 secondfile = firstfile;
1397 if ((rev2 == rev1-1)&&(changedpath->lCopyFromRev > 0)) // is it an added file with history?
1399 secondfile = changedpath->sCopyFromPath;
1400 rev2 = changedpath->lCopyFromRev;
1403 else
1405 firstfile = m_currentChangedPathList[selIndex].GetGitPathString();
1406 secondfile = firstfile;
1409 firstfile = filepath + firstfile.Trim();
1410 secondfile = filepath + secondfile.Trim();
1412 GitDiff diff(this, this->m_hWnd, true);
1413 diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
1414 diff.SetHEADPeg(m_LogRevision);
1415 if (unified)
1417 if (PromptShown())
1418 diff.ShowUnifiedDiff(CTGitPath(secondfile), rev2, CTGitPath(firstfile), rev1);
1419 else
1420 CAppUtils::StartShowUnifiedDiff(m_hWnd, CTGitPath(secondfile), rev2, CTGitPath(firstfile), rev1, GitRev(), m_LogRevision);
1422 else
1424 if (diff.ShowCompare(CTGitPath(secondfile), rev2, CTGitPath(firstfile), rev1, GitRev(), false, blame))
1426 if (firstfile.Compare(secondfile)==0)
1428 git_revnum_t baseRev = 0;
1429 diff.DiffProps(CTGitPath(firstfile), rev2, rev1, baseRev);
1434 #endif
1436 theApp.DoWaitCursor(-1);
1437 EnableOKButton();
1440 BOOL CLogDlg::Open(bool /*bOpenWith*/,CString changedpath, git_revnum_t rev)
1442 #if 0
1443 DialogEnableWindow(IDOK, FALSE);
1444 SetPromptApp(&theApp);
1445 theApp.DoWaitCursor(1);
1446 CString filepath;
1447 if (Git::PathIsURL(m_path))
1449 filepath = m_path.GetGitPathString();
1451 else
1453 filepath = GetURLFromPath(m_path);
1454 if (filepath.IsEmpty())
1456 theApp.DoWaitCursor(-1);
1457 CString temp;
1458 temp.Format(IDS_ERR_NOURLOFFILE, (LPCTSTR)filepath);
1459 CMessageBox::Show(this->m_hWnd, temp, _T("TortoiseGit"), MB_ICONERROR);
1460 TRACE(_T("could not retrieve the URL of the file!\n"));
1461 EnableOKButton();
1462 return FALSE;
1465 m_bCancelled = false;
1466 filepath = GetRepositoryRoot(CTGitPath(filepath));
1467 filepath += changedpath;
1469 CProgressDlg progDlg;
1470 progDlg.SetTitle(IDS_APPNAME);
1471 progDlg.SetAnimation(IDR_DOWNLOAD);
1472 CString sInfoLine;
1473 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, (LPCTSTR)filepath, (LPCTSTR)GitRev(rev).ToString());
1474 progDlg.SetLine(1, sInfoLine, true);
1475 SetAndClearProgressInfo(&progDlg);
1476 progDlg.ShowModeless(m_hWnd);
1478 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, CTGitPath(filepath), rev);
1479 m_bCancelled = false;
1480 if (!Cat(CTGitPath(filepath), GitRev(rev), rev, tempfile))
1482 progDlg.Stop();
1483 SetAndClearProgressInfo((HWND)NULL);
1484 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1485 EnableOKButton();
1486 theApp.DoWaitCursor(-1);
1487 return FALSE;
1489 progDlg.Stop();
1490 SetAndClearProgressInfo((HWND)NULL);
1491 SetFileAttributes(tempfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1492 if (!bOpenWith)
1494 int ret = (int)ShellExecute(this->m_hWnd, NULL, tempfile.GetWinPath(), NULL, NULL, SW_SHOWNORMAL);
1495 if (ret <= HINSTANCE_ERROR)
1496 bOpenWith = true;
1498 if (bOpenWith)
1500 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
1501 cmd += tempfile.GetWinPathString() + _T(" ");
1502 CAppUtils::LaunchApplication(cmd, NULL, false);
1504 EnableOKButton();
1505 theApp.DoWaitCursor(-1);
1506 #endif
1507 return TRUE;
1510 void CLogDlg::EditAuthor(const CLogDataVector& /*logs*/)
1512 #if 0
1513 CString url;
1514 CString name;
1515 if (logs.size() == 0)
1516 return;
1517 DialogEnableWindow(IDOK, FALSE);
1518 SetPromptApp(&theApp);
1519 theApp.DoWaitCursor(1);
1520 if (Git::PathIsURL(m_path))
1521 url = m_path.GetGitPathString();
1522 else
1524 url = GetURLFromPath(m_path);
1526 name = Git_PROP_REVISION_AUTHOR;
1528 CString value = RevPropertyGet(name, CTGitPath(url), logs[0]->Rev);
1529 CString sOldValue = value;
1530 value.Replace(_T("\n"), _T("\r\n"));
1531 CInputDlg dlg(this);
1532 dlg.m_sHintText.LoadString(IDS_LOG_AUTHOR);
1533 dlg.m_sInputText = value;
1534 dlg.m_sTitle.LoadString(IDS_LOG_AUTHOREDITTITLE);
1535 dlg.m_pProjectProperties = &m_ProjectProperties;
1536 dlg.m_bUseLogWidth = false;
1537 if (dlg.DoModal() == IDOK)
1539 dlg.m_sInputText.Remove('\r');
1541 LogCache::CCachedLogInfo* toUpdate = GetLogCache (CTGitPath (m_sRepositoryRoot));
1543 CProgressDlg progDlg;
1544 progDlg.SetTitle(IDS_APPNAME);
1545 progDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
1546 progDlg.SetTime(true);
1547 progDlg.SetShowProgressBar(true);
1548 progDlg.ShowModeless(m_hWnd);
1549 for (DWORD i=0; i<logs.size(); ++i)
1551 if (!RevPropertySet(name, dlg.m_sInputText, sOldValue, CTGitPath(url), logs[i]->Rev))
1553 progDlg.Stop();
1554 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1555 break;
1557 else
1560 logs[i]->sAuthor = dlg.m_sInputText;
1561 m_LogList.Invalidate();
1563 // update the log cache
1565 if (toUpdate != NULL)
1567 // log caching is active
1569 LogCache::CCachedLogInfo newInfo;
1570 newInfo.Insert ( logs[i]->Rev
1571 , (const char*) CUnicodeUtils::GetUTF8 (logs[i]->sAuthor)
1572 , ""
1574 , LogCache::CRevisionInfoContainer::HAS_AUTHOR);
1576 toUpdate->Update (newInfo);
1579 progDlg.SetProgress64(i, logs.size());
1581 progDlg.Stop();
1583 theApp.DoWaitCursor(-1);
1584 EnableOKButton();
1585 #endif
1588 void CLogDlg::EditLogMessage(int /*index*/)
1591 #if 0
1592 CString url;
1593 CString name;
1594 DialogEnableWindow(IDOK, FALSE);
1595 SetPromptApp(&theApp);
1596 theApp.DoWaitCursor(1);
1597 if (Git::PathIsURL(m_path))
1598 url = m_path.GetGitPathString();
1599 else
1601 url = GetURLFromPath(m_path);
1603 name = Git_PROP_REVISION_LOG;
1605 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(index));
1606 m_bCancelled = FALSE;
1607 CString value = RevPropertyGet(name, CTGitPath(url), pLogEntry->Rev);
1608 CString sOldValue = value;
1609 value.Replace(_T("\n"), _T("\r\n"));
1610 CInputDlg dlg(this);
1611 dlg.m_sHintText.LoadString(IDS_LOG_MESSAGE);
1612 dlg.m_sInputText = value;
1613 dlg.m_sTitle.LoadString(IDS_LOG_MESSAGEEDITTITLE);
1614 dlg.m_pProjectProperties = &m_ProjectProperties;
1615 dlg.m_bUseLogWidth = true;
1616 if (dlg.DoModal() == IDOK)
1618 dlg.m_sInputText.Remove('\r');
1619 if (!RevPropertySet(name, dlg.m_sInputText, sOldValue, CTGitPath(url), pLogEntry->Rev))
1621 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1623 else
1625 pLogEntry->sShortMessage = MakeShortMessage(dlg.m_sInputText);
1626 // split multi line log entries and concatenate them
1627 // again but this time with \r\n as line separators
1628 // so that the edit control recognizes them
1629 if (dlg.m_sInputText.GetLength()>0)
1631 m_sMessageBuf = dlg.m_sInputText;
1632 dlg.m_sInputText.Replace(_T("\n\r"), _T("\n"));
1633 dlg.m_sInputText.Replace(_T("\r\n"), _T("\n"));
1634 if (dlg.m_sInputText.Right(1).Compare(_T("\n"))==0)
1635 dlg.m_sInputText = dlg.m_sInputText.Left(dlg.m_sInputText.GetLength()-1);
1637 else
1638 dlg.m_sInputText.Empty();
1639 pLogEntry->sMessage = dlg.m_sInputText;
1640 pLogEntry->sBugIDs = m_ProjectProperties.FindBugID(dlg.m_sInputText);
1641 CWnd * pMsgView = GetDlgItem(IDC_MSGVIEW);
1642 pMsgView->SetWindowText(_T(" "));
1643 pMsgView->SetWindowText(dlg.m_sInputText);
1644 m_ProjectProperties.FindBugID(dlg.m_sInputText, pMsgView);
1645 m_LogList.Invalidate();
1647 // update the log cache
1648 LogCache::CCachedLogInfo* toUpdate = GetLogCache(CTGitPath (m_sRepositoryRoot));
1649 if (toUpdate != NULL)
1651 // log caching is active
1653 LogCache::CCachedLogInfo newInfo;
1654 newInfo.Insert( pLogEntry->Rev
1655 , ""
1656 , (const char*) CUnicodeUtils::GetUTF8 (pLogEntry->sMessage)
1658 , LogCache::CRevisionInfoContainer::HAS_COMMENT);
1660 toUpdate->Update(newInfo);
1664 theApp.DoWaitCursor(-1);
1665 EnableOKButton();
1666 #endif
1669 BOOL CLogDlg::PreTranslateMessage(MSG* pMsg)
1671 // Skip Ctrl-C when copying text out of the log message or search filter
1672 bool bSkipAccelerator = (pMsg->message == WM_KEYDOWN && (pMsg->wParam == 'C' || pMsg->wParam == VK_INSERT) && (GetFocus() == GetDlgItem(IDC_MSGVIEW) || GetFocus() == GetDlgItem(IDC_SEARCHEDIT)) && GetKeyState(VK_CONTROL) & 0x8000);
1673 if (pMsg->message == WM_KEYDOWN && pMsg->wParam=='\r')
1675 if (GetFocus()==GetDlgItem(IDC_LOGLIST))
1677 if (CRegDWORD(_T("Software\\TortoiseGit\\DiffByDoubleClickInLog"), FALSE))
1679 m_LogList.DiffSelectedRevWithPrevious();
1680 return TRUE;
1684 if (m_hAccel && !bSkipAccelerator)
1686 int ret = TranslateAccelerator(m_hWnd, m_hAccel, pMsg);
1687 if (ret)
1688 return TRUE;
1691 if(::IsWindow(m_tooltips.m_hWnd))
1692 m_tooltips.RelayEvent(pMsg);
1693 return __super::PreTranslateMessage(pMsg);
1697 BOOL CLogDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
1699 //if (this->IsThreadRunning())
1700 if(m_LogList.m_bNoDispUpdates)
1702 // only show the wait cursor over the list control
1703 if ((pWnd)&&
1704 ((pWnd == GetDlgItem(IDC_LOGLIST))||
1705 (pWnd == GetDlgItem(IDC_MSGVIEW))||
1706 (pWnd == GetDlgItem(IDC_LOGMSG))))
1708 HCURSOR hCur = LoadCursor(NULL, MAKEINTRESOURCE(IDC_WAIT));
1709 SetCursor(hCur);
1710 return TRUE;
1713 if ((pWnd) && (pWnd == GetDlgItem(IDC_MSGVIEW)))
1714 return CResizableStandAloneDialog::OnSetCursor(pWnd, nHitTest, message);
1716 HCURSOR hCur = LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW));
1717 SetCursor(hCur);
1718 return CResizableStandAloneDialog::OnSetCursor(pWnd, nHitTest, message);
1721 void CLogDlg::OnBnClickedHelp()
1723 OnHelp();
1726 void CLogDlg::OnLvnItemchangedLoglist(NMHDR *pNMHDR, LRESULT *pResult)
1728 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1729 *pResult = 0;
1730 //if (this->IsThreadRunning())
1731 if(m_LogList.m_bNoDispUpdates)
1732 return;
1733 if (pNMLV->iItem >= 0)
1735 this->m_LogList.m_nSearchIndex = pNMLV->iItem;
1736 GitRev* pLogEntry = reinterpret_cast<GitRev *>(m_LogList.m_arShownList.SafeGetAt(pNMLV->iItem));
1737 m_LogList.m_lastSelectedHash = pLogEntry->m_CommitHash;
1738 if (pNMLV->iSubItem != 0)
1739 return;
1740 if ((pNMLV->iItem == m_LogList.m_arShownList.GetCount()))
1742 // remove the selected state
1743 if (pNMLV->uChanged & LVIF_STATE)
1745 m_LogList.SetItemState(pNMLV->iItem, 0, LVIS_SELECTED);
1746 FillLogMessageCtrl();
1747 UpdateData(FALSE);
1748 UpdateLogInfoLabel();
1750 return;
1752 if (pNMLV->uChanged & LVIF_STATE)
1754 FillLogMessageCtrl();
1755 UpdateData(FALSE);
1758 else
1760 m_LogList.m_lastSelectedHash.Empty();
1761 FillLogMessageCtrl();
1762 UpdateData(FALSE);
1764 EnableOKButton();
1765 UpdateLogInfoLabel();
1768 void CLogDlg::OnEnLinkMsgview(NMHDR *pNMHDR, LRESULT *pResult)
1770 ENLINK *pEnLink = reinterpret_cast<ENLINK *>(pNMHDR);
1771 if (pEnLink->msg == WM_LBUTTONUP)
1773 CString url, msg;
1774 GetDlgItemText(IDC_MSGVIEW, msg);
1775 msg.Replace(_T("\r\n"), _T("\n"));
1776 url = msg.Mid(pEnLink->chrg.cpMin, pEnLink->chrg.cpMax-pEnLink->chrg.cpMin);
1777 if (!::PathIsURL(url))
1779 url = m_LogList.m_ProjectProperties.GetBugIDUrl(url);
1780 url = GetAbsoluteUrlFromRelativeUrl(url);
1782 if (!url.IsEmpty())
1783 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
1785 *pResult = 0;
1788 class CDateSorter
1790 public:
1791 class CCommitPointer
1793 public:
1794 CCommitPointer():m_cont(NULL){}
1795 CCommitPointer(const CCommitPointer& P_Right)
1796 : m_cont(NULL)
1798 *this = P_Right;
1801 CCommitPointer& operator = (const CCommitPointer& P_Right)
1803 if(IsPointer())
1805 (*m_cont->m_parDates)[m_place] = P_Right.GetDate();
1806 (*m_cont->m_parFileChanges)[m_place] = P_Right.GetChanges();
1807 (*m_cont->m_parAuthors)[m_place] = P_Right.GetAuthor();
1809 else
1811 m_Date = P_Right.GetDate();
1812 m_Changes = P_Right.GetChanges();
1813 m_csAuthor = P_Right.GetAuthor();
1815 return *this;
1818 void Clone(const CCommitPointer& P_Right)
1820 m_cont = P_Right.m_cont;
1821 m_place = P_Right.m_place;
1822 m_Date = P_Right.m_Date;
1823 m_Changes = P_Right.m_Changes;
1824 m_csAuthor = P_Right.m_csAuthor;
1827 DWORD GetDate() const {return IsPointer() ? (*m_cont->m_parDates)[m_place] : m_Date;}
1828 DWORD GetChanges() const {return IsPointer() ? (*m_cont->m_parFileChanges)[m_place] : m_Changes;}
1829 CString GetAuthor() const {return IsPointer() ? (*m_cont->m_parAuthors)[m_place] : m_csAuthor;}
1831 bool IsPointer() const {return m_cont != NULL;}
1832 //When pointer
1833 CDateSorter* m_cont;
1834 int m_place;
1836 //When element
1837 DWORD m_Date;
1838 DWORD m_Changes;
1839 CString m_csAuthor;
1842 class iterator : public std::iterator<std::random_access_iterator_tag, CCommitPointer>
1844 public:
1845 CCommitPointer m_ptr;
1847 iterator(){}
1848 iterator(const iterator& P_Right){*this = P_Right;}
1849 iterator& operator=(const iterator& P_Right)
1851 m_ptr.Clone(P_Right.m_ptr);
1852 return *this;
1855 CCommitPointer& operator*(){return m_ptr;}
1856 CCommitPointer* operator->(){return &m_ptr;}
1857 const CCommitPointer& operator*()const{return m_ptr;}
1858 const CCommitPointer* operator->()const{return &m_ptr;}
1860 iterator& operator+=(size_t P_iOffset){m_ptr.m_place += P_iOffset;return *this;}
1861 iterator& operator-=(size_t P_iOffset){m_ptr.m_place -= P_iOffset;return *this;}
1862 iterator operator+(size_t P_iOffset)const{iterator it(*this); it += P_iOffset;return it;}
1863 iterator operator-(size_t P_iOffset)const{iterator it(*this); it -= P_iOffset;return it;}
1865 iterator& operator++(){++m_ptr.m_place;return *this;}
1866 iterator& operator--(){--m_ptr.m_place;return *this;}
1867 iterator operator++(int){iterator it(*this);++*this;return it;}
1868 iterator operator--(int){iterator it(*this);--*this;return it;}
1870 size_t operator-(const iterator& P_itRight)const{return m_ptr.m_place - P_itRight->m_place;}
1872 bool operator<(const iterator& P_itRight)const{return m_ptr.m_place < P_itRight->m_place;}
1873 bool operator!=(const iterator& P_itRight)const{return m_ptr.m_place != P_itRight->m_place;}
1874 bool operator==(const iterator& P_itRight)const{return m_ptr.m_place == P_itRight->m_place;}
1875 bool operator>(const iterator& P_itRight)const{return m_ptr.m_place > P_itRight->m_place;}
1877 iterator begin()
1879 iterator it;
1880 it->m_place = 0;
1881 it->m_cont = this;
1882 return it;
1884 iterator end()
1886 iterator it;
1887 it->m_place = m_parDates->GetCount();
1888 it->m_cont = this;
1889 return it;
1892 CDWordArray * m_parDates;
1893 CDWordArray * m_parFileChanges;
1894 CStringArray * m_parAuthors;
1897 class CDateSorterLess
1899 public:
1900 bool operator () (const CDateSorter::CCommitPointer& P_Left, const CDateSorter::CCommitPointer& P_Right) const
1902 return P_Left.GetDate() > P_Right.GetDate(); //Last date first
1909 void CLogDlg::OnBnClickedStatbutton()
1911 if (this->IsThreadRunning())
1912 return;
1913 if (m_LogList.m_arShownList.IsEmpty() || m_LogList.m_arShownList.GetCount() == 1 && m_LogList.m_bShowWC)
1914 return; // nothing or just the working copy changes are shown, so no statistics.
1915 // the statistics dialog expects the log entries to be sorted by date
1916 SortByColumn(3, false);
1917 CThreadSafePtrArray shownlist(NULL);
1918 m_LogList.RecalculateShownList(&shownlist);
1919 // create arrays which are aware of the current filter
1920 CStringArray m_arAuthorsFiltered;
1921 CDWordArray m_arDatesFiltered;
1922 CDWordArray m_arFileChangesFiltered;
1923 for (INT_PTR i=0; i<shownlist.GetCount(); ++i)
1925 GitRev* pLogEntry = reinterpret_cast<GitRev*>(shownlist.SafeGetAt(i));
1927 // do not take working dir changes into statistics
1928 if (pLogEntry->m_CommitHash.IsEmpty()) {
1929 continue;
1932 CString strAuthor = pLogEntry->GetAuthorName();
1933 if ( strAuthor.IsEmpty() )
1935 strAuthor.LoadString(IDS_STATGRAPH_EMPTYAUTHOR);
1937 m_arAuthorsFiltered.Add(strAuthor);
1938 m_arDatesFiltered.Add(pLogEntry->GetCommitterDate().GetTime());
1939 m_arFileChangesFiltered.Add(pLogEntry->GetFiles(&m_LogList).GetCount());
1942 CDateSorter W_Sorter;
1943 W_Sorter.m_parAuthors = &m_arAuthorsFiltered;
1944 W_Sorter.m_parDates = &m_arDatesFiltered;
1945 W_Sorter.m_parFileChanges = &m_arFileChangesFiltered;
1946 std::sort(W_Sorter.begin(), W_Sorter.end(), CDateSorterLess());
1948 CStatGraphDlg dlg;
1949 dlg.m_parAuthors = &m_arAuthorsFiltered;
1950 dlg.m_parDates = &m_arDatesFiltered;
1951 dlg.m_parFileChanges = &m_arFileChangesFiltered;
1952 dlg.m_path = m_orgPath;
1953 dlg.DoModal();
1954 // restore the previous sorting
1955 SortByColumn(m_nSortColumn, m_bAscending);
1956 OnTimer(LOGFILTER_TIMER);
1959 void CLogDlg::DoSizeV1(int delta)
1962 RemoveAnchor(IDC_LOGLIST);
1963 RemoveAnchor(IDC_SPLITTERTOP);
1964 RemoveAnchor(IDC_MSGVIEW);
1965 RemoveAnchor(IDC_SPLITTERBOTTOM);
1966 RemoveAnchor(IDC_LOGMSG);
1967 CSplitterControl::ChangeHeight(&m_LogList, delta, CW_TOPALIGN);
1968 CSplitterControl::ChangeHeight(GetDlgItem(IDC_MSGVIEW), -delta, CW_BOTTOMALIGN);
1969 AddAnchor(IDC_LOGLIST, TOP_LEFT, TOP_RIGHT);
1970 AddAnchor(IDC_SPLITTERTOP, TOP_LEFT, TOP_RIGHT);
1971 AddAnchor(IDC_MSGVIEW, TOP_LEFT, BOTTOM_RIGHT);
1972 AddAnchor(IDC_SPLITTERBOTTOM, BOTTOM_LEFT, BOTTOM_RIGHT);
1973 AddAnchor(IDC_LOGMSG, BOTTOM_LEFT, BOTTOM_RIGHT);
1974 ArrangeLayout();
1975 AdjustMinSize();
1976 SetSplitterRange();
1977 m_LogList.Invalidate();
1978 GetDlgItem(IDC_MSGVIEW)->Invalidate();
1982 void CLogDlg::DoSizeV2(int delta)
1985 RemoveAnchor(IDC_LOGLIST);
1986 RemoveAnchor(IDC_SPLITTERTOP);
1987 RemoveAnchor(IDC_MSGVIEW);
1988 RemoveAnchor(IDC_SPLITTERBOTTOM);
1989 RemoveAnchor(IDC_LOGMSG);
1990 CSplitterControl::ChangeHeight(GetDlgItem(IDC_MSGVIEW), delta, CW_TOPALIGN);
1991 CSplitterControl::ChangeHeight(&m_ChangedFileListCtrl, -delta, CW_BOTTOMALIGN);
1992 AddAnchor(IDC_LOGLIST, TOP_LEFT, TOP_RIGHT);
1993 AddAnchor(IDC_SPLITTERTOP, TOP_LEFT, TOP_RIGHT);
1994 AddAnchor(IDC_MSGVIEW, TOP_LEFT, BOTTOM_RIGHT);
1995 AddAnchor(IDC_SPLITTERBOTTOM, BOTTOM_LEFT, BOTTOM_RIGHT);
1996 AddAnchor(IDC_LOGMSG, BOTTOM_LEFT, BOTTOM_RIGHT);
1997 ArrangeLayout();
1998 AdjustMinSize();
1999 SetSplitterRange();
2000 GetDlgItem(IDC_MSGVIEW)->Invalidate();
2001 m_ChangedFileListCtrl.Invalidate();
2005 void CLogDlg::AdjustMinSize()
2007 // adjust the minimum size of the dialog to prevent the resizing from
2008 // moving the list control too far down.
2009 CRect rcChgListView;
2010 m_ChangedFileListCtrl.GetClientRect(rcChgListView);
2011 CRect rcLogList;
2012 m_LogList.GetClientRect(rcLogList);
2014 SetMinTrackSize(CSize(m_DlgOrigRect.Width(),
2015 m_DlgOrigRect.Height()-m_ChgOrigRect.Height()-m_LogListOrigRect.Height()-m_MsgViewOrigRect.Height()
2016 +rcChgListView.Height()+rcLogList.Height()+60));
2019 LRESULT CLogDlg::DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam)
2021 switch (message) {
2022 case WM_NOTIFY:
2023 if (wParam == IDC_SPLITTERTOP)
2025 SPC_NMHDR* pHdr = (SPC_NMHDR*) lParam;
2026 DoSizeV1(pHdr->delta);
2028 else if (wParam == IDC_SPLITTERBOTTOM)
2030 SPC_NMHDR* pHdr = (SPC_NMHDR*) lParam;
2031 DoSizeV2(pHdr->delta);
2033 break;
2036 return CResizableDialog::DefWindowProc(message, wParam, lParam);
2039 void CLogDlg::SetSplitterRange()
2041 if ((m_LogList)&&(m_ChangedFileListCtrl))
2043 CRect rcTop;
2044 m_LogList.GetWindowRect(rcTop);
2045 ScreenToClient(rcTop);
2046 CRect rcMiddle;
2047 GetDlgItem(IDC_MSGVIEW)->GetWindowRect(rcMiddle);
2048 ScreenToClient(rcMiddle);
2049 m_wndSplitter1.SetRange(rcTop.top+30, rcMiddle.bottom-20);
2050 CRect rcBottom;
2051 m_ChangedFileListCtrl.GetWindowRect(rcBottom);
2052 ScreenToClient(rcBottom);
2053 m_wndSplitter2.SetRange(rcMiddle.top+30, rcBottom.bottom-20);
2057 LRESULT CLogDlg::OnClickedInfoIcon(WPARAM /*wParam*/, LPARAM lParam)
2059 // FIXME: x64 version would get this function called with unexpected parameters.
2060 if (!lParam)
2061 return 0;
2063 RECT * rect = (LPRECT)lParam;
2064 CPoint point;
2065 CString temp;
2066 point = CPoint(rect->left, rect->bottom);
2067 #define LOGMENUFLAGS(x) (MF_STRING | MF_ENABLED | (m_LogList.m_SelectedFilters & x ? MF_CHECKED : MF_UNCHECKED))
2068 CMenu popup;
2069 if (popup.CreatePopupMenu())
2071 temp.LoadString(IDS_LOG_FILTER_SUBJECT);
2072 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_SUBJECT), LOGFILTER_SUBJECT, temp);
2074 temp.LoadString(IDS_LOG_FILTER_MESSAGES);
2075 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_MESSAGES), LOGFILTER_MESSAGES, temp);
2077 temp.LoadString(IDS_LOG_FILTER_PATHS);
2078 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_PATHS), LOGFILTER_PATHS, temp);
2080 temp.LoadString(IDS_LOG_FILTER_AUTHORS);
2081 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_AUTHORS), LOGFILTER_AUTHORS, temp);
2083 temp.LoadString(IDS_LOG_FILTER_REVS);
2084 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REVS), LOGFILTER_REVS, temp);
2086 if (m_LogList.m_bShowBugtraqColumn == TRUE) {
2087 temp.LoadString(IDS_LOG_FILTER_BUGIDS);
2088 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_BUGID), LOGFILTER_BUGID, temp);
2091 popup.AppendMenu(MF_SEPARATOR, NULL);
2093 temp.LoadString(IDS_LOG_FILTER_REGEX);
2094 popup.AppendMenu(MF_STRING | MF_ENABLED | (m_bFilterWithRegex ? MF_CHECKED : MF_UNCHECKED), LOGFILTER_REGEX, temp);
2096 m_tooltips.Pop();
2097 int selection = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
2098 if (selection != 0)
2101 if (selection == LOGFILTER_REGEX)
2103 m_bFilterWithRegex = !m_bFilterWithRegex;
2104 CRegDWORD b = CRegDWORD(_T("Software\\TortoiseGit\\UseRegexFilter"), TRUE);
2105 b = m_bFilterWithRegex;
2106 m_LogList.m_bFilterWithRegex = m_bFilterWithRegex;
2107 SetFilterCueText();
2108 CheckRegexpTooltip();
2110 else
2112 m_LogList.m_SelectedFilters ^= selection;
2113 SetFilterCueText();
2115 SetTimer(LOGFILTER_TIMER, 1000, NULL);
2118 return 0L;
2121 LRESULT CLogDlg::OnClickedCancelFilter(WPARAM /*wParam*/, LPARAM /*lParam*/)
2124 KillTimer(LOGFILTER_TIMER);
2126 m_LogList.m_sFilterText.Empty();
2127 UpdateData(FALSE);
2128 theApp.DoWaitCursor(1);
2129 CStoreSelection storeselection(this);
2130 FillLogMessageCtrl(false);
2132 m_LogList.RemoveFilter();
2134 Refresh();
2136 CTime begin,end;
2137 m_LogList.GetTimeRange(begin,end);
2138 m_DateFrom.SetTime(&begin);
2139 m_DateTo.SetTime(&end);
2141 theApp.DoWaitCursor(-1);
2142 GetDlgItem(IDC_SEARCHEDIT)->ShowWindow(SW_HIDE);
2143 GetDlgItem(IDC_SEARCHEDIT)->ShowWindow(SW_SHOW);
2144 GetDlgItem(IDC_SEARCHEDIT)->SetFocus();
2145 UpdateLogInfoLabel();
2147 return 0L;
2151 void CLogDlg::SetFilterCueText()
2153 CString temp(MAKEINTRESOURCE(IDS_LOG_FILTER_BY));
2154 temp += _T(" ");
2156 if (m_LogList.m_SelectedFilters & LOGFILTER_SUBJECT)
2158 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_SUBJECT));
2161 if (m_LogList.m_SelectedFilters & LOGFILTER_MESSAGES)
2163 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
2164 temp += _T(", ");
2165 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_MESSAGES));
2168 if (m_LogList.m_SelectedFilters & LOGFILTER_PATHS)
2170 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
2171 temp += _T(", ");
2172 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_PATHS));
2175 if (m_LogList.m_SelectedFilters & LOGFILTER_AUTHORS)
2177 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
2178 temp += _T(", ");
2179 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_AUTHORS));
2182 if (m_LogList.m_SelectedFilters & LOGFILTER_REVS)
2184 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
2185 temp += _T(", ");
2186 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REVS));
2189 if (m_LogList.m_SelectedFilters & LOGFILTER_BUGID)
2191 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
2192 temp += _T(", ");
2193 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_BUGIDS));
2196 // to make the cue banner text appear more to the right of the edit control
2197 temp = _T(" ")+temp;
2198 m_cFilter.SetCueBanner(temp.TrimRight());
2201 bool CLogDlg::Validate(LPCTSTR string)
2203 if (!m_bFilterWithRegex)
2204 return true;
2205 tr1::wregex pat;
2206 return m_LogList.ValidateRegexp(string, pat, false);
2210 void CLogDlg::OnTimer(UINT_PTR nIDEvent)
2212 if (nIDEvent == LOGFTIME_TIMER)
2214 KillTimer(LOGFTIME_TIMER);
2215 m_limit = 0;
2216 m_LogList.Refresh(FALSE);
2217 FillLogMessageCtrl(false);
2220 if (nIDEvent == LOGFILTER_TIMER)
2222 KillTimer(LOGFILTER_TIMER);
2223 m_limit = 0;
2224 m_LogList.Refresh(FALSE);
2225 FillLogMessageCtrl(false);
2227 #if 0
2228 /* we will use git built-in grep to filter log */
2229 if (this->IsThreadRunning())
2231 // thread still running! So just restart the timer.
2232 SetTimer(LOGFILTER_TIMER, 1000, NULL);
2233 return;
2235 CWnd * focusWnd = GetFocus();
2236 bool bSetFocusToFilterControl = ((focusWnd != GetDlgItem(IDC_DATEFROM))&&(focusWnd != GetDlgItem(IDC_DATETO))
2237 && (focusWnd != GetDlgItem(IDC_LOGLIST)));
2238 if (m_LogList.m_sFilterText.IsEmpty())
2240 DialogEnableWindow(IDC_STATBUTTON, !(((this->IsThreadRunning())||(m_LogList.m_arShownList.IsEmpty()))));
2241 // do not return here!
2242 // we also need to run the filter if the filter text is empty:
2243 // 1. to clear an existing filter
2244 // 2. to rebuild the m_arShownList after sorting
2246 theApp.DoWaitCursor(1);
2247 CStoreSelection storeselection(this);
2248 KillTimer(LOGFILTER_TIMER);
2249 FillLogMessageCtrl(false);
2251 // now start filter the log list
2252 m_LogList.StartFilter();
2254 if ( m_LogList.GetItemCount()==1 )
2256 m_LogList.SetSelectionMark(0);
2257 m_LogList.SetItemState(0, LVIS_SELECTED, LVIS_SELECTED);
2259 theApp.DoWaitCursor(-1);
2260 GetDlgItem(IDC_SEARCHEDIT)->ShowWindow(SW_HIDE);
2261 GetDlgItem(IDC_SEARCHEDIT)->ShowWindow(SW_SHOW);
2262 if (bSetFocusToFilterControl)
2263 GetDlgItem(IDC_SEARCHEDIT)->SetFocus();
2264 UpdateLogInfoLabel();
2265 #endif
2266 } // if (nIDEvent == LOGFILTER_TIMER)
2267 DialogEnableWindow(IDC_STATBUTTON, !(((this->IsThreadRunning())||(m_LogList.m_arShownList.IsEmpty() || m_LogList.m_arShownList.GetCount() == 1 && m_LogList.m_bShowWC))));
2268 __super::OnTimer(nIDEvent);
2271 void CLogDlg::OnDtnDatetimechangeDateto(NMHDR * /*pNMHDR*/, LRESULT *pResult)
2275 CTime _time;
2276 m_DateTo.GetTime(_time);
2278 CTime time(_time.GetYear(), _time.GetMonth(), _time.GetDay(), 23, 59, 59);
2279 if (time.GetTime() != m_LogList.m_To)
2281 m_LogList.m_To = (DWORD)time.GetTime();
2282 SetTimer(LOGFTIME_TIMER, 10, NULL);
2285 catch (...)
2287 CMessageBox::Show(NULL,_T("Invalidate Parameter"),_T("TortoiseGit"),MB_OK|MB_ICONERROR);
2290 *pResult = 0;
2293 void CLogDlg::OnDtnDatetimechangeDatefrom(NMHDR * /*pNMHDR*/, LRESULT *pResult)
2298 CTime _time;
2299 m_DateFrom.GetTime(_time);
2301 CTime time(_time.GetYear(), _time.GetMonth(), _time.GetDay(), 0, 0, 0);
2302 if (time.GetTime() != m_LogList.m_From)
2304 m_LogList.m_From = (DWORD)time.GetTime();
2305 SetTimer(LOGFTIME_TIMER, 10, NULL);
2308 catch (...)
2310 CMessageBox::Show(NULL,_T("Invalidate Parameter"),_T("TortoiseGit"),MB_OK|MB_ICONERROR);
2313 *pResult = 0;
2318 CTGitPathList CLogDlg::GetChangedPathsFromSelectedRevisions(bool /*bRelativePaths*/ /* = false */, bool /*bUseFilter*/ /* = true */)
2320 CTGitPathList pathList;
2321 #if 0
2323 if (m_sRepositoryRoot.IsEmpty() && (bRelativePaths == false))
2325 m_sRepositoryRoot = GetRepositoryRoot(m_path);
2327 if (m_sRepositoryRoot.IsEmpty() && (bRelativePaths == false))
2328 return pathList;
2330 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
2331 if (pos != NULL)
2333 while (pos)
2335 int nextpos = m_LogList.GetNextSelectedItem(pos);
2336 if (nextpos >= m_arShownList.GetCount())
2337 continue;
2338 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(nextpos));
2339 LogChangedPathArray * cpatharray = pLogEntry->pArChangedPaths;
2340 for (INT_PTR cpPathIndex = 0; cpPathIndex<cpatharray->GetCount(); ++cpPathIndex)
2342 LogChangedPath * cpath = cpatharray->SafeGetAt(cpPathIndex);
2343 if (cpath == NULL)
2344 continue;
2345 CTGitPath path;
2346 if (!bRelativePaths)
2347 path.SetFromGit(m_sRepositoryRoot);
2348 path.AppendPathString(cpath->sPath);
2349 if ((!bUseFilter)||
2350 ((m_cHidePaths.GetState() & 0x0003)!=BST_CHECKED)||
2351 (cpath->sPath.Left(m_sRelativeRoot.GetLength()).Compare(m_sRelativeRoot)==0))
2352 pathList.AddPath(path);
2357 pathList.RemoveDuplicates();
2358 #endif
2359 return pathList;
2362 void CLogDlg::SortByColumn(int /*nSortColumn*/, bool /*bAscending*/)
2364 #if 0
2365 switch(nSortColumn)
2367 case 0: // Revision
2369 if(bAscending)
2370 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::AscRevSort());
2371 else
2372 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::DescRevSort());
2374 break;
2375 case 1: // action
2377 if(bAscending)
2378 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::AscActionSort());
2379 else
2380 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::DescActionSort());
2382 break;
2383 case 2: // Author
2385 if(bAscending)
2386 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::AscAuthorSort());
2387 else
2388 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::DescAuthorSort());
2390 break;
2391 case 3: // Date
2393 if(bAscending)
2394 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::AscDateSort());
2395 else
2396 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::DescDateSort());
2398 break;
2399 case 4: // Message or bug id
2400 if (m_bShowBugtraqColumn)
2402 if(bAscending)
2403 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::AscBugIDSort());
2404 else
2405 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::DescBugIDSort());
2406 break;
2408 // fall through here
2409 case 5: // Message
2411 if(bAscending)
2412 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::AscMessageSort());
2413 else
2414 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::DescMessageSort());
2416 break;
2417 default:
2418 ATLASSERT(0);
2419 break;
2421 #endif
2424 void CLogDlg::OnLvnColumnclick(NMHDR *pNMHDR, LRESULT *pResult)
2426 if (this->IsThreadRunning())
2427 return; //no sorting while the arrays are filled
2428 #if 0
2429 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
2430 const int nColumn = pNMLV->iSubItem;
2431 m_bAscending = nColumn == m_nSortColumn ? !m_bAscending : TRUE;
2432 m_nSortColumn = nColumn;
2433 SortByColumn(m_nSortColumn, m_bAscending);
2434 SetSortArrow(&m_LogList, m_nSortColumn, !!m_bAscending);
2435 SortShownListArray();
2436 m_LogList.Invalidate();
2437 UpdateLogInfoLabel();
2438 #else
2439 UNREFERENCED_PARAMETER(pNMHDR);
2440 #endif
2441 *pResult = 0;
2444 void CLogDlg::SortShownListArray()
2446 // make sure the shown list still matches the filter after sorting.
2447 OnTimer(LOGFILTER_TIMER);
2448 // clear the selection states
2449 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
2450 while (pos)
2452 m_LogList.SetItemState(m_LogList.GetNextSelectedItem(pos), 0, LVIS_SELECTED);
2454 m_LogList.SetSelectionMark(-1);
2457 void CLogDlg::SetSortArrow(CListCtrl * control, int nColumn, bool bAscending)
2459 if (control == NULL)
2460 return;
2461 // set the sort arrow
2462 CHeaderCtrl * pHeader = control->GetHeaderCtrl();
2463 HDITEM HeaderItem = {0};
2464 HeaderItem.mask = HDI_FORMAT;
2465 for (int i=0; i<pHeader->GetItemCount(); ++i)
2467 pHeader->GetItem(i, &HeaderItem);
2468 HeaderItem.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
2469 pHeader->SetItem(i, &HeaderItem);
2471 if (nColumn >= 0)
2473 pHeader->GetItem(nColumn, &HeaderItem);
2474 HeaderItem.fmt |= (bAscending ? HDF_SORTUP : HDF_SORTDOWN);
2475 pHeader->SetItem(nColumn, &HeaderItem);
2478 void CLogDlg::OnLvnColumnclickChangedFileList(NMHDR* /*pNMHDR*/, LRESULT* /*pResult*/)
2480 #if 0
2481 if (this->IsThreadRunning())
2482 return; //no sorting while the arrays are filled
2483 if (m_currentChangedArray == NULL)
2484 return;
2485 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
2486 const int nColumn = pNMLV->iSubItem;
2487 m_bAscendingPathList = nColumn == m_nSortColumnPathList ? !m_bAscendingPathList : TRUE;
2488 m_nSortColumnPathList = nColumn;
2489 // qsort(m_currentChangedArray->GetData(), m_currentChangedArray->GetSize(), sizeof(LogChangedPath*), (GENERICCOMPAREFN)SortCompare);
2491 SetSortArrow(&m_ChangedFileListCtrl, m_nSortColumnPathList, m_bAscendingPathList);
2492 m_ChangedFileListCtrl.Invalidate();
2493 *pResult = 0;
2494 #endif
2497 int CLogDlg::m_nSortColumnPathList = 0;
2498 bool CLogDlg::m_bAscendingPathList = false;
2500 int CLogDlg::SortCompare(const void * /*pElem1*/, const void * /*pElem2*/)
2502 #if 0
2503 LogChangedPath * cpath1 = *((LogChangedPath**)pElem1);
2504 LogChangedPath * cpath2 = *((LogChangedPath**)pElem2);
2506 if (m_bAscendingPathList)
2507 std::swap (cpath1, cpath2);
2509 int cmp = 0;
2510 switch (m_nSortColumnPathList)
2512 case 0: // action
2513 cmp = cpath2->GetAction().Compare(cpath1->GetAction());
2514 if (cmp)
2515 return cmp;
2516 // fall through
2517 case 1: // path
2518 cmp = cpath2->sPath.CompareNoCase(cpath1->sPath);
2519 if (cmp)
2520 return cmp;
2521 // fall through
2522 case 2: // copy from path
2523 cmp = cpath2->sCopyFromPath.Compare(cpath1->sCopyFromPath);
2524 if (cmp)
2525 return cmp;
2526 // fall through
2527 case 3: // copy from revision
2528 return cpath2->lCopyFromRev > cpath1->lCopyFromRev;
2530 #endif
2531 return 0;
2534 void CLogDlg::OnBnClickedHidepaths()
2536 FillLogMessageCtrl();
2537 m_ChangedFileListCtrl.Invalidate();
2542 void CLogDlg::OnBnClickedCheckStoponcopy()
2544 #if 0
2545 if (!GetDlgItem(IDC_GETALL)->IsWindowEnabled())
2546 return;
2548 // ignore old fetch limits when switching
2549 // between copy-following and stop-on-copy
2550 // (otherwise stop-on-copy will limit what
2551 // we see immediately after switching to
2552 // copy-following)
2554 m_endrev = 0;
2556 // now, restart the query
2557 #endif
2558 Refresh();
2562 void CLogDlg::UpdateLogInfoLabel()
2565 CGitHash rev1 ;
2566 CGitHash rev2 ;
2567 long selectedrevs = 0;
2568 int count =m_LogList.m_arShownList.GetCount();
2569 int start = 0;
2570 if (count)
2572 rev1 = (reinterpret_cast<GitRev*>(m_LogList.m_arShownList.SafeGetAt(0)))->m_CommitHash;
2573 if(this->m_LogList.m_bShowWC && rev1.IsEmpty()&&(count>1))
2574 start = 1;
2575 rev1 = (reinterpret_cast<GitRev*>(m_LogList.m_arShownList.SafeGetAt(start)))->m_CommitHash;
2576 //pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(m_arShownList.GetCount()-1));
2577 rev2 = (reinterpret_cast<GitRev*>(m_LogList.m_arShownList.SafeGetAt(count-1)))->m_CommitHash;
2578 selectedrevs = m_LogList.GetSelectedCount();
2580 CString sTemp;
2581 sTemp.Format(IDS_PROC_LOG_STATS,
2582 count - start,
2583 rev2.ToString().Left(g_Git.GetShortHASHLength()), rev1.ToString().Left(g_Git.GetShortHASHLength()), selectedrevs);
2585 if(selectedrevs == 1)
2587 CString str=m_ChangedFileListCtrl.GetStatisticsString(true);
2588 str.Replace(_T('\n'), _T(' '));
2589 sTemp += _T("\r\n") + str;
2591 m_sLogInfo = sTemp;
2593 UpdateData(FALSE);
2596 #if 0
2597 void CLogDlg::ShowContextMenuForChangedpaths(CWnd* /*pWnd*/, CPoint point)
2600 int selIndex = m_ChangedFileListCtrl.GetSelectionMark();
2601 if ((point.x == -1) && (point.y == -1))
2603 CRect rect;
2604 m_ChangedFileListCtrl.GetItemRect(selIndex, &rect, LVIR_LABEL);
2605 m_ChangedFileListCtrl.ClientToScreen(&rect);
2606 point = rect.CenterPoint();
2608 if (selIndex < 0)
2609 return;
2610 int s = m_LogList.GetSelectionMark();
2611 if (s < 0)
2612 return;
2613 std::vector<CString> changedpaths;
2614 std::vector<LogChangedPath*> changedlogpaths;
2615 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
2616 if (pos == NULL)
2617 return; // nothing is selected, get out of here
2619 bool bOneRev = true;
2620 int sel=m_LogList.GetNextSelectedItem(pos);
2621 GitRev * pLogEntry = reinterpret_cast<GitRev *>(m_LogList.m_arShownList.SafeGetAt(sel));
2622 GitRev * rev1 = pLogEntry;
2623 GitRev * rev2 = reinterpret_cast<GitRev *>(m_LogList.m_arShownList.SafeGetAt(sel+1));
2624 #if 0
2625 bool bOneRev = true;
2626 if (pos)
2628 while (pos)
2630 pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(m_LogList.GetNextSelectedItem(pos)));
2631 if (pLogEntry)
2633 rev1 = max(rev1,(git_revnum_t)pLogEntry->Rev);
2634 rev2 = min(rev2,(git_revnum_t)pLogEntry->Rev);
2635 bOneRev = false;
2638 if (!bOneRev)
2639 rev2--;
2640 POSITION pos = m_ChangedFileListCtrl.GetFirstSelectedItemPosition();
2641 while (pos)
2643 int nItem = m_ChangedFileListCtrl.GetNextSelectedItem(pos);
2644 changedpaths.push_back(m_currentChangedPathList[nItem].GetGitPathString());
2647 else
2649 // only one revision is selected in the log dialog top pane
2650 // but multiple items could be selected in the changed items list
2651 rev2 = rev1-1;
2653 POSITION pos = m_ChangedFileListCtrl.GetFirstSelectedItemPosition();
2654 while (pos)
2656 int nItem = m_ChangedFileListCtrl.GetNextSelectedItem(pos);
2657 LogChangedPath * changedlogpath = pLogEntry->pArChangedPaths->SafeGetAt(nItem);
2659 if (m_ChangedFileListCtrl.GetSelectedCount() == 1)
2661 if ((changedlogpath)&&(!changedlogpath->sCopyFromPath.IsEmpty()))
2662 rev2 = changedlogpath->lCopyFromRev;
2663 else
2665 // if the path was modified but the parent path was 'added with history'
2666 // then we have to use the copy from revision of the parent path
2667 CTGitPath cpath = CTGitPath(changedlogpath->sPath);
2668 for (int flist = 0; flist < pLogEntry->pArChangedPaths->GetCount(); ++flist)
2670 CTGitPath p = CTGitPath(pLogEntry->pArChangedPaths->SafeGetAt(flist)->sPath);
2671 if (p.IsAncestorOf(cpath))
2673 if (!pLogEntry->pArChangedPaths->SafeGetAt(flist)->sCopyFromPath.IsEmpty())
2674 rev2 = pLogEntry->pArChangedPaths->SafeGetAt(flist)->lCopyFromRev;
2679 if ((m_cHidePaths.GetState() & 0x0003)==BST_CHECKED)
2681 // some items are hidden! So find out which item the user really clicked on
2682 INT_PTR selRealIndex = -1;
2683 for (INT_PTR hiddenindex=0; hiddenindex<pLogEntry->pArChangedPaths->GetCount(); ++hiddenindex)
2685 if (pLogEntry->pArChangedPaths->SafeGetAt(hiddenindex)->sPath.Left(m_sRelativeRoot.GetLength()).Compare(m_sRelativeRoot)==0)
2686 selRealIndex++;
2687 if (selRealIndex == nItem)
2689 selIndex = hiddenindex;
2690 changedlogpath = pLogEntry->pArChangedPaths->SafeGetAt(selIndex);
2691 break;
2695 if (changedlogpath)
2697 changedpaths.push_back(changedlogpath->sPath);
2698 changedlogpaths.push_back(changedlogpath);
2702 #endif
2703 //entry is selected, now show the popup menu
2704 CIconMenu popup;
2705 if (popup.CreatePopupMenu())
2707 bool bEntryAdded = false;
2708 if (m_ChangedFileListCtrl.GetSelectedCount() == 1)
2710 // if ((!bOneRev)||(IsDiffPossible(changedlogpaths[0], rev1)))
2712 popup.AppendMenuIcon(CGitLogList::ID_DIFF, IDS_LOG_POPUP_DIFF, IDI_DIFF);
2713 popup.AppendMenuIcon(CGitLogList::ID_BLAMEDIFF, IDS_LOG_POPUP_BLAMEDIFF, IDI_BLAME);
2714 popup.SetDefaultItem(CGitLogList::ID_DIFF, FALSE);
2715 popup.AppendMenuIcon(CGitLogList::ID_GNUDIFF1, IDS_LOG_POPUP_GNUDIFF_CH, IDI_DIFF);
2716 bEntryAdded = true;
2718 // if (rev2 == rev1-1)
2720 if (bEntryAdded)
2721 popup.AppendMenu(MF_SEPARATOR, NULL);
2722 popup.AppendMenuIcon(CGitLogList::ID_OPEN, IDS_LOG_POPUP_OPEN, IDI_OPEN);
2723 popup.AppendMenuIcon(CGitLogList::ID_OPENWITH, IDS_LOG_POPUP_OPENWITH, IDI_OPEN);
2724 popup.AppendMenuIcon(CGitLogList::ID_BLAME, IDS_LOG_POPUP_BLAME, IDI_BLAME);
2725 popup.AppendMenu(MF_SEPARATOR, NULL);
2726 if (m_hasWC)
2727 popup.AppendMenuIcon(CGitLogList::ID_REVERTREV, IDS_LOG_POPUP_REVERTREV, IDI_REVERT);
2728 popup.AppendMenuIcon(CGitLogList::ID_POPPROPS, IDS_REPOBROWSE_SHOWPROP, IDI_PROPERTIES); // "Show Properties"
2729 popup.AppendMenuIcon(CGitLogList::ID_LOG, IDS_MENULOG, IDI_LOG); // "Show Log"
2730 popup.AppendMenuIcon(CGitLogList::ID_GETMERGELOGS, IDS_LOG_POPUP_GETMERGELOGS, IDI_LOG); // "Show merge log"
2731 popup.AppendMenuIcon(CGitLogList::ID_SAVEAS, IDS_LOG_POPUP_SAVE, IDI_SAVEAS);
2732 bEntryAdded = true;
2733 if (!m_ProjectProperties.sWebViewerPathRev.IsEmpty())
2735 popup.AppendMenu(MF_SEPARATOR, NULL);
2736 popup.AppendMenuIcon(CGitLogList::ID_VIEWPATHREV, IDS_LOG_POPUP_VIEWPATHREV);
2738 if (popup.GetDefaultItem(0,FALSE)==-1)
2739 popup.SetDefaultItem(CGitLogList::ID_OPEN, FALSE);
2742 else if (changedlogpaths.size())
2744 // more than one entry is selected
2745 popup.AppendMenuIcon(CGitLogList::ID_SAVEAS, IDS_LOG_POPUP_SAVE);
2746 bEntryAdded = true;
2749 if (!bEntryAdded)
2750 return;
2751 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
2752 bool bOpenWith = false;
2753 bool bMergeLog = false;
2754 m_bCancelled = false;
2756 switch (cmd)
2758 case CGitLogList::ID_DIFF:
2760 DoDiffFromLog(selIndex, rev1, rev2, false, false);
2762 break;
2763 #if 0
2764 case ID_BLAMEDIFF:
2766 DoDiffFromLog(selIndex, rev1, rev2, true, false);
2768 break;
2769 case ID_GNUDIFF1:
2771 DoDiffFromLog(selIndex, rev1, rev2, false, true);
2773 break;
2774 case ID_REVERTREV:
2776 SetPromptApp(&theApp);
2777 theApp.DoWaitCursor(1);
2778 CString sUrl;
2779 if (Git::PathIsURL(m_path))
2781 sUrl = m_path.GetGitPathString();
2783 else
2785 sUrl = GetURLFromPath(m_path);
2786 if (sUrl.IsEmpty())
2788 theApp.DoWaitCursor(-1);
2789 CString temp;
2790 temp.Format(IDS_ERR_NOURLOFFILE, m_path.GetWinPath());
2791 CMessageBox::Show(this->m_hWnd, temp, _T("TortoiseGit"), MB_ICONERROR);
2792 EnableOKButton();
2793 theApp.DoWaitCursor(-1);
2794 break; //exit
2797 // find the working copy path of the selected item from the URL
2798 m_bCancelled = false;
2799 CString sUrlRoot = GetRepositoryRoot(CTGitPath(sUrl));
2801 CString fileURL = changedpaths[0];
2802 fileURL = sUrlRoot + fileURL.Trim();
2803 // firstfile = (e.g.) http://mydomain.com/repos/trunk/folder/file1
2804 // sUrl = http://mydomain.com/repos/trunk/folder
2805 CString sUnescapedUrl = CPathUtils::PathUnescape(sUrl);
2806 // find out until which char the urls are identical
2807 int i=0;
2808 while ((i<fileURL.GetLength())&&(i<sUnescapedUrl.GetLength())&&(fileURL[i]==sUnescapedUrl[i]))
2809 i++;
2810 int leftcount = m_path.GetWinPathString().GetLength()-(sUnescapedUrl.GetLength()-i);
2811 CString wcPath = m_path.GetWinPathString().Left(leftcount);
2812 wcPath += fileURL.Mid(i);
2813 wcPath.Replace('/', '\\');
2814 CGitProgressDlg dlg;
2815 if (changedlogpaths[0]->action == LOGACTIONS_DELETED)
2817 // a deleted path! Since the path isn't there anymore, merge
2818 // won't work. So just do a copy url->wc
2819 dlg.SetCommand(CGitProgressDlg::GitProgress_Copy);
2820 dlg.SetPathList(CTGitPathList(CTGitPath(fileURL)));
2821 dlg.SetUrl(wcPath);
2822 dlg.SetRevision(rev2);
2824 else
2826 if (!PathFileExists(wcPath))
2828 // seems the path got renamed
2829 // tell the user how to work around this.
2830 CMessageBox::Show(this->m_hWnd, IDS_LOG_REVERTREV_ERROR, IDS_APPNAME, MB_ICONERROR);
2831 EnableOKButton();
2832 theApp.DoWaitCursor(-1);
2833 break; //exit
2835 dlg.SetCommand(CGitProgressDlg::GitProgress_Merge);
2836 dlg.SetPathList(CTGitPathList(CTGitPath(wcPath)));
2837 dlg.SetUrl(fileURL);
2838 dlg.SetSecondUrl(fileURL);
2839 GitRevRangeArray revarray;
2840 revarray.AddRevRange(rev1, rev2);
2841 dlg.SetRevisionRanges(revarray);
2843 CString msg;
2844 msg.Format(IDS_LOG_REVERT_CONFIRM, (LPCTSTR)wcPath);
2845 if (CMessageBox::Show(this->m_hWnd, msg, _T("TortoiseGit"), MB_YESNO | MB_ICONQUESTION) == IDYES)
2847 dlg.DoModal();
2849 theApp.DoWaitCursor(-1);
2851 break;
2852 case ID_POPPROPS:
2854 DialogEnableWindow(IDOK, FALSE);
2855 SetPromptApp(&theApp);
2856 theApp.DoWaitCursor(1);
2857 CString filepath;
2858 if (Git::PathIsURL(m_path))
2860 filepath = m_path.GetGitPathString();
2862 else
2864 filepath = GetURLFromPath(m_path);
2865 if (filepath.IsEmpty())
2867 theApp.DoWaitCursor(-1);
2868 CString temp;
2869 temp.Format(IDS_ERR_NOURLOFFILE, (LPCTSTR)filepath);
2870 CMessageBox::Show(this->m_hWnd, temp, _T("TortoiseGit"), MB_ICONERROR);
2871 TRACE(_T("could not retrieve the URL of the file!\n"));
2872 EnableOKButton();
2873 break;
2876 filepath = GetRepositoryRoot(CTGitPath(filepath));
2877 filepath += changedpaths[0];
2878 CPropDlg dlg;
2879 dlg.m_rev = rev1;
2880 dlg.m_Path = CTGitPath(filepath);
2881 dlg.DoModal();
2882 EnableOKButton();
2883 theApp.DoWaitCursor(-1);
2885 break;
2886 case ID_SAVEAS:
2888 DialogEnableWindow(IDOK, FALSE);
2889 SetPromptApp(&theApp);
2890 theApp.DoWaitCursor(1);
2891 CString filepath;
2892 if (Git::PathIsURL(m_path))
2894 filepath = m_path.GetGitPathString();
2896 else
2898 filepath = GetURLFromPath(m_path);
2899 if (filepath.IsEmpty())
2901 theApp.DoWaitCursor(-1);
2902 CString temp;
2903 temp.Format(IDS_ERR_NOURLOFFILE, (LPCTSTR)filepath);
2904 CMessageBox::Show(this->m_hWnd, temp, _T("TortoiseGit"), MB_ICONERROR);
2905 TRACE(_T("could not retrieve the URL of the file!\n"));
2906 EnableOKButton();
2907 break;
2910 m_bCancelled = false;
2911 CString sRoot = GetRepositoryRoot(CTGitPath(filepath));
2912 // if more than one entry is selected, we save them
2913 // one by one into a folder the user has selected
2914 bool bTargetSelected = false;
2915 CTGitPath TargetPath;
2916 if (m_ChangedFileListCtrl.GetSelectedCount() > 1)
2918 CBrowseFolder browseFolder;
2919 browseFolder.SetInfo(CString(MAKEINTRESOURCE(IDS_LOG_SAVEFOLDERTOHINT)));
2920 browseFolder.m_style = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS;
2921 CString strSaveAsDirectory;
2922 if (browseFolder.Show(GetSafeHwnd(), strSaveAsDirectory) == CBrowseFolder::OK)
2924 TargetPath = CTGitPath(strSaveAsDirectory);
2925 bTargetSelected = true;
2928 else
2930 // Display the Open dialog box.
2931 CString revFilename;
2932 CString temp;
2933 temp = CPathUtils::GetFileNameFromPath(changedpaths[0]);
2934 int rfind = temp.ReverseFind('.');
2935 if (rfind > 0)
2936 revFilename.Format(_T("%s-%ld%s"), (LPCTSTR)temp.Left(rfind), rev1, (LPCTSTR)temp.Mid(rfind));
2937 else
2938 revFilename.Format(_T("%s-%ld"), (LPCTSTR)temp, rev1);
2939 bTargetSelected = CAppUtils::FileOpenSave(revFilename, NULL, IDS_LOG_POPUP_SAVE, IDS_COMMONFILEFILTER, false, m_hWnd);
2940 TargetPath.SetFromWin(revFilename);
2942 if (bTargetSelected)
2944 CProgressDlg progDlg;
2945 progDlg.SetTitle(IDS_APPNAME);
2946 progDlg.SetAnimation(IDR_DOWNLOAD);
2947 for (std::vector<LogChangedPath*>::iterator it = changedlogpaths.begin(); it!= changedlogpaths.end(); ++it)
2949 GitRev getrev = ((*it)->action == LOGACTIONS_DELETED) ? rev2 : rev1;
2951 CString sInfoLine;
2952 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, (LPCTSTR)filepath, (LPCTSTR)getrev.ToString());
2953 progDlg.SetLine(1, sInfoLine, true);
2954 SetAndClearProgressInfo(&progDlg);
2955 progDlg.ShowModeless(m_hWnd);
2957 CTGitPath tempfile = TargetPath;
2958 if (changedpaths.size() > 1)
2960 // if multiple items are selected, then the TargetPath
2961 // points to a folder and we have to append the filename
2962 // to save to to that folder.
2963 CString sName = (*it)->sPath;
2964 int slashpos = sName.ReverseFind('/');
2965 if (slashpos >= 0)
2966 sName = sName.Mid(slashpos);
2967 tempfile.AppendPathString(sName);
2968 // one problem here:
2969 // a user could have selected multiple items which
2970 // have the same filename but reside in different
2971 // directories, e.g.
2972 // /folder1/file1
2973 // /folder2/file1
2974 // in that case, the second 'file1' will overwrite
2975 // the already saved 'file1'.
2977 // we could maybe find the common root of all selected
2978 // items and then create sub folders to save those files
2979 // there.
2980 // But I think we should just leave it that way: to check
2981 // out multiple items at once, the better way is still to
2982 // use the export command from the top pane of the log dialog.
2984 filepath = sRoot + (*it)->sPath;
2985 if (!Cat(CTGitPath(filepath), getrev, getrev, tempfile))
2987 progDlg.Stop();
2988 SetAndClearProgressInfo((HWND)NULL);
2989 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
2990 EnableOKButton();
2991 theApp.DoWaitCursor(-1);
2992 break;
2995 progDlg.Stop();
2996 SetAndClearProgressInfo((HWND)NULL);
2998 EnableOKButton();
2999 theApp.DoWaitCursor(-1);
3001 break;
3002 case ID_OPENWITH:
3003 bOpenWith = true;
3004 case ID_OPEN:
3006 GitRev getrev = pLogEntry->pArChangedPaths->SafeGetAt(selIndex)->action == LOGACTIONS_DELETED ? rev2 : rev1;
3007 Open(bOpenWith,changedpaths[0],getrev);
3009 break;
3010 case ID_BLAME:
3012 CString filepath;
3013 if (Git::PathIsURL(m_path))
3015 filepath = m_path.GetGitPathString();
3017 else
3019 filepath = GetURLFromPath(m_path);
3020 if (filepath.IsEmpty())
3022 theApp.DoWaitCursor(-1);
3023 CString temp;
3024 temp.Format(IDS_ERR_NOURLOFFILE, (LPCTSTR)filepath);
3025 CMessageBox::Show(this->m_hWnd, temp, _T("TortoiseGit"), MB_ICONERROR);
3026 TRACE(_T("could not retrieve the URL of the file!\n"));
3027 EnableOKButton();
3028 break;
3031 filepath = GetRepositoryRoot(CTGitPath(filepath));
3032 filepath += changedpaths[0];
3033 CBlameDlg dlg;
3034 dlg.EndRev = rev1;
3035 if (dlg.DoModal() == IDOK)
3037 CBlame blame;
3038 CString tempfile;
3039 CString logfile;
3040 tempfile = blame.BlameToTempFile(CTGitPath(filepath), dlg.StartRev, dlg.EndRev, dlg.EndRev, logfile, _T(""), dlg.m_bIncludeMerge, TRUE, TRUE);
3041 if (!tempfile.IsEmpty())
3043 if (dlg.m_bTextView)
3045 //open the default text editor for the result file
3046 CAppUtils::StartTextViewer(tempfile);
3048 else
3050 CString sParams = _T("/path:\"") + filepath + _T("\" ");
3051 if(!CAppUtils::LaunchTortoiseBlame(tempfile, logfile, CPathUtils::GetFileNameFromPath(filepath),sParams))
3053 break;
3057 else
3059 CMessageBox::Show(this->m_hWnd, blame.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
3063 break;
3064 case ID_GETMERGELOGS:
3065 bMergeLog = true;
3066 // fall through
3067 case ID_LOG:
3069 DialogEnableWindow(IDOK, FALSE);
3070 SetPromptApp(&theApp);
3071 theApp.DoWaitCursor(1);
3072 CString filepath;
3073 if (Git::PathIsURL(m_path))
3075 filepath = m_path.GetGitPathString();
3077 else
3079 filepath = GetURLFromPath(m_path);
3080 if (filepath.IsEmpty())
3082 theApp.DoWaitCursor(-1);
3083 CString temp;
3084 temp.Format(IDS_ERR_NOURLOFFILE, (LPCTSTR)filepath);
3085 CMessageBox::Show(this->m_hWnd, temp, _T("TortoiseGit"), MB_ICONERROR);
3086 TRACE(_T("could not retrieve the URL of the file!\n"));
3087 EnableOKButton();
3088 break;
3091 m_bCancelled = false;
3092 filepath = GetRepositoryRoot(CTGitPath(filepath));
3093 filepath += changedpaths[0];
3094 git_revnum_t logrev = rev1;
3095 if (changedlogpaths[0]->action == LOGACTIONS_DELETED)
3097 // if the item got deleted in this revision,
3098 // fetch the log from the previous revision where it
3099 // still existed.
3100 logrev--;
3102 CString sCmd;
3103 sCmd.Format(_T("\"%s\" /command:log /path:\"%s\" /startrev:%ld"), (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")), (LPCTSTR)filepath, logrev);
3104 if (bMergeLog)
3105 sCmd += _T(" /merge");
3106 CAppUtils::LaunchApplication(sCmd, NULL, false);
3107 EnableOKButton();
3108 theApp.DoWaitCursor(-1);
3110 break;
3111 case ID_VIEWPATHREV:
3113 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(m_LogList.GetSelectionMark()));
3114 GitRev rev = pLogEntry->Rev;
3115 CString relurl = changedpaths[0];
3116 CString url = m_ProjectProperties.sWebViewerPathRev;
3117 url.Replace(_T("%REVISION%"), rev.ToString());
3118 url.Replace(_T("%PATH%"), relurl);
3119 relurl = relurl.Mid(relurl.Find('/'));
3120 url.Replace(_T("%PATH1%"), relurl);
3121 if (!url.IsEmpty())
3122 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
3124 break;
3125 #endif
3126 default:
3127 break;
3128 } // switch (cmd)
3130 } // if (popup.CreatePopupMenu())
3132 #endif
3134 void CLogDlg::OnDtnDropdownDatefrom(NMHDR * /*pNMHDR*/, LRESULT *pResult)
3136 // the date control should not show the "today" button
3137 CMonthCalCtrl * pCtrl = m_DateFrom.GetMonthCalCtrl();
3138 if (pCtrl)
3139 SetWindowLongPtr(pCtrl->GetSafeHwnd(), GWL_STYLE, LONG_PTR(pCtrl->GetStyle() | MCS_NOTODAY));
3140 *pResult = 0;
3143 void CLogDlg::OnDtnDropdownDateto(NMHDR * /*pNMHDR*/, LRESULT *pResult)
3145 // the date control should not show the "today" button
3146 CMonthCalCtrl * pCtrl = m_DateTo.GetMonthCalCtrl();
3147 if (pCtrl)
3148 SetWindowLongPtr(pCtrl->GetSafeHwnd(), GWL_STYLE, LONG_PTR(pCtrl->GetStyle() | MCS_NOTODAY));
3149 *pResult = 0;
3152 void CLogDlg::OnSize(UINT nType, int cx, int cy)
3154 __super::OnSize(nType, cx, cy);
3155 //set range
3156 SetSplitterRange();
3159 void CLogDlg::OnRefresh()
3161 //if (GetDlgItem(IDC_GETALL)->IsWindowEnabled())
3163 m_limit = 0;
3164 this->m_LogProgress.SetPos(0);
3166 Refresh (false);
3172 void CLogDlg::OnFocusFilter()
3174 GetDlgItem(IDC_SEARCHEDIT)->SetFocus();
3177 void CLogDlg::OnEditCopy()
3179 if (GetFocus() == &m_ChangedFileListCtrl)
3180 CopyChangedSelectionToClipBoard();
3181 else
3182 m_LogList.CopySelectionToClipBoard();
3185 CString CLogDlg::GetAbsoluteUrlFromRelativeUrl(const CString& url)
3187 // is the URL a relative one?
3188 if (url.Left(2).Compare(_T("^/")) == 0)
3190 // URL is relative to the repository root
3191 CString url1 = m_sRepositoryRoot + url.Mid(1);
3192 TCHAR buf[INTERNET_MAX_URL_LENGTH];
3193 DWORD len = url.GetLength();
3194 if (UrlCanonicalize((LPCTSTR)url1, buf, &len, 0) == S_OK)
3195 return CString(buf, len);
3196 return url1;
3198 else if (url[0] == '/')
3200 // URL is relative to the server's hostname
3201 CString sHost;
3202 // find the server's hostname
3203 int schemepos = m_sRepositoryRoot.Find(_T("//"));
3204 if (schemepos >= 0)
3206 sHost = m_sRepositoryRoot.Left(m_sRepositoryRoot.Find('/', schemepos+3));
3207 CString url1 = sHost + url;
3208 TCHAR buf[INTERNET_MAX_URL_LENGTH];
3209 DWORD len = url.GetLength();
3210 if (UrlCanonicalize((LPCTSTR)url, buf, &len, 0) == S_OK)
3211 return CString(buf, len);
3212 return url1;
3215 return url;
3219 void CLogDlg::OnEnChangeSearchedit()
3221 UpdateData();
3222 if (m_LogList.m_sFilterText.IsEmpty())
3224 CStoreSelection storeselection(this);
3225 // clear the filter, i.e. make all entries appear
3226 theApp.DoWaitCursor(1);
3227 KillTimer(LOGFILTER_TIMER);
3228 FillLogMessageCtrl(false);
3230 Refresh();
3231 //m_LogList.StartFilter();
3232 #if 0
3233 InterlockedExchange(&m_bNoDispUpdates, TRUE);
3234 m_arShownList.RemoveAll();
3235 for (DWORD i=0; i<m_logEntries.size(); ++i)
3237 if (IsEntryInDateRange(i))
3238 m_arShownList.Add(m_logEntries[i]);
3240 InterlockedExchange(&m_bNoDispUpdates, FALSE);
3241 m_LogList.DeleteAllItems();
3242 m_LogList.SetItemCountEx(ShownCountWithStopped());
3243 m_LogList.RedrawItems(0, ShownCountWithStopped());
3244 m_LogList.SetRedraw(false);
3245 ResizeAllListCtrlCols();
3246 m_LogList.SetRedraw(true);
3247 #endif
3248 theApp.DoWaitCursor(-1);
3249 GetDlgItem(IDC_SEARCHEDIT)->ShowWindow(SW_HIDE);
3250 GetDlgItem(IDC_SEARCHEDIT)->ShowWindow(SW_SHOW);
3251 GetDlgItem(IDC_SEARCHEDIT)->SetFocus();
3252 DialogEnableWindow(IDC_STATBUTTON, !(((this->IsThreadRunning())||(m_LogList.m_arShownList.IsEmpty()))));
3253 return;
3255 if (Validate(m_LogList.m_sFilterText))
3256 SetTimer(LOGFILTER_TIMER, 1000, NULL);
3257 else
3258 KillTimer(LOGFILTER_TIMER);
3262 void CLogDlg::OnBnClickedAllBranch()
3264 this->UpdateData();
3266 if(this->m_bAllBranch)
3267 m_LogList.m_ShowMask|=CGit::LOG_INFO_ALL_BRANCH;
3268 else
3269 m_LogList.m_ShowMask&=~CGit::LOG_INFO_ALL_BRANCH;
3271 OnRefresh();
3273 FillLogMessageCtrl(false);
3276 void CLogDlg::OnBnClickedFollowRenames()
3278 this->UpdateData();
3280 if(m_bFollowRenames)
3282 m_LogList.m_ShowMask |= CGit::LOG_INFO_FOLLOW;
3283 if (m_bAllBranch)
3286 m_bAllBranch = FALSE;
3287 m_LogList.m_ShowMask &=~ CGit::LOG_INFO_ALL_BRANCH;
3291 else
3292 m_LogList.m_ShowMask &= ~CGit::LOG_INFO_FOLLOW;
3294 DialogEnableWindow(IDC_LOG_ALLBRANCH, !m_bFollowRenames);
3295 DialogEnableWindow(IDC_SHOWWHOLEPROJECT, !m_bFollowRenames);
3297 OnRefresh();
3299 FillLogMessageCtrl(false);
3302 void CLogDlg::OnBnClickedBrowseRef()
3304 CString newRef = CBrowseRefsDlg::PickRef(false,m_LogList.GetStartRef());
3305 if(newRef.IsEmpty())
3306 return;
3308 SetStartRef(newRef);
3309 ((CButton*)GetDlgItem(IDC_LOG_ALLBRANCH))->SetCheck(0);
3311 OnBnClickedAllBranch();
3314 void CLogDlg::ShowStartRef()
3316 //Show ref name on top
3317 if(!::IsWindow(m_hWnd))
3318 return;
3319 if(m_bAllBranch)
3321 m_staticRef.SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_LOG_ALLBRANCHES)));
3322 m_staticRef.Invalidate(TRUE);
3323 return;
3326 CString showStartRef = m_LogList.GetStartRef();
3327 if(showStartRef.IsEmpty())
3329 //Ref name is HEAD
3330 if (g_Git.Run(L"git symbolic-ref HEAD", &showStartRef, NULL, CP_UTF8))
3331 showStartRef = CString(MAKEINTRESOURCE(IDS_PROC_LOG_NOBRANCH));
3332 showStartRef.Trim(L"\r\n\t ");
3336 if(wcsncmp(showStartRef,L"refs/",5) == 0)
3337 showStartRef = showStartRef.Mid(5);
3338 if(wcsncmp(showStartRef,L"heads/",6) == 0)
3339 showStartRef = showStartRef.Mid(6);
3341 m_staticRef.SetWindowText(showStartRef);
3342 m_staticRef.Invalidate(TRUE);
3345 void CLogDlg::SetStartRef(const CString& StartRef)
3347 m_LogList.SetStartRef(StartRef);
3349 if (m_hightlightRevision.IsEmpty())
3351 m_hightlightRevision = g_Git.GetHash(StartRef);
3352 m_LogList.m_lastSelectedHash = m_hightlightRevision;
3355 ShowStartRef();
3359 void CLogDlg::OnBnClickedFirstParent()
3361 this->UpdateData();
3363 if(this->m_bFirstParent)
3364 m_LogList.m_ShowMask|=CGit::LOG_INFO_FIRST_PARENT;
3365 else
3366 m_LogList.m_ShowMask&=~CGit::LOG_INFO_FIRST_PARENT;
3368 OnRefresh();
3370 FillLogMessageCtrl(false);
3374 void CLogDlg::OnBnClickShowWholeProject()
3376 this->UpdateData();
3378 if(this->m_bWholeProject)
3380 m_LogList.m_Path.Reset();
3381 SetDlgTitle();
3382 DialogEnableWindow(IDC_LOG_FOLLOWRENAMES, FALSE);
3384 else
3386 m_LogList.m_Path=m_path;
3387 DialogEnableWindow(IDC_LOG_FOLLOWRENAMES, !(m_path.IsEmpty() || m_path.IsDirectory()));
3390 SetDlgTitle();
3392 OnRefresh();
3394 FillLogMessageCtrl(false);
3398 LRESULT CLogDlg::OnRefLogChanged(WPARAM wParam, LPARAM lParam)
3400 UNREFERENCED_PARAMETER(wParam);
3401 UNREFERENCED_PARAMETER(lParam);
3402 ShowStartRef();
3403 return 0;
3406 LRESULT CLogDlg::OnTaskbarBtnCreated(WPARAM /*wParam*/, LPARAM /*lParam*/)
3408 m_pTaskbarList.Release();
3409 m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList);
3410 return 0;