fixed missing space
[TortoiseGit.git] / src / TortoiseProc / LogDlg.cpp
blob74f304b8a044c519ca3ee7d777b1cd0a6ad9a96f
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_bSelect(false)
64 , m_bSelectionMustBeContinuous(false)
65 , m_lowestRev(_T(""))
67 , m_sLogInfo(_T(""))
69 , m_bCancelled(FALSE)
70 , m_pNotifyWindow(NULL)
72 , m_bAscending(FALSE)
74 , m_limit(0)
75 , m_childCounter(0)
76 , m_maxChild(0)
77 , m_bIncludeMerges(FALSE)
78 , m_hAccel(NULL)
80 m_bFilterWithRegex = !!CRegDWORD(_T("Software\\TortoiseGit\\UseRegexFilter"), TRUE);
82 CString str;
83 str=g_Git.m_CurrentDir;
84 str.Replace(_T(":"),_T("_"));
85 str=CString(_T("Software\\TortoiseGit\\LogDialog\\AllBranch\\"))+str;
87 m_regbAllBranch=CRegDWORD(str,FALSE);
89 m_bAllBranch=m_regbAllBranch;
91 m_bFirstParent=FALSE;
92 m_bWholeProject=FALSE;
95 CLogDlg::~CLogDlg()
98 m_regbAllBranch=m_bAllBranch;
100 m_CurrentFilteredChangedArray.RemoveAll();
104 void CLogDlg::DoDataExchange(CDataExchange* pDX)
106 CResizableStandAloneDialog::DoDataExchange(pDX);
107 DDX_Control(pDX, IDC_LOGLIST, m_LogList);
108 DDX_Control(pDX, IDC_LOGMSG, m_ChangedFileListCtrl);
109 DDX_Control(pDX, IDC_PROGRESS, m_LogProgress);
110 DDX_Control(pDX, IDC_SPLITTERTOP, m_wndSplitter1);
111 DDX_Control(pDX, IDC_SPLITTERBOTTOM, m_wndSplitter2);
112 DDX_Text(pDX, IDC_SEARCHEDIT, m_LogList.m_sFilterText);
113 DDX_Control(pDX, IDC_DATEFROM, m_DateFrom);
114 DDX_Control(pDX, IDC_DATETO, m_DateTo);
115 DDX_Control(pDX, IDC_HIDEPATHS, m_cHidePaths);
116 DDX_Text(pDX, IDC_LOGINFO, m_sLogInfo);
117 DDX_Check(pDX, IDC_LOG_FIRSTPARENT, m_bFirstParent);
118 DDX_Check(pDX, IDC_LOG_ALLBRANCH,m_bAllBranch);
119 DDX_Check(pDX, IDC_SHOWWHOLEPROJECT,m_bWholeProject);
120 DDX_Control(pDX, IDC_SEARCHEDIT, m_cFilter);
121 DDX_Control(pDX, IDC_STATIC_REF, m_staticRef);
124 BEGIN_MESSAGE_MAP(CLogDlg, CResizableStandAloneDialog)
125 //ON_BN_CLICKED(IDC_GETALL, OnBnClickedGetall)
126 //ON_NOTIFY(NM_DBLCLK, IDC_LOGMSG, OnNMDblclkChangedFileList)
127 ON_WM_CONTEXTMENU()
128 ON_WM_SETCURSOR()
129 ON_BN_CLICKED(IDHELP, OnBnClickedHelp)
130 ON_NOTIFY(LVN_ITEMCHANGED, IDC_LOGLIST, OnLvnItemchangedLoglist)
131 ON_NOTIFY(EN_LINK, IDC_MSGVIEW, OnEnLinkMsgview)
132 ON_BN_CLICKED(IDC_STATBUTTON, OnBnClickedStatbutton)
135 ON_MESSAGE(WM_FILTEREDIT_INFOCLICKED, OnClickedInfoIcon)
136 ON_MESSAGE(WM_FILTEREDIT_CANCELCLICKED, OnClickedCancelFilter)
138 ON_MESSAGE(MSG_LOAD_PERCENTAGE,OnLogListLoading)
140 ON_EN_CHANGE(IDC_SEARCHEDIT, OnEnChangeSearchedit)
141 ON_WM_TIMER()
142 ON_NOTIFY(DTN_DATETIMECHANGE, IDC_DATETO, OnDtnDatetimechangeDateto)
143 ON_NOTIFY(DTN_DATETIMECHANGE, IDC_DATEFROM, OnDtnDatetimechangeDatefrom)
144 ON_BN_CLICKED(IDC_SHOWWHOLEPROJECT, OnBnClickShowWholeProject)
145 //ON_NOTIFY(NM_CUSTOMDRAW, IDC_LOGMSG, OnNMCustomdrawChangedFileList)
146 //ON_NOTIFY(LVN_GETDISPINFO, IDC_LOGMSG, OnLvnGetdispinfoChangedFileList)
147 ON_NOTIFY(LVN_COLUMNCLICK,IDC_LOGLIST, OnLvnColumnclick)
148 //ON_NOTIFY(LVN_COLUMNCLICK, IDC_LOGMSG, OnLvnColumnclickChangedFileList)
149 ON_BN_CLICKED(IDC_HIDEPATHS, OnBnClickedHidepaths)
150 ON_COMMAND(MSG_FETCHED_DIFF, OnBnClickedHidepaths)
151 ON_BN_CLICKED(IDC_LOG_ALLBRANCH, OnBnClickedAllBranch)
153 ON_NOTIFY(DTN_DROPDOWN, IDC_DATEFROM, &CLogDlg::OnDtnDropdownDatefrom)
154 ON_NOTIFY(DTN_DROPDOWN, IDC_DATETO, &CLogDlg::OnDtnDropdownDateto)
155 ON_WM_SIZE()
156 ON_BN_CLICKED(IDC_LOG_FIRSTPARENT, &CLogDlg::OnBnClickedFirstParent)
157 ON_BN_CLICKED(IDC_REFRESH, &CLogDlg::OnBnClickedRefresh)
158 // ON_BN_CLICKED(IDC_BUTTON_BROWSE_REF, &CLogDlg::OnBnClickedBrowseRef)
159 ON_STN_CLICKED(IDC_STATIC_REF, &CLogDlg::OnBnClickedBrowseRef)
160 ON_COMMAND(ID_LOGDLG_REFRESH, &CLogDlg::OnBnClickedRefresh)
161 ON_COMMAND(ID_LOGDLG_FIND, &CLogDlg::OnFind)
162 ON_COMMAND(ID_LOGDLG_FOCUSFILTER, &CLogDlg::OnFocusFilter)
163 ON_COMMAND(ID_EDIT_COPY, &CLogDlg::OnEditCopy)
164 ON_MESSAGE(MSG_REFLOG_CHANGED, OnRefLogChanged)
165 ON_REGISTERED_MESSAGE(WM_TASKBARBTNCREATED, OnTaskbarBtnCreated)
166 END_MESSAGE_MAP()
168 void CLogDlg::SetParams(const CTGitPath& orgPath, const CTGitPath& path, CString hightlightRevision, CString startrev, CString endrev, int limit /* = FALSE */)
170 m_orgPath = orgPath;
171 m_path = path;
172 m_hightlightRevision = hightlightRevision;
174 if (startrev == GIT_REV_ZERO)
175 startrev.Empty();
176 if (endrev == GIT_REV_ZERO)
177 endrev.Empty();
179 this->m_LogList.m_startrev = startrev;
180 m_LogRevision = startrev;
181 this->m_LogList.m_endrev = endrev;
183 if(!endrev.IsEmpty())
184 this->SetStartRef(endrev);
186 m_hasWC = !path.IsUrl();
187 m_limit = limit;
188 if (::IsWindow(m_hWnd))
189 UpdateData(FALSE);
192 void CLogDlg::SetFilter(const CString& findstr, LONG findtype, bool findregex)
194 m_LogList.m_sFilterText = findstr;
195 if (findtype)
196 m_LogList.m_SelectedFilters = findtype;
197 m_LogList.m_bFilterWithRegex = m_bFilterWithRegex = findregex;
200 BOOL CLogDlg::OnInitDialog()
202 CString temp;
203 CResizableStandAloneDialog::OnInitDialog();
204 CAppUtils::MarkWindowAsUnpinnable(m_hWnd);
206 // Let the TaskbarButtonCreated message through the UIPI filter. If we don't
207 // do this, Explorer would be unable to send that message to our window if we
208 // were running elevated. It's OK to make the call all the time, since if we're
209 // not elevated, this is a no-op.
210 CHANGEFILTERSTRUCT cfs = { sizeof(CHANGEFILTERSTRUCT) };
211 typedef BOOL STDAPICALLTYPE ChangeWindowMessageFilterExDFN(HWND hWnd, UINT message, DWORD action, PCHANGEFILTERSTRUCT pChangeFilterStruct);
212 CAutoLibrary hUser = ::LoadLibrary(_T("user32.dll"));
213 if (hUser)
215 ChangeWindowMessageFilterExDFN *pfnChangeWindowMessageFilterEx = (ChangeWindowMessageFilterExDFN*)GetProcAddress(hUser, "ChangeWindowMessageFilterEx");
216 if (pfnChangeWindowMessageFilterEx)
218 pfnChangeWindowMessageFilterEx(m_hWnd, WM_TASKBARBTNCREATED, MSGFLT_ALLOW, &cfs);
221 m_pTaskbarList.Release();
222 m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList);
224 m_hAccel = LoadAccelerators(AfxGetResourceHandle(),MAKEINTRESOURCE(IDR_ACC_LOGDLG));
226 // use the state of the "stop on copy/rename" option from the last time
227 UpdateData(FALSE);
229 // set the font to use in the log message view, configured in the settings dialog
230 CAppUtils::CreateFontForLogs(m_logFont);
231 GetDlgItem(IDC_MSGVIEW)->SetFont(&m_logFont);
232 // automatically detect URLs in the log message and turn them into links
233 GetDlgItem(IDC_MSGVIEW)->SendMessage(EM_AUTOURLDETECT, TRUE, NULL);
234 // make the log message rich edit control send a message when the mouse pointer is over a link
235 GetDlgItem(IDC_MSGVIEW)->SendMessage(EM_SETEVENTMASK, NULL, ENM_LINK);
236 //m_LogList.SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER | LVS_EX_SUBITEMIMAGES);
238 // the "hide unrelated paths" checkbox should be indeterminate
239 m_cHidePaths.SetCheck(BST_INDETERMINATE);
242 //SetWindowTheme(m_LogList.GetSafeHwnd(), L"Explorer", NULL);
243 //SetWindowTheme(m_ChangedFileListCtrl.GetSafeHwnd(), L"Explorer", NULL);
245 // set up the columns
246 m_LogList.DeleteAllItems();
248 m_LogList.m_Path=m_path;
249 m_LogList.m_hasWC = m_LogList.m_bShowWC = !g_GitAdminDir.IsBareRepo(g_Git.m_CurrentDir);
250 m_LogList.InsertGitColumn();
252 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);
254 GetDlgItem(IDC_LOGLIST)->UpdateData(FALSE);
256 m_logcounter = 0;
257 m_sMessageBuf.Preallocate(100000);
259 SetDlgTitle();
261 m_tooltips.Create(this);
262 CheckRegexpTooltip();
264 SetSplitterRange();
266 // the filter control has a 'cancel' button (the red 'X'), we need to load its bitmap
267 m_cFilter.SetCancelBitmaps(IDI_CANCELNORMAL, IDI_CANCELPRESSED);
268 m_cFilter.SetInfoIcon(IDI_LOGFILTER);
269 m_cFilter.SetValidator(this);
271 AdjustControlSize(IDC_HIDEPATHS);
272 AdjustControlSize(IDC_LOG_FIRSTPARENT);
273 AdjustControlSize(IDC_LOG_ALLBRANCH);
275 GetClientRect(m_DlgOrigRect);
276 m_LogList.GetClientRect(m_LogListOrigRect);
277 GetDlgItem(IDC_MSGVIEW)->GetClientRect(m_MsgViewOrigRect);
278 m_ChangedFileListCtrl.GetClientRect(m_ChgOrigRect);
280 m_DateFrom.SendMessage(DTM_SETMCSTYLE, 0, MCS_WEEKNUMBERS|MCS_NOTODAY|MCS_NOTRAILINGDATES|MCS_NOSELCHANGEONNAV);
281 m_DateTo.SendMessage(DTM_SETMCSTYLE, 0, MCS_WEEKNUMBERS|MCS_NOTODAY|MCS_NOTRAILINGDATES|MCS_NOSELCHANGEONNAV);
283 m_staticRef.SetURL(CString());
285 // resizable stuff
286 AddAnchor(IDC_STATIC_REF, TOP_LEFT);
287 //AddAnchor(IDC_BUTTON_BROWSE_REF, TOP_LEFT);
288 AddAnchor(IDC_FROMLABEL, TOP_LEFT);
289 AddAnchor(IDC_DATEFROM, TOP_LEFT);
290 AddAnchor(IDC_TOLABEL, TOP_LEFT);
291 AddAnchor(IDC_DATETO, TOP_LEFT);
293 SetFilterCueText();
294 AddAnchor(IDC_SEARCHEDIT, TOP_LEFT, TOP_RIGHT);
296 AddAnchor(IDC_LOGLIST, TOP_LEFT, TOP_RIGHT);
297 AddAnchor(IDC_SPLITTERTOP, TOP_LEFT, TOP_RIGHT);
298 AddAnchor(IDC_MSGVIEW, TOP_LEFT, BOTTOM_RIGHT);
299 AddAnchor(IDC_SPLITTERBOTTOM, BOTTOM_LEFT, BOTTOM_RIGHT);
300 AddAnchor(IDC_LOGMSG, BOTTOM_LEFT, BOTTOM_RIGHT);
302 AddAnchor(IDC_LOGINFO, BOTTOM_LEFT, BOTTOM_RIGHT);
303 AddAnchor(IDC_HIDEPATHS, BOTTOM_LEFT);
304 AddAnchor(IDC_LOG_ALLBRANCH,BOTTOM_LEFT);
305 AddAnchor(IDC_LOG_FIRSTPARENT, BOTTOM_LEFT);
306 //AddAnchor(IDC_GETALL, BOTTOM_LEFT);
307 AddAnchor(IDC_SHOWWHOLEPROJECT, BOTTOM_LEFT);
308 AddAnchor(IDC_REFRESH, BOTTOM_LEFT);
309 AddAnchor(IDC_STATBUTTON, BOTTOM_RIGHT);
310 AddAnchor(IDC_PROGRESS, BOTTOM_LEFT, BOTTOM_RIGHT);
311 AddAnchor(IDOK, BOTTOM_RIGHT);
312 AddAnchor(IDCANCEL, BOTTOM_RIGHT);
313 AddAnchor(IDHELP, BOTTOM_RIGHT);
315 if(this->m_bAllBranch)
316 m_LogList.m_ShowMask|=CGit::LOG_INFO_ALL_BRANCH;
317 else
318 m_LogList.m_ShowMask&=~CGit::LOG_INFO_ALL_BRANCH;
320 // SetPromptParentWindow(m_hWnd);
322 if (hWndExplorer)
323 CenterWindow(CWnd::FromHandle(hWndExplorer));
324 EnableSaveRestore(_T("LogDlg"));
326 DWORD yPos1 = CRegDWORD(_T("Software\\TortoiseGit\\TortoiseProc\\ResizableState\\LogDlgSizer1"));
327 DWORD yPos2 = CRegDWORD(_T("Software\\TortoiseGit\\TortoiseProc\\ResizableState\\LogDlgSizer2"));
328 RECT rcDlg, rcLogList, rcChgMsg;
329 GetClientRect(&rcDlg);
330 m_LogList.GetWindowRect(&rcLogList);
331 ScreenToClient(&rcLogList);
332 m_ChangedFileListCtrl.GetWindowRect(&rcChgMsg);
333 ScreenToClient(&rcChgMsg);
334 if (yPos1)
336 RECT rectSplitter;
337 m_wndSplitter1.GetWindowRect(&rectSplitter);
338 ScreenToClient(&rectSplitter);
339 int delta = yPos1 - rectSplitter.top;
341 if ((rcLogList.bottom + delta > rcLogList.top)&&(rcLogList.bottom + delta < rcChgMsg.bottom - 30))
343 m_wndSplitter1.SetWindowPos(NULL, 0, yPos1, 0, 0, SWP_NOSIZE);
344 DoSizeV1(delta);
347 if (yPos2)
349 RECT rectSplitter;
350 m_wndSplitter2.GetWindowRect(&rectSplitter);
351 ScreenToClient(&rectSplitter);
352 int delta = yPos2 - rectSplitter.top;
354 if ((rcChgMsg.top + delta < rcChgMsg.bottom)&&(rcChgMsg.top + delta > rcLogList.top + 30))
356 m_wndSplitter2.SetWindowPos(NULL, 0, yPos2, 0, 0, SWP_NOSIZE);
357 DoSizeV2(delta);
362 if (m_bSelect)
364 // the dialog is used to select revisions
365 // enable the OK button if appropriate
366 EnableOKButton();
368 else
370 // the dialog is used to just view log messages
371 // hide the OK button and set text on Cancel button to OK
372 GetDlgItemText(IDOK, temp);
373 SetDlgItemText(IDCANCEL, temp);
374 GetDlgItem(IDOK)->ShowWindow(SW_HIDE);
377 m_mergedRevs.clear();
379 // first start a thread to obtain the log messages without
380 // blocking the dialog
381 //m_tTo = 0;
382 //m_tFrom = (DWORD)-1;
384 // scroll to user selected or current revision
385 if (!m_hightlightRevision.IsEmpty() && m_hightlightRevision.GetLength() >= GIT_HASH_SIZE)
386 m_LogList.m_lastSelectedHash = m_hightlightRevision;
387 else if (!m_LogList.m_endrev.IsEmpty() && m_LogList.m_endrev.GetLength() >= GIT_HASH_SIZE)
388 m_LogList.m_lastSelectedHash = m_hightlightRevision;
389 else
390 m_LogList.m_lastSelectedHash = g_Git.GetHash(_T("HEAD"));
392 m_LogList.FetchLogAsync(this);
394 GetDlgItem(IDC_LOGLIST)->SetFocus();
396 ShowStartRef();
397 return FALSE;
400 LRESULT CLogDlg::OnLogListLoading(WPARAM wParam, LPARAM /*lParam*/)
402 int cur=(int)wParam;
404 if( cur == GITLOG_START )
406 CString temp;
407 temp.LoadString(IDS_PROGRESSWAIT);
409 this->m_LogList.ShowText(temp, true);
411 // We use a progress bar while getting the logs
412 m_LogProgress.SetRange32(0, 100);
413 m_LogProgress.SetPos(0);
414 if (m_pTaskbarList)
416 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
417 m_pTaskbarList->SetProgressValue(m_hWnd, 0, 100);
420 GetDlgItem(IDC_PROGRESS)->ShowWindow(TRUE);
422 //DialogEnableWindow(IDC_GETALL, FALSE);
423 //DialogEnableWindow(IDC_SHOWWHOLEPROJECT, FALSE);
424 //DialogEnableWindow(IDC_LOG_FIRSTPARENT, FALSE);
425 DialogEnableWindow(IDC_STATBUTTON, FALSE);
426 //DialogEnableWindow(IDC_REFRESH, FALSE);
427 DialogEnableWindow(IDC_HIDEPATHS,FALSE);
430 else if( cur == GITLOG_END)
432 if(this->m_LogList.HasText())
434 this->m_LogList.ClearText();
436 UpdateLogInfoLabel();
438 if (m_pTaskbarList)
439 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NOPROGRESS);
441 //if (!m_bShowedAll)
442 DialogEnableWindow(IDC_SHOWWHOLEPROJECT, TRUE);
444 //DialogEnableWindow(IDC_GETALL, TRUE);
445 DialogEnableWindow(IDC_STATBUTTON, !(m_LogList.m_arShownList.IsEmpty() || m_LogList.m_arShownList.GetCount() == 1 && m_LogList.m_bShowWC));
446 DialogEnableWindow(IDC_REFRESH, TRUE);
447 DialogEnableWindow(IDC_HIDEPATHS,TRUE);
449 // PostMessage(WM_TIMER, LOGFILTER_TIMER);
450 GetDlgItem(IDC_PROGRESS)->ShowWindow(FALSE);
451 //CTime time=m_LogList.GetOldestTime();
452 CTime begin,end;
453 m_LogList.GetTimeRange(begin,end);
455 if(m_LogList.m_From == -1)
456 m_DateFrom.SetTime(&begin);
458 if(m_LogList.m_To == -1)
459 m_DateTo.SetTime(&end);
463 else
465 if(this->m_LogList.HasText())
467 this->m_LogList.ClearText();
468 this->m_LogList.Invalidate();
470 UpdateLogInfoLabel();
471 m_LogProgress.SetPos(cur);
472 if (m_pTaskbarList)
474 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
475 m_pTaskbarList->SetProgressValue(m_hWnd, cur, 100);
478 return 0;
480 void CLogDlg::SetDlgTitle()
482 if (m_sTitle.IsEmpty())
483 GetWindowText(m_sTitle);
485 if (m_LogList.m_Path.IsEmpty() || m_orgPath.GetWinPathString().IsEmpty())
487 CAppUtils::SetWindowTitle(m_hWnd, g_Git.m_CurrentDir, m_sTitle);
489 else
490 CAppUtils::SetWindowTitle(m_hWnd, m_orgPath.GetWinPathString(), m_sTitle);
493 void CLogDlg::CheckRegexpTooltip()
495 CWnd *pWnd = GetDlgItem(IDC_SEARCHEDIT);
496 // Since tooltip describes regexp features, show it only if regexps are enabled.
497 if (m_bFilterWithRegex)
499 m_tooltips.AddTool(pWnd, IDS_LOG_FILTER_REGEX_TT);
501 else
502 m_tooltips.DelTool(pWnd);
505 void CLogDlg::EnableOKButton()
507 if (m_bSelect)
509 // the dialog is used to select revisions
510 if (m_bSelectionMustBeSingle)
512 // enable OK button if only a single revision is selected
513 DialogEnableWindow(IDOK, (m_LogList.GetSelectedCount()==1));
515 else if (m_bSelectionMustBeContinuous)
516 DialogEnableWindow(IDOK, (m_LogList.GetSelectedCount()!=0)&&(m_LogList.IsSelectionContinuous()));
517 else
518 DialogEnableWindow(IDOK, m_LogList.GetSelectedCount()!=0);
520 else
521 DialogEnableWindow(IDOK, TRUE);
524 CString CLogDlg::GetTagInfo(GitRev* pLogEntry)
526 CString cmd;
527 CString output;
529 if(m_LogList.m_HashMap.find(pLogEntry->m_CommitHash) != m_LogList.m_HashMap.end())
531 STRING_VECTOR &vector = m_LogList.m_HashMap[pLogEntry->m_CommitHash];
532 for(int i=0;i<vector.size();i++)
534 if(vector[i].Find(_T("refs/tags/")) == 0 )
536 CString tag= vector[i];
537 int start = vector[i].Find(_T("^{}"));
538 if(start>0)
539 tag=tag.Left(start);
540 else
541 continue;
543 cmd.Format(_T("git.exe cat-file tag %s"), tag);
545 if(g_Git.Run(cmd, &output, NULL, CP_UTF8) == 0 )
546 output+=_T("\n");
551 if(!output.IsEmpty())
553 output = _T("\n*Tag Info*\n\n") + output;
556 return output;
559 void CLogDlg::FillLogMessageCtrl(bool bShow /* = true*/)
561 // we fill here the log message rich edit control,
562 // and also populate the changed files list control
563 // according to the selected revision(s).
565 CRichEditCtrl * pMsgView = (CRichEditCtrl*)GetDlgItem(IDC_MSGVIEW);
566 // empty the log message view
567 pMsgView->SetWindowText(_T(" "));
568 // empty the changed files list
569 m_ChangedFileListCtrl.SetRedraw(FALSE);
570 // InterlockedExchange(&m_bNoDispUpdates, TRUE);
571 m_currentChangedArray = NULL;
572 //m_ChangedFileListCtrl.SetExtendedStyle ( LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER );
573 m_ChangedFileListCtrl.DeleteAllItems();
575 // if we're not here to really show a selected revision, just
576 // get out of here after clearing the views, which is what is intended
577 // if that flag is not set.
578 if (!bShow)
580 // force a redraw
581 m_ChangedFileListCtrl.Invalidate();
582 // InterlockedExchange(&m_bNoDispUpdates, FALSE);
583 m_ChangedFileListCtrl.SetRedraw(TRUE);
584 return;
587 // depending on how many revisions are selected, we have to do different
588 // tasks.
589 int selCount = m_LogList.GetSelectedCount();
590 if (selCount == 0)
592 // if nothing is selected, we have nothing more to do
593 // InterlockedExchange(&m_bNoDispUpdates, FALSE);
594 m_ChangedFileListCtrl.SetRedraw(TRUE);
595 return;
597 else if (selCount == 1)
599 // if one revision is selected, we have to fill the log message view
600 // with the corresponding log message, and also fill the changed files
601 // list fully.
602 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
603 int selIndex = m_LogList.GetNextSelectedItem(pos);
604 if (selIndex >= m_LogList.m_arShownList.GetCount())
606 // InterlockedExchange(&m_bNoDispUpdates, FALSE);
607 m_ChangedFileListCtrl.SetRedraw(TRUE);
608 return;
610 GitRev* pLogEntry = reinterpret_cast<GitRev *>(m_LogList.m_arShownList.SafeGetAt(selIndex));
613 // set the log message text
614 pMsgView->SetWindowText(_T("Commit:")+pLogEntry->m_CommitHash.ToString()+_T("\r\n\r\n"));
615 // turn bug ID's into links if the bugtraq: properties have been set
616 // and we can find a match of those in the log message
618 pMsgView->SetSel(-1,-1);
619 CHARFORMAT2 format;
620 SecureZeroMemory(&format, sizeof(CHARFORMAT2));
621 format.cbSize = sizeof(CHARFORMAT2);
622 format.dwMask = CFM_BOLD;
623 format.dwEffects = CFE_BOLD;
624 pMsgView->SendMessage(EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&format);
626 CString msg=_T("* ");
627 msg+=pLogEntry->GetSubject();
628 pMsgView->ReplaceSel(msg);
630 pMsgView->SetSel(-1,-1);
631 format.dwEffects = 0;
632 pMsgView->SendMessage(EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&format);
634 msg=_T("\n");
635 msg+=pLogEntry->GetBody();
637 if(!pLogEntry->m_Notes.IsEmpty())
639 msg+= _T("\n*Notes* ");
640 msg+= pLogEntry->m_Notes;
641 msg+= _T("\n\n");
644 msg+=GetTagInfo(pLogEntry);
646 pMsgView->ReplaceSel(msg);
648 CString text;
649 pMsgView->GetWindowText(text);
650 // the rich edit control doesn't count the CR char!
651 // to be exact: CRLF is treated as one char.
652 text.Remove('\r');
654 m_LogList.m_ProjectProperties.FindBugID(text, pMsgView);
655 CAppUtils::FormatTextInRichEditControl(pMsgView);
657 int HidePaths=m_cHidePaths.GetState() & 0x0003;
658 CString matchpath=this->m_path.GetGitPathString();
660 int count = pLogEntry->GetFiles(&m_LogList).GetCount();
661 for(int i=0;i<count && (!matchpath.IsEmpty());i++)
663 if( m_bWholeProject )
664 break;
666 ((CTGitPath&)pLogEntry->GetFiles(&m_LogList)[i]).m_Action &= ~(CTGitPath::LOGACTIONS_HIDE|CTGitPath::LOGACTIONS_GRAY);
668 if(pLogEntry->GetFiles(&m_LogList)[i].GetGitPathString().Left(matchpath.GetLength()) != matchpath)
670 if(HidePaths==BST_CHECKED)
671 ((CTGitPath&)pLogEntry->GetFiles(&m_LogList)[i]).m_Action |= CTGitPath::LOGACTIONS_HIDE;
672 if(HidePaths==BST_INDETERMINATE)
673 ((CTGitPath&)pLogEntry->GetFiles(&m_LogList)[i]).m_Action |= CTGitPath::LOGACTIONS_GRAY;
677 m_ChangedFileListCtrl.UpdateWithGitPathList(pLogEntry->GetFiles(&m_LogList));
678 m_ChangedFileListCtrl.m_CurrentVersion=pLogEntry->m_CommitHash;
679 m_ChangedFileListCtrl.Show(GITSLC_SHOWVERSIONED);
681 m_ChangedFileListCtrl.SetBusyString(_T("Fetch Changed File..."));
683 if(!pLogEntry->m_IsDiffFiles)
684 m_ChangedFileListCtrl.SetBusy(TRUE);
685 else
686 m_ChangedFileListCtrl.SetBusy(FALSE);
688 m_ChangedFileListCtrl.SetRedraw(TRUE);
689 return;
693 else
695 // more than one revision is selected:
696 // the log message view must be emptied
697 // the changed files list contains all the changed paths from all
698 // selected revisions, with 'doubles' removed
699 m_currentChangedPathList = GetChangedPathsFromSelectedRevisions(true);
702 // redraw the views
703 // InterlockedExchange(&m_bNoDispUpdates, FALSE);
704 #if 0
705 if (m_currentChangedArray)
707 m_ChangedFileListCtrl.SetItemCountEx(m_currentChangedArray->GetCount());
708 m_ChangedFileListCtrl.RedrawItems(0, m_currentChangedArray->GetCount());
710 else if (m_currentChangedPathList.GetCount())
712 m_ChangedFileListCtrl.SetItemCountEx(m_currentChangedPathList.GetCount());
713 m_ChangedFileListCtrl.RedrawItems(0, m_currentChangedPathList.GetCount());
715 else
717 m_ChangedFileListCtrl.SetItemCountEx(0);
718 m_ChangedFileListCtrl.Invalidate();
720 #endif
721 // sort according to the settings
722 if (m_nSortColumnPathList > 0)
723 SetSortArrow(&m_ChangedFileListCtrl, m_nSortColumnPathList, m_bAscendingPathList);
724 else
725 SetSortArrow(&m_ChangedFileListCtrl, -1, false);
726 m_ChangedFileListCtrl.SetRedraw(TRUE);
730 void CLogDlg::OnBnClickedRefresh()
732 Refresh (true);
735 void CLogDlg::Refresh (bool clearfilter /*autoGoOnline*/)
737 m_limit = 0;
738 m_LogList.Refresh(clearfilter);
739 ShowStartRef();
740 FillLogMessageCtrl(false);
745 BOOL CLogDlg::Cancel()
747 return m_bCancelled;
750 void CLogDlg::SaveSplitterPos()
752 if (!IsIconic())
754 CRegDWORD regPos1 = CRegDWORD(_T("Software\\TortoiseGit\\TortoiseProc\\ResizableState\\LogDlgSizer1"));
755 CRegDWORD regPos2 = CRegDWORD(_T("Software\\TortoiseGit\\TortoiseProc\\ResizableState\\LogDlgSizer2"));
756 RECT rectSplitter;
757 m_wndSplitter1.GetWindowRect(&rectSplitter);
758 ScreenToClient(&rectSplitter);
759 regPos1 = rectSplitter.top;
760 m_wndSplitter2.GetWindowRect(&rectSplitter);
761 ScreenToClient(&rectSplitter);
762 regPos2 = rectSplitter.top;
766 void CLogDlg::OnCancel()
768 this->ShowWindow(SW_HIDE);
770 // canceling means stopping the working thread if it's still running.
771 m_LogList.SafeTerminateAsyncDiffThread();
772 if (this->IsThreadRunning())
774 m_LogList.SafeTerminateThread();
776 UpdateData();
778 SaveSplitterPos();
779 __super::OnCancel();
782 CString CLogDlg::MakeShortMessage(const CString& message)
784 bool bFoundShort = true;
785 CString sShortMessage = m_LogList.m_ProjectProperties.GetLogSummary(message);
786 if (sShortMessage.IsEmpty())
788 bFoundShort = false;
789 sShortMessage = message;
791 // Remove newlines and tabs 'cause those are not shown nicely in the list control
792 sShortMessage.Remove('\r');
793 sShortMessage.Replace(_T('\t'), _T(' '));
795 // Suppose the first empty line separates 'summary' from the rest of the message.
796 int found = sShortMessage.Find(_T("\n\n"));
797 // To avoid too short 'short' messages
798 // (e.g. if the message looks something like "Bugfix:\n\n*done this\n*done that")
799 // only use the empty newline as a separator if it comes after at least 15 chars.
800 if ((!bFoundShort)&&(found >= 15))
802 sShortMessage = sShortMessage.Left(found);
804 sShortMessage.Replace('\n', ' ');
805 return sShortMessage;
808 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*/)
810 #if 0
811 if (rev == SVN_INVALID_REVNUM)
813 m_childCounter--;
814 return TRUE;
817 // this is the callback function which receives the data for every revision we ask the log for
818 // we store this information here one by one.
819 m_logcounter += 1;
820 if (m_startrev == -1)
821 m_startrev = rev;
822 if (m_limit != 0)
824 m_limitcounter--;
825 m_LogProgress.SetPos(m_limit - m_limitcounter);
827 else if (m_startrev.IsNumber() && m_startrev.IsNumber())
828 m_LogProgress.SetPos((git_revnum_t)m_startrev-rev+(git_revnum_t)m_endrev);
829 __time64_t ttime = time/1000000L;
830 if (m_tTo < (DWORD)ttime)
831 m_tTo = (DWORD)ttime;
832 if (m_tFrom > (DWORD)ttime)
833 m_tFrom = (DWORD)ttime;
834 if ((m_lowestRev > rev)||(m_lowestRev < 0))
835 m_lowestRev = rev;
836 // Add as many characters from the log message to the list control
837 PLOGENTRYDATA pLogItem = new LOGENTRYDATA;
838 pLogItem->bCopies = !!copies;
840 // find out if this item was copied in the revision
841 BOOL copiedself = FALSE;
842 if (copies)
844 for (INT_PTR cpPathIndex = 0; cpPathIndex < cpaths->GetCount(); ++cpPathIndex)
846 LogChangedPath * cpath = cpaths->SafeGetAt(cpPathIndex);
847 if (!cpath->sCopyFromPath.IsEmpty() && (cpath->sPath.Compare(m_sSelfRelativeURL) == 0))
849 // note: this only works if the log is fetched top-to-bottom
850 // but since we do that, it shouldn't be a problem
851 m_sSelfRelativeURL = cpath->sCopyFromPath;
852 copiedself = TRUE;
853 break;
857 pLogItem->bCopiedSelf = copiedself;
858 pLogItem->tmDate = ttime;
859 pLogItem->sAuthor = author;
860 pLogItem->sDate = date;
861 pLogItem->sShortMessage = MakeShortMessage(message);
862 pLogItem->dwFileChanges = filechanges;
863 pLogItem->actions = actions;
864 pLogItem->haschildren = haschildren;
865 pLogItem->childStackDepth = m_childCounter;
866 m_maxChild = max(m_childCounter, m_maxChild);
867 if (haschildren)
868 m_childCounter++;
869 pLogItem->sBugIDs = m_ProjectProperties.FindBugID(message).Trim();
871 // split multi line log entries and concatenate them
872 // again but this time with \r\n as line separators
873 // so that the edit control recognizes them
876 if (message.GetLength()>0)
878 m_sMessageBuf = message;
879 m_sMessageBuf.Replace(_T("\n\r"), _T("\n"));
880 m_sMessageBuf.Replace(_T("\r\n"), _T("\n"));
881 if (m_sMessageBuf.Right(1).Compare(_T("\n"))==0)
882 m_sMessageBuf = m_sMessageBuf.Left(m_sMessageBuf.GetLength()-1);
884 else
885 m_sMessageBuf.Empty();
886 pLogItem->sMessage = m_sMessageBuf;
887 pLogItem->Rev = rev;
889 // move-construct path array
891 pLogItem->pArChangedPaths = new LogChangedPathArray (*cpaths);
892 cpaths->RemoveAll();
894 catch (CException * e)
896 ::MessageBox(NULL, _T("not enough memory!"), _T("TortoiseGit"), MB_ICONERROR);
897 e->Delete();
898 m_bCancelled = TRUE;
900 m_logEntries.push_back(pLogItem);
901 m_arShownList.Add(pLogItem);
902 #endif
903 return TRUE;
906 GitRev g_rev;
907 //this is the thread function which calls the subversion function
912 void CLogDlg::CopyChangedSelectionToClipBoard()
915 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
916 if (pos == NULL)
917 return; // nothing is selected, get out of here
919 CString sPaths;
921 // CGitRev* pLogEntry = reinterpret_cast<CGitRev* >(m_LogList.m_arShownList.SafeGetAt(m_LogList.GetNextSelectedItem(pos)));
922 // if (pos)
924 POSITION pos = m_ChangedFileListCtrl.GetFirstSelectedItemPosition();
925 while (pos)
927 int nItem = m_ChangedFileListCtrl.GetNextSelectedItem(pos);
928 CTGitPath *path = (CTGitPath*)m_ChangedFileListCtrl.GetItemData(nItem);
929 if(path)
930 sPaths += path->GetGitPathString();
931 sPaths += _T("\r\n");
934 #if 0
935 else
937 // only one revision is selected in the log dialog top pane
938 // but multiple items could be selected in the changed items list
939 POSITION pos = m_ChangedFileListCtrl.GetFirstSelectedItemPosition();
940 while (pos)
942 int nItem = m_ChangedFileListCtrl.GetNextSelectedItem(pos);
943 LogChangedPath * changedlogpath = pLogEntry->pArChangedPaths->SafeGetAt(nItem);
945 if ((m_cHidePaths.GetState() & 0x0003)==BST_CHECKED)
947 // some items are hidden! So find out which item the user really selected
948 INT_PTR selRealIndex = -1;
949 for (INT_PTR hiddenindex=0; hiddenindex<pLogEntry->pArChangedPaths->GetCount(); ++hiddenindex)
951 if (pLogEntry->pArChangedPaths->SafeGetAt(hiddenindex)->sPath.Left(m_sRelativeRoot.GetLength()).Compare(m_sRelativeRoot)==0)
952 selRealIndex++;
953 if (selRealIndex == nItem)
955 changedlogpath = pLogEntry->pArChangedPaths->SafeGetAt(hiddenindex);
956 break;
960 if (changedlogpath)
962 sPaths += changedlogpath->sPath;
963 sPaths += _T("\r\n");
967 #endif
968 sPaths.Trim();
969 CStringUtils::WriteAsciiStringToClipboard(sPaths, GetSafeHwnd());
973 BOOL CLogDlg::IsDiffPossible(LogChangedPath * /*changedpath*/, git_revnum_t rev)
975 #if 0
976 CString added, deleted;
977 if (changedpath == NULL)
978 return false;
980 if ((rev > 1)&&(changedpath->action != LOGACTIONS_DELETED))
982 if (changedpath->action == LOGACTIONS_ADDED) // file is added
984 if (changedpath->lCopyFromRev == 0)
985 return FALSE; // but file was not added with history
987 return TRUE;
989 #endif
990 return FALSE;
993 void CLogDlg::OnContextMenu(CWnd* pWnd, CPoint point)
995 // we have two separate context menus:
996 // one shown on the log message list control,
997 // the other shown in the changed-files list control
998 int selCount = m_LogList.GetSelectedCount();
999 if (pWnd == &m_LogList)
1001 //ShowContextMenuForRevisions(pWnd, point);
1003 else if (pWnd == &m_ChangedFileListCtrl)
1005 //ShowContextMenuForChangedpaths(pWnd, point);
1007 else if ((selCount == 1)&&(pWnd == GetDlgItem(IDC_MSGVIEW)))
1009 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
1010 int selIndex = -1;
1011 if (pos)
1012 selIndex = m_LogList.GetNextSelectedItem(pos);
1014 GitRev *pRev = ((GitRev*)m_LogList.m_arShownList[selIndex]);
1016 if ((point.x == -1) && (point.y == -1))
1018 CRect rect;
1019 GetDlgItem(IDC_MSGVIEW)->GetClientRect(&rect);
1020 ClientToScreen(&rect);
1021 point = rect.CenterPoint();
1023 CString sMenuItemText;
1024 CIconMenu popup;
1025 if (popup.CreatePopupMenu())
1027 // add the 'default' entries
1028 sMenuItemText.LoadString(IDS_SCIEDIT_COPY);
1029 popup.AppendMenu(MF_STRING | MF_ENABLED, WM_COPY, sMenuItemText);
1030 sMenuItemText.LoadString(IDS_SCIEDIT_SELECTALL);
1031 popup.AppendMenu(MF_STRING | MF_ENABLED, EM_SETSEL, sMenuItemText);
1032 sMenuItemText.LoadString(IDS_EDIT_NOTES);
1033 popup.AppendMenuIcon( CGitLogList::ID_EDITNOTE, sMenuItemText, IDI_EDIT);
1035 //if (selIndex >= 0)
1037 // popup.AppendMenu(MF_SEPARATOR);
1038 // sMenuItemText.LoadString(IDS_LOG_POPUP_EDITLOG);
1039 // popup.AppendMenu(MF_STRING | MF_ENABLED, CGitLogList::ID_EDITAUTHOR, sMenuItemText);
1042 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
1043 switch (cmd)
1045 case 0:
1046 break; // no command selected
1047 case EM_SETSEL:
1048 case WM_COPY:
1049 ::SendMessage(GetDlgItem(IDC_MSGVIEW)->GetSafeHwnd(), cmd, 0, -1);
1050 break;
1051 case CGitLogList::ID_EDITNOTE:
1052 CAppUtils::EditNote(pRev);
1053 this->FillLogMessageCtrl(true);
1054 break;
1060 void CLogDlg::OnOK()
1062 // since the log dialog is also used to select revisions for other
1063 // dialogs, we have to do some work before closing this dialog
1064 if (GetFocus() != GetDlgItem(IDOK))
1065 return; // if the "OK" button doesn't have the focus, do nothing: this prevents closing the dialog when pressing enter
1067 m_LogList.SafeTerminateAsyncDiffThread();
1068 if (this->IsThreadRunning())
1070 m_LogList.SafeTerminateThread();
1072 UpdateData();
1073 // check that one and only one row is selected
1074 if (m_LogList.GetSelectedCount() == 1)
1076 // get the selected row
1077 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
1078 int selIndex = m_LogList.GetNextSelectedItem(pos);
1079 if (selIndex < m_LogList.m_arShownList.GetCount())
1081 // all ok, pick up the revision
1082 GitRev* pLogEntry = reinterpret_cast<GitRev *>(m_LogList.m_arShownList.SafeGetAt(selIndex));
1083 // extract the hash
1084 m_sSelectedHash = pLogEntry->m_CommitHash;
1087 UpdateData(FALSE);
1088 SaveSplitterPos();
1089 __super::OnOK();
1091 #if 0
1092 if (!GetDlgItem(IDOK)->IsWindowVisible() && GetFocus() != GetDlgItem(IDCANCEL))
1093 return; // the Cancel button works as the OK button. But if the cancel button has not the focus, do nothing.
1095 CString temp;
1096 CString buttontext;
1097 GetDlgItemText(IDOK, buttontext);
1098 temp.LoadString(IDS_MSGBOX_CANCEL);
1099 if (temp.Compare(buttontext) != 0)
1100 __super::OnOK(); // only exit if the button text matches, and that will match only if the thread isn't running anymore
1101 m_bCancelled = TRUE;
1102 m_selectedRevs.Clear();
1103 m_selectedRevsOneRange.Clear();
1104 if (m_pNotifyWindow)
1106 int selIndex = m_LogList.GetSelectionMark();
1107 if (selIndex >= 0)
1109 PLOGENTRYDATA pLogEntry = NULL;
1110 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
1111 pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(m_LogList.GetNextSelectedItem(pos)));
1112 m_selectedRevs.AddRevision(pLogEntry->Rev);
1113 git_revnum_t lowerRev = pLogEntry->Rev;
1114 git_revnum_t higherRev = lowerRev;
1115 while (pos)
1117 pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(m_LogList.GetNextSelectedItem(pos)));
1118 git_revnum_t rev = pLogEntry->Rev;
1119 m_selectedRevs.AddRevision(pLogEntry->Rev);
1120 if (lowerRev > rev)
1121 lowerRev = rev;
1122 if (higherRev < rev)
1123 higherRev = rev;
1125 if (m_sFilterText.IsEmpty() && m_nSortColumn == 0 && IsSelectionContinuous())
1127 m_selectedRevsOneRange.AddRevRange(lowerRev, higherRev);
1129 BOOL bSentMessage = FALSE;
1130 if (m_LogList.GetSelectedCount() == 1)
1132 // if only one revision is selected, check if the path/url with which the dialog was started
1133 // was directly affected in that revision. If it was, then check if our path was copied from somewhere.
1134 // if it was copied, use the copy from revision as lowerRev
1135 if ((pLogEntry)&&(pLogEntry->pArChangedPaths)&&(lowerRev == higherRev))
1137 CString sUrl = m_path.GetGitPathString();
1138 if (!m_path.IsUrl())
1140 sUrl = GetURLFromPath(m_path);
1142 sUrl = sUrl.Mid(m_sRepositoryRoot.GetLength());
1143 for (int cp = 0; cp < pLogEntry->pArChangedPaths->GetCount(); ++cp)
1145 LogChangedPath * pData = pLogEntry->pArChangedPaths->SafeGetAt(cp);
1146 if (pData)
1148 if (sUrl.Compare(pData->sPath) == 0)
1150 if (!pData->sCopyFromPath.IsEmpty())
1152 lowerRev = pData->lCopyFromRev;
1153 m_pNotifyWindow->SendMessage(WM_REVSELECTED, m_wParam & (MERGE_REVSELECTSTART), lowerRev);
1154 m_pNotifyWindow->SendMessage(WM_REVSELECTED, m_wParam & (MERGE_REVSELECTEND), higherRev);
1155 m_pNotifyWindow->SendMessage(WM_REVLIST, m_selectedRevs.GetCount(), (LPARAM)&m_selectedRevs);
1156 bSentMessage = TRUE;
1163 if ( !bSentMessage )
1165 m_pNotifyWindow->SendMessage(WM_REVSELECTED, m_wParam & (MERGE_REVSELECTSTART | MERGE_REVSELECTMINUSONE), lowerRev);
1166 m_pNotifyWindow->SendMessage(WM_REVSELECTED, m_wParam & (MERGE_REVSELECTEND | MERGE_REVSELECTMINUSONE), higherRev);
1167 m_pNotifyWindow->SendMessage(WM_REVLIST, m_selectedRevs.GetCount(), (LPARAM)&m_selectedRevs);
1168 if (m_selectedRevsOneRange.GetCount())
1169 m_pNotifyWindow->SendMessage(WM_REVLISTONERANGE, 0, (LPARAM)&m_selectedRevsOneRange);
1173 UpdateData();
1174 CRegDWORD reg = CRegDWORD(_T("Software\\TortoiseGit\\ShowAllEntry"));
1175 reg = m_btnShow.GetCurrentEntry();
1176 SaveSplitterPos();
1177 #endif
1180 void CLogDlg::OnNMDblclkChangedFileList(NMHDR * /*pNMHDR*/, LRESULT *pResult)
1182 // a double click on an entry in the changed-files list has happened
1183 *pResult = 0;
1185 DiffSelectedFile();
1188 void CLogDlg::DiffSelectedFile()
1190 #if 0
1191 if (m_bThreadRunning)
1192 return;
1193 UpdateLogInfoLabel();
1194 INT_PTR selIndex = m_ChangedFileListCtrl.GetSelectionMark();
1195 if (selIndex < 0)
1196 return;
1197 if (m_ChangedFileListCtrl.GetSelectedCount() == 0)
1198 return;
1199 // find out if there's an entry selected in the log list
1200 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
1201 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(m_LogList.GetNextSelectedItem(pos)));
1202 git_revnum_t rev1 = pLogEntry->Rev;
1203 git_revnum_t rev2 = rev1;
1204 if (pos)
1206 while (pos)
1208 // there's at least a second entry selected in the log list: several revisions selected!
1209 pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(m_LogList.GetNextSelectedItem(pos)));
1210 if (pLogEntry)
1212 rev1 = max(rev1,(long)pLogEntry->Rev);
1213 rev2 = min(rev2,(long)pLogEntry->Rev);
1216 rev2--;
1217 // now we have both revisions selected in the log list, so we can do a diff of the selected
1218 // entry in the changed files list with these two revisions.
1219 DoDiffFromLog(selIndex, rev1, rev2, false, false);
1221 else
1223 rev2 = rev1-1;
1224 // nothing or only one revision selected in the log list
1225 LogChangedPath * changedpath = pLogEntry->pArChangedPaths->SafeGetAt(selIndex);
1227 if ((m_cHidePaths.GetState() & 0x0003)==BST_CHECKED)
1229 // some items are hidden! So find out which item the user really clicked on
1230 INT_PTR selRealIndex = -1;
1231 for (INT_PTR hiddenindex=0; hiddenindex<pLogEntry->pArChangedPaths->GetCount(); ++hiddenindex)
1233 if (pLogEntry->pArChangedPaths->SafeGetAt(hiddenindex)->sPath.Left(m_sRelativeRoot.GetLength()).Compare(m_sRelativeRoot)==0)
1234 selRealIndex++;
1235 if (selRealIndex == selIndex)
1237 selIndex = hiddenindex;
1238 changedpath = pLogEntry->pArChangedPaths->SafeGetAt(selIndex);
1239 break;
1244 if (IsDiffPossible(changedpath, rev1))
1246 // diffs with renamed files are possible
1247 if ((changedpath)&&(!changedpath->sCopyFromPath.IsEmpty()))
1248 rev2 = changedpath->lCopyFromRev;
1249 else
1251 // if the path was modified but the parent path was 'added with history'
1252 // then we have to use the copy from revision of the parent path
1253 CTGitPath cpath = CTGitPath(changedpath->sPath);
1254 for (int flist = 0; flist < pLogEntry->pArChangedPaths->GetCount(); ++flist)
1256 CTGitPath p = CTGitPath(pLogEntry->pArChangedPaths->SafeGetAt(flist)->sPath);
1257 if (p.IsAncestorOf(cpath))
1259 if (!pLogEntry->pArChangedPaths->SafeGetAt(flist)->sCopyFromPath.IsEmpty())
1260 rev2 = pLogEntry->pArChangedPaths->SafeGetAt(flist)->lCopyFromRev;
1264 DoDiffFromLog(selIndex, rev1, rev2, false, false);
1266 else
1268 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, CTGitPath(changedpath->sPath));
1269 CTGitPath tempfile2 = CTempFiles::Instance().GetTempFilePath(false, CTGitPath(changedpath->sPath));
1270 GitRev r = rev1;
1271 // deleted files must be opened from the revision before the deletion
1272 if (changedpath->action == LOGACTIONS_DELETED)
1273 r = rev1-1;
1274 m_bCancelled = false;
1276 CProgressDlg progDlg;
1277 progDlg.SetTitle(IDS_APPNAME);
1278 progDlg.SetAnimation(IDR_DOWNLOAD);
1279 CString sInfoLine;
1280 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, (LPCTSTR)(m_sRepositoryRoot + changedpath->sPath), (LPCTSTR)r.ToString());
1281 progDlg.SetLine(1, sInfoLine, true);
1282 SetAndClearProgressInfo(&progDlg);
1283 progDlg.ShowModeless(m_hWnd);
1285 if (!Cat(CTGitPath(m_sRepositoryRoot + changedpath->sPath), r, r, tempfile))
1287 m_bCancelled = false;
1288 if (!Cat(CTGitPath(m_sRepositoryRoot + changedpath->sPath), GitRev::REV_HEAD, r, tempfile))
1290 progDlg.Stop();
1291 SetAndClearProgressInfo((HWND)NULL);
1292 CMessageBox::Show(m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1293 return;
1296 progDlg.Stop();
1297 SetAndClearProgressInfo((HWND)NULL);
1299 CString sName1, sName2;
1300 sName1.Format(_T("%s - Revision %ld"), (LPCTSTR)CPathUtils::GetFileNameFromPath(changedpath->sPath), (git_revnum_t)rev1);
1301 sName2.Format(_T("%s - Revision %ld"), (LPCTSTR)CPathUtils::GetFileNameFromPath(changedpath->sPath), (git_revnum_t)rev1-1);
1302 CAppUtils::DiffFlags flags;
1303 flags.AlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
1304 if (changedpath->action == LOGACTIONS_DELETED)
1305 CAppUtils::StartExtDiff(tempfile, tempfile2, sName2, sName1, flags);
1306 else
1307 CAppUtils::StartExtDiff(tempfile2, tempfile, sName2, sName1, flags);
1310 #endif
1314 void CLogDlg::DoDiffFromLog(INT_PTR selIndex, GitRev* rev1, GitRev* rev2, bool /*blame*/, bool /*unified*/)
1316 DialogEnableWindow(IDOK, FALSE);
1317 // SetPromptApp(&theApp);
1318 theApp.DoWaitCursor(1);
1320 CString temppath;
1321 GetTempPath(temppath);
1323 CString file1;
1324 file1.Format(_T("%s%s_%s%s"),
1325 temppath,
1326 (*m_currentChangedArray)[selIndex].GetBaseFilename(),
1327 rev1->m_CommitHash.ToString().Left(6),
1328 (*m_currentChangedArray)[selIndex].GetFileExtension());
1330 CString file2;
1331 file2.Format(_T("%s\\%s_%s%s"),
1332 temppath,
1333 (*m_currentChangedArray)[selIndex].GetBaseFilename(),
1334 rev2->m_CommitHash.ToString().Left(6),
1335 (*m_currentChangedArray)[selIndex].GetFileExtension());
1337 CString cmd;
1339 g_Git.GetOneFile(rev1->m_CommitHash.ToString(), (CTGitPath &)(*m_currentChangedArray)[selIndex],file1);
1341 g_Git.GetOneFile(rev2->m_CommitHash.ToString(), (CTGitPath &)(*m_currentChangedArray)[selIndex],file2);
1343 CAppUtils::DiffFlags flags;
1344 CAppUtils::StartExtDiff(file1,file2,_T("A"),_T("B"),flags);
1346 #if 0
1347 //get the filename
1348 CString filepath;
1349 if (Git::PathIsURL(m_path))
1351 filepath = m_path.GetGitPathString();
1353 else
1355 filepath = GetURLFromPath(m_path);
1356 if (filepath.IsEmpty())
1358 theApp.DoWaitCursor(-1);
1359 CString temp;
1360 temp.Format(IDS_ERR_NOURLOFFILE, (LPCTSTR)filepath);
1361 CMessageBox::Show(this->m_hWnd, temp, _T("TortoiseGit"), MB_ICONERROR);
1362 TRACE(_T("could not retrieve the URL of the file!\n"));
1363 EnableOKButton();
1364 theApp.DoWaitCursor(-11);
1365 return; //exit
1368 m_bCancelled = FALSE;
1369 filepath = GetRepositoryRoot(CTGitPath(filepath));
1371 CString firstfile, secondfile;
1372 if (m_LogList.GetSelectedCount()==1)
1374 int s = m_LogList.GetSelectionMark();
1375 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(s));
1376 LogChangedPath * changedpath = pLogEntry->pArChangedPaths->SafeGetAt(selIndex);
1377 firstfile = changedpath->sPath;
1378 secondfile = firstfile;
1379 if ((rev2 == rev1-1)&&(changedpath->lCopyFromRev > 0)) // is it an added file with history?
1381 secondfile = changedpath->sCopyFromPath;
1382 rev2 = changedpath->lCopyFromRev;
1385 else
1387 firstfile = m_currentChangedPathList[selIndex].GetGitPathString();
1388 secondfile = firstfile;
1391 firstfile = filepath + firstfile.Trim();
1392 secondfile = filepath + secondfile.Trim();
1394 GitDiff diff(this, this->m_hWnd, true);
1395 diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
1396 diff.SetHEADPeg(m_LogRevision);
1397 if (unified)
1399 if (PromptShown())
1400 diff.ShowUnifiedDiff(CTGitPath(secondfile), rev2, CTGitPath(firstfile), rev1);
1401 else
1402 CAppUtils::StartShowUnifiedDiff(m_hWnd, CTGitPath(secondfile), rev2, CTGitPath(firstfile), rev1, GitRev(), m_LogRevision);
1404 else
1406 if (diff.ShowCompare(CTGitPath(secondfile), rev2, CTGitPath(firstfile), rev1, GitRev(), false, blame))
1408 if (firstfile.Compare(secondfile)==0)
1410 git_revnum_t baseRev = 0;
1411 diff.DiffProps(CTGitPath(firstfile), rev2, rev1, baseRev);
1416 #endif
1418 theApp.DoWaitCursor(-1);
1419 EnableOKButton();
1422 BOOL CLogDlg::Open(bool /*bOpenWith*/,CString changedpath, git_revnum_t rev)
1424 #if 0
1425 DialogEnableWindow(IDOK, FALSE);
1426 SetPromptApp(&theApp);
1427 theApp.DoWaitCursor(1);
1428 CString filepath;
1429 if (Git::PathIsURL(m_path))
1431 filepath = m_path.GetGitPathString();
1433 else
1435 filepath = GetURLFromPath(m_path);
1436 if (filepath.IsEmpty())
1438 theApp.DoWaitCursor(-1);
1439 CString temp;
1440 temp.Format(IDS_ERR_NOURLOFFILE, (LPCTSTR)filepath);
1441 CMessageBox::Show(this->m_hWnd, temp, _T("TortoiseGit"), MB_ICONERROR);
1442 TRACE(_T("could not retrieve the URL of the file!\n"));
1443 EnableOKButton();
1444 return FALSE;
1447 m_bCancelled = false;
1448 filepath = GetRepositoryRoot(CTGitPath(filepath));
1449 filepath += changedpath;
1451 CProgressDlg progDlg;
1452 progDlg.SetTitle(IDS_APPNAME);
1453 progDlg.SetAnimation(IDR_DOWNLOAD);
1454 CString sInfoLine;
1455 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, (LPCTSTR)filepath, (LPCTSTR)GitRev(rev).ToString());
1456 progDlg.SetLine(1, sInfoLine, true);
1457 SetAndClearProgressInfo(&progDlg);
1458 progDlg.ShowModeless(m_hWnd);
1460 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, CTGitPath(filepath), rev);
1461 m_bCancelled = false;
1462 if (!Cat(CTGitPath(filepath), GitRev(rev), rev, tempfile))
1464 progDlg.Stop();
1465 SetAndClearProgressInfo((HWND)NULL);
1466 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1467 EnableOKButton();
1468 theApp.DoWaitCursor(-1);
1469 return FALSE;
1471 progDlg.Stop();
1472 SetAndClearProgressInfo((HWND)NULL);
1473 SetFileAttributes(tempfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1474 if (!bOpenWith)
1476 int ret = (int)ShellExecute(this->m_hWnd, NULL, tempfile.GetWinPath(), NULL, NULL, SW_SHOWNORMAL);
1477 if (ret <= HINSTANCE_ERROR)
1478 bOpenWith = true;
1480 if (bOpenWith)
1482 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
1483 cmd += tempfile.GetWinPathString() + _T(" ");
1484 CAppUtils::LaunchApplication(cmd, NULL, false);
1486 EnableOKButton();
1487 theApp.DoWaitCursor(-1);
1488 #endif
1489 return TRUE;
1492 void CLogDlg::EditAuthor(const CLogDataVector& /*logs*/)
1494 #if 0
1495 CString url;
1496 CString name;
1497 if (logs.size() == 0)
1498 return;
1499 DialogEnableWindow(IDOK, FALSE);
1500 SetPromptApp(&theApp);
1501 theApp.DoWaitCursor(1);
1502 if (Git::PathIsURL(m_path))
1503 url = m_path.GetGitPathString();
1504 else
1506 url = GetURLFromPath(m_path);
1508 name = Git_PROP_REVISION_AUTHOR;
1510 CString value = RevPropertyGet(name, CTGitPath(url), logs[0]->Rev);
1511 CString sOldValue = value;
1512 value.Replace(_T("\n"), _T("\r\n"));
1513 CInputDlg dlg(this);
1514 dlg.m_sHintText.LoadString(IDS_LOG_AUTHOR);
1515 dlg.m_sInputText = value;
1516 dlg.m_sTitle.LoadString(IDS_LOG_AUTHOREDITTITLE);
1517 dlg.m_pProjectProperties = &m_ProjectProperties;
1518 dlg.m_bUseLogWidth = false;
1519 if (dlg.DoModal() == IDOK)
1521 dlg.m_sInputText.Remove('\r');
1523 LogCache::CCachedLogInfo* toUpdate = GetLogCache (CTGitPath (m_sRepositoryRoot));
1525 CProgressDlg progDlg;
1526 progDlg.SetTitle(IDS_APPNAME);
1527 progDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
1528 progDlg.SetTime(true);
1529 progDlg.SetShowProgressBar(true);
1530 progDlg.ShowModeless(m_hWnd);
1531 for (DWORD i=0; i<logs.size(); ++i)
1533 if (!RevPropertySet(name, dlg.m_sInputText, sOldValue, CTGitPath(url), logs[i]->Rev))
1535 progDlg.Stop();
1536 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1537 break;
1539 else
1542 logs[i]->sAuthor = dlg.m_sInputText;
1543 m_LogList.Invalidate();
1545 // update the log cache
1547 if (toUpdate != NULL)
1549 // log caching is active
1551 LogCache::CCachedLogInfo newInfo;
1552 newInfo.Insert ( logs[i]->Rev
1553 , (const char*) CUnicodeUtils::GetUTF8 (logs[i]->sAuthor)
1554 , ""
1556 , LogCache::CRevisionInfoContainer::HAS_AUTHOR);
1558 toUpdate->Update (newInfo);
1561 progDlg.SetProgress64(i, logs.size());
1563 progDlg.Stop();
1565 theApp.DoWaitCursor(-1);
1566 EnableOKButton();
1567 #endif
1570 void CLogDlg::EditLogMessage(int /*index*/)
1573 #if 0
1574 CString url;
1575 CString name;
1576 DialogEnableWindow(IDOK, FALSE);
1577 SetPromptApp(&theApp);
1578 theApp.DoWaitCursor(1);
1579 if (Git::PathIsURL(m_path))
1580 url = m_path.GetGitPathString();
1581 else
1583 url = GetURLFromPath(m_path);
1585 name = Git_PROP_REVISION_LOG;
1587 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(index));
1588 m_bCancelled = FALSE;
1589 CString value = RevPropertyGet(name, CTGitPath(url), pLogEntry->Rev);
1590 CString sOldValue = value;
1591 value.Replace(_T("\n"), _T("\r\n"));
1592 CInputDlg dlg(this);
1593 dlg.m_sHintText.LoadString(IDS_LOG_MESSAGE);
1594 dlg.m_sInputText = value;
1595 dlg.m_sTitle.LoadString(IDS_LOG_MESSAGEEDITTITLE);
1596 dlg.m_pProjectProperties = &m_ProjectProperties;
1597 dlg.m_bUseLogWidth = true;
1598 if (dlg.DoModal() == IDOK)
1600 dlg.m_sInputText.Remove('\r');
1601 if (!RevPropertySet(name, dlg.m_sInputText, sOldValue, CTGitPath(url), pLogEntry->Rev))
1603 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1605 else
1607 pLogEntry->sShortMessage = MakeShortMessage(dlg.m_sInputText);
1608 // split multi line log entries and concatenate them
1609 // again but this time with \r\n as line separators
1610 // so that the edit control recognizes them
1611 if (dlg.m_sInputText.GetLength()>0)
1613 m_sMessageBuf = dlg.m_sInputText;
1614 dlg.m_sInputText.Replace(_T("\n\r"), _T("\n"));
1615 dlg.m_sInputText.Replace(_T("\r\n"), _T("\n"));
1616 if (dlg.m_sInputText.Right(1).Compare(_T("\n"))==0)
1617 dlg.m_sInputText = dlg.m_sInputText.Left(dlg.m_sInputText.GetLength()-1);
1619 else
1620 dlg.m_sInputText.Empty();
1621 pLogEntry->sMessage = dlg.m_sInputText;
1622 pLogEntry->sBugIDs = m_ProjectProperties.FindBugID(dlg.m_sInputText);
1623 CWnd * pMsgView = GetDlgItem(IDC_MSGVIEW);
1624 pMsgView->SetWindowText(_T(" "));
1625 pMsgView->SetWindowText(dlg.m_sInputText);
1626 m_ProjectProperties.FindBugID(dlg.m_sInputText, pMsgView);
1627 m_LogList.Invalidate();
1629 // update the log cache
1630 LogCache::CCachedLogInfo* toUpdate = GetLogCache(CTGitPath (m_sRepositoryRoot));
1631 if (toUpdate != NULL)
1633 // log caching is active
1635 LogCache::CCachedLogInfo newInfo;
1636 newInfo.Insert( pLogEntry->Rev
1637 , ""
1638 , (const char*) CUnicodeUtils::GetUTF8 (pLogEntry->sMessage)
1640 , LogCache::CRevisionInfoContainer::HAS_COMMENT);
1642 toUpdate->Update(newInfo);
1646 theApp.DoWaitCursor(-1);
1647 EnableOKButton();
1648 #endif
1651 BOOL CLogDlg::PreTranslateMessage(MSG* pMsg)
1653 // Skip Ctrl-C when copying text out of the log message or search filter
1654 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);
1655 if (pMsg->message == WM_KEYDOWN && pMsg->wParam=='\r')
1657 if (GetFocus()==GetDlgItem(IDC_LOGLIST))
1659 if (CRegDWORD(_T("Software\\TortoiseGit\\DiffByDoubleClickInLog"), FALSE))
1661 m_LogList.DiffSelectedRevWithPrevious();
1662 return TRUE;
1666 if (m_hAccel && !bSkipAccelerator)
1668 int ret = TranslateAccelerator(m_hWnd, m_hAccel, pMsg);
1669 if (ret)
1670 return TRUE;
1673 if(::IsWindow(m_tooltips.m_hWnd))
1674 m_tooltips.RelayEvent(pMsg);
1675 return __super::PreTranslateMessage(pMsg);
1679 BOOL CLogDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
1681 //if (this->IsThreadRunning())
1682 if(m_LogList.m_bNoDispUpdates)
1684 // only show the wait cursor over the list control
1685 if ((pWnd)&&
1686 ((pWnd == GetDlgItem(IDC_LOGLIST))||
1687 (pWnd == GetDlgItem(IDC_MSGVIEW))||
1688 (pWnd == GetDlgItem(IDC_LOGMSG))))
1690 HCURSOR hCur = LoadCursor(NULL, MAKEINTRESOURCE(IDC_WAIT));
1691 SetCursor(hCur);
1692 return TRUE;
1695 if ((pWnd) && (pWnd == GetDlgItem(IDC_MSGVIEW)))
1696 return CResizableStandAloneDialog::OnSetCursor(pWnd, nHitTest, message);
1698 HCURSOR hCur = LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW));
1699 SetCursor(hCur);
1700 return CResizableStandAloneDialog::OnSetCursor(pWnd, nHitTest, message);
1703 void CLogDlg::OnBnClickedHelp()
1705 OnHelp();
1708 void CLogDlg::OnLvnItemchangedLoglist(NMHDR *pNMHDR, LRESULT *pResult)
1710 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1711 *pResult = 0;
1712 //if (this->IsThreadRunning())
1713 if(m_LogList.m_bNoDispUpdates)
1714 return;
1715 if (pNMLV->iItem >= 0)
1717 this->m_LogList.m_nSearchIndex = pNMLV->iItem;
1718 GitRev* pLogEntry = reinterpret_cast<GitRev *>(m_LogList.m_arShownList.SafeGetAt(pNMLV->iItem));
1719 m_LogList.m_lastSelectedHash = pLogEntry->m_CommitHash;
1720 if (pNMLV->iSubItem != 0)
1721 return;
1722 if ((pNMLV->iItem == m_LogList.m_arShownList.GetCount()))
1724 // remove the selected state
1725 if (pNMLV->uChanged & LVIF_STATE)
1727 m_LogList.SetItemState(pNMLV->iItem, 0, LVIS_SELECTED);
1728 FillLogMessageCtrl();
1729 UpdateData(FALSE);
1730 UpdateLogInfoLabel();
1732 return;
1734 if (pNMLV->uChanged & LVIF_STATE)
1736 FillLogMessageCtrl();
1737 UpdateData(FALSE);
1740 else
1742 m_LogList.m_lastSelectedHash.Empty();
1743 FillLogMessageCtrl();
1744 UpdateData(FALSE);
1746 EnableOKButton();
1747 UpdateLogInfoLabel();
1750 void CLogDlg::OnEnLinkMsgview(NMHDR *pNMHDR, LRESULT *pResult)
1752 ENLINK *pEnLink = reinterpret_cast<ENLINK *>(pNMHDR);
1753 if (pEnLink->msg == WM_LBUTTONUP)
1755 CString url, msg;
1756 GetDlgItemText(IDC_MSGVIEW, msg);
1757 msg.Replace(_T("\r\n"), _T("\n"));
1758 url = msg.Mid(pEnLink->chrg.cpMin, pEnLink->chrg.cpMax-pEnLink->chrg.cpMin);
1759 if (!::PathIsURL(url))
1761 url = m_LogList.m_ProjectProperties.GetBugIDUrl(url);
1762 url = GetAbsoluteUrlFromRelativeUrl(url);
1764 if (!url.IsEmpty())
1765 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
1767 *pResult = 0;
1770 class CDateSorter
1772 public:
1773 class CCommitPointer
1775 public:
1776 CCommitPointer():m_cont(NULL){}
1777 CCommitPointer(const CCommitPointer& P_Right)
1778 : m_cont(NULL)
1780 *this = P_Right;
1783 CCommitPointer& operator = (const CCommitPointer& P_Right)
1785 if(IsPointer())
1787 (*m_cont->m_parDates)[m_place] = P_Right.GetDate();
1788 (*m_cont->m_parFileChanges)[m_place] = P_Right.GetChanges();
1789 (*m_cont->m_parAuthors)[m_place] = P_Right.GetAuthor();
1791 else
1793 m_Date = P_Right.GetDate();
1794 m_Changes = P_Right.GetChanges();
1795 m_csAuthor = P_Right.GetAuthor();
1797 return *this;
1800 void Clone(const CCommitPointer& P_Right)
1802 m_cont = P_Right.m_cont;
1803 m_place = P_Right.m_place;
1804 m_Date = P_Right.m_Date;
1805 m_Changes = P_Right.m_Changes;
1806 m_csAuthor = P_Right.m_csAuthor;
1809 DWORD GetDate() const {return IsPointer() ? (*m_cont->m_parDates)[m_place] : m_Date;}
1810 DWORD GetChanges() const {return IsPointer() ? (*m_cont->m_parFileChanges)[m_place] : m_Changes;}
1811 CString GetAuthor() const {return IsPointer() ? (*m_cont->m_parAuthors)[m_place] : m_csAuthor;}
1813 bool IsPointer() const {return m_cont != NULL;}
1814 //When pointer
1815 CDateSorter* m_cont;
1816 int m_place;
1818 //When element
1819 DWORD m_Date;
1820 DWORD m_Changes;
1821 CString m_csAuthor;
1824 class iterator : public std::iterator<std::random_access_iterator_tag, CCommitPointer>
1826 public:
1827 CCommitPointer m_ptr;
1829 iterator(){}
1830 iterator(const iterator& P_Right){*this = P_Right;}
1831 iterator& operator=(const iterator& P_Right)
1833 m_ptr.Clone(P_Right.m_ptr);
1834 return *this;
1837 CCommitPointer& operator*(){return m_ptr;}
1838 CCommitPointer* operator->(){return &m_ptr;}
1839 const CCommitPointer& operator*()const{return m_ptr;}
1840 const CCommitPointer* operator->()const{return &m_ptr;}
1842 iterator& operator+=(size_t P_iOffset){m_ptr.m_place += P_iOffset;return *this;}
1843 iterator& operator-=(size_t P_iOffset){m_ptr.m_place -= P_iOffset;return *this;}
1844 iterator operator+(size_t P_iOffset)const{iterator it(*this); it += P_iOffset;return it;}
1845 iterator operator-(size_t P_iOffset)const{iterator it(*this); it -= P_iOffset;return it;}
1847 iterator& operator++(){++m_ptr.m_place;return *this;}
1848 iterator& operator--(){--m_ptr.m_place;return *this;}
1849 iterator operator++(int){iterator it(*this);++*this;return it;}
1850 iterator operator--(int){iterator it(*this);--*this;return it;}
1852 size_t operator-(const iterator& P_itRight)const{return m_ptr.m_place - P_itRight->m_place;}
1854 bool operator<(const iterator& P_itRight)const{return m_ptr.m_place < P_itRight->m_place;}
1855 bool operator!=(const iterator& P_itRight)const{return m_ptr.m_place != P_itRight->m_place;}
1856 bool operator==(const iterator& P_itRight)const{return m_ptr.m_place == P_itRight->m_place;}
1857 bool operator>(const iterator& P_itRight)const{return m_ptr.m_place > P_itRight->m_place;}
1859 iterator begin()
1861 iterator it;
1862 it->m_place = 0;
1863 it->m_cont = this;
1864 return it;
1866 iterator end()
1868 iterator it;
1869 it->m_place = m_parDates->GetCount();
1870 it->m_cont = this;
1871 return it;
1874 CDWordArray * m_parDates;
1875 CDWordArray * m_parFileChanges;
1876 CStringArray * m_parAuthors;
1879 class CDateSorterLess
1881 public:
1882 bool operator () (const CDateSorter::CCommitPointer& P_Left, const CDateSorter::CCommitPointer& P_Right) const
1884 return P_Left.GetDate() > P_Right.GetDate(); //Last date first
1891 void CLogDlg::OnBnClickedStatbutton()
1893 if (this->IsThreadRunning())
1894 return;
1895 if (m_LogList.m_arShownList.IsEmpty() || m_LogList.m_arShownList.GetCount() == 1 && m_LogList.m_bShowWC)
1896 return; // nothing or just the working copy changes are shown, so no statistics.
1897 // the statistics dialog expects the log entries to be sorted by date
1898 SortByColumn(3, false);
1899 CThreadSafePtrArray shownlist(NULL);
1900 m_LogList.RecalculateShownList(&shownlist);
1901 // create arrays which are aware of the current filter
1902 CStringArray m_arAuthorsFiltered;
1903 CDWordArray m_arDatesFiltered;
1904 CDWordArray m_arFileChangesFiltered;
1905 for (INT_PTR i=0; i<shownlist.GetCount(); ++i)
1907 GitRev* pLogEntry = reinterpret_cast<GitRev*>(shownlist.SafeGetAt(i));
1909 // do not take working dir changes into statistics
1910 if (pLogEntry->m_CommitHash.IsEmpty()) {
1911 continue;
1914 CString strAuthor = pLogEntry->GetAuthorName();
1915 if ( strAuthor.IsEmpty() )
1917 strAuthor.LoadString(IDS_STATGRAPH_EMPTYAUTHOR);
1919 m_arAuthorsFiltered.Add(strAuthor);
1920 m_arDatesFiltered.Add(pLogEntry->GetCommitterDate().GetTime());
1921 m_arFileChangesFiltered.Add(pLogEntry->GetFiles(&m_LogList).GetCount());
1924 CDateSorter W_Sorter;
1925 W_Sorter.m_parAuthors = &m_arAuthorsFiltered;
1926 W_Sorter.m_parDates = &m_arDatesFiltered;
1927 W_Sorter.m_parFileChanges = &m_arFileChangesFiltered;
1928 std::sort(W_Sorter.begin(), W_Sorter.end(), CDateSorterLess());
1930 CStatGraphDlg dlg;
1931 dlg.m_parAuthors = &m_arAuthorsFiltered;
1932 dlg.m_parDates = &m_arDatesFiltered;
1933 dlg.m_parFileChanges = &m_arFileChangesFiltered;
1934 dlg.m_path = m_orgPath;
1935 dlg.DoModal();
1936 // restore the previous sorting
1937 SortByColumn(m_nSortColumn, m_bAscending);
1938 OnTimer(LOGFILTER_TIMER);
1941 void CLogDlg::DoSizeV1(int delta)
1944 RemoveAnchor(IDC_LOGLIST);
1945 RemoveAnchor(IDC_SPLITTERTOP);
1946 RemoveAnchor(IDC_MSGVIEW);
1947 RemoveAnchor(IDC_SPLITTERBOTTOM);
1948 RemoveAnchor(IDC_LOGMSG);
1949 CSplitterControl::ChangeHeight(&m_LogList, delta, CW_TOPALIGN);
1950 CSplitterControl::ChangeHeight(GetDlgItem(IDC_MSGVIEW), -delta, CW_BOTTOMALIGN);
1951 AddAnchor(IDC_LOGLIST, TOP_LEFT, TOP_RIGHT);
1952 AddAnchor(IDC_SPLITTERTOP, TOP_LEFT, TOP_RIGHT);
1953 AddAnchor(IDC_MSGVIEW, TOP_LEFT, BOTTOM_RIGHT);
1954 AddAnchor(IDC_SPLITTERBOTTOM, BOTTOM_LEFT, BOTTOM_RIGHT);
1955 AddAnchor(IDC_LOGMSG, BOTTOM_LEFT, BOTTOM_RIGHT);
1956 ArrangeLayout();
1957 AdjustMinSize();
1958 SetSplitterRange();
1959 m_LogList.Invalidate();
1960 GetDlgItem(IDC_MSGVIEW)->Invalidate();
1964 void CLogDlg::DoSizeV2(int delta)
1967 RemoveAnchor(IDC_LOGLIST);
1968 RemoveAnchor(IDC_SPLITTERTOP);
1969 RemoveAnchor(IDC_MSGVIEW);
1970 RemoveAnchor(IDC_SPLITTERBOTTOM);
1971 RemoveAnchor(IDC_LOGMSG);
1972 CSplitterControl::ChangeHeight(GetDlgItem(IDC_MSGVIEW), delta, CW_TOPALIGN);
1973 CSplitterControl::ChangeHeight(&m_ChangedFileListCtrl, -delta, CW_BOTTOMALIGN);
1974 AddAnchor(IDC_LOGLIST, TOP_LEFT, TOP_RIGHT);
1975 AddAnchor(IDC_SPLITTERTOP, TOP_LEFT, TOP_RIGHT);
1976 AddAnchor(IDC_MSGVIEW, TOP_LEFT, BOTTOM_RIGHT);
1977 AddAnchor(IDC_SPLITTERBOTTOM, BOTTOM_LEFT, BOTTOM_RIGHT);
1978 AddAnchor(IDC_LOGMSG, BOTTOM_LEFT, BOTTOM_RIGHT);
1979 ArrangeLayout();
1980 AdjustMinSize();
1981 SetSplitterRange();
1982 GetDlgItem(IDC_MSGVIEW)->Invalidate();
1983 m_ChangedFileListCtrl.Invalidate();
1987 void CLogDlg::AdjustMinSize()
1989 // adjust the minimum size of the dialog to prevent the resizing from
1990 // moving the list control too far down.
1991 CRect rcChgListView;
1992 m_ChangedFileListCtrl.GetClientRect(rcChgListView);
1993 CRect rcLogList;
1994 m_LogList.GetClientRect(rcLogList);
1996 SetMinTrackSize(CSize(m_DlgOrigRect.Width(),
1997 m_DlgOrigRect.Height()-m_ChgOrigRect.Height()-m_LogListOrigRect.Height()-m_MsgViewOrigRect.Height()
1998 +rcChgListView.Height()+rcLogList.Height()+60));
2001 LRESULT CLogDlg::DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam)
2003 switch (message) {
2004 case WM_NOTIFY:
2005 if (wParam == IDC_SPLITTERTOP)
2007 SPC_NMHDR* pHdr = (SPC_NMHDR*) lParam;
2008 DoSizeV1(pHdr->delta);
2010 else if (wParam == IDC_SPLITTERBOTTOM)
2012 SPC_NMHDR* pHdr = (SPC_NMHDR*) lParam;
2013 DoSizeV2(pHdr->delta);
2015 break;
2018 return CResizableDialog::DefWindowProc(message, wParam, lParam);
2021 void CLogDlg::SetSplitterRange()
2023 if ((m_LogList)&&(m_ChangedFileListCtrl))
2025 CRect rcTop;
2026 m_LogList.GetWindowRect(rcTop);
2027 ScreenToClient(rcTop);
2028 CRect rcMiddle;
2029 GetDlgItem(IDC_MSGVIEW)->GetWindowRect(rcMiddle);
2030 ScreenToClient(rcMiddle);
2031 m_wndSplitter1.SetRange(rcTop.top+30, rcMiddle.bottom-20);
2032 CRect rcBottom;
2033 m_ChangedFileListCtrl.GetWindowRect(rcBottom);
2034 ScreenToClient(rcBottom);
2035 m_wndSplitter2.SetRange(rcMiddle.top+30, rcBottom.bottom-20);
2039 LRESULT CLogDlg::OnClickedInfoIcon(WPARAM /*wParam*/, LPARAM lParam)
2041 // FIXME: x64 version would get this function called with unexpected parameters.
2042 if (!lParam)
2043 return 0;
2045 RECT * rect = (LPRECT)lParam;
2046 CPoint point;
2047 CString temp;
2048 point = CPoint(rect->left, rect->bottom);
2049 #define LOGMENUFLAGS(x) (MF_STRING | MF_ENABLED | (m_LogList.m_SelectedFilters & x ? MF_CHECKED : MF_UNCHECKED))
2050 CMenu popup;
2051 if (popup.CreatePopupMenu())
2053 temp.LoadString(IDS_LOG_FILTER_SUBJECT);
2054 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_SUBJECT), LOGFILTER_SUBJECT, temp);
2056 temp.LoadString(IDS_LOG_FILTER_MESSAGES);
2057 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_MESSAGES), LOGFILTER_MESSAGES, temp);
2059 temp.LoadString(IDS_LOG_FILTER_PATHS);
2060 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_PATHS), LOGFILTER_PATHS, temp);
2062 temp.LoadString(IDS_LOG_FILTER_AUTHORS);
2063 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_AUTHORS), LOGFILTER_AUTHORS, temp);
2065 temp.LoadString(IDS_LOG_FILTER_REVS);
2066 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REVS), LOGFILTER_REVS, temp);
2068 if (m_LogList.m_bShowBugtraqColumn == TRUE) {
2069 temp.LoadString(IDS_LOG_FILTER_BUGIDS);
2070 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_BUGID), LOGFILTER_BUGID, temp);
2073 popup.AppendMenu(MF_SEPARATOR, NULL);
2075 temp.LoadString(IDS_LOG_FILTER_REGEX);
2076 popup.AppendMenu(MF_STRING | MF_ENABLED | (m_bFilterWithRegex ? MF_CHECKED : MF_UNCHECKED), LOGFILTER_REGEX, temp);
2078 m_tooltips.Pop();
2079 int selection = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
2080 if (selection != 0)
2083 if (selection == LOGFILTER_REGEX)
2085 m_bFilterWithRegex = !m_bFilterWithRegex;
2086 CRegDWORD b = CRegDWORD(_T("Software\\TortoiseGit\\UseRegexFilter"), TRUE);
2087 b = m_bFilterWithRegex;
2088 m_LogList.m_bFilterWithRegex = m_bFilterWithRegex;
2089 SetFilterCueText();
2090 CheckRegexpTooltip();
2092 else
2094 m_LogList.m_SelectedFilters ^= selection;
2095 SetFilterCueText();
2097 SetTimer(LOGFILTER_TIMER, 1000, NULL);
2100 return 0L;
2103 LRESULT CLogDlg::OnClickedCancelFilter(WPARAM /*wParam*/, LPARAM /*lParam*/)
2106 KillTimer(LOGFILTER_TIMER);
2108 m_LogList.m_sFilterText.Empty();
2109 UpdateData(FALSE);
2110 theApp.DoWaitCursor(1);
2111 CStoreSelection storeselection(this);
2112 FillLogMessageCtrl(false);
2114 m_LogList.RemoveFilter();
2116 Refresh();
2118 CTime begin,end;
2119 m_LogList.GetTimeRange(begin,end);
2120 m_DateFrom.SetTime(&begin);
2121 m_DateTo.SetTime(&end);
2123 theApp.DoWaitCursor(-1);
2124 GetDlgItem(IDC_SEARCHEDIT)->ShowWindow(SW_HIDE);
2125 GetDlgItem(IDC_SEARCHEDIT)->ShowWindow(SW_SHOW);
2126 GetDlgItem(IDC_SEARCHEDIT)->SetFocus();
2127 UpdateLogInfoLabel();
2129 return 0L;
2133 void CLogDlg::SetFilterCueText()
2135 CString temp(MAKEINTRESOURCE(IDS_LOG_FILTER_BY));
2136 temp += _T(" ");
2138 if (m_LogList.m_SelectedFilters & LOGFILTER_SUBJECT)
2140 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_SUBJECT));
2143 if (m_LogList.m_SelectedFilters & LOGFILTER_MESSAGES)
2145 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
2146 temp += _T(", ");
2147 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_MESSAGES));
2150 if (m_LogList.m_SelectedFilters & LOGFILTER_PATHS)
2152 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
2153 temp += _T(", ");
2154 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_PATHS));
2157 if (m_LogList.m_SelectedFilters & LOGFILTER_AUTHORS)
2159 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
2160 temp += _T(", ");
2161 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_AUTHORS));
2164 if (m_LogList.m_SelectedFilters & LOGFILTER_REVS)
2166 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
2167 temp += _T(", ");
2168 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REVS));
2171 if (m_LogList.m_SelectedFilters & LOGFILTER_BUGID)
2173 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
2174 temp += _T(", ");
2175 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_BUGIDS));
2178 // to make the cue banner text appear more to the right of the edit control
2179 temp = _T(" ")+temp;
2180 m_cFilter.SetCueBanner(temp.TrimRight());
2183 bool CLogDlg::Validate(LPCTSTR string)
2185 if (!m_bFilterWithRegex)
2186 return true;
2187 tr1::wregex pat;
2188 return m_LogList.ValidateRegexp(string, pat, false);
2192 void CLogDlg::OnTimer(UINT_PTR nIDEvent)
2194 if (nIDEvent == LOGFTIME_TIMER)
2196 KillTimer(LOGFTIME_TIMER);
2197 m_limit = 0;
2198 m_LogList.Refresh(FALSE);
2199 FillLogMessageCtrl(false);
2202 if (nIDEvent == LOGFILTER_TIMER)
2204 KillTimer(LOGFILTER_TIMER);
2205 m_limit = 0;
2206 m_LogList.Refresh(FALSE);
2207 FillLogMessageCtrl(false);
2209 #if 0
2210 /* we will use git built-in grep to filter log */
2211 if (this->IsThreadRunning())
2213 // thread still running! So just restart the timer.
2214 SetTimer(LOGFILTER_TIMER, 1000, NULL);
2215 return;
2217 CWnd * focusWnd = GetFocus();
2218 bool bSetFocusToFilterControl = ((focusWnd != GetDlgItem(IDC_DATEFROM))&&(focusWnd != GetDlgItem(IDC_DATETO))
2219 && (focusWnd != GetDlgItem(IDC_LOGLIST)));
2220 if (m_LogList.m_sFilterText.IsEmpty())
2222 DialogEnableWindow(IDC_STATBUTTON, !(((this->IsThreadRunning())||(m_LogList.m_arShownList.IsEmpty()))));
2223 // do not return here!
2224 // we also need to run the filter if the filter text is empty:
2225 // 1. to clear an existing filter
2226 // 2. to rebuild the m_arShownList after sorting
2228 theApp.DoWaitCursor(1);
2229 CStoreSelection storeselection(this);
2230 KillTimer(LOGFILTER_TIMER);
2231 FillLogMessageCtrl(false);
2233 // now start filter the log list
2234 m_LogList.StartFilter();
2236 if ( m_LogList.GetItemCount()==1 )
2238 m_LogList.SetSelectionMark(0);
2239 m_LogList.SetItemState(0, LVIS_SELECTED, LVIS_SELECTED);
2241 theApp.DoWaitCursor(-1);
2242 GetDlgItem(IDC_SEARCHEDIT)->ShowWindow(SW_HIDE);
2243 GetDlgItem(IDC_SEARCHEDIT)->ShowWindow(SW_SHOW);
2244 if (bSetFocusToFilterControl)
2245 GetDlgItem(IDC_SEARCHEDIT)->SetFocus();
2246 UpdateLogInfoLabel();
2247 #endif
2248 } // if (nIDEvent == LOGFILTER_TIMER)
2249 DialogEnableWindow(IDC_STATBUTTON, !(((this->IsThreadRunning())||(m_LogList.m_arShownList.IsEmpty() || m_LogList.m_arShownList.GetCount() == 1 && m_LogList.m_bShowWC))));
2250 __super::OnTimer(nIDEvent);
2253 void CLogDlg::OnDtnDatetimechangeDateto(NMHDR * /*pNMHDR*/, LRESULT *pResult)
2257 CTime _time;
2258 m_DateTo.GetTime(_time);
2260 CTime time(_time.GetYear(), _time.GetMonth(), _time.GetDay(), 23, 59, 59);
2261 if (time.GetTime() != m_LogList.m_To)
2263 m_LogList.m_To = (DWORD)time.GetTime();
2264 SetTimer(LOGFTIME_TIMER, 10, NULL);
2267 catch (...)
2269 CMessageBox::Show(NULL,_T("Invalidate Parameter"),_T("TortoiseGit"),MB_OK|MB_ICONERROR);
2272 *pResult = 0;
2275 void CLogDlg::OnDtnDatetimechangeDatefrom(NMHDR * /*pNMHDR*/, LRESULT *pResult)
2280 CTime _time;
2281 m_DateFrom.GetTime(_time);
2283 CTime time(_time.GetYear(), _time.GetMonth(), _time.GetDay(), 0, 0, 0);
2284 if (time.GetTime() != m_LogList.m_From)
2286 m_LogList.m_From = (DWORD)time.GetTime();
2287 SetTimer(LOGFTIME_TIMER, 10, NULL);
2290 catch (...)
2292 CMessageBox::Show(NULL,_T("Invalidate Parameter"),_T("TortoiseGit"),MB_OK|MB_ICONERROR);
2295 *pResult = 0;
2300 CTGitPathList CLogDlg::GetChangedPathsFromSelectedRevisions(bool /*bRelativePaths*/ /* = false */, bool /*bUseFilter*/ /* = true */)
2302 CTGitPathList pathList;
2303 #if 0
2305 if (m_sRepositoryRoot.IsEmpty() && (bRelativePaths == false))
2307 m_sRepositoryRoot = GetRepositoryRoot(m_path);
2309 if (m_sRepositoryRoot.IsEmpty() && (bRelativePaths == false))
2310 return pathList;
2312 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
2313 if (pos != NULL)
2315 while (pos)
2317 int nextpos = m_LogList.GetNextSelectedItem(pos);
2318 if (nextpos >= m_arShownList.GetCount())
2319 continue;
2320 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(nextpos));
2321 LogChangedPathArray * cpatharray = pLogEntry->pArChangedPaths;
2322 for (INT_PTR cpPathIndex = 0; cpPathIndex<cpatharray->GetCount(); ++cpPathIndex)
2324 LogChangedPath * cpath = cpatharray->SafeGetAt(cpPathIndex);
2325 if (cpath == NULL)
2326 continue;
2327 CTGitPath path;
2328 if (!bRelativePaths)
2329 path.SetFromGit(m_sRepositoryRoot);
2330 path.AppendPathString(cpath->sPath);
2331 if ((!bUseFilter)||
2332 ((m_cHidePaths.GetState() & 0x0003)!=BST_CHECKED)||
2333 (cpath->sPath.Left(m_sRelativeRoot.GetLength()).Compare(m_sRelativeRoot)==0))
2334 pathList.AddPath(path);
2339 pathList.RemoveDuplicates();
2340 #endif
2341 return pathList;
2344 void CLogDlg::SortByColumn(int /*nSortColumn*/, bool /*bAscending*/)
2346 #if 0
2347 switch(nSortColumn)
2349 case 0: // Revision
2351 if(bAscending)
2352 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::AscRevSort());
2353 else
2354 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::DescRevSort());
2356 break;
2357 case 1: // action
2359 if(bAscending)
2360 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::AscActionSort());
2361 else
2362 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::DescActionSort());
2364 break;
2365 case 2: // Author
2367 if(bAscending)
2368 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::AscAuthorSort());
2369 else
2370 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::DescAuthorSort());
2372 break;
2373 case 3: // Date
2375 if(bAscending)
2376 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::AscDateSort());
2377 else
2378 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::DescDateSort());
2380 break;
2381 case 4: // Message or bug id
2382 if (m_bShowBugtraqColumn)
2384 if(bAscending)
2385 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::AscBugIDSort());
2386 else
2387 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::DescBugIDSort());
2388 break;
2390 // fall through here
2391 case 5: // Message
2393 if(bAscending)
2394 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::AscMessageSort());
2395 else
2396 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::DescMessageSort());
2398 break;
2399 default:
2400 ATLASSERT(0);
2401 break;
2403 #endif
2406 void CLogDlg::OnLvnColumnclick(NMHDR *pNMHDR, LRESULT *pResult)
2408 if (this->IsThreadRunning())
2409 return; //no sorting while the arrays are filled
2410 #if 0
2411 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
2412 const int nColumn = pNMLV->iSubItem;
2413 m_bAscending = nColumn == m_nSortColumn ? !m_bAscending : TRUE;
2414 m_nSortColumn = nColumn;
2415 SortByColumn(m_nSortColumn, m_bAscending);
2416 SetSortArrow(&m_LogList, m_nSortColumn, !!m_bAscending);
2417 SortShownListArray();
2418 m_LogList.Invalidate();
2419 UpdateLogInfoLabel();
2420 #else
2421 UNREFERENCED_PARAMETER(pNMHDR);
2422 #endif
2423 *pResult = 0;
2426 void CLogDlg::SortShownListArray()
2428 // make sure the shown list still matches the filter after sorting.
2429 OnTimer(LOGFILTER_TIMER);
2430 // clear the selection states
2431 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
2432 while (pos)
2434 m_LogList.SetItemState(m_LogList.GetNextSelectedItem(pos), 0, LVIS_SELECTED);
2436 m_LogList.SetSelectionMark(-1);
2439 void CLogDlg::SetSortArrow(CListCtrl * control, int nColumn, bool bAscending)
2441 if (control == NULL)
2442 return;
2443 // set the sort arrow
2444 CHeaderCtrl * pHeader = control->GetHeaderCtrl();
2445 HDITEM HeaderItem = {0};
2446 HeaderItem.mask = HDI_FORMAT;
2447 for (int i=0; i<pHeader->GetItemCount(); ++i)
2449 pHeader->GetItem(i, &HeaderItem);
2450 HeaderItem.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
2451 pHeader->SetItem(i, &HeaderItem);
2453 if (nColumn >= 0)
2455 pHeader->GetItem(nColumn, &HeaderItem);
2456 HeaderItem.fmt |= (bAscending ? HDF_SORTUP : HDF_SORTDOWN);
2457 pHeader->SetItem(nColumn, &HeaderItem);
2460 void CLogDlg::OnLvnColumnclickChangedFileList(NMHDR* /*pNMHDR*/, LRESULT* /*pResult*/)
2462 #if 0
2463 if (this->IsThreadRunning())
2464 return; //no sorting while the arrays are filled
2465 if (m_currentChangedArray == NULL)
2466 return;
2467 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
2468 const int nColumn = pNMLV->iSubItem;
2469 m_bAscendingPathList = nColumn == m_nSortColumnPathList ? !m_bAscendingPathList : TRUE;
2470 m_nSortColumnPathList = nColumn;
2471 // qsort(m_currentChangedArray->GetData(), m_currentChangedArray->GetSize(), sizeof(LogChangedPath*), (GENERICCOMPAREFN)SortCompare);
2473 SetSortArrow(&m_ChangedFileListCtrl, m_nSortColumnPathList, m_bAscendingPathList);
2474 m_ChangedFileListCtrl.Invalidate();
2475 *pResult = 0;
2476 #endif
2479 int CLogDlg::m_nSortColumnPathList = 0;
2480 bool CLogDlg::m_bAscendingPathList = false;
2482 int CLogDlg::SortCompare(const void * /*pElem1*/, const void * /*pElem2*/)
2484 #if 0
2485 LogChangedPath * cpath1 = *((LogChangedPath**)pElem1);
2486 LogChangedPath * cpath2 = *((LogChangedPath**)pElem2);
2488 if (m_bAscendingPathList)
2489 std::swap (cpath1, cpath2);
2491 int cmp = 0;
2492 switch (m_nSortColumnPathList)
2494 case 0: // action
2495 cmp = cpath2->GetAction().Compare(cpath1->GetAction());
2496 if (cmp)
2497 return cmp;
2498 // fall through
2499 case 1: // path
2500 cmp = cpath2->sPath.CompareNoCase(cpath1->sPath);
2501 if (cmp)
2502 return cmp;
2503 // fall through
2504 case 2: // copy from path
2505 cmp = cpath2->sCopyFromPath.Compare(cpath1->sCopyFromPath);
2506 if (cmp)
2507 return cmp;
2508 // fall through
2509 case 3: // copy from revision
2510 return cpath2->lCopyFromRev > cpath1->lCopyFromRev;
2512 #endif
2513 return 0;
2516 void CLogDlg::OnBnClickedHidepaths()
2518 FillLogMessageCtrl();
2519 m_ChangedFileListCtrl.Invalidate();
2524 void CLogDlg::OnBnClickedCheckStoponcopy()
2526 #if 0
2527 if (!GetDlgItem(IDC_GETALL)->IsWindowEnabled())
2528 return;
2530 // ignore old fetch limits when switching
2531 // between copy-following and stop-on-copy
2532 // (otherwise stop-on-copy will limit what
2533 // we see immediately after switching to
2534 // copy-following)
2536 m_endrev = 0;
2538 // now, restart the query
2539 #endif
2540 Refresh();
2544 void CLogDlg::UpdateLogInfoLabel()
2547 CGitHash rev1 ;
2548 CGitHash rev2 ;
2549 long selectedrevs = 0;
2550 int count =m_LogList.m_arShownList.GetCount();
2551 int start = 0;
2552 if (count)
2554 rev1 = (reinterpret_cast<GitRev*>(m_LogList.m_arShownList.SafeGetAt(0)))->m_CommitHash;
2555 if(this->m_LogList.m_bShowWC && rev1.IsEmpty()&&(count>1))
2556 start = 1;
2557 rev1 = (reinterpret_cast<GitRev*>(m_LogList.m_arShownList.SafeGetAt(start)))->m_CommitHash;
2558 //pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(m_arShownList.GetCount()-1));
2559 rev2 = (reinterpret_cast<GitRev*>(m_LogList.m_arShownList.SafeGetAt(count-1)))->m_CommitHash;
2560 selectedrevs = m_LogList.GetSelectedCount();
2562 CString sTemp;
2563 sTemp.Format(IDS_PROC_LOG_STATS,
2564 count - start,
2565 rev2.ToString().Left(6), rev1.ToString().Left(6), selectedrevs);
2567 if(selectedrevs == 1)
2569 CString str=m_ChangedFileListCtrl.GetStatisticsString(true);
2570 str.Replace(_T('\n'), _T(' '));
2571 sTemp += _T(' ') + str;
2573 m_sLogInfo = sTemp;
2575 UpdateData(FALSE);
2578 #if 0
2579 void CLogDlg::ShowContextMenuForChangedpaths(CWnd* /*pWnd*/, CPoint point)
2582 int selIndex = m_ChangedFileListCtrl.GetSelectionMark();
2583 if ((point.x == -1) && (point.y == -1))
2585 CRect rect;
2586 m_ChangedFileListCtrl.GetItemRect(selIndex, &rect, LVIR_LABEL);
2587 m_ChangedFileListCtrl.ClientToScreen(&rect);
2588 point = rect.CenterPoint();
2590 if (selIndex < 0)
2591 return;
2592 int s = m_LogList.GetSelectionMark();
2593 if (s < 0)
2594 return;
2595 std::vector<CString> changedpaths;
2596 std::vector<LogChangedPath*> changedlogpaths;
2597 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
2598 if (pos == NULL)
2599 return; // nothing is selected, get out of here
2601 bool bOneRev = true;
2602 int sel=m_LogList.GetNextSelectedItem(pos);
2603 GitRev * pLogEntry = reinterpret_cast<GitRev *>(m_LogList.m_arShownList.SafeGetAt(sel));
2604 GitRev * rev1 = pLogEntry;
2605 GitRev * rev2 = reinterpret_cast<GitRev *>(m_LogList.m_arShownList.SafeGetAt(sel+1));
2606 #if 0
2607 bool bOneRev = true;
2608 if (pos)
2610 while (pos)
2612 pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(m_LogList.GetNextSelectedItem(pos)));
2613 if (pLogEntry)
2615 rev1 = max(rev1,(git_revnum_t)pLogEntry->Rev);
2616 rev2 = min(rev2,(git_revnum_t)pLogEntry->Rev);
2617 bOneRev = false;
2620 if (!bOneRev)
2621 rev2--;
2622 POSITION pos = m_ChangedFileListCtrl.GetFirstSelectedItemPosition();
2623 while (pos)
2625 int nItem = m_ChangedFileListCtrl.GetNextSelectedItem(pos);
2626 changedpaths.push_back(m_currentChangedPathList[nItem].GetGitPathString());
2629 else
2631 // only one revision is selected in the log dialog top pane
2632 // but multiple items could be selected in the changed items list
2633 rev2 = rev1-1;
2635 POSITION pos = m_ChangedFileListCtrl.GetFirstSelectedItemPosition();
2636 while (pos)
2638 int nItem = m_ChangedFileListCtrl.GetNextSelectedItem(pos);
2639 LogChangedPath * changedlogpath = pLogEntry->pArChangedPaths->SafeGetAt(nItem);
2641 if (m_ChangedFileListCtrl.GetSelectedCount() == 1)
2643 if ((changedlogpath)&&(!changedlogpath->sCopyFromPath.IsEmpty()))
2644 rev2 = changedlogpath->lCopyFromRev;
2645 else
2647 // if the path was modified but the parent path was 'added with history'
2648 // then we have to use the copy from revision of the parent path
2649 CTGitPath cpath = CTGitPath(changedlogpath->sPath);
2650 for (int flist = 0; flist < pLogEntry->pArChangedPaths->GetCount(); ++flist)
2652 CTGitPath p = CTGitPath(pLogEntry->pArChangedPaths->SafeGetAt(flist)->sPath);
2653 if (p.IsAncestorOf(cpath))
2655 if (!pLogEntry->pArChangedPaths->SafeGetAt(flist)->sCopyFromPath.IsEmpty())
2656 rev2 = pLogEntry->pArChangedPaths->SafeGetAt(flist)->lCopyFromRev;
2661 if ((m_cHidePaths.GetState() & 0x0003)==BST_CHECKED)
2663 // some items are hidden! So find out which item the user really clicked on
2664 INT_PTR selRealIndex = -1;
2665 for (INT_PTR hiddenindex=0; hiddenindex<pLogEntry->pArChangedPaths->GetCount(); ++hiddenindex)
2667 if (pLogEntry->pArChangedPaths->SafeGetAt(hiddenindex)->sPath.Left(m_sRelativeRoot.GetLength()).Compare(m_sRelativeRoot)==0)
2668 selRealIndex++;
2669 if (selRealIndex == nItem)
2671 selIndex = hiddenindex;
2672 changedlogpath = pLogEntry->pArChangedPaths->SafeGetAt(selIndex);
2673 break;
2677 if (changedlogpath)
2679 changedpaths.push_back(changedlogpath->sPath);
2680 changedlogpaths.push_back(changedlogpath);
2684 #endif
2685 //entry is selected, now show the popup menu
2686 CIconMenu popup;
2687 if (popup.CreatePopupMenu())
2689 bool bEntryAdded = false;
2690 if (m_ChangedFileListCtrl.GetSelectedCount() == 1)
2692 // if ((!bOneRev)||(IsDiffPossible(changedlogpaths[0], rev1)))
2694 popup.AppendMenuIcon(CGitLogList::ID_DIFF, IDS_LOG_POPUP_DIFF, IDI_DIFF);
2695 popup.AppendMenuIcon(CGitLogList::ID_BLAMEDIFF, IDS_LOG_POPUP_BLAMEDIFF, IDI_BLAME);
2696 popup.SetDefaultItem(CGitLogList::ID_DIFF, FALSE);
2697 popup.AppendMenuIcon(CGitLogList::ID_GNUDIFF1, IDS_LOG_POPUP_GNUDIFF_CH, IDI_DIFF);
2698 bEntryAdded = true;
2700 // if (rev2 == rev1-1)
2702 if (bEntryAdded)
2703 popup.AppendMenu(MF_SEPARATOR, NULL);
2704 popup.AppendMenuIcon(CGitLogList::ID_OPEN, IDS_LOG_POPUP_OPEN, IDI_OPEN);
2705 popup.AppendMenuIcon(CGitLogList::ID_OPENWITH, IDS_LOG_POPUP_OPENWITH, IDI_OPEN);
2706 popup.AppendMenuIcon(CGitLogList::ID_BLAME, IDS_LOG_POPUP_BLAME, IDI_BLAME);
2707 popup.AppendMenu(MF_SEPARATOR, NULL);
2708 if (m_hasWC)
2709 popup.AppendMenuIcon(CGitLogList::ID_REVERTREV, IDS_LOG_POPUP_REVERTREV, IDI_REVERT);
2710 popup.AppendMenuIcon(CGitLogList::ID_POPPROPS, IDS_REPOBROWSE_SHOWPROP, IDI_PROPERTIES); // "Show Properties"
2711 popup.AppendMenuIcon(CGitLogList::ID_LOG, IDS_MENULOG, IDI_LOG); // "Show Log"
2712 popup.AppendMenuIcon(CGitLogList::ID_GETMERGELOGS, IDS_LOG_POPUP_GETMERGELOGS, IDI_LOG); // "Show merge log"
2713 popup.AppendMenuIcon(CGitLogList::ID_SAVEAS, IDS_LOG_POPUP_SAVE, IDI_SAVEAS);
2714 bEntryAdded = true;
2715 if (!m_ProjectProperties.sWebViewerPathRev.IsEmpty())
2717 popup.AppendMenu(MF_SEPARATOR, NULL);
2718 popup.AppendMenuIcon(CGitLogList::ID_VIEWPATHREV, IDS_LOG_POPUP_VIEWPATHREV);
2720 if (popup.GetDefaultItem(0,FALSE)==-1)
2721 popup.SetDefaultItem(CGitLogList::ID_OPEN, FALSE);
2724 else if (changedlogpaths.size())
2726 // more than one entry is selected
2727 popup.AppendMenuIcon(CGitLogList::ID_SAVEAS, IDS_LOG_POPUP_SAVE);
2728 bEntryAdded = true;
2731 if (!bEntryAdded)
2732 return;
2733 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
2734 bool bOpenWith = false;
2735 bool bMergeLog = false;
2736 m_bCancelled = false;
2738 switch (cmd)
2740 case CGitLogList::ID_DIFF:
2742 DoDiffFromLog(selIndex, rev1, rev2, false, false);
2744 break;
2745 #if 0
2746 case ID_BLAMEDIFF:
2748 DoDiffFromLog(selIndex, rev1, rev2, true, false);
2750 break;
2751 case ID_GNUDIFF1:
2753 DoDiffFromLog(selIndex, rev1, rev2, false, true);
2755 break;
2756 case ID_REVERTREV:
2758 SetPromptApp(&theApp);
2759 theApp.DoWaitCursor(1);
2760 CString sUrl;
2761 if (Git::PathIsURL(m_path))
2763 sUrl = m_path.GetGitPathString();
2765 else
2767 sUrl = GetURLFromPath(m_path);
2768 if (sUrl.IsEmpty())
2770 theApp.DoWaitCursor(-1);
2771 CString temp;
2772 temp.Format(IDS_ERR_NOURLOFFILE, m_path.GetWinPath());
2773 CMessageBox::Show(this->m_hWnd, temp, _T("TortoiseGit"), MB_ICONERROR);
2774 EnableOKButton();
2775 theApp.DoWaitCursor(-1);
2776 break; //exit
2779 // find the working copy path of the selected item from the URL
2780 m_bCancelled = false;
2781 CString sUrlRoot = GetRepositoryRoot(CTGitPath(sUrl));
2783 CString fileURL = changedpaths[0];
2784 fileURL = sUrlRoot + fileURL.Trim();
2785 // firstfile = (e.g.) http://mydomain.com/repos/trunk/folder/file1
2786 // sUrl = http://mydomain.com/repos/trunk/folder
2787 CString sUnescapedUrl = CPathUtils::PathUnescape(sUrl);
2788 // find out until which char the urls are identical
2789 int i=0;
2790 while ((i<fileURL.GetLength())&&(i<sUnescapedUrl.GetLength())&&(fileURL[i]==sUnescapedUrl[i]))
2791 i++;
2792 int leftcount = m_path.GetWinPathString().GetLength()-(sUnescapedUrl.GetLength()-i);
2793 CString wcPath = m_path.GetWinPathString().Left(leftcount);
2794 wcPath += fileURL.Mid(i);
2795 wcPath.Replace('/', '\\');
2796 CGitProgressDlg dlg;
2797 if (changedlogpaths[0]->action == LOGACTIONS_DELETED)
2799 // a deleted path! Since the path isn't there anymore, merge
2800 // won't work. So just do a copy url->wc
2801 dlg.SetCommand(CGitProgressDlg::GitProgress_Copy);
2802 dlg.SetPathList(CTGitPathList(CTGitPath(fileURL)));
2803 dlg.SetUrl(wcPath);
2804 dlg.SetRevision(rev2);
2806 else
2808 if (!PathFileExists(wcPath))
2810 // seems the path got renamed
2811 // tell the user how to work around this.
2812 CMessageBox::Show(this->m_hWnd, IDS_LOG_REVERTREV_ERROR, IDS_APPNAME, MB_ICONERROR);
2813 EnableOKButton();
2814 theApp.DoWaitCursor(-1);
2815 break; //exit
2817 dlg.SetCommand(CGitProgressDlg::GitProgress_Merge);
2818 dlg.SetPathList(CTGitPathList(CTGitPath(wcPath)));
2819 dlg.SetUrl(fileURL);
2820 dlg.SetSecondUrl(fileURL);
2821 GitRevRangeArray revarray;
2822 revarray.AddRevRange(rev1, rev2);
2823 dlg.SetRevisionRanges(revarray);
2825 CString msg;
2826 msg.Format(IDS_LOG_REVERT_CONFIRM, (LPCTSTR)wcPath);
2827 if (CMessageBox::Show(this->m_hWnd, msg, _T("TortoiseGit"), MB_YESNO | MB_ICONQUESTION) == IDYES)
2829 dlg.DoModal();
2831 theApp.DoWaitCursor(-1);
2833 break;
2834 case ID_POPPROPS:
2836 DialogEnableWindow(IDOK, FALSE);
2837 SetPromptApp(&theApp);
2838 theApp.DoWaitCursor(1);
2839 CString filepath;
2840 if (Git::PathIsURL(m_path))
2842 filepath = m_path.GetGitPathString();
2844 else
2846 filepath = GetURLFromPath(m_path);
2847 if (filepath.IsEmpty())
2849 theApp.DoWaitCursor(-1);
2850 CString temp;
2851 temp.Format(IDS_ERR_NOURLOFFILE, (LPCTSTR)filepath);
2852 CMessageBox::Show(this->m_hWnd, temp, _T("TortoiseGit"), MB_ICONERROR);
2853 TRACE(_T("could not retrieve the URL of the file!\n"));
2854 EnableOKButton();
2855 break;
2858 filepath = GetRepositoryRoot(CTGitPath(filepath));
2859 filepath += changedpaths[0];
2860 CPropDlg dlg;
2861 dlg.m_rev = rev1;
2862 dlg.m_Path = CTGitPath(filepath);
2863 dlg.DoModal();
2864 EnableOKButton();
2865 theApp.DoWaitCursor(-1);
2867 break;
2868 case ID_SAVEAS:
2870 DialogEnableWindow(IDOK, FALSE);
2871 SetPromptApp(&theApp);
2872 theApp.DoWaitCursor(1);
2873 CString filepath;
2874 if (Git::PathIsURL(m_path))
2876 filepath = m_path.GetGitPathString();
2878 else
2880 filepath = GetURLFromPath(m_path);
2881 if (filepath.IsEmpty())
2883 theApp.DoWaitCursor(-1);
2884 CString temp;
2885 temp.Format(IDS_ERR_NOURLOFFILE, (LPCTSTR)filepath);
2886 CMessageBox::Show(this->m_hWnd, temp, _T("TortoiseGit"), MB_ICONERROR);
2887 TRACE(_T("could not retrieve the URL of the file!\n"));
2888 EnableOKButton();
2889 break;
2892 m_bCancelled = false;
2893 CString sRoot = GetRepositoryRoot(CTGitPath(filepath));
2894 // if more than one entry is selected, we save them
2895 // one by one into a folder the user has selected
2896 bool bTargetSelected = false;
2897 CTGitPath TargetPath;
2898 if (m_ChangedFileListCtrl.GetSelectedCount() > 1)
2900 CBrowseFolder browseFolder;
2901 browseFolder.SetInfo(CString(MAKEINTRESOURCE(IDS_LOG_SAVEFOLDERTOHINT)));
2902 browseFolder.m_style = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS;
2903 CString strSaveAsDirectory;
2904 if (browseFolder.Show(GetSafeHwnd(), strSaveAsDirectory) == CBrowseFolder::OK)
2906 TargetPath = CTGitPath(strSaveAsDirectory);
2907 bTargetSelected = true;
2910 else
2912 // Display the Open dialog box.
2913 CString revFilename;
2914 CString temp;
2915 temp = CPathUtils::GetFileNameFromPath(changedpaths[0]);
2916 int rfind = temp.ReverseFind('.');
2917 if (rfind > 0)
2918 revFilename.Format(_T("%s-%ld%s"), (LPCTSTR)temp.Left(rfind), rev1, (LPCTSTR)temp.Mid(rfind));
2919 else
2920 revFilename.Format(_T("%s-%ld"), (LPCTSTR)temp, rev1);
2921 bTargetSelected = CAppUtils::FileOpenSave(revFilename, NULL, IDS_LOG_POPUP_SAVE, IDS_COMMONFILEFILTER, false, m_hWnd);
2922 TargetPath.SetFromWin(revFilename);
2924 if (bTargetSelected)
2926 CProgressDlg progDlg;
2927 progDlg.SetTitle(IDS_APPNAME);
2928 progDlg.SetAnimation(IDR_DOWNLOAD);
2929 for (std::vector<LogChangedPath*>::iterator it = changedlogpaths.begin(); it!= changedlogpaths.end(); ++it)
2931 GitRev getrev = ((*it)->action == LOGACTIONS_DELETED) ? rev2 : rev1;
2933 CString sInfoLine;
2934 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, (LPCTSTR)filepath, (LPCTSTR)getrev.ToString());
2935 progDlg.SetLine(1, sInfoLine, true);
2936 SetAndClearProgressInfo(&progDlg);
2937 progDlg.ShowModeless(m_hWnd);
2939 CTGitPath tempfile = TargetPath;
2940 if (changedpaths.size() > 1)
2942 // if multiple items are selected, then the TargetPath
2943 // points to a folder and we have to append the filename
2944 // to save to to that folder.
2945 CString sName = (*it)->sPath;
2946 int slashpos = sName.ReverseFind('/');
2947 if (slashpos >= 0)
2948 sName = sName.Mid(slashpos);
2949 tempfile.AppendPathString(sName);
2950 // one problem here:
2951 // a user could have selected multiple items which
2952 // have the same filename but reside in different
2953 // directories, e.g.
2954 // /folder1/file1
2955 // /folder2/file1
2956 // in that case, the second 'file1' will overwrite
2957 // the already saved 'file1'.
2959 // we could maybe find the common root of all selected
2960 // items and then create sub folders to save those files
2961 // there.
2962 // But I think we should just leave it that way: to check
2963 // out multiple items at once, the better way is still to
2964 // use the export command from the top pane of the log dialog.
2966 filepath = sRoot + (*it)->sPath;
2967 if (!Cat(CTGitPath(filepath), getrev, getrev, tempfile))
2969 progDlg.Stop();
2970 SetAndClearProgressInfo((HWND)NULL);
2971 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
2972 EnableOKButton();
2973 theApp.DoWaitCursor(-1);
2974 break;
2977 progDlg.Stop();
2978 SetAndClearProgressInfo((HWND)NULL);
2980 EnableOKButton();
2981 theApp.DoWaitCursor(-1);
2983 break;
2984 case ID_OPENWITH:
2985 bOpenWith = true;
2986 case ID_OPEN:
2988 GitRev getrev = pLogEntry->pArChangedPaths->SafeGetAt(selIndex)->action == LOGACTIONS_DELETED ? rev2 : rev1;
2989 Open(bOpenWith,changedpaths[0],getrev);
2991 break;
2992 case ID_BLAME:
2994 CString filepath;
2995 if (Git::PathIsURL(m_path))
2997 filepath = m_path.GetGitPathString();
2999 else
3001 filepath = GetURLFromPath(m_path);
3002 if (filepath.IsEmpty())
3004 theApp.DoWaitCursor(-1);
3005 CString temp;
3006 temp.Format(IDS_ERR_NOURLOFFILE, (LPCTSTR)filepath);
3007 CMessageBox::Show(this->m_hWnd, temp, _T("TortoiseGit"), MB_ICONERROR);
3008 TRACE(_T("could not retrieve the URL of the file!\n"));
3009 EnableOKButton();
3010 break;
3013 filepath = GetRepositoryRoot(CTGitPath(filepath));
3014 filepath += changedpaths[0];
3015 CBlameDlg dlg;
3016 dlg.EndRev = rev1;
3017 if (dlg.DoModal() == IDOK)
3019 CBlame blame;
3020 CString tempfile;
3021 CString logfile;
3022 tempfile = blame.BlameToTempFile(CTGitPath(filepath), dlg.StartRev, dlg.EndRev, dlg.EndRev, logfile, _T(""), dlg.m_bIncludeMerge, TRUE, TRUE);
3023 if (!tempfile.IsEmpty())
3025 if (dlg.m_bTextView)
3027 //open the default text editor for the result file
3028 CAppUtils::StartTextViewer(tempfile);
3030 else
3032 CString sParams = _T("/path:\"") + filepath + _T("\" ");
3033 if(!CAppUtils::LaunchTortoiseBlame(tempfile, logfile, CPathUtils::GetFileNameFromPath(filepath),sParams))
3035 break;
3039 else
3041 CMessageBox::Show(this->m_hWnd, blame.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
3045 break;
3046 case ID_GETMERGELOGS:
3047 bMergeLog = true;
3048 // fall through
3049 case ID_LOG:
3051 DialogEnableWindow(IDOK, FALSE);
3052 SetPromptApp(&theApp);
3053 theApp.DoWaitCursor(1);
3054 CString filepath;
3055 if (Git::PathIsURL(m_path))
3057 filepath = m_path.GetGitPathString();
3059 else
3061 filepath = GetURLFromPath(m_path);
3062 if (filepath.IsEmpty())
3064 theApp.DoWaitCursor(-1);
3065 CString temp;
3066 temp.Format(IDS_ERR_NOURLOFFILE, (LPCTSTR)filepath);
3067 CMessageBox::Show(this->m_hWnd, temp, _T("TortoiseGit"), MB_ICONERROR);
3068 TRACE(_T("could not retrieve the URL of the file!\n"));
3069 EnableOKButton();
3070 break;
3073 m_bCancelled = false;
3074 filepath = GetRepositoryRoot(CTGitPath(filepath));
3075 filepath += changedpaths[0];
3076 git_revnum_t logrev = rev1;
3077 if (changedlogpaths[0]->action == LOGACTIONS_DELETED)
3079 // if the item got deleted in this revision,
3080 // fetch the log from the previous revision where it
3081 // still existed.
3082 logrev--;
3084 CString sCmd;
3085 sCmd.Format(_T("\"%s\" /command:log /path:\"%s\" /startrev:%ld"), (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")), (LPCTSTR)filepath, logrev);
3086 if (bMergeLog)
3087 sCmd += _T(" /merge");
3088 CAppUtils::LaunchApplication(sCmd, NULL, false);
3089 EnableOKButton();
3090 theApp.DoWaitCursor(-1);
3092 break;
3093 case ID_VIEWPATHREV:
3095 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(m_LogList.GetSelectionMark()));
3096 GitRev rev = pLogEntry->Rev;
3097 CString relurl = changedpaths[0];
3098 CString url = m_ProjectProperties.sWebViewerPathRev;
3099 url.Replace(_T("%REVISION%"), rev.ToString());
3100 url.Replace(_T("%PATH%"), relurl);
3101 relurl = relurl.Mid(relurl.Find('/'));
3102 url.Replace(_T("%PATH1%"), relurl);
3103 if (!url.IsEmpty())
3104 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
3106 break;
3107 #endif
3108 default:
3109 break;
3110 } // switch (cmd)
3112 } // if (popup.CreatePopupMenu())
3114 #endif
3116 void CLogDlg::OnDtnDropdownDatefrom(NMHDR * /*pNMHDR*/, LRESULT *pResult)
3118 // the date control should not show the "today" button
3119 CMonthCalCtrl * pCtrl = m_DateFrom.GetMonthCalCtrl();
3120 if (pCtrl)
3121 SetWindowLongPtr(pCtrl->GetSafeHwnd(), GWL_STYLE, LONG_PTR(pCtrl->GetStyle() | MCS_NOTODAY));
3122 *pResult = 0;
3125 void CLogDlg::OnDtnDropdownDateto(NMHDR * /*pNMHDR*/, LRESULT *pResult)
3127 // the date control should not show the "today" button
3128 CMonthCalCtrl * pCtrl = m_DateTo.GetMonthCalCtrl();
3129 if (pCtrl)
3130 SetWindowLongPtr(pCtrl->GetSafeHwnd(), GWL_STYLE, LONG_PTR(pCtrl->GetStyle() | MCS_NOTODAY));
3131 *pResult = 0;
3134 void CLogDlg::OnSize(UINT nType, int cx, int cy)
3136 __super::OnSize(nType, cx, cy);
3137 //set range
3138 SetSplitterRange();
3141 void CLogDlg::OnRefresh()
3143 //if (GetDlgItem(IDC_GETALL)->IsWindowEnabled())
3145 m_limit = 0;
3146 this->m_LogProgress.SetPos(0);
3148 Refresh (false);
3154 void CLogDlg::OnFocusFilter()
3156 GetDlgItem(IDC_SEARCHEDIT)->SetFocus();
3159 void CLogDlg::OnEditCopy()
3161 if (GetFocus() == &m_ChangedFileListCtrl)
3162 CopyChangedSelectionToClipBoard();
3163 else
3164 m_LogList.CopySelectionToClipBoard();
3167 CString CLogDlg::GetAbsoluteUrlFromRelativeUrl(const CString& url)
3169 // is the URL a relative one?
3170 if (url.Left(2).Compare(_T("^/")) == 0)
3172 // URL is relative to the repository root
3173 CString url1 = m_sRepositoryRoot + url.Mid(1);
3174 TCHAR buf[INTERNET_MAX_URL_LENGTH];
3175 DWORD len = url.GetLength();
3176 if (UrlCanonicalize((LPCTSTR)url1, buf, &len, 0) == S_OK)
3177 return CString(buf, len);
3178 return url1;
3180 else if (url[0] == '/')
3182 // URL is relative to the server's hostname
3183 CString sHost;
3184 // find the server's hostname
3185 int schemepos = m_sRepositoryRoot.Find(_T("//"));
3186 if (schemepos >= 0)
3188 sHost = m_sRepositoryRoot.Left(m_sRepositoryRoot.Find('/', schemepos+3));
3189 CString url1 = sHost + url;
3190 TCHAR buf[INTERNET_MAX_URL_LENGTH];
3191 DWORD len = url.GetLength();
3192 if (UrlCanonicalize((LPCTSTR)url, buf, &len, 0) == S_OK)
3193 return CString(buf, len);
3194 return url1;
3197 return url;
3201 void CLogDlg::OnEnChangeSearchedit()
3203 UpdateData();
3204 if (m_LogList.m_sFilterText.IsEmpty())
3206 CStoreSelection storeselection(this);
3207 // clear the filter, i.e. make all entries appear
3208 theApp.DoWaitCursor(1);
3209 KillTimer(LOGFILTER_TIMER);
3210 FillLogMessageCtrl(false);
3212 Refresh();
3213 //m_LogList.StartFilter();
3214 #if 0
3215 InterlockedExchange(&m_bNoDispUpdates, TRUE);
3216 m_arShownList.RemoveAll();
3217 for (DWORD i=0; i<m_logEntries.size(); ++i)
3219 if (IsEntryInDateRange(i))
3220 m_arShownList.Add(m_logEntries[i]);
3222 InterlockedExchange(&m_bNoDispUpdates, FALSE);
3223 m_LogList.DeleteAllItems();
3224 m_LogList.SetItemCountEx(ShownCountWithStopped());
3225 m_LogList.RedrawItems(0, ShownCountWithStopped());
3226 m_LogList.SetRedraw(false);
3227 ResizeAllListCtrlCols();
3228 m_LogList.SetRedraw(true);
3229 #endif
3230 theApp.DoWaitCursor(-1);
3231 GetDlgItem(IDC_SEARCHEDIT)->ShowWindow(SW_HIDE);
3232 GetDlgItem(IDC_SEARCHEDIT)->ShowWindow(SW_SHOW);
3233 GetDlgItem(IDC_SEARCHEDIT)->SetFocus();
3234 DialogEnableWindow(IDC_STATBUTTON, !(((this->IsThreadRunning())||(m_LogList.m_arShownList.IsEmpty()))));
3235 return;
3237 if (Validate(m_LogList.m_sFilterText))
3238 SetTimer(LOGFILTER_TIMER, 1000, NULL);
3239 else
3240 KillTimer(LOGFILTER_TIMER);
3244 void CLogDlg::OnBnClickedAllBranch()
3246 this->UpdateData();
3248 if(this->m_bAllBranch)
3249 m_LogList.m_ShowMask|=CGit::LOG_INFO_ALL_BRANCH;
3250 else
3251 m_LogList.m_ShowMask&=~CGit::LOG_INFO_ALL_BRANCH;
3253 OnRefresh();
3255 FillLogMessageCtrl(false);
3258 void CLogDlg::OnBnClickedBrowseRef()
3260 CString newRef = CBrowseRefsDlg::PickRef(false,m_LogList.GetStartRef());
3261 if(newRef.IsEmpty())
3262 return;
3264 SetStartRef(newRef);
3265 ((CButton*)GetDlgItem(IDC_LOG_ALLBRANCH))->SetCheck(0);
3267 OnBnClickedAllBranch();
3270 void CLogDlg::ShowStartRef()
3272 //Show ref name on top
3273 if(!::IsWindow(m_hWnd))
3274 return;
3275 if(m_bAllBranch)
3277 m_staticRef.SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_LOG_ALLBRANCHES)));
3278 m_staticRef.Invalidate(TRUE);
3279 return;
3282 CString showStartRef = m_LogList.GetStartRef();
3283 if(showStartRef.IsEmpty())
3285 //Ref name is HEAD
3286 if (g_Git.Run(L"git symbolic-ref HEAD", &showStartRef, NULL, CP_UTF8))
3287 showStartRef = CString(MAKEINTRESOURCE(IDS_PROC_LOG_NOBRANCH));
3288 showStartRef.Trim(L"\r\n\t ");
3292 if(wcsncmp(showStartRef,L"refs/",5) == 0)
3293 showStartRef = showStartRef.Mid(5);
3294 if(wcsncmp(showStartRef,L"heads/",6) == 0)
3295 showStartRef = showStartRef.Mid(6);
3297 m_staticRef.SetWindowText(showStartRef);
3298 m_staticRef.Invalidate(TRUE);
3301 void CLogDlg::SetStartRef(const CString& StartRef)
3303 m_LogList.SetStartRef(StartRef);
3305 if (m_hightlightRevision.IsEmpty())
3307 m_hightlightRevision = g_Git.GetHash(StartRef);
3308 m_LogList.m_lastSelectedHash = m_hightlightRevision;
3311 ShowStartRef();
3315 void CLogDlg::OnBnClickedFirstParent()
3317 this->UpdateData();
3319 if(this->m_bFirstParent)
3320 m_LogList.m_ShowMask|=CGit::LOG_INFO_FIRST_PARENT;
3321 else
3322 m_LogList.m_ShowMask&=~CGit::LOG_INFO_FIRST_PARENT;
3324 OnRefresh();
3326 FillLogMessageCtrl(false);
3330 void CLogDlg::OnBnClickShowWholeProject()
3332 this->UpdateData();
3334 if(this->m_bWholeProject)
3336 m_LogList.m_Path.Reset();
3337 SetDlgTitle();
3339 else
3341 m_LogList.m_Path=m_path;
3344 SetDlgTitle();
3346 OnRefresh();
3348 FillLogMessageCtrl(false);
3352 LRESULT CLogDlg::OnRefLogChanged(WPARAM wParam, LPARAM lParam)
3354 UNREFERENCED_PARAMETER(wParam);
3355 UNREFERENCED_PARAMETER(lParam);
3356 ShowStartRef();
3357 return 0;
3360 LRESULT CLogDlg::OnTaskbarBtnCreated(WPARAM /*wParam*/, LPARAM /*lParam*/)
3362 m_pTaskbarList.Release();
3363 m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList);
3364 return 0;