Avoid send multi GITLOG_START when parser commit list
[TortoiseGit.git] / src / TortoiseProc / LogDlg.cpp
blobfe576a47374fa831304d159dcab1eb23a861b025
1 // TortoiseGit - a Windows shell extension for easy version control
2 // Copyright (C) 2003-2008 - TortoiseGit
3 // This program is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU General Public License
5 // as published by the Free Software Foundation; either version 2
6 // of the License, or (at your option) any later version.
7 // This program is distributed in the hope that it will be useful,
8 // but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // GNU General Public License for more details.
12 // You should have received a copy of the GNU General Public License
13 // along with this program; if not, write to the Free Software Foundation,
14 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
16 #include "stdafx.h"
17 #include "TortoiseProc.h"
18 #include "cursor.h"
19 #include "InputDlg.h"
20 #include "PropDlg.h"
21 #include "SVNProgressDlg.h"
22 #include "ProgressDlg.h"
23 //#include "RepositoryBrowser.h"
24 //#include "CopyDlg.h"
25 #include "StatGraphDlg.h"
26 #include "Logdlg.h"
27 #include "MessageBox.h"
28 #include "Registry.h"
29 #include "AppUtils.h"
30 #include "PathUtils.h"
31 #include "StringUtils.h"
32 #include "UnicodeUtils.h"
33 #include "TempFile.h"
34 //#include "GitInfo.h"
35 //#include "GitDiff.h"
36 #include "IconMenu.h"
37 //#include "RevisionRangeDlg.h"
38 //#include "BrowseFolder.h"
39 //#include "BlameDlg.h"
40 //#include "Blame.h"
41 //#include "GitHelpers.h"
42 #include "GitStatus.h"
43 //#include "LogDlgHelper.h"
44 //#include "CachedLogInfo.h"
45 //#include "RepositoryInfo.h"
46 //#include "EditPropertiesDlg.h"
47 #include "FileDiffDlg.h"
48 #include "BrowseRefsDlg.h"
50 const UINT CLogDlg::m_FindDialogMessage = RegisterWindowMessage(FINDMSGSTRING);
53 IMPLEMENT_DYNAMIC(CLogDlg, CResizableStandAloneDialog)
54 CLogDlg::CLogDlg(CWnd* pParent /*=NULL*/)
55 : CResizableStandAloneDialog(CLogDlg::IDD, pParent)
56 , m_logcounter(0)
57 , m_nSearchIndex(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_bShowBugtraqColumn(false)
66 , m_lowestRev(_T(""))
68 , m_sLogInfo(_T(""))
69 , m_pFindDialog(NULL)
70 , m_bCancelled(FALSE)
71 , m_pNotifyWindow(NULL)
73 , m_bAscending(FALSE)
75 , m_limit(0)
76 , m_childCounter(0)
77 , m_maxChild(0)
78 , m_bIncludeMerges(FALSE)
79 , m_hAccel(NULL)
81 m_bFilterWithRegex = !!CRegDWORD(_T("Software\\TortoiseGit\\UseRegexFilter"), TRUE);
83 CString str;
84 str=g_Git.m_CurrentDir;
85 str.Replace(_T(":"),_T("_"));
86 str=CString(_T("Software\\TortoiseGit\\LogDialog\\AllBranch\\"))+str;
88 m_regbAllBranch=CRegDWORD(str,FALSE);
90 m_bAllBranch=m_regbAllBranch;
92 m_bFirstParent=FALSE;
93 m_bWholeProject=FALSE;
96 CLogDlg::~CLogDlg()
99 m_regbAllBranch=m_bAllBranch;
101 m_CurrentFilteredChangedArray.RemoveAll();
105 void CLogDlg::DoDataExchange(CDataExchange* pDX)
107 CResizableStandAloneDialog::DoDataExchange(pDX);
108 DDX_Control(pDX, IDC_LOGLIST, m_LogList);
109 DDX_Control(pDX, IDC_LOGMSG, m_ChangedFileListCtrl);
110 DDX_Control(pDX, IDC_PROGRESS, m_LogProgress);
111 DDX_Control(pDX, IDC_SPLITTERTOP, m_wndSplitter1);
112 DDX_Control(pDX, IDC_SPLITTERBOTTOM, m_wndSplitter2);
113 DDX_Text(pDX, IDC_SEARCHEDIT, m_LogList.m_sFilterText);
114 DDX_Control(pDX, IDC_DATEFROM, m_DateFrom);
115 DDX_Control(pDX, IDC_DATETO, m_DateTo);
116 DDX_Control(pDX, IDC_HIDEPATHS, m_cHidePaths);
117 DDX_Text(pDX, IDC_LOGINFO, m_sLogInfo);
118 DDX_Check(pDX, IDC_LOG_FIRSTPARENT, m_bFirstParent);
119 DDX_Check(pDX, IDC_LOG_ALLBRANCH,m_bAllBranch);
120 DDX_Check(pDX, IDC_SHOWWHOLEPROJECT,m_bWholeProject);
121 DDX_Control(pDX, IDC_SEARCHEDIT, m_cFilter);
122 DDX_Control(pDX, IDC_STATIC_REF, m_staticRef);
125 BEGIN_MESSAGE_MAP(CLogDlg, CResizableStandAloneDialog)
126 ON_REGISTERED_MESSAGE(m_FindDialogMessage, OnFindDialogMessage)
127 //ON_BN_CLICKED(IDC_GETALL, OnBnClickedGetall)
128 //ON_NOTIFY(NM_DBLCLK, IDC_LOGMSG, OnNMDblclkChangedFileList)
129 ON_WM_CONTEXTMENU()
130 ON_WM_SETCURSOR()
131 ON_BN_CLICKED(IDHELP, OnBnClickedHelp)
132 ON_NOTIFY(LVN_ITEMCHANGED, IDC_LOGLIST, OnLvnItemchangedLoglist)
133 ON_NOTIFY(EN_LINK, IDC_MSGVIEW, OnEnLinkMsgview)
134 ON_BN_CLICKED(IDC_STATBUTTON, OnBnClickedStatbutton)
137 ON_MESSAGE(WM_FILTEREDIT_INFOCLICKED, OnClickedInfoIcon)
138 ON_MESSAGE(WM_FILTEREDIT_CANCELCLICKED, OnClickedCancelFilter)
140 ON_MESSAGE(MSG_LOAD_PERCENTAGE,OnLogListLoading)
142 ON_EN_CHANGE(IDC_SEARCHEDIT, OnEnChangeSearchedit)
143 ON_WM_TIMER()
144 ON_NOTIFY(DTN_DATETIMECHANGE, IDC_DATETO, OnDtnDatetimechangeDateto)
145 ON_NOTIFY(DTN_DATETIMECHANGE, IDC_DATEFROM, OnDtnDatetimechangeDatefrom)
146 ON_BN_CLICKED(IDC_SHOWWHOLEPROJECT, OnBnClickShowWholeProject)
147 //ON_NOTIFY(NM_CUSTOMDRAW, IDC_LOGMSG, OnNMCustomdrawChangedFileList)
148 //ON_NOTIFY(LVN_GETDISPINFO, IDC_LOGMSG, OnLvnGetdispinfoChangedFileList)
149 ON_NOTIFY(LVN_COLUMNCLICK,IDC_LOGLIST , OnLvnColumnclick)
150 //ON_NOTIFY(LVN_COLUMNCLICK, IDC_LOGMSG, OnLvnColumnclickChangedFileList)
151 ON_BN_CLICKED(IDC_HIDEPATHS, OnBnClickedHidepaths)
152 ON_BN_CLICKED(IDC_LOG_ALLBRANCH,OnBnClickedAllBranch)
154 ON_NOTIFY(DTN_DROPDOWN, IDC_DATEFROM, &CLogDlg::OnDtnDropdownDatefrom)
155 ON_NOTIFY(DTN_DROPDOWN, IDC_DATETO, &CLogDlg::OnDtnDropdownDateto)
156 ON_WM_SIZE()
157 ON_BN_CLICKED(IDC_LOG_FIRSTPARENT, &CLogDlg::OnBnClickedFirstParent)
158 ON_BN_CLICKED(IDC_REFRESH, &CLogDlg::OnBnClickedRefresh)
159 // ON_BN_CLICKED(IDC_BUTTON_BROWSE_REF, &CLogDlg::OnBnClickedBrowseRef)
160 ON_STN_CLICKED(IDC_STATIC_REF, &CLogDlg::OnBnClickedBrowseRef)
161 ON_COMMAND(ID_LOGDLG_REFRESH,&CLogDlg::OnRefresh)
162 ON_COMMAND(ID_LOGDLG_FIND,&CLogDlg::OnFind)
163 ON_COMMAND(ID_LOGDLG_FOCUSFILTER,&CLogDlg::OnFocusFilter)
164 ON_COMMAND(ID_EDIT_COPY, &CLogDlg::OnEditCopy)
165 END_MESSAGE_MAP()
167 void CLogDlg::SetParams(const CTGitPath& path, GitRev pegrev, GitRev startrev, GitRev endrev, int limit /* = FALSE */)
169 m_path = path;
170 m_pegrev = pegrev;
171 m_startrev = startrev;
172 m_LogRevision = startrev;
173 m_endrev = endrev;
174 m_hasWC = !path.IsUrl();
175 m_limit = limit;
176 if (::IsWindow(m_hWnd))
177 UpdateData(FALSE);
180 BOOL CLogDlg::OnInitDialog()
182 CString temp;
183 CResizableStandAloneDialog::OnInitDialog();
185 m_hAccel = LoadAccelerators(AfxGetResourceHandle(),MAKEINTRESOURCE(IDR_ACC_LOGDLG));
188 // use the state of the "stop on copy/rename" option from the last time
189 UpdateData(FALSE);
191 // set the font to use in the log message view, configured in the settings dialog
192 CAppUtils::CreateFontForLogs(m_logFont);
193 GetDlgItem(IDC_MSGVIEW)->SetFont(&m_logFont);
194 // automatically detect URLs in the log message and turn them into links
195 GetDlgItem(IDC_MSGVIEW)->SendMessage(EM_AUTOURLDETECT, TRUE, NULL);
196 // make the log message rich edit control send a message when the mouse pointer is over a link
197 GetDlgItem(IDC_MSGVIEW)->SendMessage(EM_SETEVENTMASK, NULL, ENM_LINK);
198 //m_LogList.SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER | LVS_EX_SUBITEMIMAGES);
200 // the "hide unrelated paths" checkbox should be indeterminate
201 m_cHidePaths.SetCheck(BST_INDETERMINATE);
204 // if there is a working copy, load the project properties
205 // to get information about the bugtraq: integration
206 if (m_hasWC)
207 m_ProjectProperties.ReadProps(m_path);
209 // the bugtraq issue id column is only shown if the bugtraq:url or bugtraq:regex is set
210 if ((!m_ProjectProperties.sUrl.IsEmpty())||(!m_ProjectProperties.sCheckRe.IsEmpty()))
211 m_bShowBugtraqColumn = true;
213 //theme.SetWindowTheme(m_LogList.GetSafeHwnd(), L"Explorer", NULL);
214 //theme.SetWindowTheme(m_ChangedFileListCtrl.GetSafeHwnd(), L"Explorer", NULL);
216 // set up the columns
217 m_LogList.DeleteAllItems();
218 m_LogList.InsertGitColumn();
220 m_ChangedFileListCtrl.Init(SVNSLC_COLEXT | SVNSLC_COLSTATUS |SVNSLC_COLADD|SVNSLC_COLDEL , _T("LogDlg"),(SVNSLC_POPALL ^ (SVNSLC_POPCOMMIT|SVNSLC_POPREVERT)),false);
222 GetDlgItem(IDC_LOGLIST)->UpdateData(FALSE);
224 m_logcounter = 0;
225 m_sMessageBuf.Preallocate(100000);
227 // set the dialog title to "Log - path/to/whatever/we/show/the/log/for"
228 SetDlgTitle(false);
230 m_tooltips.Create(this);
231 CheckRegexpTooltip();
233 SetSplitterRange();
235 // the filter control has a 'cancel' button (the red 'X'), we need to load its bitmap
236 m_cFilter.SetCancelBitmaps(IDI_CANCELNORMAL, IDI_CANCELPRESSED);
237 m_cFilter.SetInfoIcon(IDI_LOGFILTER);
238 m_cFilter.SetValidator(this);
240 AdjustControlSize(IDC_HIDEPATHS);
241 AdjustControlSize(IDC_LOG_FIRSTPARENT);
242 AdjustControlSize(IDC_LOG_ALLBRANCH);
244 GetClientRect(m_DlgOrigRect);
245 m_LogList.GetClientRect(m_LogListOrigRect);
246 GetDlgItem(IDC_MSGVIEW)->GetClientRect(m_MsgViewOrigRect);
247 m_ChangedFileListCtrl.GetClientRect(m_ChgOrigRect);
249 m_DateFrom.SendMessage(DTM_SETMCSTYLE, 0, MCS_WEEKNUMBERS|MCS_NOTODAY|MCS_NOTRAILINGDATES|MCS_NOSELCHANGEONNAV);
250 m_DateTo.SendMessage(DTM_SETMCSTYLE, 0, MCS_WEEKNUMBERS|MCS_NOTODAY|MCS_NOTRAILINGDATES|MCS_NOSELCHANGEONNAV);
252 m_staticRef.SetURL(CString());
254 // resizable stuff
255 AddAnchor(IDC_STATIC_REF, TOP_LEFT);
256 //AddAnchor(IDC_BUTTON_BROWSE_REF, TOP_LEFT);
257 AddAnchor(IDC_FROMLABEL, TOP_LEFT);
258 AddAnchor(IDC_DATEFROM, TOP_LEFT);
259 AddAnchor(IDC_TOLABEL, TOP_LEFT);
260 AddAnchor(IDC_DATETO, TOP_LEFT);
262 SetFilterCueText();
263 AddAnchor(IDC_SEARCHEDIT, TOP_LEFT, TOP_RIGHT);
265 AddAnchor(IDC_LOGLIST, TOP_LEFT, TOP_RIGHT);
266 AddAnchor(IDC_SPLITTERTOP, TOP_LEFT, TOP_RIGHT);
267 AddAnchor(IDC_MSGVIEW, TOP_LEFT, BOTTOM_RIGHT);
268 AddAnchor(IDC_SPLITTERBOTTOM, BOTTOM_LEFT, BOTTOM_RIGHT);
269 AddAnchor(IDC_LOGMSG, BOTTOM_LEFT, BOTTOM_RIGHT);
271 AddAnchor(IDC_LOGINFO, BOTTOM_LEFT, BOTTOM_RIGHT);
272 AddAnchor(IDC_HIDEPATHS, BOTTOM_LEFT);
273 AddAnchor(IDC_LOG_ALLBRANCH,BOTTOM_LEFT);
274 AddAnchor(IDC_LOG_FIRSTPARENT, BOTTOM_LEFT);
275 //AddAnchor(IDC_GETALL, BOTTOM_LEFT);
276 AddAnchor(IDC_SHOWWHOLEPROJECT, BOTTOM_LEFT);
277 AddAnchor(IDC_REFRESH, BOTTOM_LEFT);
278 AddAnchor(IDC_STATBUTTON, BOTTOM_RIGHT);
279 AddAnchor(IDC_PROGRESS, BOTTOM_LEFT, BOTTOM_RIGHT);
280 AddAnchor(IDOK, BOTTOM_RIGHT);
281 AddAnchor(IDCANCEL, BOTTOM_RIGHT);
282 AddAnchor(IDHELP, BOTTOM_RIGHT);
284 if(this->m_bAllBranch)
285 m_LogList.m_ShowMask|=CGit::LOG_INFO_ALL_BRANCH;
286 else
287 m_LogList.m_ShowMask&=~CGit::LOG_INFO_ALL_BRANCH;
289 // SetPromptParentWindow(m_hWnd);
291 if (hWndExplorer)
292 CenterWindow(CWnd::FromHandle(hWndExplorer));
293 EnableSaveRestore(_T("LogDlg"));
295 DWORD yPos1 = CRegDWORD(_T("Software\\TortoiseGit\\TortoiseProc\\ResizableState\\LogDlgSizer1"));
296 DWORD yPos2 = CRegDWORD(_T("Software\\TortoiseGit\\TortoiseProc\\ResizableState\\LogDlgSizer2"));
297 RECT rcDlg, rcLogList, rcChgMsg;
298 GetClientRect(&rcDlg);
299 m_LogList.GetWindowRect(&rcLogList);
300 ScreenToClient(&rcLogList);
301 m_ChangedFileListCtrl.GetWindowRect(&rcChgMsg);
302 ScreenToClient(&rcChgMsg);
303 if (yPos1)
305 RECT rectSplitter;
306 m_wndSplitter1.GetWindowRect(&rectSplitter);
307 ScreenToClient(&rectSplitter);
308 int delta = yPos1 - rectSplitter.top;
310 if ((rcLogList.bottom + delta > rcLogList.top)&&(rcLogList.bottom + delta < rcChgMsg.bottom - 30))
312 m_wndSplitter1.SetWindowPos(NULL, 0, yPos1, 0, 0, SWP_NOSIZE);
313 DoSizeV1(delta);
316 if (yPos2)
318 RECT rectSplitter;
319 m_wndSplitter2.GetWindowRect(&rectSplitter);
320 ScreenToClient(&rectSplitter);
321 int delta = yPos2 - rectSplitter.top;
323 if ((rcChgMsg.top + delta < rcChgMsg.bottom)&&(rcChgMsg.top + delta > rcLogList.top + 30))
325 m_wndSplitter2.SetWindowPos(NULL, 0, yPos2, 0, 0, SWP_NOSIZE);
326 DoSizeV2(delta);
331 if (m_bSelect)
333 // the dialog is used to select revisions
334 // enable the OK button if appropriate
335 EnableOKButton();
337 else
339 // the dialog is used to just view log messages
340 // hide the OK button and set text on Cancel button to OK
341 GetDlgItemText(IDOK, temp);
342 SetDlgItemText(IDCANCEL, temp);
343 GetDlgItem(IDOK)->ShowWindow(SW_HIDE);
346 m_mergedRevs.clear();
348 // first start a thread to obtain the log messages without
349 // blocking the dialog
350 //m_tTo = 0;
351 //m_tFrom = (DWORD)-1;
353 m_LogList.m_Path=m_path;
354 m_LogList.m_bShowWC = true;
355 m_LogList.FetchLogAsync(this);
357 GetDlgItem(IDC_LOGLIST)->SetFocus();
359 ShowStartRef();
360 return FALSE;
363 LRESULT CLogDlg::OnLogListLoading(WPARAM wParam, LPARAM /*lParam*/)
365 int cur=(int)wParam;
367 if( cur == GITLOG_START )
369 CString temp;
370 temp.LoadString(IDS_PROGRESSWAIT);
372 this->m_LogList.ShowText(temp, true);
374 // We use a progress bar while getting the logs
375 m_LogProgress.SetRange32(0, 100);
376 m_LogProgress.SetPos(0);
378 GetDlgItem(IDC_PROGRESS)->ShowWindow(TRUE);
380 //DialogEnableWindow(IDC_GETALL, FALSE);
381 //DialogEnableWindow(IDC_SHOWWHOLEPROJECT, FALSE);
382 //DialogEnableWindow(IDC_LOG_FIRSTPARENT, FALSE);
383 DialogEnableWindow(IDC_STATBUTTON, FALSE);
384 //DialogEnableWindow(IDC_REFRESH, FALSE);
385 DialogEnableWindow(IDC_HIDEPATHS,FALSE);
387 DialogEnableWindow(IDC_DATEFROM,FALSE);
388 DialogEnableWindow(IDC_DATETO,FALSE);
390 DialogEnableWindow(IDC_SEARCHEDIT,FALSE);
392 }else if( cur == GITLOG_END)
395 if(this->m_LogList.HasText())
397 this->m_LogList.ClearText();
400 UpdateLogInfoLabel();
403 //if (!m_bShowedAll)
404 DialogEnableWindow(IDC_SHOWWHOLEPROJECT, TRUE);
406 //DialogEnableWindow(IDC_GETALL, TRUE);
407 DialogEnableWindow(IDC_LOG_FIRSTPARENT, TRUE);
408 DialogEnableWindow(IDC_STATBUTTON, TRUE);
409 DialogEnableWindow(IDC_REFRESH, TRUE);
410 DialogEnableWindow(IDC_HIDEPATHS,TRUE);
412 DialogEnableWindow(IDC_DATEFROM,TRUE);
413 DialogEnableWindow(IDC_DATETO,TRUE);
415 DialogEnableWindow(IDC_SEARCHEDIT,TRUE);
417 // PostMessage(WM_TIMER, LOGFILTER_TIMER);
418 GetDlgItem(IDC_PROGRESS)->ShowWindow(FALSE);
419 //CTime time=m_LogList.GetOldestTime();
420 CTime begin,end;
421 m_LogList.GetTimeRange(begin,end);
422 m_DateFrom.SetTime(&begin);
423 m_DateTo.SetTime(&end);
426 }else
428 if(this->m_LogList.HasText())
430 this->m_LogList.ClearText();
431 this->m_LogList.Invalidate();
433 UpdateLogInfoLabel();
434 m_LogProgress.SetPos(cur);
436 return 0;
438 void CLogDlg::SetDlgTitle(bool bOffline)
440 if (m_sTitle.IsEmpty())
441 GetWindowText(m_sTitle);
443 if (bOffline)
445 CString sTemp;
446 if (m_path.IsUrl())
447 sTemp.Format(IDS_LOG_DLGTITLEOFFLINE, (LPCTSTR)m_sTitle, (LPCTSTR)m_path.GetUIPathString());
448 else if (m_path.IsDirectory())
449 sTemp.Format(IDS_LOG_DLGTITLEOFFLINE, (LPCTSTR)m_sTitle, (LPCTSTR)m_path.GetWinPathString());
450 else
451 sTemp.Format(IDS_LOG_DLGTITLEOFFLINE, (LPCTSTR)m_sTitle, (LPCTSTR)m_path.GetFilename());
452 SetWindowText(sTemp);
454 else
456 if (m_path.IsUrl())
457 SetWindowText(m_sTitle + _T(" - ") + m_path.GetUIPathString());
458 else if (m_path.IsEmpty())
459 SetWindowText(m_sTitle + _T(" - ") + CString(_T("Whole Project")));
460 else if (m_path.IsDirectory())
461 SetWindowText(m_sTitle + _T(" - ") + m_path.GetWinPathString());
462 else
463 SetWindowText(m_sTitle + _T(" - ") + m_path.GetFilename());
467 void CLogDlg::CheckRegexpTooltip()
469 CWnd *pWnd = GetDlgItem(IDC_SEARCHEDIT);
470 // Since tooltip describes regexp features, show it only if regexps are enabled.
471 if (m_bFilterWithRegex)
473 m_tooltips.AddTool(pWnd, IDS_LOG_FILTER_REGEX_TT);
475 else
476 m_tooltips.DelTool(pWnd);
479 void CLogDlg::EnableOKButton()
481 if (m_bSelect)
483 // the dialog is used to select revisions
484 if (m_bSelectionMustBeSingle)
486 // enable OK button if only a single revision is selected
487 DialogEnableWindow(IDOK, (m_LogList.GetSelectedCount()==1));
489 else if (m_bSelectionMustBeContinuous)
490 DialogEnableWindow(IDOK, (m_LogList.GetSelectedCount()!=0)&&(m_LogList.IsSelectionContinuous()));
491 else
492 DialogEnableWindow(IDOK, m_LogList.GetSelectedCount()!=0);
494 else
495 DialogEnableWindow(IDOK, TRUE);
498 void CLogDlg::FillLogMessageCtrl(bool bShow /* = true*/)
500 // we fill here the log message rich edit control,
501 // and also populate the changed files list control
502 // according to the selected revision(s).
504 CRichEditCtrl * pMsgView = (CRichEditCtrl*)GetDlgItem(IDC_MSGVIEW);
505 // empty the log message view
506 pMsgView->SetWindowText(_T(" "));
507 // empty the changed files list
508 m_ChangedFileListCtrl.SetRedraw(FALSE);
509 // InterlockedExchange(&m_bNoDispUpdates, TRUE);
510 m_currentChangedArray = NULL;
511 //m_ChangedFileListCtrl.SetExtendedStyle ( LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER );
512 m_ChangedFileListCtrl.DeleteAllItems();
514 // if we're not here to really show a selected revision, just
515 // get out of here after clearing the views, which is what is intended
516 // if that flag is not set.
517 if (!bShow)
519 // force a redraw
520 m_ChangedFileListCtrl.Invalidate();
521 // InterlockedExchange(&m_bNoDispUpdates, FALSE);
522 m_ChangedFileListCtrl.SetRedraw(TRUE);
523 return;
526 // depending on how many revisions are selected, we have to do different
527 // tasks.
528 int selCount = m_LogList.GetSelectedCount();
529 if (selCount == 0)
531 // if nothing is selected, we have nothing more to do
532 // InterlockedExchange(&m_bNoDispUpdates, FALSE);
533 m_ChangedFileListCtrl.SetRedraw(TRUE);
534 return;
536 else if (selCount == 1)
538 // if one revision is selected, we have to fill the log message view
539 // with the corresponding log message, and also fill the changed files
540 // list fully.
541 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
542 int selIndex = m_LogList.GetNextSelectedItem(pos);
543 if (selIndex >= m_LogList.m_arShownList.GetCount())
545 // InterlockedExchange(&m_bNoDispUpdates, FALSE);
546 m_ChangedFileListCtrl.SetRedraw(TRUE);
547 return;
549 GitRev* pLogEntry = reinterpret_cast<GitRev *>(m_LogList.m_arShownList.GetAt(selIndex));
551 if(!pLogEntry->m_IsFull)
553 pMsgView->SetWindowText(_T("load ..."));
554 }else
556 // set the log message text
557 pMsgView->SetWindowText(_T("Commit:")+pLogEntry->m_CommitHash.ToString()+_T("\r\n\r\n"));
558 // turn bug ID's into links if the bugtraq: properties have been set
559 // and we can find a match of those in the log message
561 pMsgView->SetSel(-1,-1);
562 CHARFORMAT2 format;
563 SecureZeroMemory(&format, sizeof(CHARFORMAT2));
564 format.cbSize = sizeof(CHARFORMAT2);
565 format.dwMask = CFM_BOLD;
566 format.dwEffects = CFE_BOLD;
567 pMsgView->SendMessage(EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&format);
569 CString msg=_T("* ");
570 msg+=pLogEntry->m_Subject;
571 pMsgView->ReplaceSel(msg);
573 pMsgView->SetSel(-1,-1);
574 format.dwEffects = 0;
575 pMsgView->SendMessage(EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&format);
577 msg=_T("\n\n");
578 msg+=pLogEntry->m_Body;
579 pMsgView->ReplaceSel(msg);
581 CString text;
582 pMsgView->GetWindowText(text);
583 // the rich edit control doesn't count the CR char!
584 // to be exact: CRLF is treated as one char.
585 text.Replace(_T("\r"), _T(""));
587 m_ProjectProperties.FindBugID(text, pMsgView);
588 CAppUtils::FormatTextInRichEditControl(pMsgView);
590 int HidePaths=m_cHidePaths.GetState() & 0x0003;
591 CString matchpath=this->m_path.GetGitPathString();
593 for(int i=0;i<pLogEntry->m_Files.GetCount() && (!matchpath.IsEmpty());i++)
595 if( m_bWholeProject )
596 break;
598 ((CTGitPath&)pLogEntry->m_Files[i]).m_Action &= ~(CTGitPath::LOGACTIONS_HIDE|CTGitPath::LOGACTIONS_GRAY);
600 if(pLogEntry->m_Files[i].GetGitPathString().Left(matchpath.GetLength()) != matchpath)
602 if(HidePaths==BST_CHECKED)
603 ((CTGitPath&)pLogEntry->m_Files[i]).m_Action |= CTGitPath::LOGACTIONS_HIDE;
604 if(HidePaths==BST_INDETERMINATE)
605 ((CTGitPath&)pLogEntry->m_Files[i]).m_Action |= CTGitPath::LOGACTIONS_GRAY;
608 m_ChangedFileListCtrl.UpdateWithGitPathList(pLogEntry->m_Files);
609 m_ChangedFileListCtrl.m_CurrentVersion=pLogEntry->m_CommitHash;
610 m_ChangedFileListCtrl.Show(SVNSLC_SHOWVERSIONED);
612 m_ChangedFileListCtrl.SetRedraw(TRUE);
613 return;
617 else
619 // more than one revision is selected:
620 // the log message view must be emptied
621 // the changed files list contains all the changed paths from all
622 // selected revisions, with 'doubles' removed
623 m_currentChangedPathList = GetChangedPathsFromSelectedRevisions(true);
626 // redraw the views
627 // InterlockedExchange(&m_bNoDispUpdates, FALSE);
628 #if 0
629 if (m_currentChangedArray)
631 m_ChangedFileListCtrl.SetItemCountEx(m_currentChangedArray->GetCount());
632 m_ChangedFileListCtrl.RedrawItems(0, m_currentChangedArray->GetCount());
634 else if (m_currentChangedPathList.GetCount())
636 m_ChangedFileListCtrl.SetItemCountEx(m_currentChangedPathList.GetCount());
637 m_ChangedFileListCtrl.RedrawItems(0, m_currentChangedPathList.GetCount());
639 else
641 m_ChangedFileListCtrl.SetItemCountEx(0);
642 m_ChangedFileListCtrl.Invalidate();
644 #endif
645 // sort according to the settings
646 if (m_nSortColumnPathList > 0)
647 SetSortArrow(&m_ChangedFileListCtrl, m_nSortColumnPathList, m_bAscendingPathList);
648 else
649 SetSortArrow(&m_ChangedFileListCtrl, -1, false);
650 m_ChangedFileListCtrl.SetRedraw(TRUE);
654 void CLogDlg::OnBnClickedRefresh()
657 Refresh (true);
660 void CLogDlg::Refresh (bool /*autoGoOnline*/)
662 m_limit = 0;
663 m_LogList.Refresh();
664 FillLogMessageCtrl(false);
669 BOOL CLogDlg::Cancel()
671 return m_bCancelled;
674 void CLogDlg::SaveSplitterPos()
676 if (!IsIconic())
678 CRegDWORD regPos1 = CRegDWORD(_T("Software\\TortoiseGit\\TortoiseProc\\ResizableState\\LogDlgSizer1"));
679 CRegDWORD regPos2 = CRegDWORD(_T("Software\\TortoiseGit\\TortoiseProc\\ResizableState\\LogDlgSizer2"));
680 RECT rectSplitter;
681 m_wndSplitter1.GetWindowRect(&rectSplitter);
682 ScreenToClient(&rectSplitter);
683 regPos1 = rectSplitter.top;
684 m_wndSplitter2.GetWindowRect(&rectSplitter);
685 ScreenToClient(&rectSplitter);
686 regPos2 = rectSplitter.top;
690 void CLogDlg::OnCancel()
692 // canceling means stopping the working thread if it's still running.
693 if (this->IsThreadRunning())
695 m_LogList.TerminateThread();
697 UpdateData();
699 SaveSplitterPos();
700 __super::OnCancel();
703 CString CLogDlg::MakeShortMessage(const CString& message)
705 bool bFoundShort = true;
706 CString sShortMessage = m_ProjectProperties.GetLogSummary(message);
707 if (sShortMessage.IsEmpty())
709 bFoundShort = false;
710 sShortMessage = message;
712 // Remove newlines and tabs 'cause those are not shown nicely in the list control
713 sShortMessage.Replace(_T("\r"), _T(""));
714 sShortMessage.Replace(_T("\t"), _T(" "));
716 // Suppose the first empty line separates 'summary' from the rest of the message.
717 int found = sShortMessage.Find(_T("\n\n"));
718 // To avoid too short 'short' messages
719 // (e.g. if the message looks something like "Bugfix:\n\n*done this\n*done that")
720 // only use the empty newline as a separator if it comes after at least 15 chars.
721 if ((!bFoundShort)&&(found >= 15))
723 sShortMessage = sShortMessage.Left(found);
725 sShortMessage.Replace('\n', ' ');
726 return sShortMessage;
729 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*/)
731 #if 0
732 if (rev == SVN_INVALID_REVNUM)
734 m_childCounter--;
735 return TRUE;
738 // this is the callback function which receives the data for every revision we ask the log for
739 // we store this information here one by one.
740 m_logcounter += 1;
741 if (m_startrev == -1)
742 m_startrev = rev;
743 if (m_limit != 0)
745 m_limitcounter--;
746 m_LogProgress.SetPos(m_limit - m_limitcounter);
748 else if (m_startrev.IsNumber() && m_startrev.IsNumber())
749 m_LogProgress.SetPos((git_revnum_t)m_startrev-rev+(git_revnum_t)m_endrev);
750 __time64_t ttime = time/1000000L;
751 if (m_tTo < (DWORD)ttime)
752 m_tTo = (DWORD)ttime;
753 if (m_tFrom > (DWORD)ttime)
754 m_tFrom = (DWORD)ttime;
755 if ((m_lowestRev > rev)||(m_lowestRev < 0))
756 m_lowestRev = rev;
757 // Add as many characters from the log message to the list control
758 PLOGENTRYDATA pLogItem = new LOGENTRYDATA;
759 pLogItem->bCopies = !!copies;
761 // find out if this item was copied in the revision
762 BOOL copiedself = FALSE;
763 if (copies)
765 for (INT_PTR cpPathIndex = 0; cpPathIndex < cpaths->GetCount(); ++cpPathIndex)
767 LogChangedPath * cpath = cpaths->GetAt(cpPathIndex);
768 if (!cpath->sCopyFromPath.IsEmpty() && (cpath->sPath.Compare(m_sSelfRelativeURL) == 0))
770 // note: this only works if the log is fetched top-to-bottom
771 // but since we do that, it shouldn't be a problem
772 m_sSelfRelativeURL = cpath->sCopyFromPath;
773 copiedself = TRUE;
774 break;
778 pLogItem->bCopiedSelf = copiedself;
779 pLogItem->tmDate = ttime;
780 pLogItem->sAuthor = author;
781 pLogItem->sDate = date;
782 pLogItem->sShortMessage = MakeShortMessage(message);
783 pLogItem->dwFileChanges = filechanges;
784 pLogItem->actions = actions;
785 pLogItem->haschildren = haschildren;
786 pLogItem->childStackDepth = m_childCounter;
787 m_maxChild = max(m_childCounter, m_maxChild);
788 if (haschildren)
789 m_childCounter++;
790 pLogItem->sBugIDs = m_ProjectProperties.FindBugID(message).Trim();
792 // split multi line log entries and concatenate them
793 // again but this time with \r\n as line separators
794 // so that the edit control recognizes them
797 if (message.GetLength()>0)
799 m_sMessageBuf = message;
800 m_sMessageBuf.Replace(_T("\n\r"), _T("\n"));
801 m_sMessageBuf.Replace(_T("\r\n"), _T("\n"));
802 if (m_sMessageBuf.Right(1).Compare(_T("\n"))==0)
803 m_sMessageBuf = m_sMessageBuf.Left(m_sMessageBuf.GetLength()-1);
805 else
806 m_sMessageBuf.Empty();
807 pLogItem->sMessage = m_sMessageBuf;
808 pLogItem->Rev = rev;
810 // move-construct path array
812 pLogItem->pArChangedPaths = new LogChangedPathArray (*cpaths);
813 cpaths->RemoveAll();
815 catch (CException * e)
817 ::MessageBox(NULL, _T("not enough memory!"), _T("TortoiseGit"), MB_ICONERROR);
818 e->Delete();
819 m_bCancelled = TRUE;
821 m_logEntries.push_back(pLogItem);
822 m_arShownList.Add(pLogItem);
823 #endif
824 return TRUE;
827 GitRev g_rev;
828 //this is the thread function which calls the subversion function
833 void CLogDlg::CopyChangedSelectionToClipBoard()
836 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
837 if (pos == NULL)
838 return; // nothing is selected, get out of here
840 CString sPaths;
842 // CGitRev* pLogEntry = reinterpret_cast<CGitRev* >(m_LogList.m_arShownList.GetAt(m_LogList.GetNextSelectedItem(pos)));
843 // if (pos)
845 POSITION pos = m_ChangedFileListCtrl.GetFirstSelectedItemPosition();
846 while (pos)
848 int nItem = m_ChangedFileListCtrl.GetNextSelectedItem(pos);
849 CTGitPath *path = (CTGitPath*)m_ChangedFileListCtrl.GetItemData(nItem);
850 if(path)
851 sPaths += path->GetGitPathString();
852 sPaths += _T("\r\n");
855 #if 0
856 else
858 // only one revision is selected in the log dialog top pane
859 // but multiple items could be selected in the changed items list
860 POSITION pos = m_ChangedFileListCtrl.GetFirstSelectedItemPosition();
861 while (pos)
863 int nItem = m_ChangedFileListCtrl.GetNextSelectedItem(pos);
864 LogChangedPath * changedlogpath = pLogEntry->pArChangedPaths->GetAt(nItem);
866 if ((m_cHidePaths.GetState() & 0x0003)==BST_CHECKED)
868 // some items are hidden! So find out which item the user really selected
869 INT_PTR selRealIndex = -1;
870 for (INT_PTR hiddenindex=0; hiddenindex<pLogEntry->pArChangedPaths->GetCount(); ++hiddenindex)
872 if (pLogEntry->pArChangedPaths->GetAt(hiddenindex)->sPath.Left(m_sRelativeRoot.GetLength()).Compare(m_sRelativeRoot)==0)
873 selRealIndex++;
874 if (selRealIndex == nItem)
876 changedlogpath = pLogEntry->pArChangedPaths->GetAt(hiddenindex);
877 break;
881 if (changedlogpath)
883 sPaths += changedlogpath->sPath;
884 sPaths += _T("\r\n");
888 #endif
889 sPaths.Trim();
890 CStringUtils::WriteAsciiStringToClipboard(sPaths, GetSafeHwnd());
894 BOOL CLogDlg::IsDiffPossible(LogChangedPath * /*changedpath*/, git_revnum_t rev)
896 #if 0
897 CString added, deleted;
898 if (changedpath == NULL)
899 return false;
901 if ((rev > 1)&&(changedpath->action != LOGACTIONS_DELETED))
903 if (changedpath->action == LOGACTIONS_ADDED) // file is added
905 if (changedpath->lCopyFromRev == 0)
906 return FALSE; // but file was not added with history
908 return TRUE;
910 #endif
911 return FALSE;
914 void CLogDlg::OnContextMenu(CWnd* pWnd, CPoint point)
916 // we have two separate context menus:
917 // one shown on the log message list control,
918 // the other shown in the changed-files list control
919 int selCount = m_LogList.GetSelectedCount();
920 if (pWnd == &m_LogList)
922 //ShowContextMenuForRevisions(pWnd, point);
924 else if (pWnd == &m_ChangedFileListCtrl)
926 //ShowContextMenuForChangedpaths(pWnd, point);
928 else if ((selCount == 1)&&(pWnd == GetDlgItem(IDC_MSGVIEW)))
930 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
931 int selIndex = -1;
932 if (pos)
933 selIndex = m_LogList.GetNextSelectedItem(pos);
934 if ((point.x == -1) && (point.y == -1))
936 CRect rect;
937 GetDlgItem(IDC_MSGVIEW)->GetClientRect(&rect);
938 ClientToScreen(&rect);
939 point = rect.CenterPoint();
941 CString sMenuItemText;
942 CMenu popup;
943 if (popup.CreatePopupMenu())
945 // add the 'default' entries
946 sMenuItemText.LoadString(IDS_SCIEDIT_COPY);
947 popup.AppendMenu(MF_STRING | MF_ENABLED, WM_COPY, sMenuItemText);
948 sMenuItemText.LoadString(IDS_SCIEDIT_SELECTALL);
949 popup.AppendMenu(MF_STRING | MF_ENABLED, EM_SETSEL, sMenuItemText);
951 //if (selIndex >= 0)
953 // popup.AppendMenu(MF_SEPARATOR);
954 // sMenuItemText.LoadString(IDS_LOG_POPUP_EDITLOG);
955 // popup.AppendMenu(MF_STRING | MF_ENABLED, CGitLogList::ID_EDITAUTHOR, sMenuItemText);
958 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
959 switch (cmd)
961 case 0:
962 break; // no command selected
963 case EM_SETSEL:
964 case WM_COPY:
965 ::SendMessage(GetDlgItem(IDC_MSGVIEW)->GetSafeHwnd(), cmd, 0, -1);
966 break;
967 case CGitLogList::ID_EDITAUTHOR:
968 EditLogMessage(selIndex);
969 break;
976 LRESULT CLogDlg::OnFindDialogMessage(WPARAM /*wParam*/, LPARAM /*lParam*/)
978 #if 0
979 ASSERT(m_pFindDialog != NULL);
981 if (m_pFindDialog->IsTerminating())
983 // invalidate the handle identifying the dialog box.
984 m_pFindDialog = NULL;
985 return 0;
988 if(m_pFindDialog->FindNext())
990 //read data from dialog
991 CString FindText = m_pFindDialog->GetFindString();
992 bool bMatchCase = (m_pFindDialog->MatchCase() == TRUE);
993 bool bFound = false;
994 tr1::wregex pat;
995 bool bRegex = ValidateRegexp(FindText, pat, bMatchCase);
997 tr1::regex_constants::match_flag_type flags = tr1::regex_constants::match_not_null;
999 int i;
1000 for (i = this->m_nSearchIndex; i<m_arShownList.GetCount()&&!bFound; i++)
1002 if (bRegex)
1004 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(i));
1006 if (regex_search(wstring((LPCTSTR)pLogEntry->sMessage), pat, flags))
1008 bFound = true;
1009 break;
1011 LogChangedPathArray * cpatharray = pLogEntry->pArChangedPaths;
1012 for (INT_PTR cpPathIndex = 0; cpPathIndex<cpatharray->GetCount(); ++cpPathIndex)
1014 LogChangedPath * cpath = cpatharray->GetAt(cpPathIndex);
1015 if (regex_search(wstring((LPCTSTR)cpath->sCopyFromPath), pat, flags))
1017 bFound = true;
1018 --i;
1019 break;
1021 if (regex_search(wstring((LPCTSTR)cpath->sPath), pat, flags))
1023 bFound = true;
1024 --i;
1025 break;
1029 else
1031 if (bMatchCase)
1033 if (m_logEntries[i]->sMessage.Find(FindText) >= 0)
1035 bFound = true;
1036 break;
1038 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(i));
1039 LogChangedPathArray * cpatharray = pLogEntry->pArChangedPaths;
1040 for (INT_PTR cpPathIndex = 0; cpPathIndex<cpatharray->GetCount(); ++cpPathIndex)
1042 LogChangedPath * cpath = cpatharray->GetAt(cpPathIndex);
1043 if (cpath->sCopyFromPath.Find(FindText)>=0)
1045 bFound = true;
1046 --i;
1047 break;
1049 if (cpath->sPath.Find(FindText)>=0)
1051 bFound = true;
1052 --i;
1053 break;
1057 else
1059 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(i));
1060 CString msg = pLogEntry->sMessage;
1061 msg = msg.MakeLower();
1062 CString find = FindText.MakeLower();
1063 if (msg.Find(find) >= 0)
1065 bFound = TRUE;
1066 break;
1068 LogChangedPathArray * cpatharray = pLogEntry->pArChangedPaths;
1069 for (INT_PTR cpPathIndex = 0; cpPathIndex<cpatharray->GetCount(); ++cpPathIndex)
1071 LogChangedPath * cpath = cpatharray->GetAt(cpPathIndex);
1072 CString lowerpath = cpath->sCopyFromPath;
1073 lowerpath.MakeLower();
1074 if (lowerpath.Find(find)>=0)
1076 bFound = TRUE;
1077 --i;
1078 break;
1080 lowerpath = cpath->sPath;
1081 lowerpath.MakeLower();
1082 if (lowerpath.Find(find)>=0)
1084 bFound = TRUE;
1085 --i;
1086 break;
1091 } // for (i = this->m_nSearchIndex; i<m_arShownList.GetItemCount()&&!bFound; i++)
1092 if (bFound)
1094 this->m_nSearchIndex = (i+1);
1095 m_LogList.EnsureVisible(i, FALSE);
1096 m_LogList.SetItemState(m_LogList.GetSelectionMark(), 0, LVIS_SELECTED);
1097 m_LogList.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
1098 m_LogList.SetSelectionMark(i);
1099 FillLogMessageCtrl();
1100 UpdateData(FALSE);
1101 m_nSearchIndex++;
1102 if (m_nSearchIndex >= m_arShownList.GetCount())
1103 m_nSearchIndex = (int)m_arShownList.GetCount()-1;
1105 } // if(m_pFindDialog->FindNext())
1106 UpdateLogInfoLabel();
1107 #endif
1108 return 0;
1111 void CLogDlg::OnOK()
1113 // since the log dialog is also used to select revisions for other
1114 // dialogs, we have to do some work before closing this dialog
1115 if (GetFocus() != GetDlgItem(IDOK))
1116 return; // if the "OK" button doesn't have the focus, do nothing: this prevents closing the dialog when pressing enter
1119 if (this->IsThreadRunning())
1121 m_LogList.TerminateThread();
1123 UpdateData();
1124 // check that one and only one row is selected
1125 if (m_LogList.GetSelectedCount() == 1)
1127 // get the selected row
1128 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
1129 int selIndex = m_LogList.GetNextSelectedItem(pos);
1130 if (selIndex < m_LogList.m_arShownList.GetCount())
1132 // all ok, pick up the revision
1133 GitRev* pLogEntry = reinterpret_cast<GitRev *>(m_LogList.m_arShownList.GetAt(selIndex));
1134 // extract the hash
1135 m_sSelectedHash = pLogEntry->m_CommitHash;
1138 UpdateData(FALSE);
1139 SaveSplitterPos();
1140 __super::OnOK();
1142 #if 0
1143 if (!GetDlgItem(IDOK)->IsWindowVisible() && GetFocus() != GetDlgItem(IDCANCEL))
1144 return; // the Cancel button works as the OK button. But if the cancel button has not the focus, do nothing.
1146 CString temp;
1147 CString buttontext;
1148 GetDlgItemText(IDOK, buttontext);
1149 temp.LoadString(IDS_MSGBOX_CANCEL);
1150 if (temp.Compare(buttontext) != 0)
1151 __super::OnOK(); // only exit if the button text matches, and that will match only if the thread isn't running anymore
1152 m_bCancelled = TRUE;
1153 m_selectedRevs.Clear();
1154 m_selectedRevsOneRange.Clear();
1155 if (m_pNotifyWindow)
1157 int selIndex = m_LogList.GetSelectionMark();
1158 if (selIndex >= 0)
1160 PLOGENTRYDATA pLogEntry = NULL;
1161 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
1162 pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(m_LogList.GetNextSelectedItem(pos)));
1163 m_selectedRevs.AddRevision(pLogEntry->Rev);
1164 git_revnum_t lowerRev = pLogEntry->Rev;
1165 git_revnum_t higherRev = lowerRev;
1166 while (pos)
1168 pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(m_LogList.GetNextSelectedItem(pos)));
1169 git_revnum_t rev = pLogEntry->Rev;
1170 m_selectedRevs.AddRevision(pLogEntry->Rev);
1171 if (lowerRev > rev)
1172 lowerRev = rev;
1173 if (higherRev < rev)
1174 higherRev = rev;
1176 if (m_sFilterText.IsEmpty() && m_nSortColumn == 0 && IsSelectionContinuous())
1178 m_selectedRevsOneRange.AddRevRange(lowerRev, higherRev);
1180 BOOL bSentMessage = FALSE;
1181 if (m_LogList.GetSelectedCount() == 1)
1183 // if only one revision is selected, check if the path/url with which the dialog was started
1184 // was directly affected in that revision. If it was, then check if our path was copied from somewhere.
1185 // if it was copied, use the copy from revision as lowerRev
1186 if ((pLogEntry)&&(pLogEntry->pArChangedPaths)&&(lowerRev == higherRev))
1188 CString sUrl = m_path.GetGitPathString();
1189 if (!m_path.IsUrl())
1191 sUrl = GetURLFromPath(m_path);
1193 sUrl = sUrl.Mid(m_sRepositoryRoot.GetLength());
1194 for (int cp = 0; cp < pLogEntry->pArChangedPaths->GetCount(); ++cp)
1196 LogChangedPath * pData = pLogEntry->pArChangedPaths->GetAt(cp);
1197 if (pData)
1199 if (sUrl.Compare(pData->sPath) == 0)
1201 if (!pData->sCopyFromPath.IsEmpty())
1203 lowerRev = pData->lCopyFromRev;
1204 m_pNotifyWindow->SendMessage(WM_REVSELECTED, m_wParam & (MERGE_REVSELECTSTART), lowerRev);
1205 m_pNotifyWindow->SendMessage(WM_REVSELECTED, m_wParam & (MERGE_REVSELECTEND), higherRev);
1206 m_pNotifyWindow->SendMessage(WM_REVLIST, m_selectedRevs.GetCount(), (LPARAM)&m_selectedRevs);
1207 bSentMessage = TRUE;
1214 if ( !bSentMessage )
1216 m_pNotifyWindow->SendMessage(WM_REVSELECTED, m_wParam & (MERGE_REVSELECTSTART | MERGE_REVSELECTMINUSONE), lowerRev);
1217 m_pNotifyWindow->SendMessage(WM_REVSELECTED, m_wParam & (MERGE_REVSELECTEND | MERGE_REVSELECTMINUSONE), higherRev);
1218 m_pNotifyWindow->SendMessage(WM_REVLIST, m_selectedRevs.GetCount(), (LPARAM)&m_selectedRevs);
1219 if (m_selectedRevsOneRange.GetCount())
1220 m_pNotifyWindow->SendMessage(WM_REVLISTONERANGE, 0, (LPARAM)&m_selectedRevsOneRange);
1224 UpdateData();
1225 CRegDWORD reg = CRegDWORD(_T("Software\\TortoiseGit\\ShowAllEntry"));
1226 reg = m_btnShow.GetCurrentEntry();
1227 SaveSplitterPos();
1228 #endif
1231 void CLogDlg::OnNMDblclkChangedFileList(NMHDR * /*pNMHDR*/, LRESULT *pResult)
1233 // a double click on an entry in the changed-files list has happened
1234 *pResult = 0;
1236 DiffSelectedFile();
1239 void CLogDlg::DiffSelectedFile()
1241 #if 0
1242 if (m_bThreadRunning)
1243 return;
1244 UpdateLogInfoLabel();
1245 INT_PTR selIndex = m_ChangedFileListCtrl.GetSelectionMark();
1246 if (selIndex < 0)
1247 return;
1248 if (m_ChangedFileListCtrl.GetSelectedCount() == 0)
1249 return;
1250 // find out if there's an entry selected in the log list
1251 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
1252 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(m_LogList.GetNextSelectedItem(pos)));
1253 git_revnum_t rev1 = pLogEntry->Rev;
1254 git_revnum_t rev2 = rev1;
1255 if (pos)
1257 while (pos)
1259 // there's at least a second entry selected in the log list: several revisions selected!
1260 pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(m_LogList.GetNextSelectedItem(pos)));
1261 if (pLogEntry)
1263 rev1 = max(rev1,(long)pLogEntry->Rev);
1264 rev2 = min(rev2,(long)pLogEntry->Rev);
1267 rev2--;
1268 // now we have both revisions selected in the log list, so we can do a diff of the selected
1269 // entry in the changed files list with these two revisions.
1270 DoDiffFromLog(selIndex, rev1, rev2, false, false);
1272 else
1274 rev2 = rev1-1;
1275 // nothing or only one revision selected in the log list
1276 LogChangedPath * changedpath = pLogEntry->pArChangedPaths->GetAt(selIndex);
1278 if ((m_cHidePaths.GetState() & 0x0003)==BST_CHECKED)
1280 // some items are hidden! So find out which item the user really clicked on
1281 INT_PTR selRealIndex = -1;
1282 for (INT_PTR hiddenindex=0; hiddenindex<pLogEntry->pArChangedPaths->GetCount(); ++hiddenindex)
1284 if (pLogEntry->pArChangedPaths->GetAt(hiddenindex)->sPath.Left(m_sRelativeRoot.GetLength()).Compare(m_sRelativeRoot)==0)
1285 selRealIndex++;
1286 if (selRealIndex == selIndex)
1288 selIndex = hiddenindex;
1289 changedpath = pLogEntry->pArChangedPaths->GetAt(selIndex);
1290 break;
1295 if (IsDiffPossible(changedpath, rev1))
1297 // diffs with renamed files are possible
1298 if ((changedpath)&&(!changedpath->sCopyFromPath.IsEmpty()))
1299 rev2 = changedpath->lCopyFromRev;
1300 else
1302 // if the path was modified but the parent path was 'added with history'
1303 // then we have to use the copy from revision of the parent path
1304 CTGitPath cpath = CTGitPath(changedpath->sPath);
1305 for (int flist = 0; flist < pLogEntry->pArChangedPaths->GetCount(); ++flist)
1307 CTGitPath p = CTGitPath(pLogEntry->pArChangedPaths->GetAt(flist)->sPath);
1308 if (p.IsAncestorOf(cpath))
1310 if (!pLogEntry->pArChangedPaths->GetAt(flist)->sCopyFromPath.IsEmpty())
1311 rev2 = pLogEntry->pArChangedPaths->GetAt(flist)->lCopyFromRev;
1315 DoDiffFromLog(selIndex, rev1, rev2, false, false);
1317 else
1319 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, CTGitPath(changedpath->sPath));
1320 CTGitPath tempfile2 = CTempFiles::Instance().GetTempFilePath(false, CTGitPath(changedpath->sPath));
1321 GitRev r = rev1;
1322 // deleted files must be opened from the revision before the deletion
1323 if (changedpath->action == LOGACTIONS_DELETED)
1324 r = rev1-1;
1325 m_bCancelled = false;
1327 CProgressDlg progDlg;
1328 progDlg.SetTitle(IDS_APPNAME);
1329 progDlg.SetAnimation(IDR_DOWNLOAD);
1330 CString sInfoLine;
1331 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, (LPCTSTR)(m_sRepositoryRoot + changedpath->sPath), (LPCTSTR)r.ToString());
1332 progDlg.SetLine(1, sInfoLine, true);
1333 SetAndClearProgressInfo(&progDlg);
1334 progDlg.ShowModeless(m_hWnd);
1336 if (!Cat(CTGitPath(m_sRepositoryRoot + changedpath->sPath), r, r, tempfile))
1338 m_bCancelled = false;
1339 if (!Cat(CTGitPath(m_sRepositoryRoot + changedpath->sPath), GitRev::REV_HEAD, r, tempfile))
1341 progDlg.Stop();
1342 SetAndClearProgressInfo((HWND)NULL);
1343 CMessageBox::Show(m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1344 return;
1347 progDlg.Stop();
1348 SetAndClearProgressInfo((HWND)NULL);
1350 CString sName1, sName2;
1351 sName1.Format(_T("%s - Revision %ld"), (LPCTSTR)CPathUtils::GetFileNameFromPath(changedpath->sPath), (git_revnum_t)rev1);
1352 sName2.Format(_T("%s - Revision %ld"), (LPCTSTR)CPathUtils::GetFileNameFromPath(changedpath->sPath), (git_revnum_t)rev1-1);
1353 CAppUtils::DiffFlags flags;
1354 flags.AlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
1355 if (changedpath->action == LOGACTIONS_DELETED)
1356 CAppUtils::StartExtDiff(tempfile, tempfile2, sName2, sName1, flags);
1357 else
1358 CAppUtils::StartExtDiff(tempfile2, tempfile, sName2, sName1, flags);
1361 #endif
1365 void CLogDlg::DoDiffFromLog(INT_PTR selIndex, GitRev* rev1, GitRev* rev2, bool blame, bool unified)
1367 DialogEnableWindow(IDOK, FALSE);
1368 // SetPromptApp(&theApp);
1369 theApp.DoWaitCursor(1);
1371 CString temppath;
1372 GetTempPath(temppath);
1374 CString file1;
1375 file1.Format(_T("%s%s_%s%s"),
1376 temppath,
1377 (*m_currentChangedArray)[selIndex].GetBaseFilename(),
1378 rev1->m_CommitHash.ToString().Left(6),
1379 (*m_currentChangedArray)[selIndex].GetFileExtension());
1381 CString file2;
1382 file2.Format(_T("%s\\%s_%s%s"),
1383 temppath,
1384 (*m_currentChangedArray)[selIndex].GetBaseFilename(),
1385 rev2->m_CommitHash.ToString().Left(6),
1386 (*m_currentChangedArray)[selIndex].GetFileExtension());
1388 CString cmd;
1390 cmd.Format(_T("git.exe cat-file -p %s:%s"),rev1->m_CommitHash,(*m_currentChangedArray)[selIndex].GetGitPathString());
1391 g_Git.RunLogFile(cmd,file1);
1392 cmd.Format(_T("git.exe cat-file -p %s:%s"),rev2->m_CommitHash,(*m_currentChangedArray)[selIndex].GetGitPathString());
1393 g_Git.RunLogFile(cmd,file2);
1395 CAppUtils::DiffFlags flags;
1396 CAppUtils::StartExtDiff(file1,file2,_T("A"),_T("B"),flags);
1398 #if 0
1399 //get the filename
1400 CString filepath;
1401 if (Git::PathIsURL(m_path))
1403 filepath = m_path.GetGitPathString();
1405 else
1407 filepath = GetURLFromPath(m_path);
1408 if (filepath.IsEmpty())
1410 theApp.DoWaitCursor(-1);
1411 CString temp;
1412 temp.Format(IDS_ERR_NOURLOFFILE, (LPCTSTR)filepath);
1413 CMessageBox::Show(this->m_hWnd, temp, _T("TortoiseGit"), MB_ICONERROR);
1414 TRACE(_T("could not retrieve the URL of the file!\n"));
1415 EnableOKButton();
1416 theApp.DoWaitCursor(-11);
1417 return; //exit
1420 m_bCancelled = FALSE;
1421 filepath = GetRepositoryRoot(CTGitPath(filepath));
1423 CString firstfile, secondfile;
1424 if (m_LogList.GetSelectedCount()==1)
1426 int s = m_LogList.GetSelectionMark();
1427 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(s));
1428 LogChangedPath * changedpath = pLogEntry->pArChangedPaths->GetAt(selIndex);
1429 firstfile = changedpath->sPath;
1430 secondfile = firstfile;
1431 if ((rev2 == rev1-1)&&(changedpath->lCopyFromRev > 0)) // is it an added file with history?
1433 secondfile = changedpath->sCopyFromPath;
1434 rev2 = changedpath->lCopyFromRev;
1437 else
1439 firstfile = m_currentChangedPathList[selIndex].GetGitPathString();
1440 secondfile = firstfile;
1443 firstfile = filepath + firstfile.Trim();
1444 secondfile = filepath + secondfile.Trim();
1446 GitDiff diff(this, this->m_hWnd, true);
1447 diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
1448 diff.SetHEADPeg(m_LogRevision);
1449 if (unified)
1451 if (PromptShown())
1452 diff.ShowUnifiedDiff(CTGitPath(secondfile), rev2, CTGitPath(firstfile), rev1);
1453 else
1454 CAppUtils::StartShowUnifiedDiff(m_hWnd, CTGitPath(secondfile), rev2, CTGitPath(firstfile), rev1, GitRev(), m_LogRevision);
1456 else
1458 if (diff.ShowCompare(CTGitPath(secondfile), rev2, CTGitPath(firstfile), rev1, GitRev(), false, blame))
1460 if (firstfile.Compare(secondfile)==0)
1462 git_revnum_t baseRev = 0;
1463 diff.DiffProps(CTGitPath(firstfile), rev2, rev1, baseRev);
1468 #endif
1470 theApp.DoWaitCursor(-1);
1471 EnableOKButton();
1474 BOOL CLogDlg::Open(bool /*bOpenWith*/,CString changedpath, git_revnum_t rev)
1476 #if 0
1477 DialogEnableWindow(IDOK, FALSE);
1478 SetPromptApp(&theApp);
1479 theApp.DoWaitCursor(1);
1480 CString filepath;
1481 if (Git::PathIsURL(m_path))
1483 filepath = m_path.GetGitPathString();
1485 else
1487 filepath = GetURLFromPath(m_path);
1488 if (filepath.IsEmpty())
1490 theApp.DoWaitCursor(-1);
1491 CString temp;
1492 temp.Format(IDS_ERR_NOURLOFFILE, (LPCTSTR)filepath);
1493 CMessageBox::Show(this->m_hWnd, temp, _T("TortoiseGit"), MB_ICONERROR);
1494 TRACE(_T("could not retrieve the URL of the file!\n"));
1495 EnableOKButton();
1496 return FALSE;
1499 m_bCancelled = false;
1500 filepath = GetRepositoryRoot(CTGitPath(filepath));
1501 filepath += changedpath;
1503 CProgressDlg progDlg;
1504 progDlg.SetTitle(IDS_APPNAME);
1505 progDlg.SetAnimation(IDR_DOWNLOAD);
1506 CString sInfoLine;
1507 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, (LPCTSTR)filepath, (LPCTSTR)GitRev(rev).ToString());
1508 progDlg.SetLine(1, sInfoLine, true);
1509 SetAndClearProgressInfo(&progDlg);
1510 progDlg.ShowModeless(m_hWnd);
1512 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, CTGitPath(filepath), rev);
1513 m_bCancelled = false;
1514 if (!Cat(CTGitPath(filepath), GitRev(rev), rev, tempfile))
1516 progDlg.Stop();
1517 SetAndClearProgressInfo((HWND)NULL);
1518 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1519 EnableOKButton();
1520 theApp.DoWaitCursor(-1);
1521 return FALSE;
1523 progDlg.Stop();
1524 SetAndClearProgressInfo((HWND)NULL);
1525 SetFileAttributes(tempfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1526 if (!bOpenWith)
1528 int ret = (int)ShellExecute(this->m_hWnd, NULL, tempfile.GetWinPath(), NULL, NULL, SW_SHOWNORMAL);
1529 if (ret <= HINSTANCE_ERROR)
1530 bOpenWith = true;
1532 if (bOpenWith)
1534 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
1535 cmd += tempfile.GetWinPathString() + _T(" ");
1536 CAppUtils::LaunchApplication(cmd, NULL, false);
1538 EnableOKButton();
1539 theApp.DoWaitCursor(-1);
1540 #endif
1541 return TRUE;
1544 void CLogDlg::EditAuthor(const CLogDataVector& /*logs*/)
1546 #if 0
1547 CString url;
1548 CString name;
1549 if (logs.size() == 0)
1550 return;
1551 DialogEnableWindow(IDOK, FALSE);
1552 SetPromptApp(&theApp);
1553 theApp.DoWaitCursor(1);
1554 if (Git::PathIsURL(m_path))
1555 url = m_path.GetGitPathString();
1556 else
1558 url = GetURLFromPath(m_path);
1560 name = Git_PROP_REVISION_AUTHOR;
1562 CString value = RevPropertyGet(name, CTGitPath(url), logs[0]->Rev);
1563 CString sOldValue = value;
1564 value.Replace(_T("\n"), _T("\r\n"));
1565 CInputDlg dlg(this);
1566 dlg.m_sHintText.LoadString(IDS_LOG_AUTHOR);
1567 dlg.m_sInputText = value;
1568 dlg.m_sTitle.LoadString(IDS_LOG_AUTHOREDITTITLE);
1569 dlg.m_pProjectProperties = &m_ProjectProperties;
1570 dlg.m_bUseLogWidth = false;
1571 if (dlg.DoModal() == IDOK)
1573 dlg.m_sInputText.Replace(_T("\r"), _T(""));
1575 LogCache::CCachedLogInfo* toUpdate
1576 = GetLogCache (CTGitPath (m_sRepositoryRoot));
1578 CProgressDlg progDlg;
1579 progDlg.SetTitle(IDS_APPNAME);
1580 progDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
1581 progDlg.SetTime(true);
1582 progDlg.SetShowProgressBar(true);
1583 progDlg.ShowModeless(m_hWnd);
1584 for (DWORD i=0; i<logs.size(); ++i)
1586 if (!RevPropertySet(name, dlg.m_sInputText, sOldValue, CTGitPath(url), logs[i]->Rev))
1588 progDlg.Stop();
1589 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1590 break;
1592 else
1595 logs[i]->sAuthor = dlg.m_sInputText;
1596 m_LogList.Invalidate();
1598 // update the log cache
1600 if (toUpdate != NULL)
1602 // log caching is active
1604 LogCache::CCachedLogInfo newInfo;
1605 newInfo.Insert ( logs[i]->Rev
1606 , (const char*) CUnicodeUtils::GetUTF8 (logs[i]->sAuthor)
1607 , ""
1609 , LogCache::CRevisionInfoContainer::HAS_AUTHOR);
1611 toUpdate->Update (newInfo);
1614 progDlg.SetProgress64(i, logs.size());
1616 progDlg.Stop();
1618 theApp.DoWaitCursor(-1);
1619 EnableOKButton();
1620 #endif
1623 void CLogDlg::EditLogMessage(int /*index*/)
1625 #if 0
1626 CString url;
1627 CString name;
1628 DialogEnableWindow(IDOK, FALSE);
1629 SetPromptApp(&theApp);
1630 theApp.DoWaitCursor(1);
1631 if (Git::PathIsURL(m_path))
1632 url = m_path.GetGitPathString();
1633 else
1635 url = GetURLFromPath(m_path);
1637 name = Git_PROP_REVISION_LOG;
1639 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(index));
1640 m_bCancelled = FALSE;
1641 CString value = RevPropertyGet(name, CTGitPath(url), pLogEntry->Rev);
1642 CString sOldValue = value;
1643 value.Replace(_T("\n"), _T("\r\n"));
1644 CInputDlg dlg(this);
1645 dlg.m_sHintText.LoadString(IDS_LOG_MESSAGE);
1646 dlg.m_sInputText = value;
1647 dlg.m_sTitle.LoadString(IDS_LOG_MESSAGEEDITTITLE);
1648 dlg.m_pProjectProperties = &m_ProjectProperties;
1649 dlg.m_bUseLogWidth = true;
1650 if (dlg.DoModal() == IDOK)
1652 dlg.m_sInputText.Replace(_T("\r"), _T(""));
1653 if (!RevPropertySet(name, dlg.m_sInputText, sOldValue, CTGitPath(url), pLogEntry->Rev))
1655 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1657 else
1659 pLogEntry->sShortMessage = MakeShortMessage(dlg.m_sInputText);
1660 // split multi line log entries and concatenate them
1661 // again but this time with \r\n as line separators
1662 // so that the edit control recognizes them
1663 if (dlg.m_sInputText.GetLength()>0)
1665 m_sMessageBuf = dlg.m_sInputText;
1666 dlg.m_sInputText.Replace(_T("\n\r"), _T("\n"));
1667 dlg.m_sInputText.Replace(_T("\r\n"), _T("\n"));
1668 if (dlg.m_sInputText.Right(1).Compare(_T("\n"))==0)
1669 dlg.m_sInputText = dlg.m_sInputText.Left(dlg.m_sInputText.GetLength()-1);
1671 else
1672 dlg.m_sInputText.Empty();
1673 pLogEntry->sMessage = dlg.m_sInputText;
1674 pLogEntry->sBugIDs = m_ProjectProperties.FindBugID(dlg.m_sInputText);
1675 CWnd * pMsgView = GetDlgItem(IDC_MSGVIEW);
1676 pMsgView->SetWindowText(_T(" "));
1677 pMsgView->SetWindowText(dlg.m_sInputText);
1678 m_ProjectProperties.FindBugID(dlg.m_sInputText, pMsgView);
1679 m_LogList.Invalidate();
1681 // update the log cache
1683 LogCache::CCachedLogInfo* toUpdate
1684 = GetLogCache (CTGitPath (m_sRepositoryRoot));
1685 if (toUpdate != NULL)
1687 // log caching is active
1689 LogCache::CCachedLogInfo newInfo;
1690 newInfo.Insert ( pLogEntry->Rev
1691 , ""
1692 , (const char*) CUnicodeUtils::GetUTF8 (pLogEntry->sMessage)
1694 , LogCache::CRevisionInfoContainer::HAS_COMMENT);
1696 toUpdate->Update (newInfo);
1700 theApp.DoWaitCursor(-1);
1701 EnableOKButton();
1702 #endif
1705 BOOL CLogDlg::PreTranslateMessage(MSG* pMsg)
1707 // Skip Ctrl-C when copying text out of the log message or search filter
1708 BOOL bSkipAccelerator = ( pMsg->message == WM_KEYDOWN && pMsg->wParam=='C' && (GetFocus()==GetDlgItem(IDC_MSGVIEW) || GetFocus()==GetDlgItem(IDC_SEARCHEDIT) ) && GetKeyState(VK_CONTROL)&0x8000 );
1709 if (pMsg->message == WM_KEYDOWN && pMsg->wParam=='\r')
1711 if (GetFocus()==GetDlgItem(IDC_LOGLIST))
1713 if (CRegDWORD(_T("Software\\TortoiseGit\\DiffByDoubleClickInLog"), FALSE))
1715 m_LogList.DiffSelectedRevWithPrevious();
1716 return TRUE;
1719 if (GetFocus()==GetDlgItem(IDC_LOGMSG))
1721 DiffSelectedFile();
1722 return TRUE;
1725 if (m_hAccel && !bSkipAccelerator)
1727 int ret = TranslateAccelerator(m_hWnd, m_hAccel, pMsg);
1728 if (ret)
1729 return TRUE;
1732 if(::IsWindow(m_tooltips.m_hWnd))
1733 m_tooltips.RelayEvent(pMsg);
1734 return __super::PreTranslateMessage(pMsg);
1738 BOOL CLogDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
1740 //if (this->IsThreadRunning())
1741 if(m_LogList.m_bNoDispUpdates)
1743 // only show the wait cursor over the list control
1744 if ((pWnd)&&
1745 ((pWnd == GetDlgItem(IDC_LOGLIST))||
1746 (pWnd == GetDlgItem(IDC_MSGVIEW))||
1747 (pWnd == GetDlgItem(IDC_LOGMSG))))
1749 HCURSOR hCur = LoadCursor(NULL, MAKEINTRESOURCE(IDC_WAIT));
1750 SetCursor(hCur);
1751 return TRUE;
1754 if ((pWnd) && (pWnd == GetDlgItem(IDC_MSGVIEW)))
1755 return CResizableStandAloneDialog::OnSetCursor(pWnd, nHitTest, message);
1757 HCURSOR hCur = LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW));
1758 SetCursor(hCur);
1759 return CResizableStandAloneDialog::OnSetCursor(pWnd, nHitTest, message);
1762 void CLogDlg::OnBnClickedHelp()
1764 OnHelp();
1767 void CLogDlg::OnLvnItemchangedLoglist(NMHDR *pNMHDR, LRESULT *pResult)
1769 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1770 *pResult = 0;
1771 //if (this->IsThreadRunning())
1772 if(m_LogList.m_bNoDispUpdates)
1773 return;
1774 if (pNMLV->iItem >= 0)
1776 m_nSearchIndex = pNMLV->iItem;
1777 if (pNMLV->iSubItem != 0)
1778 return;
1779 if ((pNMLV->iItem == m_LogList.m_arShownList.GetCount()))
1781 // remove the selected state
1782 if (pNMLV->uChanged & LVIF_STATE)
1784 m_LogList.SetItemState(pNMLV->iItem, 0, LVIS_SELECTED);
1785 FillLogMessageCtrl();
1786 UpdateData(FALSE);
1787 UpdateLogInfoLabel();
1789 return;
1791 if (pNMLV->uChanged & LVIF_STATE)
1793 FillLogMessageCtrl();
1794 UpdateData(FALSE);
1797 else
1799 FillLogMessageCtrl();
1800 UpdateData(FALSE);
1802 EnableOKButton();
1803 UpdateLogInfoLabel();
1806 void CLogDlg::OnEnLinkMsgview(NMHDR *pNMHDR, LRESULT *pResult)
1808 ENLINK *pEnLink = reinterpret_cast<ENLINK *>(pNMHDR);
1809 if (pEnLink->msg == WM_LBUTTONUP)
1811 CString url, msg;
1812 GetDlgItemText(IDC_MSGVIEW, msg);
1813 msg.Replace(_T("\r\n"), _T("\n"));
1814 url = msg.Mid(pEnLink->chrg.cpMin, pEnLink->chrg.cpMax-pEnLink->chrg.cpMin);
1815 if (!::PathIsURL(url))
1817 url = m_ProjectProperties.GetBugIDUrl(url);
1818 url = GetAbsoluteUrlFromRelativeUrl(url);
1820 if (!url.IsEmpty())
1821 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
1823 *pResult = 0;
1826 class CDateSorter
1828 public:
1829 class CCommitPointer
1831 public:
1832 CCommitPointer():m_cont(NULL){}
1833 CCommitPointer(const CCommitPointer& P_Right)
1834 : m_cont(NULL)
1836 *this = P_Right;
1839 CCommitPointer& operator = (const CCommitPointer& P_Right)
1841 if(IsPointer())
1843 (*m_cont->m_parDates)[m_place] = P_Right.GetDate();
1844 (*m_cont->m_parFileChanges)[m_place] = P_Right.GetChanges();
1845 (*m_cont->m_parAuthors)[m_place] = P_Right.GetAuthor();
1847 else
1849 m_Date = P_Right.GetDate();
1850 m_Changes = P_Right.GetChanges();
1851 m_csAuthor = P_Right.GetAuthor();
1853 return *this;
1856 void Clone(const CCommitPointer& P_Right)
1858 m_cont = P_Right.m_cont;
1859 m_place = P_Right.m_place;
1860 m_Date = P_Right.m_Date;
1861 m_Changes = P_Right.m_Changes;
1862 m_csAuthor = P_Right.m_csAuthor;
1865 DWORD GetDate() const {return IsPointer() ? (*m_cont->m_parDates)[m_place] : m_Date;}
1866 DWORD GetChanges() const {return IsPointer() ? (*m_cont->m_parFileChanges)[m_place] : m_Changes;}
1867 CString GetAuthor() const {return IsPointer() ? (*m_cont->m_parAuthors)[m_place] : m_csAuthor;}
1869 bool IsPointer() const {return m_cont != NULL;}
1870 //When pointer
1871 CDateSorter* m_cont;
1872 int m_place;
1874 //When element
1875 DWORD m_Date;
1876 DWORD m_Changes;
1877 CString m_csAuthor;
1880 class iterator : public std::iterator<std::random_access_iterator_tag, CCommitPointer>
1882 public:
1883 CCommitPointer m_ptr;
1885 iterator(){}
1886 iterator(const iterator& P_Right){*this = P_Right;}
1887 iterator& operator=(const iterator& P_Right)
1889 m_ptr.Clone(P_Right.m_ptr);
1890 return *this;
1893 CCommitPointer& operator*(){return m_ptr;}
1894 CCommitPointer* operator->(){return &m_ptr;}
1895 const CCommitPointer& operator*()const{return m_ptr;}
1896 const CCommitPointer* operator->()const{return &m_ptr;}
1898 iterator& operator+=(size_t P_iOffset){m_ptr.m_place += P_iOffset;return *this;}
1899 iterator& operator-=(size_t P_iOffset){m_ptr.m_place -= P_iOffset;return *this;}
1900 iterator operator+(size_t P_iOffset)const{iterator it(*this); it += P_iOffset;return it;}
1901 iterator operator-(size_t P_iOffset)const{iterator it(*this); it -= P_iOffset;return it;}
1903 iterator& operator++(){++m_ptr.m_place;return *this;}
1904 iterator& operator--(){--m_ptr.m_place;return *this;}
1905 iterator operator++(int){iterator it(*this);++*this;return it;}
1906 iterator operator--(int){iterator it(*this);--*this;return it;}
1908 size_t operator-(const iterator& P_itRight)const{return m_ptr.m_place - P_itRight->m_place;}
1910 bool operator<(const iterator& P_itRight)const{return m_ptr.m_place < P_itRight->m_place;}
1911 bool operator!=(const iterator& P_itRight)const{return m_ptr.m_place != P_itRight->m_place;}
1912 bool operator==(const iterator& P_itRight)const{return m_ptr.m_place == P_itRight->m_place;}
1913 bool operator>(const iterator& P_itRight)const{return m_ptr.m_place > P_itRight->m_place;}
1915 iterator begin()
1917 iterator it;
1918 it->m_place = 0;
1919 it->m_cont = this;
1920 return it;
1922 iterator end()
1924 iterator it;
1925 it->m_place = m_parDates->GetCount();
1926 it->m_cont = this;
1927 return it;
1930 CDWordArray * m_parDates;
1931 CDWordArray * m_parFileChanges;
1932 CStringArray * m_parAuthors;
1935 class CDateSorterLess
1937 public:
1938 bool operator () (const CDateSorter::CCommitPointer& P_Left, const CDateSorter::CCommitPointer& P_Right) const
1940 return P_Left.GetDate() > P_Right.GetDate(); //Last date first
1947 void CLogDlg::OnBnClickedStatbutton()
1950 if (this->IsThreadRunning())
1951 return;
1952 if (m_LogList.m_arShownList.IsEmpty())
1953 return; // nothing is shown, so no statistics.
1954 // the statistics dialog expects the log entries to be sorted by date
1955 SortByColumn(3, false);
1956 CPtrArray shownlist;
1957 m_LogList.RecalculateShownList(&shownlist);
1958 // create arrays which are aware of the current filter
1959 CStringArray m_arAuthorsFiltered;
1960 CDWordArray m_arDatesFiltered;
1961 CDWordArray m_arFileChangesFiltered;
1962 for (INT_PTR i=0; i<shownlist.GetCount(); ++i)
1964 GitRev* pLogEntry = reinterpret_cast<GitRev*>(shownlist.GetAt(i));
1965 CString strAuthor = pLogEntry->m_AuthorName;
1966 if ( strAuthor.IsEmpty() )
1968 strAuthor.LoadString(IDS_STATGRAPH_EMPTYAUTHOR);
1970 m_arAuthorsFiltered.Add(strAuthor);
1971 m_arDatesFiltered.Add(pLogEntry->m_AuthorDate.GetTime());
1972 m_arFileChangesFiltered.Add(pLogEntry->m_Files.GetCount());
1975 CDateSorter W_Sorter;
1976 W_Sorter.m_parAuthors = &m_arAuthorsFiltered;
1977 W_Sorter.m_parDates = &m_arDatesFiltered;
1978 W_Sorter.m_parFileChanges = &m_arFileChangesFiltered;
1979 std::sort(W_Sorter.begin(), W_Sorter.end(), CDateSorterLess());
1981 CStatGraphDlg dlg;
1982 dlg.m_parAuthors = &m_arAuthorsFiltered;
1983 dlg.m_parDates = &m_arDatesFiltered;
1984 dlg.m_parFileChanges = &m_arFileChangesFiltered;
1985 dlg.m_path = m_path;
1986 dlg.DoModal();
1987 // restore the previous sorting
1988 SortByColumn(m_nSortColumn, m_bAscending);
1989 OnTimer(LOGFILTER_TIMER);
1993 void CLogDlg::DoSizeV1(int delta)
1996 RemoveAnchor(IDC_LOGLIST);
1997 RemoveAnchor(IDC_SPLITTERTOP);
1998 RemoveAnchor(IDC_MSGVIEW);
1999 RemoveAnchor(IDC_SPLITTERBOTTOM);
2000 RemoveAnchor(IDC_LOGMSG);
2001 CSplitterControl::ChangeHeight(&m_LogList, delta, CW_TOPALIGN);
2002 CSplitterControl::ChangeHeight(GetDlgItem(IDC_MSGVIEW), -delta, CW_BOTTOMALIGN);
2003 AddAnchor(IDC_LOGLIST, TOP_LEFT, TOP_RIGHT);
2004 AddAnchor(IDC_SPLITTERTOP, TOP_LEFT, TOP_RIGHT);
2005 AddAnchor(IDC_MSGVIEW, TOP_LEFT, BOTTOM_RIGHT);
2006 AddAnchor(IDC_SPLITTERBOTTOM, BOTTOM_LEFT, BOTTOM_RIGHT);
2007 AddAnchor(IDC_LOGMSG, BOTTOM_LEFT, BOTTOM_RIGHT);
2008 ArrangeLayout();
2009 AdjustMinSize();
2010 SetSplitterRange();
2011 m_LogList.Invalidate();
2012 GetDlgItem(IDC_MSGVIEW)->Invalidate();
2016 void CLogDlg::DoSizeV2(int delta)
2019 RemoveAnchor(IDC_LOGLIST);
2020 RemoveAnchor(IDC_SPLITTERTOP);
2021 RemoveAnchor(IDC_MSGVIEW);
2022 RemoveAnchor(IDC_SPLITTERBOTTOM);
2023 RemoveAnchor(IDC_LOGMSG);
2024 CSplitterControl::ChangeHeight(GetDlgItem(IDC_MSGVIEW), delta, CW_TOPALIGN);
2025 CSplitterControl::ChangeHeight(&m_ChangedFileListCtrl, -delta, CW_BOTTOMALIGN);
2026 AddAnchor(IDC_LOGLIST, TOP_LEFT, TOP_RIGHT);
2027 AddAnchor(IDC_SPLITTERTOP, TOP_LEFT, TOP_RIGHT);
2028 AddAnchor(IDC_MSGVIEW, TOP_LEFT, BOTTOM_RIGHT);
2029 AddAnchor(IDC_SPLITTERBOTTOM, BOTTOM_LEFT, BOTTOM_RIGHT);
2030 AddAnchor(IDC_LOGMSG, BOTTOM_LEFT, BOTTOM_RIGHT);
2031 ArrangeLayout();
2032 AdjustMinSize();
2033 SetSplitterRange();
2034 GetDlgItem(IDC_MSGVIEW)->Invalidate();
2035 m_ChangedFileListCtrl.Invalidate();
2039 void CLogDlg::AdjustMinSize()
2041 // adjust the minimum size of the dialog to prevent the resizing from
2042 // moving the list control too far down.
2043 CRect rcChgListView;
2044 m_ChangedFileListCtrl.GetClientRect(rcChgListView);
2045 CRect rcLogList;
2046 m_LogList.GetClientRect(rcLogList);
2048 SetMinTrackSize(CSize(m_DlgOrigRect.Width(),
2049 m_DlgOrigRect.Height()-m_ChgOrigRect.Height()-m_LogListOrigRect.Height()-m_MsgViewOrigRect.Height()
2050 +rcChgListView.Height()+rcLogList.Height()+60));
2053 LRESULT CLogDlg::DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam)
2055 switch (message) {
2056 case WM_NOTIFY:
2057 if (wParam == IDC_SPLITTERTOP)
2059 SPC_NMHDR* pHdr = (SPC_NMHDR*) lParam;
2060 DoSizeV1(pHdr->delta);
2062 else if (wParam == IDC_SPLITTERBOTTOM)
2064 SPC_NMHDR* pHdr = (SPC_NMHDR*) lParam;
2065 DoSizeV2(pHdr->delta);
2067 break;
2070 return CResizableDialog::DefWindowProc(message, wParam, lParam);
2073 void CLogDlg::SetSplitterRange()
2075 if ((m_LogList)&&(m_ChangedFileListCtrl))
2077 CRect rcTop;
2078 m_LogList.GetWindowRect(rcTop);
2079 ScreenToClient(rcTop);
2080 CRect rcMiddle;
2081 GetDlgItem(IDC_MSGVIEW)->GetWindowRect(rcMiddle);
2082 ScreenToClient(rcMiddle);
2083 m_wndSplitter1.SetRange(rcTop.top+30, rcMiddle.bottom-20);
2084 CRect rcBottom;
2085 m_ChangedFileListCtrl.GetWindowRect(rcBottom);
2086 ScreenToClient(rcBottom);
2087 m_wndSplitter2.SetRange(rcMiddle.top+30, rcBottom.bottom-20);
2091 LRESULT CLogDlg::OnClickedInfoIcon(WPARAM /*wParam*/, LPARAM lParam)
2093 // FIXME: x64 version would get this function called with unexpected parameters.
2094 if (!lParam)
2095 return 0;
2097 RECT * rect = (LPRECT)lParam;
2098 CPoint point;
2099 CString temp;
2100 point = CPoint(rect->left, rect->bottom);
2101 #define LOGMENUFLAGS(x) (MF_STRING | MF_ENABLED | (m_LogList.m_nSelectedFilter == x ? MF_CHECKED : MF_UNCHECKED))
2102 CMenu popup;
2103 if (popup.CreatePopupMenu())
2105 temp.LoadString(IDS_LOG_FILTER_ALL);
2106 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_ALL), LOGFILTER_ALL, temp);
2108 popup.AppendMenu(MF_SEPARATOR, NULL);
2110 temp.LoadString(IDS_LOG_FILTER_MESSAGES);
2111 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_MESSAGES), LOGFILTER_MESSAGES, temp);
2112 temp.LoadString(IDS_LOG_FILTER_PATHS);
2113 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_PATHS), LOGFILTER_PATHS, temp);
2114 temp.LoadString(IDS_LOG_FILTER_AUTHORS);
2115 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_AUTHORS), LOGFILTER_AUTHORS, temp);
2116 temp.LoadString(IDS_LOG_FILTER_REVS);
2117 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REVS), LOGFILTER_REVS, temp);
2118 temp.LoadString(IDS_LOG_FILTER_BUGIDS);
2119 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_BUGID), LOGFILTER_BUGID, temp);
2121 popup.AppendMenu(MF_SEPARATOR, NULL);
2123 temp.LoadString(IDS_LOG_FILTER_REGEX);
2124 popup.AppendMenu(MF_STRING | MF_ENABLED | (m_bFilterWithRegex ? MF_CHECKED : MF_UNCHECKED), LOGFILTER_REGEX, temp);
2126 m_tooltips.Pop();
2127 int selection = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
2128 if (selection != 0)
2131 if (selection == LOGFILTER_REGEX)
2133 m_bFilterWithRegex = !m_bFilterWithRegex;
2134 CRegDWORD b = CRegDWORD(_T("Software\\TortoiseGit\\UseRegexFilter"), TRUE);
2135 b = m_bFilterWithRegex;
2136 CheckRegexpTooltip();
2138 else
2140 m_LogList.m_nSelectedFilter = selection;
2141 SetFilterCueText();
2143 SetTimer(LOGFILTER_TIMER, 1000, NULL);
2146 return 0L;
2149 LRESULT CLogDlg::OnClickedCancelFilter(WPARAM /*wParam*/, LPARAM /*lParam*/)
2152 KillTimer(LOGFILTER_TIMER);
2154 m_LogList.m_sFilterText.Empty();
2155 UpdateData(FALSE);
2156 theApp.DoWaitCursor(1);
2157 CStoreSelection storeselection(this);
2158 FillLogMessageCtrl(false);
2160 m_LogList.RemoveFilter();
2162 CTime begin,end;
2163 m_LogList.GetTimeRange(begin,end);
2164 m_DateFrom.SetTime(&begin);
2165 m_DateTo.SetTime(&end);
2167 theApp.DoWaitCursor(-1);
2168 GetDlgItem(IDC_SEARCHEDIT)->ShowWindow(SW_HIDE);
2169 GetDlgItem(IDC_SEARCHEDIT)->ShowWindow(SW_SHOW);
2170 GetDlgItem(IDC_SEARCHEDIT)->SetFocus();
2171 UpdateLogInfoLabel();
2173 return 0L;
2177 void CLogDlg::SetFilterCueText()
2179 CString temp;
2180 switch (m_LogList.m_nSelectedFilter)
2182 case LOGFILTER_ALL:
2183 temp.LoadString(IDS_LOG_FILTER_ALL);
2184 break;
2185 case LOGFILTER_MESSAGES:
2186 temp.LoadString(IDS_LOG_FILTER_MESSAGES);
2187 break;
2188 case LOGFILTER_PATHS:
2189 temp.LoadString(IDS_LOG_FILTER_PATHS);
2190 break;
2191 case LOGFILTER_AUTHORS:
2192 temp.LoadString(IDS_LOG_FILTER_AUTHORS);
2193 break;
2194 case LOGFILTER_REVS:
2195 temp.LoadString(IDS_LOG_FILTER_REVS);
2196 break;
2198 // to make the cue banner text appear more to the right of the edit control
2199 temp = _T(" ")+temp;
2200 m_cFilter.SetCueBanner(temp);
2203 bool CLogDlg::Validate(LPCTSTR string)
2205 if (!m_bFilterWithRegex)
2206 return true;
2207 tr1::wregex pat;
2208 return m_LogList.ValidateRegexp(string, pat, false);
2212 void CLogDlg::OnTimer(UINT_PTR nIDEvent)
2214 if (nIDEvent == LOGFILTER_TIMER)
2216 if (this->IsThreadRunning())
2218 // thread still running! So just restart the timer.
2219 SetTimer(LOGFILTER_TIMER, 1000, NULL);
2220 return;
2222 CWnd * focusWnd = GetFocus();
2223 bool bSetFocusToFilterControl = ((focusWnd != GetDlgItem(IDC_DATEFROM))&&(focusWnd != GetDlgItem(IDC_DATETO))
2224 && (focusWnd != GetDlgItem(IDC_LOGLIST)));
2225 if (m_LogList.m_sFilterText.IsEmpty())
2227 DialogEnableWindow(IDC_STATBUTTON, !(((this->IsThreadRunning())||(m_LogList.m_arShownList.IsEmpty()))));
2228 // do not return here!
2229 // we also need to run the filter if the filter text is empty:
2230 // 1. to clear an existing filter
2231 // 2. to rebuild the m_arShownList after sorting
2233 theApp.DoWaitCursor(1);
2234 CStoreSelection storeselection(this);
2235 KillTimer(LOGFILTER_TIMER);
2236 FillLogMessageCtrl(false);
2238 // now start filter the log list
2239 m_LogList.StartFilter();
2241 if ( m_LogList.GetItemCount()==1 )
2243 m_LogList.SetSelectionMark(0);
2244 m_LogList.SetItemState(0, LVIS_SELECTED, LVIS_SELECTED);
2246 theApp.DoWaitCursor(-1);
2247 GetDlgItem(IDC_SEARCHEDIT)->ShowWindow(SW_HIDE);
2248 GetDlgItem(IDC_SEARCHEDIT)->ShowWindow(SW_SHOW);
2249 if (bSetFocusToFilterControl)
2250 GetDlgItem(IDC_SEARCHEDIT)->SetFocus();
2251 UpdateLogInfoLabel();
2252 } // if (nIDEvent == LOGFILTER_TIMER)
2253 DialogEnableWindow(IDC_STATBUTTON, !(((this->IsThreadRunning())||(m_LogList.m_arShownList.IsEmpty()))));
2254 __super::OnTimer(nIDEvent);
2257 void CLogDlg::OnDtnDatetimechangeDateto(NMHDR * /*pNMHDR*/, LRESULT *pResult)
2259 CTime _time;
2260 m_DateTo.GetTime(_time);
2263 CTime time(_time.GetYear(), _time.GetMonth(), _time.GetDay(), 23, 59, 59);
2264 if (time.GetTime() != m_LogList.m_To.GetTime())
2266 m_LogList.m_To = (DWORD)time.GetTime();
2267 SetTimer(LOGFILTER_TIMER, 10, NULL);
2270 catch (CAtlException)
2274 *pResult = 0;
2277 void CLogDlg::OnDtnDatetimechangeDatefrom(NMHDR * /*pNMHDR*/, LRESULT *pResult)
2279 CTime _time;
2280 m_DateFrom.GetTime(_time);
2283 CTime time(_time.GetYear(), _time.GetMonth(), _time.GetDay(), 0, 0, 0);
2284 if (time.GetTime() != m_LogList.m_From.GetTime())
2286 m_LogList.m_From = (DWORD)time.GetTime();
2287 SetTimer(LOGFILTER_TIMER, 10, NULL);
2290 catch (CAtlException)
2294 *pResult = 0;
2299 CTGitPathList CLogDlg::GetChangedPathsFromSelectedRevisions(bool /*bRelativePaths*/ /* = false */, bool /*bUseFilter*/ /* = true */)
2301 CTGitPathList pathList;
2302 #if 0
2304 if (m_sRepositoryRoot.IsEmpty() && (bRelativePaths == false))
2306 m_sRepositoryRoot = GetRepositoryRoot(m_path);
2308 if (m_sRepositoryRoot.IsEmpty() && (bRelativePaths == false))
2309 return pathList;
2311 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
2312 if (pos != NULL)
2314 while (pos)
2316 int nextpos = m_LogList.GetNextSelectedItem(pos);
2317 if (nextpos >= m_arShownList.GetCount())
2318 continue;
2319 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(nextpos));
2320 LogChangedPathArray * cpatharray = pLogEntry->pArChangedPaths;
2321 for (INT_PTR cpPathIndex = 0; cpPathIndex<cpatharray->GetCount(); ++cpPathIndex)
2323 LogChangedPath * cpath = cpatharray->GetAt(cpPathIndex);
2324 if (cpath == NULL)
2325 continue;
2326 CTGitPath path;
2327 if (!bRelativePaths)
2328 path.SetFromGit(m_sRepositoryRoot);
2329 path.AppendPathString(cpath->sPath);
2330 if ((!bUseFilter)||
2331 ((m_cHidePaths.GetState() & 0x0003)!=BST_CHECKED)||
2332 (cpath->sPath.Left(m_sRelativeRoot.GetLength()).Compare(m_sRelativeRoot)==0))
2333 pathList.AddPath(path);
2338 pathList.RemoveDuplicates();
2339 #endif
2340 return pathList;
2343 void CLogDlg::SortByColumn(int /*nSortColumn*/, bool /*bAscending*/)
2345 #if 0
2346 switch(nSortColumn)
2348 case 0: // Revision
2350 if(bAscending)
2351 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::AscRevSort());
2352 else
2353 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::DescRevSort());
2355 break;
2356 case 1: // action
2358 if(bAscending)
2359 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::AscActionSort());
2360 else
2361 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::DescActionSort());
2363 break;
2364 case 2: // Author
2366 if(bAscending)
2367 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::AscAuthorSort());
2368 else
2369 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::DescAuthorSort());
2371 break;
2372 case 3: // Date
2374 if(bAscending)
2375 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::AscDateSort());
2376 else
2377 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::DescDateSort());
2379 break;
2380 case 4: // Message or bug id
2381 if (m_bShowBugtraqColumn)
2383 if(bAscending)
2384 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::AscBugIDSort());
2385 else
2386 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::DescBugIDSort());
2387 break;
2389 // fall through here
2390 case 5: // Message
2392 if(bAscending)
2393 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::AscMessageSort());
2394 else
2395 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::DescMessageSort());
2397 break;
2398 default:
2399 ATLASSERT(0);
2400 break;
2402 #endif
2405 void CLogDlg::OnLvnColumnclick(NMHDR *pNMHDR, LRESULT *pResult)
2407 if (this->IsThreadRunning())
2408 return; //no sorting while the arrays are filled
2409 #if 0
2410 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
2411 const int nColumn = pNMLV->iSubItem;
2412 m_bAscending = nColumn == m_nSortColumn ? !m_bAscending : TRUE;
2413 m_nSortColumn = nColumn;
2414 SortByColumn(m_nSortColumn, m_bAscending);
2415 SetSortArrow(&m_LogList, m_nSortColumn, !!m_bAscending);
2416 SortShownListArray();
2417 m_LogList.Invalidate();
2418 UpdateLogInfoLabel();
2419 #endif
2420 *pResult = 0;
2423 void CLogDlg::SortShownListArray()
2425 // make sure the shown list still matches the filter after sorting.
2426 OnTimer(LOGFILTER_TIMER);
2427 // clear the selection states
2428 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
2429 while (pos)
2431 m_LogList.SetItemState(m_LogList.GetNextSelectedItem(pos), 0, LVIS_SELECTED);
2433 m_LogList.SetSelectionMark(-1);
2436 void CLogDlg::SetSortArrow(CListCtrl * control, int nColumn, bool bAscending)
2438 if (control == NULL)
2439 return;
2440 // set the sort arrow
2441 CHeaderCtrl * pHeader = control->GetHeaderCtrl();
2442 HDITEM HeaderItem = {0};
2443 HeaderItem.mask = HDI_FORMAT;
2444 for (int i=0; i<pHeader->GetItemCount(); ++i)
2446 pHeader->GetItem(i, &HeaderItem);
2447 HeaderItem.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
2448 pHeader->SetItem(i, &HeaderItem);
2450 if (nColumn >= 0)
2452 pHeader->GetItem(nColumn, &HeaderItem);
2453 HeaderItem.fmt |= (bAscending ? HDF_SORTUP : HDF_SORTDOWN);
2454 pHeader->SetItem(nColumn, &HeaderItem);
2457 void CLogDlg::OnLvnColumnclickChangedFileList(NMHDR* /*pNMHDR*/, LRESULT* /*pResult*/)
2459 #if 0
2460 if (this->IsThreadRunning())
2461 return; //no sorting while the arrays are filled
2462 if (m_currentChangedArray == NULL)
2463 return;
2464 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
2465 const int nColumn = pNMLV->iSubItem;
2466 m_bAscendingPathList = nColumn == m_nSortColumnPathList ? !m_bAscendingPathList : TRUE;
2467 m_nSortColumnPathList = nColumn;
2468 // qsort(m_currentChangedArray->GetData(), m_currentChangedArray->GetSize(), sizeof(LogChangedPath*), (GENERICCOMPAREFN)SortCompare);
2470 SetSortArrow(&m_ChangedFileListCtrl, m_nSortColumnPathList, m_bAscendingPathList);
2471 m_ChangedFileListCtrl.Invalidate();
2472 *pResult = 0;
2473 #endif
2476 int CLogDlg::m_nSortColumnPathList = 0;
2477 bool CLogDlg::m_bAscendingPathList = false;
2479 int CLogDlg::SortCompare(const void * /*pElem1*/, const void * /*pElem2*/)
2481 #if 0
2482 LogChangedPath * cpath1 = *((LogChangedPath**)pElem1);
2483 LogChangedPath * cpath2 = *((LogChangedPath**)pElem2);
2485 if (m_bAscendingPathList)
2486 std::swap (cpath1, cpath2);
2488 int cmp = 0;
2489 switch (m_nSortColumnPathList)
2491 case 0: // action
2492 cmp = cpath2->GetAction().Compare(cpath1->GetAction());
2493 if (cmp)
2494 return cmp;
2495 // fall through
2496 case 1: // path
2497 cmp = cpath2->sPath.CompareNoCase(cpath1->sPath);
2498 if (cmp)
2499 return cmp;
2500 // fall through
2501 case 2: // copy from path
2502 cmp = cpath2->sCopyFromPath.Compare(cpath1->sCopyFromPath);
2503 if (cmp)
2504 return cmp;
2505 // fall through
2506 case 3: // copy from revision
2507 return cpath2->lCopyFromRev > cpath1->lCopyFromRev;
2509 #endif
2510 return 0;
2513 void CLogDlg::OnBnClickedHidepaths()
2515 FillLogMessageCtrl();
2516 m_ChangedFileListCtrl.Invalidate();
2521 void CLogDlg::OnBnClickedCheckStoponcopy()
2523 #if 0
2524 if (!GetDlgItem(IDC_GETALL)->IsWindowEnabled())
2525 return;
2527 // ignore old fetch limits when switching
2528 // between copy-following and stop-on-copy
2529 // (otherwise stop-on-copy will limit what
2530 // we see immediately after switching to
2531 // copy-following)
2533 m_endrev = 0;
2535 // now, restart the query
2536 #endif
2537 Refresh();
2541 void CLogDlg::UpdateLogInfoLabel()
2544 CGitHash rev1 ;
2545 CGitHash rev2 ;
2546 long selectedrevs = 0;
2547 int count =m_LogList.m_arShownList.GetCount();
2548 int start = 0;
2549 if (count)
2551 rev1 = (reinterpret_cast<GitRev*>(m_LogList.m_arShownList.GetAt(0)))->m_CommitHash;
2552 if(this->m_LogList.m_bShowWC && rev1.IsEmpty())
2553 start = 1;
2554 rev1 = (reinterpret_cast<GitRev*>(m_LogList.m_arShownList.GetAt(start)))->m_CommitHash;
2555 //pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(m_arShownList.GetCount()-1));
2556 rev2 = (reinterpret_cast<GitRev*>(m_LogList.m_arShownList.GetAt(count-1)))->m_CommitHash;
2557 selectedrevs = m_LogList.GetSelectedCount();
2559 CString sTemp;
2560 sTemp.Format(_T("Showing %ld revision(s), from revision %s to revision %s - %ld revision(s) selected"),
2561 count - start,
2562 rev2.ToString().Left(6), rev1.ToString().Left(6), selectedrevs);
2564 m_sLogInfo = sTemp;
2566 UpdateData(FALSE);
2569 #if 0
2570 void CLogDlg::ShowContextMenuForChangedpaths(CWnd* /*pWnd*/, CPoint point)
2573 int selIndex = m_ChangedFileListCtrl.GetSelectionMark();
2574 if ((point.x == -1) && (point.y == -1))
2576 CRect rect;
2577 m_ChangedFileListCtrl.GetItemRect(selIndex, &rect, LVIR_LABEL);
2578 m_ChangedFileListCtrl.ClientToScreen(&rect);
2579 point = rect.CenterPoint();
2581 if (selIndex < 0)
2582 return;
2583 int s = m_LogList.GetSelectionMark();
2584 if (s < 0)
2585 return;
2586 std::vector<CString> changedpaths;
2587 std::vector<LogChangedPath*> changedlogpaths;
2588 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
2589 if (pos == NULL)
2590 return; // nothing is selected, get out of here
2592 bool bOneRev = true;
2593 int sel=m_LogList.GetNextSelectedItem(pos);
2594 GitRev * pLogEntry = reinterpret_cast<GitRev *>(m_LogList.m_arShownList.GetAt(sel));
2595 GitRev * rev1 = pLogEntry;
2596 GitRev * rev2 = reinterpret_cast<GitRev *>(m_LogList.m_arShownList.GetAt(sel+1));
2597 #if 0
2598 bool bOneRev = true;
2599 if (pos)
2601 while (pos)
2603 pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(m_LogList.GetNextSelectedItem(pos)));
2604 if (pLogEntry)
2606 rev1 = max(rev1,(git_revnum_t)pLogEntry->Rev);
2607 rev2 = min(rev2,(git_revnum_t)pLogEntry->Rev);
2608 bOneRev = false;
2611 if (!bOneRev)
2612 rev2--;
2613 POSITION pos = m_ChangedFileListCtrl.GetFirstSelectedItemPosition();
2614 while (pos)
2616 int nItem = m_ChangedFileListCtrl.GetNextSelectedItem(pos);
2617 changedpaths.push_back(m_currentChangedPathList[nItem].GetGitPathString());
2620 else
2622 // only one revision is selected in the log dialog top pane
2623 // but multiple items could be selected in the changed items list
2624 rev2 = rev1-1;
2626 POSITION pos = m_ChangedFileListCtrl.GetFirstSelectedItemPosition();
2627 while (pos)
2629 int nItem = m_ChangedFileListCtrl.GetNextSelectedItem(pos);
2630 LogChangedPath * changedlogpath = pLogEntry->pArChangedPaths->GetAt(nItem);
2632 if (m_ChangedFileListCtrl.GetSelectedCount() == 1)
2634 if ((changedlogpath)&&(!changedlogpath->sCopyFromPath.IsEmpty()))
2635 rev2 = changedlogpath->lCopyFromRev;
2636 else
2638 // if the path was modified but the parent path was 'added with history'
2639 // then we have to use the copy from revision of the parent path
2640 CTGitPath cpath = CTGitPath(changedlogpath->sPath);
2641 for (int flist = 0; flist < pLogEntry->pArChangedPaths->GetCount(); ++flist)
2643 CTGitPath p = CTGitPath(pLogEntry->pArChangedPaths->GetAt(flist)->sPath);
2644 if (p.IsAncestorOf(cpath))
2646 if (!pLogEntry->pArChangedPaths->GetAt(flist)->sCopyFromPath.IsEmpty())
2647 rev2 = pLogEntry->pArChangedPaths->GetAt(flist)->lCopyFromRev;
2652 if ((m_cHidePaths.GetState() & 0x0003)==BST_CHECKED)
2654 // some items are hidden! So find out which item the user really clicked on
2655 INT_PTR selRealIndex = -1;
2656 for (INT_PTR hiddenindex=0; hiddenindex<pLogEntry->pArChangedPaths->GetCount(); ++hiddenindex)
2658 if (pLogEntry->pArChangedPaths->GetAt(hiddenindex)->sPath.Left(m_sRelativeRoot.GetLength()).Compare(m_sRelativeRoot)==0)
2659 selRealIndex++;
2660 if (selRealIndex == nItem)
2662 selIndex = hiddenindex;
2663 changedlogpath = pLogEntry->pArChangedPaths->GetAt(selIndex);
2664 break;
2668 if (changedlogpath)
2670 changedpaths.push_back(changedlogpath->sPath);
2671 changedlogpaths.push_back(changedlogpath);
2675 #endif
2676 //entry is selected, now show the popup menu
2677 CIconMenu popup;
2678 if (popup.CreatePopupMenu())
2680 bool bEntryAdded = false;
2681 if (m_ChangedFileListCtrl.GetSelectedCount() == 1)
2683 // if ((!bOneRev)||(IsDiffPossible(changedlogpaths[0], rev1)))
2685 popup.AppendMenuIcon(CGitLogList::ID_DIFF, IDS_LOG_POPUP_DIFF, IDI_DIFF);
2686 popup.AppendMenuIcon(CGitLogList::ID_BLAMEDIFF, IDS_LOG_POPUP_BLAMEDIFF, IDI_BLAME);
2687 popup.SetDefaultItem(CGitLogList::ID_DIFF, FALSE);
2688 popup.AppendMenuIcon(CGitLogList::ID_GNUDIFF1, IDS_LOG_POPUP_GNUDIFF_CH, IDI_DIFF);
2689 bEntryAdded = true;
2691 // if (rev2 == rev1-1)
2693 if (bEntryAdded)
2694 popup.AppendMenu(MF_SEPARATOR, NULL);
2695 popup.AppendMenuIcon(CGitLogList::ID_OPEN, IDS_LOG_POPUP_OPEN, IDI_OPEN);
2696 popup.AppendMenuIcon(CGitLogList::ID_OPENWITH, IDS_LOG_POPUP_OPENWITH, IDI_OPEN);
2697 popup.AppendMenuIcon(CGitLogList::ID_BLAME, IDS_LOG_POPUP_BLAME, IDI_BLAME);
2698 popup.AppendMenu(MF_SEPARATOR, NULL);
2699 if (m_hasWC)
2700 popup.AppendMenuIcon(CGitLogList::ID_REVERTREV, IDS_LOG_POPUP_REVERTREV, IDI_REVERT);
2701 popup.AppendMenuIcon(CGitLogList::ID_POPPROPS, IDS_REPOBROWSE_SHOWPROP, IDI_PROPERTIES); // "Show Properties"
2702 popup.AppendMenuIcon(CGitLogList::ID_LOG, IDS_MENULOG, IDI_LOG); // "Show Log"
2703 popup.AppendMenuIcon(CGitLogList::ID_GETMERGELOGS, IDS_LOG_POPUP_GETMERGELOGS, IDI_LOG); // "Show merge log"
2704 popup.AppendMenuIcon(CGitLogList::ID_SAVEAS, IDS_LOG_POPUP_SAVE, IDI_SAVEAS);
2705 bEntryAdded = true;
2706 if (!m_ProjectProperties.sWebViewerPathRev.IsEmpty())
2708 popup.AppendMenu(MF_SEPARATOR, NULL);
2709 popup.AppendMenuIcon(CGitLogList::ID_VIEWPATHREV, IDS_LOG_POPUP_VIEWPATHREV);
2711 if (popup.GetDefaultItem(0,FALSE)==-1)
2712 popup.SetDefaultItem(CGitLogList::ID_OPEN, FALSE);
2715 else if (changedlogpaths.size())
2717 // more than one entry is selected
2718 popup.AppendMenuIcon(CGitLogList::ID_SAVEAS, IDS_LOG_POPUP_SAVE);
2719 bEntryAdded = true;
2722 if (!bEntryAdded)
2723 return;
2724 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
2725 bool bOpenWith = false;
2726 bool bMergeLog = false;
2727 m_bCancelled = false;
2729 switch (cmd)
2731 case CGitLogList::ID_DIFF:
2733 DoDiffFromLog(selIndex, rev1, rev2, false, false);
2735 break;
2736 #if 0
2737 case ID_BLAMEDIFF:
2739 DoDiffFromLog(selIndex, rev1, rev2, true, false);
2741 break;
2742 case ID_GNUDIFF1:
2744 DoDiffFromLog(selIndex, rev1, rev2, false, true);
2746 break;
2747 case ID_REVERTREV:
2749 SetPromptApp(&theApp);
2750 theApp.DoWaitCursor(1);
2751 CString sUrl;
2752 if (Git::PathIsURL(m_path))
2754 sUrl = m_path.GetGitPathString();
2756 else
2758 sUrl = GetURLFromPath(m_path);
2759 if (sUrl.IsEmpty())
2761 theApp.DoWaitCursor(-1);
2762 CString temp;
2763 temp.Format(IDS_ERR_NOURLOFFILE, m_path.GetWinPath());
2764 CMessageBox::Show(this->m_hWnd, temp, _T("TortoiseGit"), MB_ICONERROR);
2765 EnableOKButton();
2766 theApp.DoWaitCursor(-1);
2767 break; //exit
2770 // find the working copy path of the selected item from the URL
2771 m_bCancelled = false;
2772 CString sUrlRoot = GetRepositoryRoot(CTGitPath(sUrl));
2774 CString fileURL = changedpaths[0];
2775 fileURL = sUrlRoot + fileURL.Trim();
2776 // firstfile = (e.g.) http://mydomain.com/repos/trunk/folder/file1
2777 // sUrl = http://mydomain.com/repos/trunk/folder
2778 CString sUnescapedUrl = CPathUtils::PathUnescape(sUrl);
2779 // find out until which char the urls are identical
2780 int i=0;
2781 while ((i<fileURL.GetLength())&&(i<sUnescapedUrl.GetLength())&&(fileURL[i]==sUnescapedUrl[i]))
2782 i++;
2783 int leftcount = m_path.GetWinPathString().GetLength()-(sUnescapedUrl.GetLength()-i);
2784 CString wcPath = m_path.GetWinPathString().Left(leftcount);
2785 wcPath += fileURL.Mid(i);
2786 wcPath.Replace('/', '\\');
2787 CGitProgressDlg dlg;
2788 if (changedlogpaths[0]->action == LOGACTIONS_DELETED)
2790 // a deleted path! Since the path isn't there anymore, merge
2791 // won't work. So just do a copy url->wc
2792 dlg.SetCommand(CGitProgressDlg::GitProgress_Copy);
2793 dlg.SetPathList(CTGitPathList(CTGitPath(fileURL)));
2794 dlg.SetUrl(wcPath);
2795 dlg.SetRevision(rev2);
2797 else
2799 if (!PathFileExists(wcPath))
2801 // seems the path got renamed
2802 // tell the user how to work around this.
2803 CMessageBox::Show(this->m_hWnd, IDS_LOG_REVERTREV_ERROR, IDS_APPNAME, MB_ICONERROR);
2804 EnableOKButton();
2805 theApp.DoWaitCursor(-1);
2806 break; //exit
2808 dlg.SetCommand(CGitProgressDlg::GitProgress_Merge);
2809 dlg.SetPathList(CTGitPathList(CTGitPath(wcPath)));
2810 dlg.SetUrl(fileURL);
2811 dlg.SetSecondUrl(fileURL);
2812 GitRevRangeArray revarray;
2813 revarray.AddRevRange(rev1, rev2);
2814 dlg.SetRevisionRanges(revarray);
2816 CString msg;
2817 msg.Format(IDS_LOG_REVERT_CONFIRM, (LPCTSTR)wcPath);
2818 if (CMessageBox::Show(this->m_hWnd, msg, _T("TortoiseGit"), MB_YESNO | MB_ICONQUESTION) == IDYES)
2820 dlg.DoModal();
2822 theApp.DoWaitCursor(-1);
2824 break;
2825 case ID_POPPROPS:
2827 DialogEnableWindow(IDOK, FALSE);
2828 SetPromptApp(&theApp);
2829 theApp.DoWaitCursor(1);
2830 CString filepath;
2831 if (Git::PathIsURL(m_path))
2833 filepath = m_path.GetGitPathString();
2835 else
2837 filepath = GetURLFromPath(m_path);
2838 if (filepath.IsEmpty())
2840 theApp.DoWaitCursor(-1);
2841 CString temp;
2842 temp.Format(IDS_ERR_NOURLOFFILE, (LPCTSTR)filepath);
2843 CMessageBox::Show(this->m_hWnd, temp, _T("TortoiseGit"), MB_ICONERROR);
2844 TRACE(_T("could not retrieve the URL of the file!\n"));
2845 EnableOKButton();
2846 break;
2849 filepath = GetRepositoryRoot(CTGitPath(filepath));
2850 filepath += changedpaths[0];
2851 CPropDlg dlg;
2852 dlg.m_rev = rev1;
2853 dlg.m_Path = CTGitPath(filepath);
2854 dlg.DoModal();
2855 EnableOKButton();
2856 theApp.DoWaitCursor(-1);
2858 break;
2859 case ID_SAVEAS:
2861 DialogEnableWindow(IDOK, FALSE);
2862 SetPromptApp(&theApp);
2863 theApp.DoWaitCursor(1);
2864 CString filepath;
2865 if (Git::PathIsURL(m_path))
2867 filepath = m_path.GetGitPathString();
2869 else
2871 filepath = GetURLFromPath(m_path);
2872 if (filepath.IsEmpty())
2874 theApp.DoWaitCursor(-1);
2875 CString temp;
2876 temp.Format(IDS_ERR_NOURLOFFILE, (LPCTSTR)filepath);
2877 CMessageBox::Show(this->m_hWnd, temp, _T("TortoiseGit"), MB_ICONERROR);
2878 TRACE(_T("could not retrieve the URL of the file!\n"));
2879 EnableOKButton();
2880 break;
2883 m_bCancelled = false;
2884 CString sRoot = GetRepositoryRoot(CTGitPath(filepath));
2885 // if more than one entry is selected, we save them
2886 // one by one into a folder the user has selected
2887 bool bTargetSelected = false;
2888 CTGitPath TargetPath;
2889 if (m_ChangedFileListCtrl.GetSelectedCount() > 1)
2891 CBrowseFolder browseFolder;
2892 browseFolder.SetInfo(CString(MAKEINTRESOURCE(IDS_LOG_SAVEFOLDERTOHINT)));
2893 browseFolder.m_style = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS;
2894 CString strSaveAsDirectory;
2895 if (browseFolder.Show(GetSafeHwnd(), strSaveAsDirectory) == CBrowseFolder::OK)
2897 TargetPath = CTGitPath(strSaveAsDirectory);
2898 bTargetSelected = true;
2901 else
2903 // Display the Open dialog box.
2904 CString revFilename;
2905 CString temp;
2906 temp = CPathUtils::GetFileNameFromPath(changedpaths[0]);
2907 int rfind = temp.ReverseFind('.');
2908 if (rfind > 0)
2909 revFilename.Format(_T("%s-%ld%s"), (LPCTSTR)temp.Left(rfind), rev1, (LPCTSTR)temp.Mid(rfind));
2910 else
2911 revFilename.Format(_T("%s-%ld"), (LPCTSTR)temp, rev1);
2912 bTargetSelected = CAppUtils::FileOpenSave(revFilename, NULL, IDS_LOG_POPUP_SAVE, IDS_COMMONFILEFILTER, false, m_hWnd);
2913 TargetPath.SetFromWin(revFilename);
2915 if (bTargetSelected)
2917 CProgressDlg progDlg;
2918 progDlg.SetTitle(IDS_APPNAME);
2919 progDlg.SetAnimation(IDR_DOWNLOAD);
2920 for (std::vector<LogChangedPath*>::iterator it = changedlogpaths.begin(); it!= changedlogpaths.end(); ++it)
2922 GitRev getrev = ((*it)->action == LOGACTIONS_DELETED) ? rev2 : rev1;
2924 CString sInfoLine;
2925 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, (LPCTSTR)filepath, (LPCTSTR)getrev.ToString());
2926 progDlg.SetLine(1, sInfoLine, true);
2927 SetAndClearProgressInfo(&progDlg);
2928 progDlg.ShowModeless(m_hWnd);
2930 CTGitPath tempfile = TargetPath;
2931 if (changedpaths.size() > 1)
2933 // if multiple items are selected, then the TargetPath
2934 // points to a folder and we have to append the filename
2935 // to save to to that folder.
2936 CString sName = (*it)->sPath;
2937 int slashpos = sName.ReverseFind('/');
2938 if (slashpos >= 0)
2939 sName = sName.Mid(slashpos);
2940 tempfile.AppendPathString(sName);
2941 // one problem here:
2942 // a user could have selected multiple items which
2943 // have the same filename but reside in different
2944 // directories, e.g.
2945 // /folder1/file1
2946 // /folder2/file1
2947 // in that case, the second 'file1' will overwrite
2948 // the already saved 'file1'.
2950 // we could maybe find the common root of all selected
2951 // items and then create sub folders to save those files
2952 // there.
2953 // But I think we should just leave it that way: to check
2954 // out multiple items at once, the better way is still to
2955 // use the export command from the top pane of the log dialog.
2957 filepath = sRoot + (*it)->sPath;
2958 if (!Cat(CTGitPath(filepath), getrev, getrev, tempfile))
2960 progDlg.Stop();
2961 SetAndClearProgressInfo((HWND)NULL);
2962 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
2963 EnableOKButton();
2964 theApp.DoWaitCursor(-1);
2965 break;
2968 progDlg.Stop();
2969 SetAndClearProgressInfo((HWND)NULL);
2971 EnableOKButton();
2972 theApp.DoWaitCursor(-1);
2974 break;
2975 case ID_OPENWITH:
2976 bOpenWith = true;
2977 case ID_OPEN:
2979 GitRev getrev = pLogEntry->pArChangedPaths->GetAt(selIndex)->action == LOGACTIONS_DELETED ? rev2 : rev1;
2980 Open(bOpenWith,changedpaths[0],getrev);
2982 break;
2983 case ID_BLAME:
2985 CString filepath;
2986 if (Git::PathIsURL(m_path))
2988 filepath = m_path.GetGitPathString();
2990 else
2992 filepath = GetURLFromPath(m_path);
2993 if (filepath.IsEmpty())
2995 theApp.DoWaitCursor(-1);
2996 CString temp;
2997 temp.Format(IDS_ERR_NOURLOFFILE, (LPCTSTR)filepath);
2998 CMessageBox::Show(this->m_hWnd, temp, _T("TortoiseGit"), MB_ICONERROR);
2999 TRACE(_T("could not retrieve the URL of the file!\n"));
3000 EnableOKButton();
3001 break;
3004 filepath = GetRepositoryRoot(CTGitPath(filepath));
3005 filepath += changedpaths[0];
3006 CBlameDlg dlg;
3007 dlg.EndRev = rev1;
3008 if (dlg.DoModal() == IDOK)
3010 CBlame blame;
3011 CString tempfile;
3012 CString logfile;
3013 tempfile = blame.BlameToTempFile(CTGitPath(filepath), dlg.StartRev, dlg.EndRev, dlg.EndRev, logfile, _T(""), dlg.m_bIncludeMerge, TRUE, TRUE);
3014 if (!tempfile.IsEmpty())
3016 if (dlg.m_bTextView)
3018 //open the default text editor for the result file
3019 CAppUtils::StartTextViewer(tempfile);
3021 else
3023 CString sParams = _T("/path:\"") + filepath + _T("\" ");
3024 if(!CAppUtils::LaunchTortoiseBlame(tempfile, logfile, CPathUtils::GetFileNameFromPath(filepath),sParams))
3026 break;
3030 else
3032 CMessageBox::Show(this->m_hWnd, blame.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
3036 break;
3037 case ID_GETMERGELOGS:
3038 bMergeLog = true;
3039 // fall through
3040 case ID_LOG:
3042 DialogEnableWindow(IDOK, FALSE);
3043 SetPromptApp(&theApp);
3044 theApp.DoWaitCursor(1);
3045 CString filepath;
3046 if (Git::PathIsURL(m_path))
3048 filepath = m_path.GetGitPathString();
3050 else
3052 filepath = GetURLFromPath(m_path);
3053 if (filepath.IsEmpty())
3055 theApp.DoWaitCursor(-1);
3056 CString temp;
3057 temp.Format(IDS_ERR_NOURLOFFILE, (LPCTSTR)filepath);
3058 CMessageBox::Show(this->m_hWnd, temp, _T("TortoiseGit"), MB_ICONERROR);
3059 TRACE(_T("could not retrieve the URL of the file!\n"));
3060 EnableOKButton();
3061 break;
3064 m_bCancelled = false;
3065 filepath = GetRepositoryRoot(CTGitPath(filepath));
3066 filepath += changedpaths[0];
3067 git_revnum_t logrev = rev1;
3068 if (changedlogpaths[0]->action == LOGACTIONS_DELETED)
3070 // if the item got deleted in this revision,
3071 // fetch the log from the previous revision where it
3072 // still existed.
3073 logrev--;
3075 CString sCmd;
3076 sCmd.Format(_T("\"%s\" /command:log /path:\"%s\" /startrev:%ld"), (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")), (LPCTSTR)filepath, logrev);
3077 if (bMergeLog)
3078 sCmd += _T(" /merge");
3079 CAppUtils::LaunchApplication(sCmd, NULL, false);
3080 EnableOKButton();
3081 theApp.DoWaitCursor(-1);
3083 break;
3084 case ID_VIEWPATHREV:
3086 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(m_LogList.GetSelectionMark()));
3087 GitRev rev = pLogEntry->Rev;
3088 CString relurl = changedpaths[0];
3089 CString url = m_ProjectProperties.sWebViewerPathRev;
3090 url.Replace(_T("%REVISION%"), rev.ToString());
3091 url.Replace(_T("%PATH%"), relurl);
3092 relurl = relurl.Mid(relurl.Find('/'));
3093 url.Replace(_T("%PATH1%"), relurl);
3094 if (!url.IsEmpty())
3095 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
3097 break;
3098 #endif
3099 default:
3100 break;
3101 } // switch (cmd)
3103 } // if (popup.CreatePopupMenu())
3105 #endif
3107 void CLogDlg::OnDtnDropdownDatefrom(NMHDR * /*pNMHDR*/, LRESULT *pResult)
3109 // the date control should not show the "today" button
3110 CMonthCalCtrl * pCtrl = m_DateFrom.GetMonthCalCtrl();
3111 if (pCtrl)
3112 SetWindowLongPtr(pCtrl->GetSafeHwnd(), GWL_STYLE, LONG_PTR(pCtrl->GetStyle() | MCS_NOTODAY));
3113 *pResult = 0;
3116 void CLogDlg::OnDtnDropdownDateto(NMHDR * /*pNMHDR*/, LRESULT *pResult)
3118 // the date control should not show the "today" button
3119 CMonthCalCtrl * pCtrl = m_DateTo.GetMonthCalCtrl();
3120 if (pCtrl)
3121 SetWindowLongPtr(pCtrl->GetSafeHwnd(), GWL_STYLE, LONG_PTR(pCtrl->GetStyle() | MCS_NOTODAY));
3122 *pResult = 0;
3125 void CLogDlg::OnSize(UINT nType, int cx, int cy)
3127 __super::OnSize(nType, cx, cy);
3128 //set range
3129 SetSplitterRange();
3132 void CLogDlg::OnRefresh()
3134 //if (GetDlgItem(IDC_GETALL)->IsWindowEnabled())
3135 ShowStartRef();
3137 m_limit = 0;
3138 this->m_LogProgress.SetPos(0);
3140 Refresh (true);
3144 void CLogDlg::OnFind()
3146 if (!m_pFindDialog)
3148 m_pFindDialog = new CFindReplaceDialog();
3149 m_pFindDialog->Create(TRUE, NULL, NULL, FR_HIDEUPDOWN | FR_HIDEWHOLEWORD, this);
3153 void CLogDlg::OnFocusFilter()
3155 GetDlgItem(IDC_SEARCHEDIT)->SetFocus();
3158 void CLogDlg::OnEditCopy()
3160 if (GetFocus() == &m_ChangedFileListCtrl)
3161 CopyChangedSelectionToClipBoard();
3162 else
3163 m_LogList.CopySelectionToClipBoard();
3166 CString CLogDlg::GetAbsoluteUrlFromRelativeUrl(const CString& url)
3168 // is the URL a relative one?
3169 if (url.Left(2).Compare(_T("^/")) == 0)
3171 // URL is relative to the repository root
3172 CString url1 = m_sRepositoryRoot + url.Mid(1);
3173 TCHAR buf[INTERNET_MAX_URL_LENGTH];
3174 DWORD len = url.GetLength();
3175 if (UrlCanonicalize((LPCTSTR)url1, buf, &len, 0) == S_OK)
3176 return CString(buf, len);
3177 return url1;
3179 else if (url[0] == '/')
3181 // URL is relative to the server's hostname
3182 CString sHost;
3183 // find the server's hostname
3184 int schemepos = m_sRepositoryRoot.Find(_T("//"));
3185 if (schemepos >= 0)
3187 sHost = m_sRepositoryRoot.Left(m_sRepositoryRoot.Find('/', schemepos+3));
3188 CString url1 = sHost + url;
3189 TCHAR buf[INTERNET_MAX_URL_LENGTH];
3190 DWORD len = url.GetLength();
3191 if (UrlCanonicalize((LPCTSTR)url, buf, &len, 0) == S_OK)
3192 return CString(buf, len);
3193 return url1;
3196 return url;
3200 void CLogDlg::OnEnChangeSearchedit()
3202 UpdateData();
3203 if (m_LogList.m_sFilterText.IsEmpty())
3205 CStoreSelection storeselection(this);
3206 // clear the filter, i.e. make all entries appear
3207 theApp.DoWaitCursor(1);
3208 KillTimer(LOGFILTER_TIMER);
3209 FillLogMessageCtrl(false);
3210 m_LogList.StartFilter();
3211 #if 0
3212 InterlockedExchange(&m_bNoDispUpdates, TRUE);
3213 m_arShownList.RemoveAll();
3214 for (DWORD i=0; i<m_logEntries.size(); ++i)
3216 if (IsEntryInDateRange(i))
3217 m_arShownList.Add(m_logEntries[i]);
3219 InterlockedExchange(&m_bNoDispUpdates, FALSE);
3220 m_LogList.DeleteAllItems();
3221 m_LogList.SetItemCountEx(ShownCountWithStopped());
3222 m_LogList.RedrawItems(0, ShownCountWithStopped());
3223 m_LogList.SetRedraw(false);
3224 ResizeAllListCtrlCols();
3225 m_LogList.SetRedraw(true);
3226 #endif
3227 theApp.DoWaitCursor(-1);
3228 GetDlgItem(IDC_SEARCHEDIT)->ShowWindow(SW_HIDE);
3229 GetDlgItem(IDC_SEARCHEDIT)->ShowWindow(SW_SHOW);
3230 GetDlgItem(IDC_SEARCHEDIT)->SetFocus();
3231 DialogEnableWindow(IDC_STATBUTTON, !(((this->IsThreadRunning())||(m_LogList.m_arShownList.IsEmpty()))));
3232 return;
3234 if (Validate(m_LogList.m_sFilterText))
3235 SetTimer(LOGFILTER_TIMER, 1000, NULL);
3236 else
3237 KillTimer(LOGFILTER_TIMER);
3241 void CLogDlg::OnBnClickedAllBranch()
3243 this->UpdateData();
3245 if(this->m_bAllBranch)
3246 m_LogList.m_ShowMask|=CGit::LOG_INFO_ALL_BRANCH;
3247 else
3248 m_LogList.m_ShowMask&=~CGit::LOG_INFO_ALL_BRANCH;
3250 OnRefresh();
3252 FillLogMessageCtrl(false);
3255 void CLogDlg::OnBnClickedBrowseRef()
3257 CString newRef = CBrowseRefsDlg::PickRef(false,m_LogList.GetStartRef());
3258 if(newRef.IsEmpty())
3259 return;
3261 SetStartRef(newRef);
3262 ((CButton*)GetDlgItem(IDC_LOG_ALLBRANCH))->SetCheck(0);
3264 OnBnClickedAllBranch();
3267 void CLogDlg::ShowStartRef()
3269 //Show ref name on top
3270 if(!::IsWindow(m_hWnd))
3271 return;
3272 if(m_bAllBranch)
3274 m_staticRef.SetWindowText(L"<All Branches>");
3275 m_staticRef.Invalidate(TRUE);
3276 return;
3279 CString showStartRef = m_LogList.GetStartRef();
3280 if(showStartRef.IsEmpty())
3282 //Ref name is HEAD
3283 if( g_Git.Run(L"git symbolic-ref HEAD",&showStartRef,CP_UTF8) )
3284 showStartRef = _T("<No branch>");
3285 showStartRef.Trim(L"\r\n\t ");
3289 if(wcsncmp(showStartRef,L"refs/",5) == 0)
3290 showStartRef = showStartRef.Mid(5);
3291 if(wcsncmp(showStartRef,L"heads/",6) == 0)
3292 showStartRef = showStartRef.Mid(6);
3294 m_staticRef.SetWindowText(showStartRef);
3295 m_staticRef.Invalidate(TRUE);
3298 void CLogDlg::SetStartRef(const CString& StartRef)
3300 m_LogList.SetStartRef(StartRef);
3302 ShowStartRef();
3307 void CLogDlg::OnBnClickedFirstParent()
3309 this->UpdateData();
3311 if(this->m_bFirstParent)
3312 m_LogList.m_ShowMask|=CGit::LOG_INFO_FIRST_PARENT;
3313 else
3314 m_LogList.m_ShowMask&=~CGit::LOG_INFO_FIRST_PARENT;
3316 OnRefresh();
3318 FillLogMessageCtrl(false);
3322 void CLogDlg::OnBnClickShowWholeProject()
3324 this->UpdateData();
3326 if(this->m_bWholeProject)
3328 m_LogList.m_Path.Reset();
3329 SetWindowText(m_sTitle + _T(" - ") + CString(_T("Whole Project")));
3331 else
3333 m_LogList.m_Path=m_path;
3334 if(!m_path.IsEmpty())
3335 SetWindowText(m_sTitle + _T(" - ") + m_path.GetGitPathString());
3338 OnRefresh();
3340 FillLogMessageCtrl(false);