Fixed issue #507: Help Spell error and push wrongly link to sync
[TortoiseGit.git] / src / TortoiseProc / LogDlg.cpp
blob9e03c5e33538311a4f72748be82994878ee69075
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"
51 IMPLEMENT_DYNAMIC(CLogDlg, CResizableStandAloneDialog)
52 CLogDlg::CLogDlg(CWnd* pParent /*=NULL*/)
53 : CResizableStandAloneDialog(CLogDlg::IDD, pParent)
54 , m_logcounter(0)
55 , m_wParam(0)
56 , m_currentChangedArray(NULL)
57 , m_nSortColumn(0)
58 , m_bShowedAll(false)
59 , m_bSelect(false)
61 , m_bSelectionMustBeContinuous(false)
62 , m_bShowBugtraqColumn(false)
63 , m_lowestRev(_T(""))
65 , m_sLogInfo(_T(""))
67 , m_bCancelled(FALSE)
68 , m_pNotifyWindow(NULL)
70 , m_bAscending(FALSE)
72 , m_limit(0)
73 , m_childCounter(0)
74 , m_maxChild(0)
75 , m_bIncludeMerges(FALSE)
76 , m_hAccel(NULL)
78 m_bFilterWithRegex = !!CRegDWORD(_T("Software\\TortoiseGit\\UseRegexFilter"), TRUE);
80 CString str;
81 str=g_Git.m_CurrentDir;
82 str.Replace(_T(":"),_T("_"));
83 str=CString(_T("Software\\TortoiseGit\\LogDialog\\AllBranch\\"))+str;
85 m_regbAllBranch=CRegDWORD(str,FALSE);
87 m_bAllBranch=m_regbAllBranch;
89 m_bFirstParent=FALSE;
90 m_bWholeProject=FALSE;
93 CLogDlg::~CLogDlg()
96 m_regbAllBranch=m_bAllBranch;
98 m_CurrentFilteredChangedArray.RemoveAll();
102 void CLogDlg::DoDataExchange(CDataExchange* pDX)
104 CResizableStandAloneDialog::DoDataExchange(pDX);
105 DDX_Control(pDX, IDC_LOGLIST, m_LogList);
106 DDX_Control(pDX, IDC_LOGMSG, m_ChangedFileListCtrl);
107 DDX_Control(pDX, IDC_PROGRESS, m_LogProgress);
108 DDX_Control(pDX, IDC_SPLITTERTOP, m_wndSplitter1);
109 DDX_Control(pDX, IDC_SPLITTERBOTTOM, m_wndSplitter2);
110 DDX_Text(pDX, IDC_SEARCHEDIT, m_LogList.m_sFilterText);
111 DDX_Control(pDX, IDC_DATEFROM, m_DateFrom);
112 DDX_Control(pDX, IDC_DATETO, m_DateTo);
113 DDX_Control(pDX, IDC_HIDEPATHS, m_cHidePaths);
114 DDX_Text(pDX, IDC_LOGINFO, m_sLogInfo);
115 DDX_Check(pDX, IDC_LOG_FIRSTPARENT, m_bFirstParent);
116 DDX_Check(pDX, IDC_LOG_ALLBRANCH,m_bAllBranch);
117 DDX_Check(pDX, IDC_SHOWWHOLEPROJECT,m_bWholeProject);
118 DDX_Control(pDX, IDC_SEARCHEDIT, m_cFilter);
119 DDX_Control(pDX, IDC_STATIC_REF, m_staticRef);
122 BEGIN_MESSAGE_MAP(CLogDlg, CResizableStandAloneDialog)
123 //ON_BN_CLICKED(IDC_GETALL, OnBnClickedGetall)
124 //ON_NOTIFY(NM_DBLCLK, IDC_LOGMSG, OnNMDblclkChangedFileList)
125 ON_WM_CONTEXTMENU()
126 ON_WM_SETCURSOR()
127 ON_BN_CLICKED(IDHELP, OnBnClickedHelp)
128 ON_NOTIFY(LVN_ITEMCHANGED, IDC_LOGLIST, OnLvnItemchangedLoglist)
129 ON_NOTIFY(EN_LINK, IDC_MSGVIEW, OnEnLinkMsgview)
130 ON_BN_CLICKED(IDC_STATBUTTON, OnBnClickedStatbutton)
133 ON_MESSAGE(WM_FILTEREDIT_INFOCLICKED, OnClickedInfoIcon)
134 ON_MESSAGE(WM_FILTEREDIT_CANCELCLICKED, OnClickedCancelFilter)
136 ON_MESSAGE(MSG_LOAD_PERCENTAGE,OnLogListLoading)
138 ON_EN_CHANGE(IDC_SEARCHEDIT, OnEnChangeSearchedit)
139 ON_WM_TIMER()
140 ON_NOTIFY(DTN_DATETIMECHANGE, IDC_DATETO, OnDtnDatetimechangeDateto)
141 ON_NOTIFY(DTN_DATETIMECHANGE, IDC_DATEFROM, OnDtnDatetimechangeDatefrom)
142 ON_BN_CLICKED(IDC_SHOWWHOLEPROJECT, OnBnClickShowWholeProject)
143 //ON_NOTIFY(NM_CUSTOMDRAW, IDC_LOGMSG, OnNMCustomdrawChangedFileList)
144 //ON_NOTIFY(LVN_GETDISPINFO, IDC_LOGMSG, OnLvnGetdispinfoChangedFileList)
145 ON_NOTIFY(LVN_COLUMNCLICK,IDC_LOGLIST , OnLvnColumnclick)
146 //ON_NOTIFY(LVN_COLUMNCLICK, IDC_LOGMSG, OnLvnColumnclickChangedFileList)
147 ON_BN_CLICKED(IDC_HIDEPATHS, OnBnClickedHidepaths)
148 ON_BN_CLICKED(IDC_LOG_ALLBRANCH,OnBnClickedAllBranch)
150 ON_NOTIFY(DTN_DROPDOWN, IDC_DATEFROM, &CLogDlg::OnDtnDropdownDatefrom)
151 ON_NOTIFY(DTN_DROPDOWN, IDC_DATETO, &CLogDlg::OnDtnDropdownDateto)
152 ON_WM_SIZE()
153 ON_BN_CLICKED(IDC_LOG_FIRSTPARENT, &CLogDlg::OnBnClickedFirstParent)
154 ON_BN_CLICKED(IDC_REFRESH, &CLogDlg::OnBnClickedRefresh)
155 // ON_BN_CLICKED(IDC_BUTTON_BROWSE_REF, &CLogDlg::OnBnClickedBrowseRef)
156 ON_STN_CLICKED(IDC_STATIC_REF, &CLogDlg::OnBnClickedBrowseRef)
157 ON_COMMAND(ID_LOGDLG_REFRESH,&CLogDlg::OnRefresh)
158 ON_COMMAND(ID_LOGDLG_FIND,&CLogDlg::OnFind)
159 ON_COMMAND(ID_LOGDLG_FOCUSFILTER,&CLogDlg::OnFocusFilter)
160 ON_COMMAND(ID_EDIT_COPY, &CLogDlg::OnEditCopy)
161 END_MESSAGE_MAP()
163 void CLogDlg::SetParams(const CTGitPath& path, CString pegrev, CString startrev, CString endrev, int limit /* = FALSE */)
165 m_path = path;
166 m_pegrev = pegrev;
167 this->m_LogList.m_startrev = startrev;
168 m_LogRevision = startrev;
169 this->m_LogList.m_endrev = endrev;
170 m_hasWC = !path.IsUrl();
171 m_limit = limit;
172 if (::IsWindow(m_hWnd))
173 UpdateData(FALSE);
176 BOOL CLogDlg::OnInitDialog()
178 CString temp;
179 CResizableStandAloneDialog::OnInitDialog();
181 m_hAccel = LoadAccelerators(AfxGetResourceHandle(),MAKEINTRESOURCE(IDR_ACC_LOGDLG));
184 // use the state of the "stop on copy/rename" option from the last time
185 UpdateData(FALSE);
187 // set the font to use in the log message view, configured in the settings dialog
188 CAppUtils::CreateFontForLogs(m_logFont);
189 GetDlgItem(IDC_MSGVIEW)->SetFont(&m_logFont);
190 // automatically detect URLs in the log message and turn them into links
191 GetDlgItem(IDC_MSGVIEW)->SendMessage(EM_AUTOURLDETECT, TRUE, NULL);
192 // make the log message rich edit control send a message when the mouse pointer is over a link
193 GetDlgItem(IDC_MSGVIEW)->SendMessage(EM_SETEVENTMASK, NULL, ENM_LINK);
194 //m_LogList.SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER | LVS_EX_SUBITEMIMAGES);
196 // the "hide unrelated paths" checkbox should be indeterminate
197 m_cHidePaths.SetCheck(BST_INDETERMINATE);
200 // if there is a working copy, load the project properties
201 // to get information about the bugtraq: integration
202 if (m_hasWC)
203 m_ProjectProperties.ReadProps(m_path);
205 // the bugtraq issue id column is only shown if the bugtraq:url or bugtraq:regex is set
206 if ((!m_ProjectProperties.sUrl.IsEmpty())||(!m_ProjectProperties.sCheckRe.IsEmpty()))
207 m_bShowBugtraqColumn = true;
209 //theme.SetWindowTheme(m_LogList.GetSafeHwnd(), L"Explorer", NULL);
210 //theme.SetWindowTheme(m_ChangedFileListCtrl.GetSafeHwnd(), L"Explorer", NULL);
212 // set up the columns
213 m_LogList.DeleteAllItems();
214 m_LogList.InsertGitColumn();
216 m_ChangedFileListCtrl.Init(SVNSLC_COLEXT | SVNSLC_COLSTATUS |SVNSLC_COLADD|SVNSLC_COLDEL , _T("LogDlg"),(SVNSLC_POPALL ^ (SVNSLC_POPCOMMIT)),false);
218 GetDlgItem(IDC_LOGLIST)->UpdateData(FALSE);
220 m_logcounter = 0;
221 m_sMessageBuf.Preallocate(100000);
223 // set the dialog title to "Log - path/to/whatever/we/show/the/log/for"
224 SetDlgTitle(false);
226 m_tooltips.Create(this);
227 CheckRegexpTooltip();
229 SetSplitterRange();
231 // the filter control has a 'cancel' button (the red 'X'), we need to load its bitmap
232 m_cFilter.SetCancelBitmaps(IDI_CANCELNORMAL, IDI_CANCELPRESSED);
233 m_cFilter.SetInfoIcon(IDI_LOGFILTER);
234 m_cFilter.SetValidator(this);
236 AdjustControlSize(IDC_HIDEPATHS);
237 AdjustControlSize(IDC_LOG_FIRSTPARENT);
238 AdjustControlSize(IDC_LOG_ALLBRANCH);
240 GetClientRect(m_DlgOrigRect);
241 m_LogList.GetClientRect(m_LogListOrigRect);
242 GetDlgItem(IDC_MSGVIEW)->GetClientRect(m_MsgViewOrigRect);
243 m_ChangedFileListCtrl.GetClientRect(m_ChgOrigRect);
245 m_DateFrom.SendMessage(DTM_SETMCSTYLE, 0, MCS_WEEKNUMBERS|MCS_NOTODAY|MCS_NOTRAILINGDATES|MCS_NOSELCHANGEONNAV);
246 m_DateTo.SendMessage(DTM_SETMCSTYLE, 0, MCS_WEEKNUMBERS|MCS_NOTODAY|MCS_NOTRAILINGDATES|MCS_NOSELCHANGEONNAV);
248 m_staticRef.SetURL(CString());
250 // resizable stuff
251 AddAnchor(IDC_STATIC_REF, TOP_LEFT);
252 //AddAnchor(IDC_BUTTON_BROWSE_REF, TOP_LEFT);
253 AddAnchor(IDC_FROMLABEL, TOP_LEFT);
254 AddAnchor(IDC_DATEFROM, TOP_LEFT);
255 AddAnchor(IDC_TOLABEL, TOP_LEFT);
256 AddAnchor(IDC_DATETO, TOP_LEFT);
258 SetFilterCueText();
259 AddAnchor(IDC_SEARCHEDIT, TOP_LEFT, TOP_RIGHT);
261 AddAnchor(IDC_LOGLIST, TOP_LEFT, TOP_RIGHT);
262 AddAnchor(IDC_SPLITTERTOP, TOP_LEFT, TOP_RIGHT);
263 AddAnchor(IDC_MSGVIEW, TOP_LEFT, BOTTOM_RIGHT);
264 AddAnchor(IDC_SPLITTERBOTTOM, BOTTOM_LEFT, BOTTOM_RIGHT);
265 AddAnchor(IDC_LOGMSG, BOTTOM_LEFT, BOTTOM_RIGHT);
267 AddAnchor(IDC_LOGINFO, BOTTOM_LEFT, BOTTOM_RIGHT);
268 AddAnchor(IDC_HIDEPATHS, BOTTOM_LEFT);
269 AddAnchor(IDC_LOG_ALLBRANCH,BOTTOM_LEFT);
270 AddAnchor(IDC_LOG_FIRSTPARENT, BOTTOM_LEFT);
271 //AddAnchor(IDC_GETALL, BOTTOM_LEFT);
272 AddAnchor(IDC_SHOWWHOLEPROJECT, BOTTOM_LEFT);
273 AddAnchor(IDC_REFRESH, BOTTOM_LEFT);
274 AddAnchor(IDC_STATBUTTON, BOTTOM_RIGHT);
275 AddAnchor(IDC_PROGRESS, BOTTOM_LEFT, BOTTOM_RIGHT);
276 AddAnchor(IDOK, BOTTOM_RIGHT);
277 AddAnchor(IDCANCEL, BOTTOM_RIGHT);
278 AddAnchor(IDHELP, BOTTOM_RIGHT);
280 if(this->m_bAllBranch)
281 m_LogList.m_ShowMask|=CGit::LOG_INFO_ALL_BRANCH;
282 else
283 m_LogList.m_ShowMask&=~CGit::LOG_INFO_ALL_BRANCH;
285 // SetPromptParentWindow(m_hWnd);
287 if (hWndExplorer)
288 CenterWindow(CWnd::FromHandle(hWndExplorer));
289 EnableSaveRestore(_T("LogDlg"));
291 DWORD yPos1 = CRegDWORD(_T("Software\\TortoiseGit\\TortoiseProc\\ResizableState\\LogDlgSizer1"));
292 DWORD yPos2 = CRegDWORD(_T("Software\\TortoiseGit\\TortoiseProc\\ResizableState\\LogDlgSizer2"));
293 RECT rcDlg, rcLogList, rcChgMsg;
294 GetClientRect(&rcDlg);
295 m_LogList.GetWindowRect(&rcLogList);
296 ScreenToClient(&rcLogList);
297 m_ChangedFileListCtrl.GetWindowRect(&rcChgMsg);
298 ScreenToClient(&rcChgMsg);
299 if (yPos1)
301 RECT rectSplitter;
302 m_wndSplitter1.GetWindowRect(&rectSplitter);
303 ScreenToClient(&rectSplitter);
304 int delta = yPos1 - rectSplitter.top;
306 if ((rcLogList.bottom + delta > rcLogList.top)&&(rcLogList.bottom + delta < rcChgMsg.bottom - 30))
308 m_wndSplitter1.SetWindowPos(NULL, 0, yPos1, 0, 0, SWP_NOSIZE);
309 DoSizeV1(delta);
312 if (yPos2)
314 RECT rectSplitter;
315 m_wndSplitter2.GetWindowRect(&rectSplitter);
316 ScreenToClient(&rectSplitter);
317 int delta = yPos2 - rectSplitter.top;
319 if ((rcChgMsg.top + delta < rcChgMsg.bottom)&&(rcChgMsg.top + delta > rcLogList.top + 30))
321 m_wndSplitter2.SetWindowPos(NULL, 0, yPos2, 0, 0, SWP_NOSIZE);
322 DoSizeV2(delta);
327 if (m_bSelect)
329 // the dialog is used to select revisions
330 // enable the OK button if appropriate
331 EnableOKButton();
333 else
335 // the dialog is used to just view log messages
336 // hide the OK button and set text on Cancel button to OK
337 GetDlgItemText(IDOK, temp);
338 SetDlgItemText(IDCANCEL, temp);
339 GetDlgItem(IDOK)->ShowWindow(SW_HIDE);
342 m_mergedRevs.clear();
344 // first start a thread to obtain the log messages without
345 // blocking the dialog
346 //m_tTo = 0;
347 //m_tFrom = (DWORD)-1;
349 m_LogList.m_Path=m_path;
350 m_LogList.m_bShowWC = true;
351 m_LogList.FetchLogAsync(this);
353 GetDlgItem(IDC_LOGLIST)->SetFocus();
355 ShowStartRef();
356 return FALSE;
359 LRESULT CLogDlg::OnLogListLoading(WPARAM wParam, LPARAM /*lParam*/)
361 int cur=(int)wParam;
363 if( cur == GITLOG_START )
365 CString temp;
366 temp.LoadString(IDS_PROGRESSWAIT);
368 this->m_LogList.ShowText(temp, true);
370 // We use a progress bar while getting the logs
371 m_LogProgress.SetRange32(0, 100);
372 m_LogProgress.SetPos(0);
374 GetDlgItem(IDC_PROGRESS)->ShowWindow(TRUE);
376 //DialogEnableWindow(IDC_GETALL, FALSE);
377 //DialogEnableWindow(IDC_SHOWWHOLEPROJECT, FALSE);
378 //DialogEnableWindow(IDC_LOG_FIRSTPARENT, FALSE);
379 DialogEnableWindow(IDC_STATBUTTON, FALSE);
380 //DialogEnableWindow(IDC_REFRESH, FALSE);
381 DialogEnableWindow(IDC_HIDEPATHS,FALSE);
383 DialogEnableWindow(IDC_DATEFROM,FALSE);
384 DialogEnableWindow(IDC_DATETO,FALSE);
386 DialogEnableWindow(IDC_SEARCHEDIT,FALSE);
388 }else if( cur == GITLOG_END)
391 if(this->m_LogList.HasText())
393 this->m_LogList.ClearText();
396 UpdateLogInfoLabel();
399 //if (!m_bShowedAll)
400 DialogEnableWindow(IDC_SHOWWHOLEPROJECT, TRUE);
402 //DialogEnableWindow(IDC_GETALL, TRUE);
403 DialogEnableWindow(IDC_LOG_FIRSTPARENT, TRUE);
404 DialogEnableWindow(IDC_STATBUTTON, TRUE);
405 DialogEnableWindow(IDC_REFRESH, TRUE);
406 DialogEnableWindow(IDC_HIDEPATHS,TRUE);
408 DialogEnableWindow(IDC_DATEFROM,TRUE);
409 DialogEnableWindow(IDC_DATETO,TRUE);
411 DialogEnableWindow(IDC_SEARCHEDIT,TRUE);
413 // PostMessage(WM_TIMER, LOGFILTER_TIMER);
414 GetDlgItem(IDC_PROGRESS)->ShowWindow(FALSE);
415 //CTime time=m_LogList.GetOldestTime();
416 CTime begin,end;
417 m_LogList.GetTimeRange(begin,end);
418 m_DateFrom.SetTime(&begin);
419 m_DateTo.SetTime(&end);
422 }else
424 if(this->m_LogList.HasText())
426 this->m_LogList.ClearText();
427 this->m_LogList.Invalidate();
429 UpdateLogInfoLabel();
430 m_LogProgress.SetPos(cur);
432 return 0;
434 void CLogDlg::SetDlgTitle(bool bOffline)
436 if (m_sTitle.IsEmpty())
437 GetWindowText(m_sTitle);
439 if (bOffline)
441 CString sTemp;
442 if (m_path.IsUrl())
443 sTemp.Format(IDS_LOG_DLGTITLEOFFLINE, (LPCTSTR)m_sTitle, (LPCTSTR)m_path.GetUIPathString());
444 else if (m_path.IsDirectory())
445 sTemp.Format(IDS_LOG_DLGTITLEOFFLINE, (LPCTSTR)m_sTitle, (LPCTSTR)m_path.GetWinPathString());
446 else
447 sTemp.Format(IDS_LOG_DLGTITLEOFFLINE, (LPCTSTR)m_sTitle, (LPCTSTR)m_path.GetFilename());
448 SetWindowText(sTemp);
450 else
452 if (m_path.IsUrl())
453 SetWindowText(m_sTitle + _T(" - ") + m_path.GetUIPathString());
454 else if (m_path.IsEmpty())
455 SetWindowText(m_sTitle + _T(" - ") + CString(_T("Whole Project")));
456 else if (m_path.IsDirectory())
457 SetWindowText(m_sTitle + _T(" - ") + m_path.GetWinPathString());
458 else
459 SetWindowText(m_sTitle + _T(" - ") + m_path.GetFilename());
463 void CLogDlg::CheckRegexpTooltip()
465 CWnd *pWnd = GetDlgItem(IDC_SEARCHEDIT);
466 // Since tooltip describes regexp features, show it only if regexps are enabled.
467 if (m_bFilterWithRegex)
469 m_tooltips.AddTool(pWnd, IDS_LOG_FILTER_REGEX_TT);
471 else
472 m_tooltips.DelTool(pWnd);
475 void CLogDlg::EnableOKButton()
477 if (m_bSelect)
479 // the dialog is used to select revisions
480 if (m_bSelectionMustBeSingle)
482 // enable OK button if only a single revision is selected
483 DialogEnableWindow(IDOK, (m_LogList.GetSelectedCount()==1));
485 else if (m_bSelectionMustBeContinuous)
486 DialogEnableWindow(IDOK, (m_LogList.GetSelectedCount()!=0)&&(m_LogList.IsSelectionContinuous()));
487 else
488 DialogEnableWindow(IDOK, m_LogList.GetSelectedCount()!=0);
490 else
491 DialogEnableWindow(IDOK, TRUE);
494 CString CLogDlg::GetTagInfo(GitRev* pLogEntry)
496 CString cmd;
497 CString output;
499 if(m_LogList.m_HashMap.find(pLogEntry->m_CommitHash) != m_LogList.m_HashMap.end())
501 STRING_VECTOR &vector = m_LogList.m_HashMap[pLogEntry->m_CommitHash];
502 for(int i=0;i<vector.size();i++)
504 if(vector[i].Find(_T("refs/tags/")) == 0 )
506 CString tag= vector[i];
507 int start = vector[i].Find(_T("^{}"));
508 if(start>0)
509 tag=tag.Left(start);
510 else
511 continue;
513 cmd.Format(_T("git.exe cat-file tag %s"), tag);
515 if(g_Git.Run(cmd, &output, CP_UTF8) == 0 )
516 output+=_T("\n");
521 if(!output.IsEmpty())
523 output = _T("\n*Tag Info*\n\n") + output;
526 return output;
529 void CLogDlg::FillLogMessageCtrl(bool bShow /* = true*/)
531 // we fill here the log message rich edit control,
532 // and also populate the changed files list control
533 // according to the selected revision(s).
535 CRichEditCtrl * pMsgView = (CRichEditCtrl*)GetDlgItem(IDC_MSGVIEW);
536 // empty the log message view
537 pMsgView->SetWindowText(_T(" "));
538 // empty the changed files list
539 m_ChangedFileListCtrl.SetRedraw(FALSE);
540 // InterlockedExchange(&m_bNoDispUpdates, TRUE);
541 m_currentChangedArray = NULL;
542 //m_ChangedFileListCtrl.SetExtendedStyle ( LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER );
543 m_ChangedFileListCtrl.DeleteAllItems();
545 // if we're not here to really show a selected revision, just
546 // get out of here after clearing the views, which is what is intended
547 // if that flag is not set.
548 if (!bShow)
550 // force a redraw
551 m_ChangedFileListCtrl.Invalidate();
552 // InterlockedExchange(&m_bNoDispUpdates, FALSE);
553 m_ChangedFileListCtrl.SetRedraw(TRUE);
554 return;
557 // depending on how many revisions are selected, we have to do different
558 // tasks.
559 int selCount = m_LogList.GetSelectedCount();
560 if (selCount == 0)
562 // if nothing is selected, we have nothing more to do
563 // InterlockedExchange(&m_bNoDispUpdates, FALSE);
564 m_ChangedFileListCtrl.SetRedraw(TRUE);
565 return;
567 else if (selCount == 1)
569 // if one revision is selected, we have to fill the log message view
570 // with the corresponding log message, and also fill the changed files
571 // list fully.
572 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
573 int selIndex = m_LogList.GetNextSelectedItem(pos);
574 if (selIndex >= m_LogList.m_arShownList.GetCount())
576 // InterlockedExchange(&m_bNoDispUpdates, FALSE);
577 m_ChangedFileListCtrl.SetRedraw(TRUE);
578 return;
580 GitRev* pLogEntry = reinterpret_cast<GitRev *>(m_LogList.m_arShownList.GetAt(selIndex));
582 if(!pLogEntry->m_IsFull)
584 pMsgView->SetWindowText(_T("load ..."));
585 }else
587 // set the log message text
588 pMsgView->SetWindowText(_T("Commit:")+pLogEntry->m_CommitHash.ToString()+_T("\r\n\r\n"));
589 // turn bug ID's into links if the bugtraq: properties have been set
590 // and we can find a match of those in the log message
592 pMsgView->SetSel(-1,-1);
593 CHARFORMAT2 format;
594 SecureZeroMemory(&format, sizeof(CHARFORMAT2));
595 format.cbSize = sizeof(CHARFORMAT2);
596 format.dwMask = CFM_BOLD;
597 format.dwEffects = CFE_BOLD;
598 pMsgView->SendMessage(EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&format);
600 CString msg=_T("* ");
601 msg+=pLogEntry->m_Subject;
602 pMsgView->ReplaceSel(msg);
604 pMsgView->SetSel(-1,-1);
605 format.dwEffects = 0;
606 pMsgView->SendMessage(EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&format);
608 msg=_T("\n\n");
609 msg+=pLogEntry->m_Body;
611 msg+=GetTagInfo(pLogEntry);
613 pMsgView->ReplaceSel(msg);
615 CString text;
616 pMsgView->GetWindowText(text);
617 // the rich edit control doesn't count the CR char!
618 // to be exact: CRLF is treated as one char.
619 text.Replace(_T("\r"), _T(""));
621 m_ProjectProperties.FindBugID(text, pMsgView);
622 CAppUtils::FormatTextInRichEditControl(pMsgView);
624 int HidePaths=m_cHidePaths.GetState() & 0x0003;
625 CString matchpath=this->m_path.GetGitPathString();
627 for(int i=0;i<pLogEntry->m_Files.GetCount() && (!matchpath.IsEmpty());i++)
629 if( m_bWholeProject )
630 break;
632 ((CTGitPath&)pLogEntry->m_Files[i]).m_Action &= ~(CTGitPath::LOGACTIONS_HIDE|CTGitPath::LOGACTIONS_GRAY);
634 if(pLogEntry->m_Files[i].GetGitPathString().Left(matchpath.GetLength()) != matchpath)
636 if(HidePaths==BST_CHECKED)
637 ((CTGitPath&)pLogEntry->m_Files[i]).m_Action |= CTGitPath::LOGACTIONS_HIDE;
638 if(HidePaths==BST_INDETERMINATE)
639 ((CTGitPath&)pLogEntry->m_Files[i]).m_Action |= CTGitPath::LOGACTIONS_GRAY;
643 m_ChangedFileListCtrl.UpdateWithGitPathList(pLogEntry->m_Files);
644 m_ChangedFileListCtrl.m_CurrentVersion=pLogEntry->m_CommitHash;
645 m_ChangedFileListCtrl.Show(SVNSLC_SHOWVERSIONED);
647 m_ChangedFileListCtrl.SetRedraw(TRUE);
648 return;
652 else
654 // more than one revision is selected:
655 // the log message view must be emptied
656 // the changed files list contains all the changed paths from all
657 // selected revisions, with 'doubles' removed
658 m_currentChangedPathList = GetChangedPathsFromSelectedRevisions(true);
661 // redraw the views
662 // InterlockedExchange(&m_bNoDispUpdates, FALSE);
663 #if 0
664 if (m_currentChangedArray)
666 m_ChangedFileListCtrl.SetItemCountEx(m_currentChangedArray->GetCount());
667 m_ChangedFileListCtrl.RedrawItems(0, m_currentChangedArray->GetCount());
669 else if (m_currentChangedPathList.GetCount())
671 m_ChangedFileListCtrl.SetItemCountEx(m_currentChangedPathList.GetCount());
672 m_ChangedFileListCtrl.RedrawItems(0, m_currentChangedPathList.GetCount());
674 else
676 m_ChangedFileListCtrl.SetItemCountEx(0);
677 m_ChangedFileListCtrl.Invalidate();
679 #endif
680 // sort according to the settings
681 if (m_nSortColumnPathList > 0)
682 SetSortArrow(&m_ChangedFileListCtrl, m_nSortColumnPathList, m_bAscendingPathList);
683 else
684 SetSortArrow(&m_ChangedFileListCtrl, -1, false);
685 m_ChangedFileListCtrl.SetRedraw(TRUE);
689 void CLogDlg::OnBnClickedRefresh()
692 Refresh (true);
695 void CLogDlg::Refresh (bool /*autoGoOnline*/)
697 m_limit = 0;
698 m_LogList.Refresh();
699 FillLogMessageCtrl(false);
704 BOOL CLogDlg::Cancel()
706 return m_bCancelled;
709 void CLogDlg::SaveSplitterPos()
711 if (!IsIconic())
713 CRegDWORD regPos1 = CRegDWORD(_T("Software\\TortoiseGit\\TortoiseProc\\ResizableState\\LogDlgSizer1"));
714 CRegDWORD regPos2 = CRegDWORD(_T("Software\\TortoiseGit\\TortoiseProc\\ResizableState\\LogDlgSizer2"));
715 RECT rectSplitter;
716 m_wndSplitter1.GetWindowRect(&rectSplitter);
717 ScreenToClient(&rectSplitter);
718 regPos1 = rectSplitter.top;
719 m_wndSplitter2.GetWindowRect(&rectSplitter);
720 ScreenToClient(&rectSplitter);
721 regPos2 = rectSplitter.top;
725 void CLogDlg::OnCancel()
727 // canceling means stopping the working thread if it's still running.
728 if (this->IsThreadRunning())
730 m_LogList.SafeTerminateThread();
732 UpdateData();
734 SaveSplitterPos();
735 __super::OnCancel();
738 CString CLogDlg::MakeShortMessage(const CString& message)
740 bool bFoundShort = true;
741 CString sShortMessage = m_ProjectProperties.GetLogSummary(message);
742 if (sShortMessage.IsEmpty())
744 bFoundShort = false;
745 sShortMessage = message;
747 // Remove newlines and tabs 'cause those are not shown nicely in the list control
748 sShortMessage.Replace(_T("\r"), _T(""));
749 sShortMessage.Replace(_T("\t"), _T(" "));
751 // Suppose the first empty line separates 'summary' from the rest of the message.
752 int found = sShortMessage.Find(_T("\n\n"));
753 // To avoid too short 'short' messages
754 // (e.g. if the message looks something like "Bugfix:\n\n*done this\n*done that")
755 // only use the empty newline as a separator if it comes after at least 15 chars.
756 if ((!bFoundShort)&&(found >= 15))
758 sShortMessage = sShortMessage.Left(found);
760 sShortMessage.Replace('\n', ' ');
761 return sShortMessage;
764 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*/)
766 #if 0
767 if (rev == SVN_INVALID_REVNUM)
769 m_childCounter--;
770 return TRUE;
773 // this is the callback function which receives the data for every revision we ask the log for
774 // we store this information here one by one.
775 m_logcounter += 1;
776 if (m_startrev == -1)
777 m_startrev = rev;
778 if (m_limit != 0)
780 m_limitcounter--;
781 m_LogProgress.SetPos(m_limit - m_limitcounter);
783 else if (m_startrev.IsNumber() && m_startrev.IsNumber())
784 m_LogProgress.SetPos((git_revnum_t)m_startrev-rev+(git_revnum_t)m_endrev);
785 __time64_t ttime = time/1000000L;
786 if (m_tTo < (DWORD)ttime)
787 m_tTo = (DWORD)ttime;
788 if (m_tFrom > (DWORD)ttime)
789 m_tFrom = (DWORD)ttime;
790 if ((m_lowestRev > rev)||(m_lowestRev < 0))
791 m_lowestRev = rev;
792 // Add as many characters from the log message to the list control
793 PLOGENTRYDATA pLogItem = new LOGENTRYDATA;
794 pLogItem->bCopies = !!copies;
796 // find out if this item was copied in the revision
797 BOOL copiedself = FALSE;
798 if (copies)
800 for (INT_PTR cpPathIndex = 0; cpPathIndex < cpaths->GetCount(); ++cpPathIndex)
802 LogChangedPath * cpath = cpaths->GetAt(cpPathIndex);
803 if (!cpath->sCopyFromPath.IsEmpty() && (cpath->sPath.Compare(m_sSelfRelativeURL) == 0))
805 // note: this only works if the log is fetched top-to-bottom
806 // but since we do that, it shouldn't be a problem
807 m_sSelfRelativeURL = cpath->sCopyFromPath;
808 copiedself = TRUE;
809 break;
813 pLogItem->bCopiedSelf = copiedself;
814 pLogItem->tmDate = ttime;
815 pLogItem->sAuthor = author;
816 pLogItem->sDate = date;
817 pLogItem->sShortMessage = MakeShortMessage(message);
818 pLogItem->dwFileChanges = filechanges;
819 pLogItem->actions = actions;
820 pLogItem->haschildren = haschildren;
821 pLogItem->childStackDepth = m_childCounter;
822 m_maxChild = max(m_childCounter, m_maxChild);
823 if (haschildren)
824 m_childCounter++;
825 pLogItem->sBugIDs = m_ProjectProperties.FindBugID(message).Trim();
827 // split multi line log entries and concatenate them
828 // again but this time with \r\n as line separators
829 // so that the edit control recognizes them
832 if (message.GetLength()>0)
834 m_sMessageBuf = message;
835 m_sMessageBuf.Replace(_T("\n\r"), _T("\n"));
836 m_sMessageBuf.Replace(_T("\r\n"), _T("\n"));
837 if (m_sMessageBuf.Right(1).Compare(_T("\n"))==0)
838 m_sMessageBuf = m_sMessageBuf.Left(m_sMessageBuf.GetLength()-1);
840 else
841 m_sMessageBuf.Empty();
842 pLogItem->sMessage = m_sMessageBuf;
843 pLogItem->Rev = rev;
845 // move-construct path array
847 pLogItem->pArChangedPaths = new LogChangedPathArray (*cpaths);
848 cpaths->RemoveAll();
850 catch (CException * e)
852 ::MessageBox(NULL, _T("not enough memory!"), _T("TortoiseGit"), MB_ICONERROR);
853 e->Delete();
854 m_bCancelled = TRUE;
856 m_logEntries.push_back(pLogItem);
857 m_arShownList.Add(pLogItem);
858 #endif
859 return TRUE;
862 GitRev g_rev;
863 //this is the thread function which calls the subversion function
868 void CLogDlg::CopyChangedSelectionToClipBoard()
871 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
872 if (pos == NULL)
873 return; // nothing is selected, get out of here
875 CString sPaths;
877 // CGitRev* pLogEntry = reinterpret_cast<CGitRev* >(m_LogList.m_arShownList.GetAt(m_LogList.GetNextSelectedItem(pos)));
878 // if (pos)
880 POSITION pos = m_ChangedFileListCtrl.GetFirstSelectedItemPosition();
881 while (pos)
883 int nItem = m_ChangedFileListCtrl.GetNextSelectedItem(pos);
884 CTGitPath *path = (CTGitPath*)m_ChangedFileListCtrl.GetItemData(nItem);
885 if(path)
886 sPaths += path->GetGitPathString();
887 sPaths += _T("\r\n");
890 #if 0
891 else
893 // only one revision is selected in the log dialog top pane
894 // but multiple items could be selected in the changed items list
895 POSITION pos = m_ChangedFileListCtrl.GetFirstSelectedItemPosition();
896 while (pos)
898 int nItem = m_ChangedFileListCtrl.GetNextSelectedItem(pos);
899 LogChangedPath * changedlogpath = pLogEntry->pArChangedPaths->GetAt(nItem);
901 if ((m_cHidePaths.GetState() & 0x0003)==BST_CHECKED)
903 // some items are hidden! So find out which item the user really selected
904 INT_PTR selRealIndex = -1;
905 for (INT_PTR hiddenindex=0; hiddenindex<pLogEntry->pArChangedPaths->GetCount(); ++hiddenindex)
907 if (pLogEntry->pArChangedPaths->GetAt(hiddenindex)->sPath.Left(m_sRelativeRoot.GetLength()).Compare(m_sRelativeRoot)==0)
908 selRealIndex++;
909 if (selRealIndex == nItem)
911 changedlogpath = pLogEntry->pArChangedPaths->GetAt(hiddenindex);
912 break;
916 if (changedlogpath)
918 sPaths += changedlogpath->sPath;
919 sPaths += _T("\r\n");
923 #endif
924 sPaths.Trim();
925 CStringUtils::WriteAsciiStringToClipboard(sPaths, GetSafeHwnd());
929 BOOL CLogDlg::IsDiffPossible(LogChangedPath * /*changedpath*/, git_revnum_t rev)
931 #if 0
932 CString added, deleted;
933 if (changedpath == NULL)
934 return false;
936 if ((rev > 1)&&(changedpath->action != LOGACTIONS_DELETED))
938 if (changedpath->action == LOGACTIONS_ADDED) // file is added
940 if (changedpath->lCopyFromRev == 0)
941 return FALSE; // but file was not added with history
943 return TRUE;
945 #endif
946 return FALSE;
949 void CLogDlg::OnContextMenu(CWnd* pWnd, CPoint point)
951 // we have two separate context menus:
952 // one shown on the log message list control,
953 // the other shown in the changed-files list control
954 int selCount = m_LogList.GetSelectedCount();
955 if (pWnd == &m_LogList)
957 //ShowContextMenuForRevisions(pWnd, point);
959 else if (pWnd == &m_ChangedFileListCtrl)
961 //ShowContextMenuForChangedpaths(pWnd, point);
963 else if ((selCount == 1)&&(pWnd == GetDlgItem(IDC_MSGVIEW)))
965 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
966 int selIndex = -1;
967 if (pos)
968 selIndex = m_LogList.GetNextSelectedItem(pos);
969 if ((point.x == -1) && (point.y == -1))
971 CRect rect;
972 GetDlgItem(IDC_MSGVIEW)->GetClientRect(&rect);
973 ClientToScreen(&rect);
974 point = rect.CenterPoint();
976 CString sMenuItemText;
977 CMenu popup;
978 if (popup.CreatePopupMenu())
980 // add the 'default' entries
981 sMenuItemText.LoadString(IDS_SCIEDIT_COPY);
982 popup.AppendMenu(MF_STRING | MF_ENABLED, WM_COPY, sMenuItemText);
983 sMenuItemText.LoadString(IDS_SCIEDIT_SELECTALL);
984 popup.AppendMenu(MF_STRING | MF_ENABLED, EM_SETSEL, sMenuItemText);
986 //if (selIndex >= 0)
988 // popup.AppendMenu(MF_SEPARATOR);
989 // sMenuItemText.LoadString(IDS_LOG_POPUP_EDITLOG);
990 // popup.AppendMenu(MF_STRING | MF_ENABLED, CGitLogList::ID_EDITAUTHOR, sMenuItemText);
993 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
994 switch (cmd)
996 case 0:
997 break; // no command selected
998 case EM_SETSEL:
999 case WM_COPY:
1000 ::SendMessage(GetDlgItem(IDC_MSGVIEW)->GetSafeHwnd(), cmd, 0, -1);
1001 break;
1002 case CGitLogList::ID_EDITAUTHOR:
1003 EditLogMessage(selIndex);
1004 break;
1013 void CLogDlg::OnOK()
1015 // since the log dialog is also used to select revisions for other
1016 // dialogs, we have to do some work before closing this dialog
1017 if (GetFocus() != GetDlgItem(IDOK))
1018 return; // if the "OK" button doesn't have the focus, do nothing: this prevents closing the dialog when pressing enter
1021 if (this->IsThreadRunning())
1023 m_LogList.SafeTerminateThread();
1025 UpdateData();
1026 // check that one and only one row is selected
1027 if (m_LogList.GetSelectedCount() == 1)
1029 // get the selected row
1030 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
1031 int selIndex = m_LogList.GetNextSelectedItem(pos);
1032 if (selIndex < m_LogList.m_arShownList.GetCount())
1034 // all ok, pick up the revision
1035 GitRev* pLogEntry = reinterpret_cast<GitRev *>(m_LogList.m_arShownList.GetAt(selIndex));
1036 // extract the hash
1037 m_sSelectedHash = pLogEntry->m_CommitHash;
1040 UpdateData(FALSE);
1041 SaveSplitterPos();
1042 __super::OnOK();
1044 #if 0
1045 if (!GetDlgItem(IDOK)->IsWindowVisible() && GetFocus() != GetDlgItem(IDCANCEL))
1046 return; // the Cancel button works as the OK button. But if the cancel button has not the focus, do nothing.
1048 CString temp;
1049 CString buttontext;
1050 GetDlgItemText(IDOK, buttontext);
1051 temp.LoadString(IDS_MSGBOX_CANCEL);
1052 if (temp.Compare(buttontext) != 0)
1053 __super::OnOK(); // only exit if the button text matches, and that will match only if the thread isn't running anymore
1054 m_bCancelled = TRUE;
1055 m_selectedRevs.Clear();
1056 m_selectedRevsOneRange.Clear();
1057 if (m_pNotifyWindow)
1059 int selIndex = m_LogList.GetSelectionMark();
1060 if (selIndex >= 0)
1062 PLOGENTRYDATA pLogEntry = NULL;
1063 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
1064 pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(m_LogList.GetNextSelectedItem(pos)));
1065 m_selectedRevs.AddRevision(pLogEntry->Rev);
1066 git_revnum_t lowerRev = pLogEntry->Rev;
1067 git_revnum_t higherRev = lowerRev;
1068 while (pos)
1070 pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(m_LogList.GetNextSelectedItem(pos)));
1071 git_revnum_t rev = pLogEntry->Rev;
1072 m_selectedRevs.AddRevision(pLogEntry->Rev);
1073 if (lowerRev > rev)
1074 lowerRev = rev;
1075 if (higherRev < rev)
1076 higherRev = rev;
1078 if (m_sFilterText.IsEmpty() && m_nSortColumn == 0 && IsSelectionContinuous())
1080 m_selectedRevsOneRange.AddRevRange(lowerRev, higherRev);
1082 BOOL bSentMessage = FALSE;
1083 if (m_LogList.GetSelectedCount() == 1)
1085 // if only one revision is selected, check if the path/url with which the dialog was started
1086 // was directly affected in that revision. If it was, then check if our path was copied from somewhere.
1087 // if it was copied, use the copy from revision as lowerRev
1088 if ((pLogEntry)&&(pLogEntry->pArChangedPaths)&&(lowerRev == higherRev))
1090 CString sUrl = m_path.GetGitPathString();
1091 if (!m_path.IsUrl())
1093 sUrl = GetURLFromPath(m_path);
1095 sUrl = sUrl.Mid(m_sRepositoryRoot.GetLength());
1096 for (int cp = 0; cp < pLogEntry->pArChangedPaths->GetCount(); ++cp)
1098 LogChangedPath * pData = pLogEntry->pArChangedPaths->GetAt(cp);
1099 if (pData)
1101 if (sUrl.Compare(pData->sPath) == 0)
1103 if (!pData->sCopyFromPath.IsEmpty())
1105 lowerRev = pData->lCopyFromRev;
1106 m_pNotifyWindow->SendMessage(WM_REVSELECTED, m_wParam & (MERGE_REVSELECTSTART), lowerRev);
1107 m_pNotifyWindow->SendMessage(WM_REVSELECTED, m_wParam & (MERGE_REVSELECTEND), higherRev);
1108 m_pNotifyWindow->SendMessage(WM_REVLIST, m_selectedRevs.GetCount(), (LPARAM)&m_selectedRevs);
1109 bSentMessage = TRUE;
1116 if ( !bSentMessage )
1118 m_pNotifyWindow->SendMessage(WM_REVSELECTED, m_wParam & (MERGE_REVSELECTSTART | MERGE_REVSELECTMINUSONE), lowerRev);
1119 m_pNotifyWindow->SendMessage(WM_REVSELECTED, m_wParam & (MERGE_REVSELECTEND | MERGE_REVSELECTMINUSONE), higherRev);
1120 m_pNotifyWindow->SendMessage(WM_REVLIST, m_selectedRevs.GetCount(), (LPARAM)&m_selectedRevs);
1121 if (m_selectedRevsOneRange.GetCount())
1122 m_pNotifyWindow->SendMessage(WM_REVLISTONERANGE, 0, (LPARAM)&m_selectedRevsOneRange);
1126 UpdateData();
1127 CRegDWORD reg = CRegDWORD(_T("Software\\TortoiseGit\\ShowAllEntry"));
1128 reg = m_btnShow.GetCurrentEntry();
1129 SaveSplitterPos();
1130 #endif
1133 void CLogDlg::OnNMDblclkChangedFileList(NMHDR * /*pNMHDR*/, LRESULT *pResult)
1135 // a double click on an entry in the changed-files list has happened
1136 *pResult = 0;
1138 DiffSelectedFile();
1141 void CLogDlg::DiffSelectedFile()
1143 #if 0
1144 if (m_bThreadRunning)
1145 return;
1146 UpdateLogInfoLabel();
1147 INT_PTR selIndex = m_ChangedFileListCtrl.GetSelectionMark();
1148 if (selIndex < 0)
1149 return;
1150 if (m_ChangedFileListCtrl.GetSelectedCount() == 0)
1151 return;
1152 // find out if there's an entry selected in the log list
1153 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
1154 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(m_LogList.GetNextSelectedItem(pos)));
1155 git_revnum_t rev1 = pLogEntry->Rev;
1156 git_revnum_t rev2 = rev1;
1157 if (pos)
1159 while (pos)
1161 // there's at least a second entry selected in the log list: several revisions selected!
1162 pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(m_LogList.GetNextSelectedItem(pos)));
1163 if (pLogEntry)
1165 rev1 = max(rev1,(long)pLogEntry->Rev);
1166 rev2 = min(rev2,(long)pLogEntry->Rev);
1169 rev2--;
1170 // now we have both revisions selected in the log list, so we can do a diff of the selected
1171 // entry in the changed files list with these two revisions.
1172 DoDiffFromLog(selIndex, rev1, rev2, false, false);
1174 else
1176 rev2 = rev1-1;
1177 // nothing or only one revision selected in the log list
1178 LogChangedPath * changedpath = pLogEntry->pArChangedPaths->GetAt(selIndex);
1180 if ((m_cHidePaths.GetState() & 0x0003)==BST_CHECKED)
1182 // some items are hidden! So find out which item the user really clicked on
1183 INT_PTR selRealIndex = -1;
1184 for (INT_PTR hiddenindex=0; hiddenindex<pLogEntry->pArChangedPaths->GetCount(); ++hiddenindex)
1186 if (pLogEntry->pArChangedPaths->GetAt(hiddenindex)->sPath.Left(m_sRelativeRoot.GetLength()).Compare(m_sRelativeRoot)==0)
1187 selRealIndex++;
1188 if (selRealIndex == selIndex)
1190 selIndex = hiddenindex;
1191 changedpath = pLogEntry->pArChangedPaths->GetAt(selIndex);
1192 break;
1197 if (IsDiffPossible(changedpath, rev1))
1199 // diffs with renamed files are possible
1200 if ((changedpath)&&(!changedpath->sCopyFromPath.IsEmpty()))
1201 rev2 = changedpath->lCopyFromRev;
1202 else
1204 // if the path was modified but the parent path was 'added with history'
1205 // then we have to use the copy from revision of the parent path
1206 CTGitPath cpath = CTGitPath(changedpath->sPath);
1207 for (int flist = 0; flist < pLogEntry->pArChangedPaths->GetCount(); ++flist)
1209 CTGitPath p = CTGitPath(pLogEntry->pArChangedPaths->GetAt(flist)->sPath);
1210 if (p.IsAncestorOf(cpath))
1212 if (!pLogEntry->pArChangedPaths->GetAt(flist)->sCopyFromPath.IsEmpty())
1213 rev2 = pLogEntry->pArChangedPaths->GetAt(flist)->lCopyFromRev;
1217 DoDiffFromLog(selIndex, rev1, rev2, false, false);
1219 else
1221 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, CTGitPath(changedpath->sPath));
1222 CTGitPath tempfile2 = CTempFiles::Instance().GetTempFilePath(false, CTGitPath(changedpath->sPath));
1223 GitRev r = rev1;
1224 // deleted files must be opened from the revision before the deletion
1225 if (changedpath->action == LOGACTIONS_DELETED)
1226 r = rev1-1;
1227 m_bCancelled = false;
1229 CProgressDlg progDlg;
1230 progDlg.SetTitle(IDS_APPNAME);
1231 progDlg.SetAnimation(IDR_DOWNLOAD);
1232 CString sInfoLine;
1233 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, (LPCTSTR)(m_sRepositoryRoot + changedpath->sPath), (LPCTSTR)r.ToString());
1234 progDlg.SetLine(1, sInfoLine, true);
1235 SetAndClearProgressInfo(&progDlg);
1236 progDlg.ShowModeless(m_hWnd);
1238 if (!Cat(CTGitPath(m_sRepositoryRoot + changedpath->sPath), r, r, tempfile))
1240 m_bCancelled = false;
1241 if (!Cat(CTGitPath(m_sRepositoryRoot + changedpath->sPath), GitRev::REV_HEAD, r, tempfile))
1243 progDlg.Stop();
1244 SetAndClearProgressInfo((HWND)NULL);
1245 CMessageBox::Show(m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1246 return;
1249 progDlg.Stop();
1250 SetAndClearProgressInfo((HWND)NULL);
1252 CString sName1, sName2;
1253 sName1.Format(_T("%s - Revision %ld"), (LPCTSTR)CPathUtils::GetFileNameFromPath(changedpath->sPath), (git_revnum_t)rev1);
1254 sName2.Format(_T("%s - Revision %ld"), (LPCTSTR)CPathUtils::GetFileNameFromPath(changedpath->sPath), (git_revnum_t)rev1-1);
1255 CAppUtils::DiffFlags flags;
1256 flags.AlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
1257 if (changedpath->action == LOGACTIONS_DELETED)
1258 CAppUtils::StartExtDiff(tempfile, tempfile2, sName2, sName1, flags);
1259 else
1260 CAppUtils::StartExtDiff(tempfile2, tempfile, sName2, sName1, flags);
1263 #endif
1267 void CLogDlg::DoDiffFromLog(INT_PTR selIndex, GitRev* rev1, GitRev* rev2, bool blame, bool unified)
1269 DialogEnableWindow(IDOK, FALSE);
1270 // SetPromptApp(&theApp);
1271 theApp.DoWaitCursor(1);
1273 CString temppath;
1274 GetTempPath(temppath);
1276 CString file1;
1277 file1.Format(_T("%s%s_%s%s"),
1278 temppath,
1279 (*m_currentChangedArray)[selIndex].GetBaseFilename(),
1280 rev1->m_CommitHash.ToString().Left(6),
1281 (*m_currentChangedArray)[selIndex].GetFileExtension());
1283 CString file2;
1284 file2.Format(_T("%s\\%s_%s%s"),
1285 temppath,
1286 (*m_currentChangedArray)[selIndex].GetBaseFilename(),
1287 rev2->m_CommitHash.ToString().Left(6),
1288 (*m_currentChangedArray)[selIndex].GetFileExtension());
1290 CString cmd;
1292 cmd.Format(_T("git.exe cat-file -p %s:%s"),rev1->m_CommitHash.ToString(),(*m_currentChangedArray)[selIndex].GetGitPathString());
1293 g_Git.RunLogFile(cmd,file1);
1294 cmd.Format(_T("git.exe cat-file -p %s:%s"),rev2->m_CommitHash.ToString(),(*m_currentChangedArray)[selIndex].GetGitPathString());
1295 g_Git.RunLogFile(cmd,file2);
1297 CAppUtils::DiffFlags flags;
1298 CAppUtils::StartExtDiff(file1,file2,_T("A"),_T("B"),flags);
1300 #if 0
1301 //get the filename
1302 CString filepath;
1303 if (Git::PathIsURL(m_path))
1305 filepath = m_path.GetGitPathString();
1307 else
1309 filepath = GetURLFromPath(m_path);
1310 if (filepath.IsEmpty())
1312 theApp.DoWaitCursor(-1);
1313 CString temp;
1314 temp.Format(IDS_ERR_NOURLOFFILE, (LPCTSTR)filepath);
1315 CMessageBox::Show(this->m_hWnd, temp, _T("TortoiseGit"), MB_ICONERROR);
1316 TRACE(_T("could not retrieve the URL of the file!\n"));
1317 EnableOKButton();
1318 theApp.DoWaitCursor(-11);
1319 return; //exit
1322 m_bCancelled = FALSE;
1323 filepath = GetRepositoryRoot(CTGitPath(filepath));
1325 CString firstfile, secondfile;
1326 if (m_LogList.GetSelectedCount()==1)
1328 int s = m_LogList.GetSelectionMark();
1329 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(s));
1330 LogChangedPath * changedpath = pLogEntry->pArChangedPaths->GetAt(selIndex);
1331 firstfile = changedpath->sPath;
1332 secondfile = firstfile;
1333 if ((rev2 == rev1-1)&&(changedpath->lCopyFromRev > 0)) // is it an added file with history?
1335 secondfile = changedpath->sCopyFromPath;
1336 rev2 = changedpath->lCopyFromRev;
1339 else
1341 firstfile = m_currentChangedPathList[selIndex].GetGitPathString();
1342 secondfile = firstfile;
1345 firstfile = filepath + firstfile.Trim();
1346 secondfile = filepath + secondfile.Trim();
1348 GitDiff diff(this, this->m_hWnd, true);
1349 diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
1350 diff.SetHEADPeg(m_LogRevision);
1351 if (unified)
1353 if (PromptShown())
1354 diff.ShowUnifiedDiff(CTGitPath(secondfile), rev2, CTGitPath(firstfile), rev1);
1355 else
1356 CAppUtils::StartShowUnifiedDiff(m_hWnd, CTGitPath(secondfile), rev2, CTGitPath(firstfile), rev1, GitRev(), m_LogRevision);
1358 else
1360 if (diff.ShowCompare(CTGitPath(secondfile), rev2, CTGitPath(firstfile), rev1, GitRev(), false, blame))
1362 if (firstfile.Compare(secondfile)==0)
1364 git_revnum_t baseRev = 0;
1365 diff.DiffProps(CTGitPath(firstfile), rev2, rev1, baseRev);
1370 #endif
1372 theApp.DoWaitCursor(-1);
1373 EnableOKButton();
1376 BOOL CLogDlg::Open(bool /*bOpenWith*/,CString changedpath, git_revnum_t rev)
1378 #if 0
1379 DialogEnableWindow(IDOK, FALSE);
1380 SetPromptApp(&theApp);
1381 theApp.DoWaitCursor(1);
1382 CString filepath;
1383 if (Git::PathIsURL(m_path))
1385 filepath = m_path.GetGitPathString();
1387 else
1389 filepath = GetURLFromPath(m_path);
1390 if (filepath.IsEmpty())
1392 theApp.DoWaitCursor(-1);
1393 CString temp;
1394 temp.Format(IDS_ERR_NOURLOFFILE, (LPCTSTR)filepath);
1395 CMessageBox::Show(this->m_hWnd, temp, _T("TortoiseGit"), MB_ICONERROR);
1396 TRACE(_T("could not retrieve the URL of the file!\n"));
1397 EnableOKButton();
1398 return FALSE;
1401 m_bCancelled = false;
1402 filepath = GetRepositoryRoot(CTGitPath(filepath));
1403 filepath += changedpath;
1405 CProgressDlg progDlg;
1406 progDlg.SetTitle(IDS_APPNAME);
1407 progDlg.SetAnimation(IDR_DOWNLOAD);
1408 CString sInfoLine;
1409 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, (LPCTSTR)filepath, (LPCTSTR)GitRev(rev).ToString());
1410 progDlg.SetLine(1, sInfoLine, true);
1411 SetAndClearProgressInfo(&progDlg);
1412 progDlg.ShowModeless(m_hWnd);
1414 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, CTGitPath(filepath), rev);
1415 m_bCancelled = false;
1416 if (!Cat(CTGitPath(filepath), GitRev(rev), rev, tempfile))
1418 progDlg.Stop();
1419 SetAndClearProgressInfo((HWND)NULL);
1420 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1421 EnableOKButton();
1422 theApp.DoWaitCursor(-1);
1423 return FALSE;
1425 progDlg.Stop();
1426 SetAndClearProgressInfo((HWND)NULL);
1427 SetFileAttributes(tempfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
1428 if (!bOpenWith)
1430 int ret = (int)ShellExecute(this->m_hWnd, NULL, tempfile.GetWinPath(), NULL, NULL, SW_SHOWNORMAL);
1431 if (ret <= HINSTANCE_ERROR)
1432 bOpenWith = true;
1434 if (bOpenWith)
1436 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
1437 cmd += tempfile.GetWinPathString() + _T(" ");
1438 CAppUtils::LaunchApplication(cmd, NULL, false);
1440 EnableOKButton();
1441 theApp.DoWaitCursor(-1);
1442 #endif
1443 return TRUE;
1446 void CLogDlg::EditAuthor(const CLogDataVector& /*logs*/)
1448 #if 0
1449 CString url;
1450 CString name;
1451 if (logs.size() == 0)
1452 return;
1453 DialogEnableWindow(IDOK, FALSE);
1454 SetPromptApp(&theApp);
1455 theApp.DoWaitCursor(1);
1456 if (Git::PathIsURL(m_path))
1457 url = m_path.GetGitPathString();
1458 else
1460 url = GetURLFromPath(m_path);
1462 name = Git_PROP_REVISION_AUTHOR;
1464 CString value = RevPropertyGet(name, CTGitPath(url), logs[0]->Rev);
1465 CString sOldValue = value;
1466 value.Replace(_T("\n"), _T("\r\n"));
1467 CInputDlg dlg(this);
1468 dlg.m_sHintText.LoadString(IDS_LOG_AUTHOR);
1469 dlg.m_sInputText = value;
1470 dlg.m_sTitle.LoadString(IDS_LOG_AUTHOREDITTITLE);
1471 dlg.m_pProjectProperties = &m_ProjectProperties;
1472 dlg.m_bUseLogWidth = false;
1473 if (dlg.DoModal() == IDOK)
1475 dlg.m_sInputText.Replace(_T("\r"), _T(""));
1477 LogCache::CCachedLogInfo* toUpdate
1478 = GetLogCache (CTGitPath (m_sRepositoryRoot));
1480 CProgressDlg progDlg;
1481 progDlg.SetTitle(IDS_APPNAME);
1482 progDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
1483 progDlg.SetTime(true);
1484 progDlg.SetShowProgressBar(true);
1485 progDlg.ShowModeless(m_hWnd);
1486 for (DWORD i=0; i<logs.size(); ++i)
1488 if (!RevPropertySet(name, dlg.m_sInputText, sOldValue, CTGitPath(url), logs[i]->Rev))
1490 progDlg.Stop();
1491 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1492 break;
1494 else
1497 logs[i]->sAuthor = dlg.m_sInputText;
1498 m_LogList.Invalidate();
1500 // update the log cache
1502 if (toUpdate != NULL)
1504 // log caching is active
1506 LogCache::CCachedLogInfo newInfo;
1507 newInfo.Insert ( logs[i]->Rev
1508 , (const char*) CUnicodeUtils::GetUTF8 (logs[i]->sAuthor)
1509 , ""
1511 , LogCache::CRevisionInfoContainer::HAS_AUTHOR);
1513 toUpdate->Update (newInfo);
1516 progDlg.SetProgress64(i, logs.size());
1518 progDlg.Stop();
1520 theApp.DoWaitCursor(-1);
1521 EnableOKButton();
1522 #endif
1525 void CLogDlg::EditLogMessage(int /*index*/)
1527 #if 0
1528 CString url;
1529 CString name;
1530 DialogEnableWindow(IDOK, FALSE);
1531 SetPromptApp(&theApp);
1532 theApp.DoWaitCursor(1);
1533 if (Git::PathIsURL(m_path))
1534 url = m_path.GetGitPathString();
1535 else
1537 url = GetURLFromPath(m_path);
1539 name = Git_PROP_REVISION_LOG;
1541 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(index));
1542 m_bCancelled = FALSE;
1543 CString value = RevPropertyGet(name, CTGitPath(url), pLogEntry->Rev);
1544 CString sOldValue = value;
1545 value.Replace(_T("\n"), _T("\r\n"));
1546 CInputDlg dlg(this);
1547 dlg.m_sHintText.LoadString(IDS_LOG_MESSAGE);
1548 dlg.m_sInputText = value;
1549 dlg.m_sTitle.LoadString(IDS_LOG_MESSAGEEDITTITLE);
1550 dlg.m_pProjectProperties = &m_ProjectProperties;
1551 dlg.m_bUseLogWidth = true;
1552 if (dlg.DoModal() == IDOK)
1554 dlg.m_sInputText.Replace(_T("\r"), _T(""));
1555 if (!RevPropertySet(name, dlg.m_sInputText, sOldValue, CTGitPath(url), pLogEntry->Rev))
1557 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
1559 else
1561 pLogEntry->sShortMessage = MakeShortMessage(dlg.m_sInputText);
1562 // split multi line log entries and concatenate them
1563 // again but this time with \r\n as line separators
1564 // so that the edit control recognizes them
1565 if (dlg.m_sInputText.GetLength()>0)
1567 m_sMessageBuf = dlg.m_sInputText;
1568 dlg.m_sInputText.Replace(_T("\n\r"), _T("\n"));
1569 dlg.m_sInputText.Replace(_T("\r\n"), _T("\n"));
1570 if (dlg.m_sInputText.Right(1).Compare(_T("\n"))==0)
1571 dlg.m_sInputText = dlg.m_sInputText.Left(dlg.m_sInputText.GetLength()-1);
1573 else
1574 dlg.m_sInputText.Empty();
1575 pLogEntry->sMessage = dlg.m_sInputText;
1576 pLogEntry->sBugIDs = m_ProjectProperties.FindBugID(dlg.m_sInputText);
1577 CWnd * pMsgView = GetDlgItem(IDC_MSGVIEW);
1578 pMsgView->SetWindowText(_T(" "));
1579 pMsgView->SetWindowText(dlg.m_sInputText);
1580 m_ProjectProperties.FindBugID(dlg.m_sInputText, pMsgView);
1581 m_LogList.Invalidate();
1583 // update the log cache
1585 LogCache::CCachedLogInfo* toUpdate
1586 = GetLogCache (CTGitPath (m_sRepositoryRoot));
1587 if (toUpdate != NULL)
1589 // log caching is active
1591 LogCache::CCachedLogInfo newInfo;
1592 newInfo.Insert ( pLogEntry->Rev
1593 , ""
1594 , (const char*) CUnicodeUtils::GetUTF8 (pLogEntry->sMessage)
1596 , LogCache::CRevisionInfoContainer::HAS_COMMENT);
1598 toUpdate->Update (newInfo);
1602 theApp.DoWaitCursor(-1);
1603 EnableOKButton();
1604 #endif
1607 BOOL CLogDlg::PreTranslateMessage(MSG* pMsg)
1609 // Skip Ctrl-C when copying text out of the log message or search filter
1610 BOOL bSkipAccelerator = ( pMsg->message == WM_KEYDOWN && pMsg->wParam=='C' && (GetFocus()==GetDlgItem(IDC_MSGVIEW) || GetFocus()==GetDlgItem(IDC_SEARCHEDIT) ) && GetKeyState(VK_CONTROL)&0x8000 );
1611 if (pMsg->message == WM_KEYDOWN && pMsg->wParam=='\r')
1613 if (GetFocus()==GetDlgItem(IDC_LOGLIST))
1615 if (CRegDWORD(_T("Software\\TortoiseGit\\DiffByDoubleClickInLog"), FALSE))
1617 m_LogList.DiffSelectedRevWithPrevious();
1618 return TRUE;
1622 if (m_hAccel && !bSkipAccelerator)
1624 int ret = TranslateAccelerator(m_hWnd, m_hAccel, pMsg);
1625 if (ret)
1626 return TRUE;
1629 if(::IsWindow(m_tooltips.m_hWnd))
1630 m_tooltips.RelayEvent(pMsg);
1631 return __super::PreTranslateMessage(pMsg);
1635 BOOL CLogDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
1637 //if (this->IsThreadRunning())
1638 if(m_LogList.m_bNoDispUpdates)
1640 // only show the wait cursor over the list control
1641 if ((pWnd)&&
1642 ((pWnd == GetDlgItem(IDC_LOGLIST))||
1643 (pWnd == GetDlgItem(IDC_MSGVIEW))||
1644 (pWnd == GetDlgItem(IDC_LOGMSG))))
1646 HCURSOR hCur = LoadCursor(NULL, MAKEINTRESOURCE(IDC_WAIT));
1647 SetCursor(hCur);
1648 return TRUE;
1651 if ((pWnd) && (pWnd == GetDlgItem(IDC_MSGVIEW)))
1652 return CResizableStandAloneDialog::OnSetCursor(pWnd, nHitTest, message);
1654 HCURSOR hCur = LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW));
1655 SetCursor(hCur);
1656 return CResizableStandAloneDialog::OnSetCursor(pWnd, nHitTest, message);
1659 void CLogDlg::OnBnClickedHelp()
1661 OnHelp();
1664 void CLogDlg::OnLvnItemchangedLoglist(NMHDR *pNMHDR, LRESULT *pResult)
1666 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1667 *pResult = 0;
1668 //if (this->IsThreadRunning())
1669 if(m_LogList.m_bNoDispUpdates)
1670 return;
1671 if (pNMLV->iItem >= 0)
1673 this->m_LogList.m_nSearchIndex = pNMLV->iItem;
1674 if (pNMLV->iSubItem != 0)
1675 return;
1676 if ((pNMLV->iItem == m_LogList.m_arShownList.GetCount()))
1678 // remove the selected state
1679 if (pNMLV->uChanged & LVIF_STATE)
1681 m_LogList.SetItemState(pNMLV->iItem, 0, LVIS_SELECTED);
1682 FillLogMessageCtrl();
1683 UpdateData(FALSE);
1684 UpdateLogInfoLabel();
1686 return;
1688 if (pNMLV->uChanged & LVIF_STATE)
1690 FillLogMessageCtrl();
1691 UpdateData(FALSE);
1694 else
1696 FillLogMessageCtrl();
1697 UpdateData(FALSE);
1699 EnableOKButton();
1700 UpdateLogInfoLabel();
1703 void CLogDlg::OnEnLinkMsgview(NMHDR *pNMHDR, LRESULT *pResult)
1705 ENLINK *pEnLink = reinterpret_cast<ENLINK *>(pNMHDR);
1706 if (pEnLink->msg == WM_LBUTTONUP)
1708 CString url, msg;
1709 GetDlgItemText(IDC_MSGVIEW, msg);
1710 msg.Replace(_T("\r\n"), _T("\n"));
1711 url = msg.Mid(pEnLink->chrg.cpMin, pEnLink->chrg.cpMax-pEnLink->chrg.cpMin);
1712 if (!::PathIsURL(url))
1714 url = m_ProjectProperties.GetBugIDUrl(url);
1715 url = GetAbsoluteUrlFromRelativeUrl(url);
1717 if (!url.IsEmpty())
1718 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
1720 *pResult = 0;
1723 class CDateSorter
1725 public:
1726 class CCommitPointer
1728 public:
1729 CCommitPointer():m_cont(NULL){}
1730 CCommitPointer(const CCommitPointer& P_Right)
1731 : m_cont(NULL)
1733 *this = P_Right;
1736 CCommitPointer& operator = (const CCommitPointer& P_Right)
1738 if(IsPointer())
1740 (*m_cont->m_parDates)[m_place] = P_Right.GetDate();
1741 (*m_cont->m_parFileChanges)[m_place] = P_Right.GetChanges();
1742 (*m_cont->m_parAuthors)[m_place] = P_Right.GetAuthor();
1744 else
1746 m_Date = P_Right.GetDate();
1747 m_Changes = P_Right.GetChanges();
1748 m_csAuthor = P_Right.GetAuthor();
1750 return *this;
1753 void Clone(const CCommitPointer& P_Right)
1755 m_cont = P_Right.m_cont;
1756 m_place = P_Right.m_place;
1757 m_Date = P_Right.m_Date;
1758 m_Changes = P_Right.m_Changes;
1759 m_csAuthor = P_Right.m_csAuthor;
1762 DWORD GetDate() const {return IsPointer() ? (*m_cont->m_parDates)[m_place] : m_Date;}
1763 DWORD GetChanges() const {return IsPointer() ? (*m_cont->m_parFileChanges)[m_place] : m_Changes;}
1764 CString GetAuthor() const {return IsPointer() ? (*m_cont->m_parAuthors)[m_place] : m_csAuthor;}
1766 bool IsPointer() const {return m_cont != NULL;}
1767 //When pointer
1768 CDateSorter* m_cont;
1769 int m_place;
1771 //When element
1772 DWORD m_Date;
1773 DWORD m_Changes;
1774 CString m_csAuthor;
1777 class iterator : public std::iterator<std::random_access_iterator_tag, CCommitPointer>
1779 public:
1780 CCommitPointer m_ptr;
1782 iterator(){}
1783 iterator(const iterator& P_Right){*this = P_Right;}
1784 iterator& operator=(const iterator& P_Right)
1786 m_ptr.Clone(P_Right.m_ptr);
1787 return *this;
1790 CCommitPointer& operator*(){return m_ptr;}
1791 CCommitPointer* operator->(){return &m_ptr;}
1792 const CCommitPointer& operator*()const{return m_ptr;}
1793 const CCommitPointer* operator->()const{return &m_ptr;}
1795 iterator& operator+=(size_t P_iOffset){m_ptr.m_place += P_iOffset;return *this;}
1796 iterator& operator-=(size_t P_iOffset){m_ptr.m_place -= P_iOffset;return *this;}
1797 iterator operator+(size_t P_iOffset)const{iterator it(*this); it += P_iOffset;return it;}
1798 iterator operator-(size_t P_iOffset)const{iterator it(*this); it -= P_iOffset;return it;}
1800 iterator& operator++(){++m_ptr.m_place;return *this;}
1801 iterator& operator--(){--m_ptr.m_place;return *this;}
1802 iterator operator++(int){iterator it(*this);++*this;return it;}
1803 iterator operator--(int){iterator it(*this);--*this;return it;}
1805 size_t operator-(const iterator& P_itRight)const{return m_ptr.m_place - P_itRight->m_place;}
1807 bool operator<(const iterator& P_itRight)const{return m_ptr.m_place < P_itRight->m_place;}
1808 bool operator!=(const iterator& P_itRight)const{return m_ptr.m_place != P_itRight->m_place;}
1809 bool operator==(const iterator& P_itRight)const{return m_ptr.m_place == P_itRight->m_place;}
1810 bool operator>(const iterator& P_itRight)const{return m_ptr.m_place > P_itRight->m_place;}
1812 iterator begin()
1814 iterator it;
1815 it->m_place = 0;
1816 it->m_cont = this;
1817 return it;
1819 iterator end()
1821 iterator it;
1822 it->m_place = m_parDates->GetCount();
1823 it->m_cont = this;
1824 return it;
1827 CDWordArray * m_parDates;
1828 CDWordArray * m_parFileChanges;
1829 CStringArray * m_parAuthors;
1832 class CDateSorterLess
1834 public:
1835 bool operator () (const CDateSorter::CCommitPointer& P_Left, const CDateSorter::CCommitPointer& P_Right) const
1837 return P_Left.GetDate() > P_Right.GetDate(); //Last date first
1844 void CLogDlg::OnBnClickedStatbutton()
1847 if (this->IsThreadRunning())
1848 return;
1849 if (m_LogList.m_arShownList.IsEmpty())
1850 return; // nothing is shown, so no statistics.
1851 // the statistics dialog expects the log entries to be sorted by date
1852 SortByColumn(3, false);
1853 CPtrArray shownlist;
1854 m_LogList.RecalculateShownList(&shownlist);
1855 // create arrays which are aware of the current filter
1856 CStringArray m_arAuthorsFiltered;
1857 CDWordArray m_arDatesFiltered;
1858 CDWordArray m_arFileChangesFiltered;
1859 for (INT_PTR i=0; i<shownlist.GetCount(); ++i)
1861 GitRev* pLogEntry = reinterpret_cast<GitRev*>(shownlist.GetAt(i));
1862 CString strAuthor = pLogEntry->m_AuthorName;
1863 if ( strAuthor.IsEmpty() )
1865 strAuthor.LoadString(IDS_STATGRAPH_EMPTYAUTHOR);
1867 m_arAuthorsFiltered.Add(strAuthor);
1868 m_arDatesFiltered.Add(pLogEntry->m_AuthorDate.GetTime());
1869 m_arFileChangesFiltered.Add(pLogEntry->m_Files.GetCount());
1872 CDateSorter W_Sorter;
1873 W_Sorter.m_parAuthors = &m_arAuthorsFiltered;
1874 W_Sorter.m_parDates = &m_arDatesFiltered;
1875 W_Sorter.m_parFileChanges = &m_arFileChangesFiltered;
1876 std::sort(W_Sorter.begin(), W_Sorter.end(), CDateSorterLess());
1878 CStatGraphDlg dlg;
1879 dlg.m_parAuthors = &m_arAuthorsFiltered;
1880 dlg.m_parDates = &m_arDatesFiltered;
1881 dlg.m_parFileChanges = &m_arFileChangesFiltered;
1882 dlg.m_path = m_path;
1883 dlg.DoModal();
1884 // restore the previous sorting
1885 SortByColumn(m_nSortColumn, m_bAscending);
1886 OnTimer(LOGFILTER_TIMER);
1890 void CLogDlg::DoSizeV1(int delta)
1893 RemoveAnchor(IDC_LOGLIST);
1894 RemoveAnchor(IDC_SPLITTERTOP);
1895 RemoveAnchor(IDC_MSGVIEW);
1896 RemoveAnchor(IDC_SPLITTERBOTTOM);
1897 RemoveAnchor(IDC_LOGMSG);
1898 CSplitterControl::ChangeHeight(&m_LogList, delta, CW_TOPALIGN);
1899 CSplitterControl::ChangeHeight(GetDlgItem(IDC_MSGVIEW), -delta, CW_BOTTOMALIGN);
1900 AddAnchor(IDC_LOGLIST, TOP_LEFT, TOP_RIGHT);
1901 AddAnchor(IDC_SPLITTERTOP, TOP_LEFT, TOP_RIGHT);
1902 AddAnchor(IDC_MSGVIEW, TOP_LEFT, BOTTOM_RIGHT);
1903 AddAnchor(IDC_SPLITTERBOTTOM, BOTTOM_LEFT, BOTTOM_RIGHT);
1904 AddAnchor(IDC_LOGMSG, BOTTOM_LEFT, BOTTOM_RIGHT);
1905 ArrangeLayout();
1906 AdjustMinSize();
1907 SetSplitterRange();
1908 m_LogList.Invalidate();
1909 GetDlgItem(IDC_MSGVIEW)->Invalidate();
1913 void CLogDlg::DoSizeV2(int delta)
1916 RemoveAnchor(IDC_LOGLIST);
1917 RemoveAnchor(IDC_SPLITTERTOP);
1918 RemoveAnchor(IDC_MSGVIEW);
1919 RemoveAnchor(IDC_SPLITTERBOTTOM);
1920 RemoveAnchor(IDC_LOGMSG);
1921 CSplitterControl::ChangeHeight(GetDlgItem(IDC_MSGVIEW), delta, CW_TOPALIGN);
1922 CSplitterControl::ChangeHeight(&m_ChangedFileListCtrl, -delta, CW_BOTTOMALIGN);
1923 AddAnchor(IDC_LOGLIST, TOP_LEFT, TOP_RIGHT);
1924 AddAnchor(IDC_SPLITTERTOP, TOP_LEFT, TOP_RIGHT);
1925 AddAnchor(IDC_MSGVIEW, TOP_LEFT, BOTTOM_RIGHT);
1926 AddAnchor(IDC_SPLITTERBOTTOM, BOTTOM_LEFT, BOTTOM_RIGHT);
1927 AddAnchor(IDC_LOGMSG, BOTTOM_LEFT, BOTTOM_RIGHT);
1928 ArrangeLayout();
1929 AdjustMinSize();
1930 SetSplitterRange();
1931 GetDlgItem(IDC_MSGVIEW)->Invalidate();
1932 m_ChangedFileListCtrl.Invalidate();
1936 void CLogDlg::AdjustMinSize()
1938 // adjust the minimum size of the dialog to prevent the resizing from
1939 // moving the list control too far down.
1940 CRect rcChgListView;
1941 m_ChangedFileListCtrl.GetClientRect(rcChgListView);
1942 CRect rcLogList;
1943 m_LogList.GetClientRect(rcLogList);
1945 SetMinTrackSize(CSize(m_DlgOrigRect.Width(),
1946 m_DlgOrigRect.Height()-m_ChgOrigRect.Height()-m_LogListOrigRect.Height()-m_MsgViewOrigRect.Height()
1947 +rcChgListView.Height()+rcLogList.Height()+60));
1950 LRESULT CLogDlg::DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam)
1952 switch (message) {
1953 case WM_NOTIFY:
1954 if (wParam == IDC_SPLITTERTOP)
1956 SPC_NMHDR* pHdr = (SPC_NMHDR*) lParam;
1957 DoSizeV1(pHdr->delta);
1959 else if (wParam == IDC_SPLITTERBOTTOM)
1961 SPC_NMHDR* pHdr = (SPC_NMHDR*) lParam;
1962 DoSizeV2(pHdr->delta);
1964 break;
1967 return CResizableDialog::DefWindowProc(message, wParam, lParam);
1970 void CLogDlg::SetSplitterRange()
1972 if ((m_LogList)&&(m_ChangedFileListCtrl))
1974 CRect rcTop;
1975 m_LogList.GetWindowRect(rcTop);
1976 ScreenToClient(rcTop);
1977 CRect rcMiddle;
1978 GetDlgItem(IDC_MSGVIEW)->GetWindowRect(rcMiddle);
1979 ScreenToClient(rcMiddle);
1980 m_wndSplitter1.SetRange(rcTop.top+30, rcMiddle.bottom-20);
1981 CRect rcBottom;
1982 m_ChangedFileListCtrl.GetWindowRect(rcBottom);
1983 ScreenToClient(rcBottom);
1984 m_wndSplitter2.SetRange(rcMiddle.top+30, rcBottom.bottom-20);
1988 LRESULT CLogDlg::OnClickedInfoIcon(WPARAM /*wParam*/, LPARAM lParam)
1990 // FIXME: x64 version would get this function called with unexpected parameters.
1991 if (!lParam)
1992 return 0;
1994 RECT * rect = (LPRECT)lParam;
1995 CPoint point;
1996 CString temp;
1997 point = CPoint(rect->left, rect->bottom);
1998 #define LOGMENUFLAGS(x) (MF_STRING | MF_ENABLED | (m_LogList.m_nSelectedFilter == x ? MF_CHECKED : MF_UNCHECKED))
1999 CMenu popup;
2000 if (popup.CreatePopupMenu())
2002 temp.LoadString(IDS_LOG_FILTER_ALL);
2003 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_ALL), LOGFILTER_ALL, temp);
2005 popup.AppendMenu(MF_SEPARATOR, NULL);
2007 temp.LoadString(IDS_LOG_FILTER_MESSAGES);
2008 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_MESSAGES), LOGFILTER_MESSAGES, temp);
2009 temp.LoadString(IDS_LOG_FILTER_PATHS);
2010 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_PATHS), LOGFILTER_PATHS, temp);
2011 temp.LoadString(IDS_LOG_FILTER_AUTHORS);
2012 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_AUTHORS), LOGFILTER_AUTHORS, temp);
2013 temp.LoadString(IDS_LOG_FILTER_REVS);
2014 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REVS), LOGFILTER_REVS, temp);
2015 temp.LoadString(IDS_LOG_FILTER_BUGIDS);
2016 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_BUGID), LOGFILTER_BUGID, temp);
2018 popup.AppendMenu(MF_SEPARATOR, NULL);
2020 temp.LoadString(IDS_LOG_FILTER_REGEX);
2021 popup.AppendMenu(MF_STRING | MF_ENABLED | (m_bFilterWithRegex ? MF_CHECKED : MF_UNCHECKED), LOGFILTER_REGEX, temp);
2023 m_tooltips.Pop();
2024 int selection = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
2025 if (selection != 0)
2028 if (selection == LOGFILTER_REGEX)
2030 m_bFilterWithRegex = !m_bFilterWithRegex;
2031 CRegDWORD b = CRegDWORD(_T("Software\\TortoiseGit\\UseRegexFilter"), TRUE);
2032 b = m_bFilterWithRegex;
2033 CheckRegexpTooltip();
2035 else
2037 m_LogList.m_nSelectedFilter = selection;
2038 SetFilterCueText();
2040 SetTimer(LOGFILTER_TIMER, 1000, NULL);
2043 return 0L;
2046 LRESULT CLogDlg::OnClickedCancelFilter(WPARAM /*wParam*/, LPARAM /*lParam*/)
2049 KillTimer(LOGFILTER_TIMER);
2051 m_LogList.m_sFilterText.Empty();
2052 UpdateData(FALSE);
2053 theApp.DoWaitCursor(1);
2054 CStoreSelection storeselection(this);
2055 FillLogMessageCtrl(false);
2057 m_LogList.RemoveFilter();
2059 CTime begin,end;
2060 m_LogList.GetTimeRange(begin,end);
2061 m_DateFrom.SetTime(&begin);
2062 m_DateTo.SetTime(&end);
2064 theApp.DoWaitCursor(-1);
2065 GetDlgItem(IDC_SEARCHEDIT)->ShowWindow(SW_HIDE);
2066 GetDlgItem(IDC_SEARCHEDIT)->ShowWindow(SW_SHOW);
2067 GetDlgItem(IDC_SEARCHEDIT)->SetFocus();
2068 UpdateLogInfoLabel();
2070 return 0L;
2074 void CLogDlg::SetFilterCueText()
2076 CString temp;
2077 switch (m_LogList.m_nSelectedFilter)
2079 case LOGFILTER_ALL:
2080 temp.LoadString(IDS_LOG_FILTER_ALL);
2081 break;
2082 case LOGFILTER_MESSAGES:
2083 temp.LoadString(IDS_LOG_FILTER_MESSAGES);
2084 break;
2085 case LOGFILTER_PATHS:
2086 temp.LoadString(IDS_LOG_FILTER_PATHS);
2087 break;
2088 case LOGFILTER_AUTHORS:
2089 temp.LoadString(IDS_LOG_FILTER_AUTHORS);
2090 break;
2091 case LOGFILTER_REVS:
2092 temp.LoadString(IDS_LOG_FILTER_REVS);
2093 break;
2095 // to make the cue banner text appear more to the right of the edit control
2096 temp = _T(" ")+temp;
2097 m_cFilter.SetCueBanner(temp);
2100 bool CLogDlg::Validate(LPCTSTR string)
2102 if (!m_bFilterWithRegex)
2103 return true;
2104 tr1::wregex pat;
2105 return m_LogList.ValidateRegexp(string, pat, false);
2109 void CLogDlg::OnTimer(UINT_PTR nIDEvent)
2111 if (nIDEvent == LOGFILTER_TIMER)
2113 if (this->IsThreadRunning())
2115 // thread still running! So just restart the timer.
2116 SetTimer(LOGFILTER_TIMER, 1000, NULL);
2117 return;
2119 CWnd * focusWnd = GetFocus();
2120 bool bSetFocusToFilterControl = ((focusWnd != GetDlgItem(IDC_DATEFROM))&&(focusWnd != GetDlgItem(IDC_DATETO))
2121 && (focusWnd != GetDlgItem(IDC_LOGLIST)));
2122 if (m_LogList.m_sFilterText.IsEmpty())
2124 DialogEnableWindow(IDC_STATBUTTON, !(((this->IsThreadRunning())||(m_LogList.m_arShownList.IsEmpty()))));
2125 // do not return here!
2126 // we also need to run the filter if the filter text is empty:
2127 // 1. to clear an existing filter
2128 // 2. to rebuild the m_arShownList after sorting
2130 theApp.DoWaitCursor(1);
2131 CStoreSelection storeselection(this);
2132 KillTimer(LOGFILTER_TIMER);
2133 FillLogMessageCtrl(false);
2135 // now start filter the log list
2136 m_LogList.StartFilter();
2138 if ( m_LogList.GetItemCount()==1 )
2140 m_LogList.SetSelectionMark(0);
2141 m_LogList.SetItemState(0, LVIS_SELECTED, LVIS_SELECTED);
2143 theApp.DoWaitCursor(-1);
2144 GetDlgItem(IDC_SEARCHEDIT)->ShowWindow(SW_HIDE);
2145 GetDlgItem(IDC_SEARCHEDIT)->ShowWindow(SW_SHOW);
2146 if (bSetFocusToFilterControl)
2147 GetDlgItem(IDC_SEARCHEDIT)->SetFocus();
2148 UpdateLogInfoLabel();
2149 } // if (nIDEvent == LOGFILTER_TIMER)
2150 DialogEnableWindow(IDC_STATBUTTON, !(((this->IsThreadRunning())||(m_LogList.m_arShownList.IsEmpty()))));
2151 __super::OnTimer(nIDEvent);
2154 void CLogDlg::OnDtnDatetimechangeDateto(NMHDR * /*pNMHDR*/, LRESULT *pResult)
2158 CTime _time;
2159 m_DateTo.GetTime(_time);
2161 CTime time(_time.GetYear(), _time.GetMonth(), _time.GetDay(), 23, 59, 59);
2162 if (time.GetTime() != m_LogList.m_To.GetTime())
2164 m_LogList.m_To = (DWORD)time.GetTime();
2165 SetTimer(LOGFILTER_TIMER, 10, NULL);
2168 catch (...)
2170 CMessageBox::Show(NULL,_T("Invalidate Parameter"),_T("TortoiseGit"),MB_OK|MB_ICONERROR);
2173 *pResult = 0;
2176 void CLogDlg::OnDtnDatetimechangeDatefrom(NMHDR * /*pNMHDR*/, LRESULT *pResult)
2181 CTime _time;
2182 m_DateFrom.GetTime(_time);
2184 CTime time(_time.GetYear(), _time.GetMonth(), _time.GetDay(), 0, 0, 0);
2185 if (time.GetTime() != m_LogList.m_From.GetTime())
2187 m_LogList.m_From = (DWORD)time.GetTime();
2188 SetTimer(LOGFILTER_TIMER, 10, NULL);
2191 catch (...)
2193 CMessageBox::Show(NULL,_T("Invalidate Parameter"),_T("TortoiseGit"),MB_OK|MB_ICONERROR);
2196 *pResult = 0;
2201 CTGitPathList CLogDlg::GetChangedPathsFromSelectedRevisions(bool /*bRelativePaths*/ /* = false */, bool /*bUseFilter*/ /* = true */)
2203 CTGitPathList pathList;
2204 #if 0
2206 if (m_sRepositoryRoot.IsEmpty() && (bRelativePaths == false))
2208 m_sRepositoryRoot = GetRepositoryRoot(m_path);
2210 if (m_sRepositoryRoot.IsEmpty() && (bRelativePaths == false))
2211 return pathList;
2213 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
2214 if (pos != NULL)
2216 while (pos)
2218 int nextpos = m_LogList.GetNextSelectedItem(pos);
2219 if (nextpos >= m_arShownList.GetCount())
2220 continue;
2221 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(nextpos));
2222 LogChangedPathArray * cpatharray = pLogEntry->pArChangedPaths;
2223 for (INT_PTR cpPathIndex = 0; cpPathIndex<cpatharray->GetCount(); ++cpPathIndex)
2225 LogChangedPath * cpath = cpatharray->GetAt(cpPathIndex);
2226 if (cpath == NULL)
2227 continue;
2228 CTGitPath path;
2229 if (!bRelativePaths)
2230 path.SetFromGit(m_sRepositoryRoot);
2231 path.AppendPathString(cpath->sPath);
2232 if ((!bUseFilter)||
2233 ((m_cHidePaths.GetState() & 0x0003)!=BST_CHECKED)||
2234 (cpath->sPath.Left(m_sRelativeRoot.GetLength()).Compare(m_sRelativeRoot)==0))
2235 pathList.AddPath(path);
2240 pathList.RemoveDuplicates();
2241 #endif
2242 return pathList;
2245 void CLogDlg::SortByColumn(int /*nSortColumn*/, bool /*bAscending*/)
2247 #if 0
2248 switch(nSortColumn)
2250 case 0: // Revision
2252 if(bAscending)
2253 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::AscRevSort());
2254 else
2255 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::DescRevSort());
2257 break;
2258 case 1: // action
2260 if(bAscending)
2261 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::AscActionSort());
2262 else
2263 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::DescActionSort());
2265 break;
2266 case 2: // Author
2268 if(bAscending)
2269 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::AscAuthorSort());
2270 else
2271 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::DescAuthorSort());
2273 break;
2274 case 3: // Date
2276 if(bAscending)
2277 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::AscDateSort());
2278 else
2279 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::DescDateSort());
2281 break;
2282 case 4: // Message or bug id
2283 if (m_bShowBugtraqColumn)
2285 if(bAscending)
2286 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::AscBugIDSort());
2287 else
2288 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::DescBugIDSort());
2289 break;
2291 // fall through here
2292 case 5: // Message
2294 if(bAscending)
2295 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::AscMessageSort());
2296 else
2297 std::sort(m_logEntries.begin(), m_logEntries.end(), CLogDataVector::DescMessageSort());
2299 break;
2300 default:
2301 ATLASSERT(0);
2302 break;
2304 #endif
2307 void CLogDlg::OnLvnColumnclick(NMHDR *pNMHDR, LRESULT *pResult)
2309 if (this->IsThreadRunning())
2310 return; //no sorting while the arrays are filled
2311 #if 0
2312 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
2313 const int nColumn = pNMLV->iSubItem;
2314 m_bAscending = nColumn == m_nSortColumn ? !m_bAscending : TRUE;
2315 m_nSortColumn = nColumn;
2316 SortByColumn(m_nSortColumn, m_bAscending);
2317 SetSortArrow(&m_LogList, m_nSortColumn, !!m_bAscending);
2318 SortShownListArray();
2319 m_LogList.Invalidate();
2320 UpdateLogInfoLabel();
2321 #endif
2322 *pResult = 0;
2325 void CLogDlg::SortShownListArray()
2327 // make sure the shown list still matches the filter after sorting.
2328 OnTimer(LOGFILTER_TIMER);
2329 // clear the selection states
2330 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
2331 while (pos)
2333 m_LogList.SetItemState(m_LogList.GetNextSelectedItem(pos), 0, LVIS_SELECTED);
2335 m_LogList.SetSelectionMark(-1);
2338 void CLogDlg::SetSortArrow(CListCtrl * control, int nColumn, bool bAscending)
2340 if (control == NULL)
2341 return;
2342 // set the sort arrow
2343 CHeaderCtrl * pHeader = control->GetHeaderCtrl();
2344 HDITEM HeaderItem = {0};
2345 HeaderItem.mask = HDI_FORMAT;
2346 for (int i=0; i<pHeader->GetItemCount(); ++i)
2348 pHeader->GetItem(i, &HeaderItem);
2349 HeaderItem.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
2350 pHeader->SetItem(i, &HeaderItem);
2352 if (nColumn >= 0)
2354 pHeader->GetItem(nColumn, &HeaderItem);
2355 HeaderItem.fmt |= (bAscending ? HDF_SORTUP : HDF_SORTDOWN);
2356 pHeader->SetItem(nColumn, &HeaderItem);
2359 void CLogDlg::OnLvnColumnclickChangedFileList(NMHDR* /*pNMHDR*/, LRESULT* /*pResult*/)
2361 #if 0
2362 if (this->IsThreadRunning())
2363 return; //no sorting while the arrays are filled
2364 if (m_currentChangedArray == NULL)
2365 return;
2366 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
2367 const int nColumn = pNMLV->iSubItem;
2368 m_bAscendingPathList = nColumn == m_nSortColumnPathList ? !m_bAscendingPathList : TRUE;
2369 m_nSortColumnPathList = nColumn;
2370 // qsort(m_currentChangedArray->GetData(), m_currentChangedArray->GetSize(), sizeof(LogChangedPath*), (GENERICCOMPAREFN)SortCompare);
2372 SetSortArrow(&m_ChangedFileListCtrl, m_nSortColumnPathList, m_bAscendingPathList);
2373 m_ChangedFileListCtrl.Invalidate();
2374 *pResult = 0;
2375 #endif
2378 int CLogDlg::m_nSortColumnPathList = 0;
2379 bool CLogDlg::m_bAscendingPathList = false;
2381 int CLogDlg::SortCompare(const void * /*pElem1*/, const void * /*pElem2*/)
2383 #if 0
2384 LogChangedPath * cpath1 = *((LogChangedPath**)pElem1);
2385 LogChangedPath * cpath2 = *((LogChangedPath**)pElem2);
2387 if (m_bAscendingPathList)
2388 std::swap (cpath1, cpath2);
2390 int cmp = 0;
2391 switch (m_nSortColumnPathList)
2393 case 0: // action
2394 cmp = cpath2->GetAction().Compare(cpath1->GetAction());
2395 if (cmp)
2396 return cmp;
2397 // fall through
2398 case 1: // path
2399 cmp = cpath2->sPath.CompareNoCase(cpath1->sPath);
2400 if (cmp)
2401 return cmp;
2402 // fall through
2403 case 2: // copy from path
2404 cmp = cpath2->sCopyFromPath.Compare(cpath1->sCopyFromPath);
2405 if (cmp)
2406 return cmp;
2407 // fall through
2408 case 3: // copy from revision
2409 return cpath2->lCopyFromRev > cpath1->lCopyFromRev;
2411 #endif
2412 return 0;
2415 void CLogDlg::OnBnClickedHidepaths()
2417 FillLogMessageCtrl();
2418 m_ChangedFileListCtrl.Invalidate();
2423 void CLogDlg::OnBnClickedCheckStoponcopy()
2425 #if 0
2426 if (!GetDlgItem(IDC_GETALL)->IsWindowEnabled())
2427 return;
2429 // ignore old fetch limits when switching
2430 // between copy-following and stop-on-copy
2431 // (otherwise stop-on-copy will limit what
2432 // we see immediately after switching to
2433 // copy-following)
2435 m_endrev = 0;
2437 // now, restart the query
2438 #endif
2439 Refresh();
2443 void CLogDlg::UpdateLogInfoLabel()
2446 CGitHash rev1 ;
2447 CGitHash rev2 ;
2448 long selectedrevs = 0;
2449 int count =m_LogList.m_arShownList.GetCount();
2450 int start = 0;
2451 if (count)
2453 rev1 = (reinterpret_cast<GitRev*>(m_LogList.m_arShownList.GetAt(0)))->m_CommitHash;
2454 if(this->m_LogList.m_bShowWC && rev1.IsEmpty()&&(count>1))
2455 start = 1;
2456 rev1 = (reinterpret_cast<GitRev*>(m_LogList.m_arShownList.GetAt(start)))->m_CommitHash;
2457 //pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(m_arShownList.GetCount()-1));
2458 rev2 = (reinterpret_cast<GitRev*>(m_LogList.m_arShownList.GetAt(count-1)))->m_CommitHash;
2459 selectedrevs = m_LogList.GetSelectedCount();
2461 CString sTemp;
2462 sTemp.Format(_T("Showing %ld revision(s), from revision %s to revision %s - %ld revision(s) selected\n"),
2463 count - start,
2464 rev2.ToString().Left(6), rev1.ToString().Left(6), selectedrevs);
2466 if(selectedrevs == 1)
2468 CString str=m_ChangedFileListCtrl.GetStatisticsString(true);
2469 str.Replace(_T('\n'), _T(' '));
2470 sTemp += str;
2472 m_sLogInfo = sTemp;
2474 UpdateData(FALSE);
2477 #if 0
2478 void CLogDlg::ShowContextMenuForChangedpaths(CWnd* /*pWnd*/, CPoint point)
2481 int selIndex = m_ChangedFileListCtrl.GetSelectionMark();
2482 if ((point.x == -1) && (point.y == -1))
2484 CRect rect;
2485 m_ChangedFileListCtrl.GetItemRect(selIndex, &rect, LVIR_LABEL);
2486 m_ChangedFileListCtrl.ClientToScreen(&rect);
2487 point = rect.CenterPoint();
2489 if (selIndex < 0)
2490 return;
2491 int s = m_LogList.GetSelectionMark();
2492 if (s < 0)
2493 return;
2494 std::vector<CString> changedpaths;
2495 std::vector<LogChangedPath*> changedlogpaths;
2496 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
2497 if (pos == NULL)
2498 return; // nothing is selected, get out of here
2500 bool bOneRev = true;
2501 int sel=m_LogList.GetNextSelectedItem(pos);
2502 GitRev * pLogEntry = reinterpret_cast<GitRev *>(m_LogList.m_arShownList.GetAt(sel));
2503 GitRev * rev1 = pLogEntry;
2504 GitRev * rev2 = reinterpret_cast<GitRev *>(m_LogList.m_arShownList.GetAt(sel+1));
2505 #if 0
2506 bool bOneRev = true;
2507 if (pos)
2509 while (pos)
2511 pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(m_LogList.GetNextSelectedItem(pos)));
2512 if (pLogEntry)
2514 rev1 = max(rev1,(git_revnum_t)pLogEntry->Rev);
2515 rev2 = min(rev2,(git_revnum_t)pLogEntry->Rev);
2516 bOneRev = false;
2519 if (!bOneRev)
2520 rev2--;
2521 POSITION pos = m_ChangedFileListCtrl.GetFirstSelectedItemPosition();
2522 while (pos)
2524 int nItem = m_ChangedFileListCtrl.GetNextSelectedItem(pos);
2525 changedpaths.push_back(m_currentChangedPathList[nItem].GetGitPathString());
2528 else
2530 // only one revision is selected in the log dialog top pane
2531 // but multiple items could be selected in the changed items list
2532 rev2 = rev1-1;
2534 POSITION pos = m_ChangedFileListCtrl.GetFirstSelectedItemPosition();
2535 while (pos)
2537 int nItem = m_ChangedFileListCtrl.GetNextSelectedItem(pos);
2538 LogChangedPath * changedlogpath = pLogEntry->pArChangedPaths->GetAt(nItem);
2540 if (m_ChangedFileListCtrl.GetSelectedCount() == 1)
2542 if ((changedlogpath)&&(!changedlogpath->sCopyFromPath.IsEmpty()))
2543 rev2 = changedlogpath->lCopyFromRev;
2544 else
2546 // if the path was modified but the parent path was 'added with history'
2547 // then we have to use the copy from revision of the parent path
2548 CTGitPath cpath = CTGitPath(changedlogpath->sPath);
2549 for (int flist = 0; flist < pLogEntry->pArChangedPaths->GetCount(); ++flist)
2551 CTGitPath p = CTGitPath(pLogEntry->pArChangedPaths->GetAt(flist)->sPath);
2552 if (p.IsAncestorOf(cpath))
2554 if (!pLogEntry->pArChangedPaths->GetAt(flist)->sCopyFromPath.IsEmpty())
2555 rev2 = pLogEntry->pArChangedPaths->GetAt(flist)->lCopyFromRev;
2560 if ((m_cHidePaths.GetState() & 0x0003)==BST_CHECKED)
2562 // some items are hidden! So find out which item the user really clicked on
2563 INT_PTR selRealIndex = -1;
2564 for (INT_PTR hiddenindex=0; hiddenindex<pLogEntry->pArChangedPaths->GetCount(); ++hiddenindex)
2566 if (pLogEntry->pArChangedPaths->GetAt(hiddenindex)->sPath.Left(m_sRelativeRoot.GetLength()).Compare(m_sRelativeRoot)==0)
2567 selRealIndex++;
2568 if (selRealIndex == nItem)
2570 selIndex = hiddenindex;
2571 changedlogpath = pLogEntry->pArChangedPaths->GetAt(selIndex);
2572 break;
2576 if (changedlogpath)
2578 changedpaths.push_back(changedlogpath->sPath);
2579 changedlogpaths.push_back(changedlogpath);
2583 #endif
2584 //entry is selected, now show the popup menu
2585 CIconMenu popup;
2586 if (popup.CreatePopupMenu())
2588 bool bEntryAdded = false;
2589 if (m_ChangedFileListCtrl.GetSelectedCount() == 1)
2591 // if ((!bOneRev)||(IsDiffPossible(changedlogpaths[0], rev1)))
2593 popup.AppendMenuIcon(CGitLogList::ID_DIFF, IDS_LOG_POPUP_DIFF, IDI_DIFF);
2594 popup.AppendMenuIcon(CGitLogList::ID_BLAMEDIFF, IDS_LOG_POPUP_BLAMEDIFF, IDI_BLAME);
2595 popup.SetDefaultItem(CGitLogList::ID_DIFF, FALSE);
2596 popup.AppendMenuIcon(CGitLogList::ID_GNUDIFF1, IDS_LOG_POPUP_GNUDIFF_CH, IDI_DIFF);
2597 bEntryAdded = true;
2599 // if (rev2 == rev1-1)
2601 if (bEntryAdded)
2602 popup.AppendMenu(MF_SEPARATOR, NULL);
2603 popup.AppendMenuIcon(CGitLogList::ID_OPEN, IDS_LOG_POPUP_OPEN, IDI_OPEN);
2604 popup.AppendMenuIcon(CGitLogList::ID_OPENWITH, IDS_LOG_POPUP_OPENWITH, IDI_OPEN);
2605 popup.AppendMenuIcon(CGitLogList::ID_BLAME, IDS_LOG_POPUP_BLAME, IDI_BLAME);
2606 popup.AppendMenu(MF_SEPARATOR, NULL);
2607 if (m_hasWC)
2608 popup.AppendMenuIcon(CGitLogList::ID_REVERTREV, IDS_LOG_POPUP_REVERTREV, IDI_REVERT);
2609 popup.AppendMenuIcon(CGitLogList::ID_POPPROPS, IDS_REPOBROWSE_SHOWPROP, IDI_PROPERTIES); // "Show Properties"
2610 popup.AppendMenuIcon(CGitLogList::ID_LOG, IDS_MENULOG, IDI_LOG); // "Show Log"
2611 popup.AppendMenuIcon(CGitLogList::ID_GETMERGELOGS, IDS_LOG_POPUP_GETMERGELOGS, IDI_LOG); // "Show merge log"
2612 popup.AppendMenuIcon(CGitLogList::ID_SAVEAS, IDS_LOG_POPUP_SAVE, IDI_SAVEAS);
2613 bEntryAdded = true;
2614 if (!m_ProjectProperties.sWebViewerPathRev.IsEmpty())
2616 popup.AppendMenu(MF_SEPARATOR, NULL);
2617 popup.AppendMenuIcon(CGitLogList::ID_VIEWPATHREV, IDS_LOG_POPUP_VIEWPATHREV);
2619 if (popup.GetDefaultItem(0,FALSE)==-1)
2620 popup.SetDefaultItem(CGitLogList::ID_OPEN, FALSE);
2623 else if (changedlogpaths.size())
2625 // more than one entry is selected
2626 popup.AppendMenuIcon(CGitLogList::ID_SAVEAS, IDS_LOG_POPUP_SAVE);
2627 bEntryAdded = true;
2630 if (!bEntryAdded)
2631 return;
2632 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
2633 bool bOpenWith = false;
2634 bool bMergeLog = false;
2635 m_bCancelled = false;
2637 switch (cmd)
2639 case CGitLogList::ID_DIFF:
2641 DoDiffFromLog(selIndex, rev1, rev2, false, false);
2643 break;
2644 #if 0
2645 case ID_BLAMEDIFF:
2647 DoDiffFromLog(selIndex, rev1, rev2, true, false);
2649 break;
2650 case ID_GNUDIFF1:
2652 DoDiffFromLog(selIndex, rev1, rev2, false, true);
2654 break;
2655 case ID_REVERTREV:
2657 SetPromptApp(&theApp);
2658 theApp.DoWaitCursor(1);
2659 CString sUrl;
2660 if (Git::PathIsURL(m_path))
2662 sUrl = m_path.GetGitPathString();
2664 else
2666 sUrl = GetURLFromPath(m_path);
2667 if (sUrl.IsEmpty())
2669 theApp.DoWaitCursor(-1);
2670 CString temp;
2671 temp.Format(IDS_ERR_NOURLOFFILE, m_path.GetWinPath());
2672 CMessageBox::Show(this->m_hWnd, temp, _T("TortoiseGit"), MB_ICONERROR);
2673 EnableOKButton();
2674 theApp.DoWaitCursor(-1);
2675 break; //exit
2678 // find the working copy path of the selected item from the URL
2679 m_bCancelled = false;
2680 CString sUrlRoot = GetRepositoryRoot(CTGitPath(sUrl));
2682 CString fileURL = changedpaths[0];
2683 fileURL = sUrlRoot + fileURL.Trim();
2684 // firstfile = (e.g.) http://mydomain.com/repos/trunk/folder/file1
2685 // sUrl = http://mydomain.com/repos/trunk/folder
2686 CString sUnescapedUrl = CPathUtils::PathUnescape(sUrl);
2687 // find out until which char the urls are identical
2688 int i=0;
2689 while ((i<fileURL.GetLength())&&(i<sUnescapedUrl.GetLength())&&(fileURL[i]==sUnescapedUrl[i]))
2690 i++;
2691 int leftcount = m_path.GetWinPathString().GetLength()-(sUnescapedUrl.GetLength()-i);
2692 CString wcPath = m_path.GetWinPathString().Left(leftcount);
2693 wcPath += fileURL.Mid(i);
2694 wcPath.Replace('/', '\\');
2695 CGitProgressDlg dlg;
2696 if (changedlogpaths[0]->action == LOGACTIONS_DELETED)
2698 // a deleted path! Since the path isn't there anymore, merge
2699 // won't work. So just do a copy url->wc
2700 dlg.SetCommand(CGitProgressDlg::GitProgress_Copy);
2701 dlg.SetPathList(CTGitPathList(CTGitPath(fileURL)));
2702 dlg.SetUrl(wcPath);
2703 dlg.SetRevision(rev2);
2705 else
2707 if (!PathFileExists(wcPath))
2709 // seems the path got renamed
2710 // tell the user how to work around this.
2711 CMessageBox::Show(this->m_hWnd, IDS_LOG_REVERTREV_ERROR, IDS_APPNAME, MB_ICONERROR);
2712 EnableOKButton();
2713 theApp.DoWaitCursor(-1);
2714 break; //exit
2716 dlg.SetCommand(CGitProgressDlg::GitProgress_Merge);
2717 dlg.SetPathList(CTGitPathList(CTGitPath(wcPath)));
2718 dlg.SetUrl(fileURL);
2719 dlg.SetSecondUrl(fileURL);
2720 GitRevRangeArray revarray;
2721 revarray.AddRevRange(rev1, rev2);
2722 dlg.SetRevisionRanges(revarray);
2724 CString msg;
2725 msg.Format(IDS_LOG_REVERT_CONFIRM, (LPCTSTR)wcPath);
2726 if (CMessageBox::Show(this->m_hWnd, msg, _T("TortoiseGit"), MB_YESNO | MB_ICONQUESTION) == IDYES)
2728 dlg.DoModal();
2730 theApp.DoWaitCursor(-1);
2732 break;
2733 case ID_POPPROPS:
2735 DialogEnableWindow(IDOK, FALSE);
2736 SetPromptApp(&theApp);
2737 theApp.DoWaitCursor(1);
2738 CString filepath;
2739 if (Git::PathIsURL(m_path))
2741 filepath = m_path.GetGitPathString();
2743 else
2745 filepath = GetURLFromPath(m_path);
2746 if (filepath.IsEmpty())
2748 theApp.DoWaitCursor(-1);
2749 CString temp;
2750 temp.Format(IDS_ERR_NOURLOFFILE, (LPCTSTR)filepath);
2751 CMessageBox::Show(this->m_hWnd, temp, _T("TortoiseGit"), MB_ICONERROR);
2752 TRACE(_T("could not retrieve the URL of the file!\n"));
2753 EnableOKButton();
2754 break;
2757 filepath = GetRepositoryRoot(CTGitPath(filepath));
2758 filepath += changedpaths[0];
2759 CPropDlg dlg;
2760 dlg.m_rev = rev1;
2761 dlg.m_Path = CTGitPath(filepath);
2762 dlg.DoModal();
2763 EnableOKButton();
2764 theApp.DoWaitCursor(-1);
2766 break;
2767 case ID_SAVEAS:
2769 DialogEnableWindow(IDOK, FALSE);
2770 SetPromptApp(&theApp);
2771 theApp.DoWaitCursor(1);
2772 CString filepath;
2773 if (Git::PathIsURL(m_path))
2775 filepath = m_path.GetGitPathString();
2777 else
2779 filepath = GetURLFromPath(m_path);
2780 if (filepath.IsEmpty())
2782 theApp.DoWaitCursor(-1);
2783 CString temp;
2784 temp.Format(IDS_ERR_NOURLOFFILE, (LPCTSTR)filepath);
2785 CMessageBox::Show(this->m_hWnd, temp, _T("TortoiseGit"), MB_ICONERROR);
2786 TRACE(_T("could not retrieve the URL of the file!\n"));
2787 EnableOKButton();
2788 break;
2791 m_bCancelled = false;
2792 CString sRoot = GetRepositoryRoot(CTGitPath(filepath));
2793 // if more than one entry is selected, we save them
2794 // one by one into a folder the user has selected
2795 bool bTargetSelected = false;
2796 CTGitPath TargetPath;
2797 if (m_ChangedFileListCtrl.GetSelectedCount() > 1)
2799 CBrowseFolder browseFolder;
2800 browseFolder.SetInfo(CString(MAKEINTRESOURCE(IDS_LOG_SAVEFOLDERTOHINT)));
2801 browseFolder.m_style = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS;
2802 CString strSaveAsDirectory;
2803 if (browseFolder.Show(GetSafeHwnd(), strSaveAsDirectory) == CBrowseFolder::OK)
2805 TargetPath = CTGitPath(strSaveAsDirectory);
2806 bTargetSelected = true;
2809 else
2811 // Display the Open dialog box.
2812 CString revFilename;
2813 CString temp;
2814 temp = CPathUtils::GetFileNameFromPath(changedpaths[0]);
2815 int rfind = temp.ReverseFind('.');
2816 if (rfind > 0)
2817 revFilename.Format(_T("%s-%ld%s"), (LPCTSTR)temp.Left(rfind), rev1, (LPCTSTR)temp.Mid(rfind));
2818 else
2819 revFilename.Format(_T("%s-%ld"), (LPCTSTR)temp, rev1);
2820 bTargetSelected = CAppUtils::FileOpenSave(revFilename, NULL, IDS_LOG_POPUP_SAVE, IDS_COMMONFILEFILTER, false, m_hWnd);
2821 TargetPath.SetFromWin(revFilename);
2823 if (bTargetSelected)
2825 CProgressDlg progDlg;
2826 progDlg.SetTitle(IDS_APPNAME);
2827 progDlg.SetAnimation(IDR_DOWNLOAD);
2828 for (std::vector<LogChangedPath*>::iterator it = changedlogpaths.begin(); it!= changedlogpaths.end(); ++it)
2830 GitRev getrev = ((*it)->action == LOGACTIONS_DELETED) ? rev2 : rev1;
2832 CString sInfoLine;
2833 sInfoLine.Format(IDS_PROGRESSGETFILEREVISION, (LPCTSTR)filepath, (LPCTSTR)getrev.ToString());
2834 progDlg.SetLine(1, sInfoLine, true);
2835 SetAndClearProgressInfo(&progDlg);
2836 progDlg.ShowModeless(m_hWnd);
2838 CTGitPath tempfile = TargetPath;
2839 if (changedpaths.size() > 1)
2841 // if multiple items are selected, then the TargetPath
2842 // points to a folder and we have to append the filename
2843 // to save to to that folder.
2844 CString sName = (*it)->sPath;
2845 int slashpos = sName.ReverseFind('/');
2846 if (slashpos >= 0)
2847 sName = sName.Mid(slashpos);
2848 tempfile.AppendPathString(sName);
2849 // one problem here:
2850 // a user could have selected multiple items which
2851 // have the same filename but reside in different
2852 // directories, e.g.
2853 // /folder1/file1
2854 // /folder2/file1
2855 // in that case, the second 'file1' will overwrite
2856 // the already saved 'file1'.
2858 // we could maybe find the common root of all selected
2859 // items and then create sub folders to save those files
2860 // there.
2861 // But I think we should just leave it that way: to check
2862 // out multiple items at once, the better way is still to
2863 // use the export command from the top pane of the log dialog.
2865 filepath = sRoot + (*it)->sPath;
2866 if (!Cat(CTGitPath(filepath), getrev, getrev, tempfile))
2868 progDlg.Stop();
2869 SetAndClearProgressInfo((HWND)NULL);
2870 CMessageBox::Show(this->m_hWnd, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
2871 EnableOKButton();
2872 theApp.DoWaitCursor(-1);
2873 break;
2876 progDlg.Stop();
2877 SetAndClearProgressInfo((HWND)NULL);
2879 EnableOKButton();
2880 theApp.DoWaitCursor(-1);
2882 break;
2883 case ID_OPENWITH:
2884 bOpenWith = true;
2885 case ID_OPEN:
2887 GitRev getrev = pLogEntry->pArChangedPaths->GetAt(selIndex)->action == LOGACTIONS_DELETED ? rev2 : rev1;
2888 Open(bOpenWith,changedpaths[0],getrev);
2890 break;
2891 case ID_BLAME:
2893 CString filepath;
2894 if (Git::PathIsURL(m_path))
2896 filepath = m_path.GetGitPathString();
2898 else
2900 filepath = GetURLFromPath(m_path);
2901 if (filepath.IsEmpty())
2903 theApp.DoWaitCursor(-1);
2904 CString temp;
2905 temp.Format(IDS_ERR_NOURLOFFILE, (LPCTSTR)filepath);
2906 CMessageBox::Show(this->m_hWnd, temp, _T("TortoiseGit"), MB_ICONERROR);
2907 TRACE(_T("could not retrieve the URL of the file!\n"));
2908 EnableOKButton();
2909 break;
2912 filepath = GetRepositoryRoot(CTGitPath(filepath));
2913 filepath += changedpaths[0];
2914 CBlameDlg dlg;
2915 dlg.EndRev = rev1;
2916 if (dlg.DoModal() == IDOK)
2918 CBlame blame;
2919 CString tempfile;
2920 CString logfile;
2921 tempfile = blame.BlameToTempFile(CTGitPath(filepath), dlg.StartRev, dlg.EndRev, dlg.EndRev, logfile, _T(""), dlg.m_bIncludeMerge, TRUE, TRUE);
2922 if (!tempfile.IsEmpty())
2924 if (dlg.m_bTextView)
2926 //open the default text editor for the result file
2927 CAppUtils::StartTextViewer(tempfile);
2929 else
2931 CString sParams = _T("/path:\"") + filepath + _T("\" ");
2932 if(!CAppUtils::LaunchTortoiseBlame(tempfile, logfile, CPathUtils::GetFileNameFromPath(filepath),sParams))
2934 break;
2938 else
2940 CMessageBox::Show(this->m_hWnd, blame.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
2944 break;
2945 case ID_GETMERGELOGS:
2946 bMergeLog = true;
2947 // fall through
2948 case ID_LOG:
2950 DialogEnableWindow(IDOK, FALSE);
2951 SetPromptApp(&theApp);
2952 theApp.DoWaitCursor(1);
2953 CString filepath;
2954 if (Git::PathIsURL(m_path))
2956 filepath = m_path.GetGitPathString();
2958 else
2960 filepath = GetURLFromPath(m_path);
2961 if (filepath.IsEmpty())
2963 theApp.DoWaitCursor(-1);
2964 CString temp;
2965 temp.Format(IDS_ERR_NOURLOFFILE, (LPCTSTR)filepath);
2966 CMessageBox::Show(this->m_hWnd, temp, _T("TortoiseGit"), MB_ICONERROR);
2967 TRACE(_T("could not retrieve the URL of the file!\n"));
2968 EnableOKButton();
2969 break;
2972 m_bCancelled = false;
2973 filepath = GetRepositoryRoot(CTGitPath(filepath));
2974 filepath += changedpaths[0];
2975 git_revnum_t logrev = rev1;
2976 if (changedlogpaths[0]->action == LOGACTIONS_DELETED)
2978 // if the item got deleted in this revision,
2979 // fetch the log from the previous revision where it
2980 // still existed.
2981 logrev--;
2983 CString sCmd;
2984 sCmd.Format(_T("\"%s\" /command:log /path:\"%s\" /startrev:%ld"), (LPCTSTR)(CPathUtils::GetAppDirectory()+_T("TortoiseProc.exe")), (LPCTSTR)filepath, logrev);
2985 if (bMergeLog)
2986 sCmd += _T(" /merge");
2987 CAppUtils::LaunchApplication(sCmd, NULL, false);
2988 EnableOKButton();
2989 theApp.DoWaitCursor(-1);
2991 break;
2992 case ID_VIEWPATHREV:
2994 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(m_LogList.GetSelectionMark()));
2995 GitRev rev = pLogEntry->Rev;
2996 CString relurl = changedpaths[0];
2997 CString url = m_ProjectProperties.sWebViewerPathRev;
2998 url.Replace(_T("%REVISION%"), rev.ToString());
2999 url.Replace(_T("%PATH%"), relurl);
3000 relurl = relurl.Mid(relurl.Find('/'));
3001 url.Replace(_T("%PATH1%"), relurl);
3002 if (!url.IsEmpty())
3003 ShellExecute(this->m_hWnd, _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
3005 break;
3006 #endif
3007 default:
3008 break;
3009 } // switch (cmd)
3011 } // if (popup.CreatePopupMenu())
3013 #endif
3015 void CLogDlg::OnDtnDropdownDatefrom(NMHDR * /*pNMHDR*/, LRESULT *pResult)
3017 // the date control should not show the "today" button
3018 CMonthCalCtrl * pCtrl = m_DateFrom.GetMonthCalCtrl();
3019 if (pCtrl)
3020 SetWindowLongPtr(pCtrl->GetSafeHwnd(), GWL_STYLE, LONG_PTR(pCtrl->GetStyle() | MCS_NOTODAY));
3021 *pResult = 0;
3024 void CLogDlg::OnDtnDropdownDateto(NMHDR * /*pNMHDR*/, LRESULT *pResult)
3026 // the date control should not show the "today" button
3027 CMonthCalCtrl * pCtrl = m_DateTo.GetMonthCalCtrl();
3028 if (pCtrl)
3029 SetWindowLongPtr(pCtrl->GetSafeHwnd(), GWL_STYLE, LONG_PTR(pCtrl->GetStyle() | MCS_NOTODAY));
3030 *pResult = 0;
3033 void CLogDlg::OnSize(UINT nType, int cx, int cy)
3035 __super::OnSize(nType, cx, cy);
3036 //set range
3037 SetSplitterRange();
3040 void CLogDlg::OnRefresh()
3042 //if (GetDlgItem(IDC_GETALL)->IsWindowEnabled())
3043 ShowStartRef();
3045 m_limit = 0;
3046 this->m_LogProgress.SetPos(0);
3048 Refresh (true);
3054 void CLogDlg::OnFocusFilter()
3056 GetDlgItem(IDC_SEARCHEDIT)->SetFocus();
3059 void CLogDlg::OnEditCopy()
3061 if (GetFocus() == &m_ChangedFileListCtrl)
3062 CopyChangedSelectionToClipBoard();
3063 else
3064 m_LogList.CopySelectionToClipBoard();
3067 CString CLogDlg::GetAbsoluteUrlFromRelativeUrl(const CString& url)
3069 // is the URL a relative one?
3070 if (url.Left(2).Compare(_T("^/")) == 0)
3072 // URL is relative to the repository root
3073 CString url1 = m_sRepositoryRoot + url.Mid(1);
3074 TCHAR buf[INTERNET_MAX_URL_LENGTH];
3075 DWORD len = url.GetLength();
3076 if (UrlCanonicalize((LPCTSTR)url1, buf, &len, 0) == S_OK)
3077 return CString(buf, len);
3078 return url1;
3080 else if (url[0] == '/')
3082 // URL is relative to the server's hostname
3083 CString sHost;
3084 // find the server's hostname
3085 int schemepos = m_sRepositoryRoot.Find(_T("//"));
3086 if (schemepos >= 0)
3088 sHost = m_sRepositoryRoot.Left(m_sRepositoryRoot.Find('/', schemepos+3));
3089 CString url1 = sHost + url;
3090 TCHAR buf[INTERNET_MAX_URL_LENGTH];
3091 DWORD len = url.GetLength();
3092 if (UrlCanonicalize((LPCTSTR)url, buf, &len, 0) == S_OK)
3093 return CString(buf, len);
3094 return url1;
3097 return url;
3101 void CLogDlg::OnEnChangeSearchedit()
3103 UpdateData();
3104 if (m_LogList.m_sFilterText.IsEmpty())
3106 CStoreSelection storeselection(this);
3107 // clear the filter, i.e. make all entries appear
3108 theApp.DoWaitCursor(1);
3109 KillTimer(LOGFILTER_TIMER);
3110 FillLogMessageCtrl(false);
3111 m_LogList.StartFilter();
3112 #if 0
3113 InterlockedExchange(&m_bNoDispUpdates, TRUE);
3114 m_arShownList.RemoveAll();
3115 for (DWORD i=0; i<m_logEntries.size(); ++i)
3117 if (IsEntryInDateRange(i))
3118 m_arShownList.Add(m_logEntries[i]);
3120 InterlockedExchange(&m_bNoDispUpdates, FALSE);
3121 m_LogList.DeleteAllItems();
3122 m_LogList.SetItemCountEx(ShownCountWithStopped());
3123 m_LogList.RedrawItems(0, ShownCountWithStopped());
3124 m_LogList.SetRedraw(false);
3125 ResizeAllListCtrlCols();
3126 m_LogList.SetRedraw(true);
3127 #endif
3128 theApp.DoWaitCursor(-1);
3129 GetDlgItem(IDC_SEARCHEDIT)->ShowWindow(SW_HIDE);
3130 GetDlgItem(IDC_SEARCHEDIT)->ShowWindow(SW_SHOW);
3131 GetDlgItem(IDC_SEARCHEDIT)->SetFocus();
3132 DialogEnableWindow(IDC_STATBUTTON, !(((this->IsThreadRunning())||(m_LogList.m_arShownList.IsEmpty()))));
3133 return;
3135 if (Validate(m_LogList.m_sFilterText))
3136 SetTimer(LOGFILTER_TIMER, 1000, NULL);
3137 else
3138 KillTimer(LOGFILTER_TIMER);
3142 void CLogDlg::OnBnClickedAllBranch()
3144 this->UpdateData();
3146 if(this->m_bAllBranch)
3147 m_LogList.m_ShowMask|=CGit::LOG_INFO_ALL_BRANCH;
3148 else
3149 m_LogList.m_ShowMask&=~CGit::LOG_INFO_ALL_BRANCH;
3151 OnRefresh();
3153 FillLogMessageCtrl(false);
3156 void CLogDlg::OnBnClickedBrowseRef()
3158 CString newRef = CBrowseRefsDlg::PickRef(false,m_LogList.GetStartRef());
3159 if(newRef.IsEmpty())
3160 return;
3162 SetStartRef(newRef);
3163 ((CButton*)GetDlgItem(IDC_LOG_ALLBRANCH))->SetCheck(0);
3165 OnBnClickedAllBranch();
3168 void CLogDlg::ShowStartRef()
3170 //Show ref name on top
3171 if(!::IsWindow(m_hWnd))
3172 return;
3173 if(m_bAllBranch)
3175 m_staticRef.SetWindowText(L"<All Branches>");
3176 m_staticRef.Invalidate(TRUE);
3177 return;
3180 CString showStartRef = m_LogList.GetStartRef();
3181 if(showStartRef.IsEmpty())
3183 //Ref name is HEAD
3184 if( g_Git.Run(L"git symbolic-ref HEAD",&showStartRef,CP_UTF8) )
3185 showStartRef = _T("<No branch>");
3186 showStartRef.Trim(L"\r\n\t ");
3190 if(wcsncmp(showStartRef,L"refs/",5) == 0)
3191 showStartRef = showStartRef.Mid(5);
3192 if(wcsncmp(showStartRef,L"heads/",6) == 0)
3193 showStartRef = showStartRef.Mid(6);
3195 m_staticRef.SetWindowText(showStartRef);
3196 m_staticRef.Invalidate(TRUE);
3199 void CLogDlg::SetStartRef(const CString& StartRef)
3201 m_LogList.SetStartRef(StartRef);
3203 ShowStartRef();
3208 void CLogDlg::OnBnClickedFirstParent()
3210 this->UpdateData();
3212 if(this->m_bFirstParent)
3213 m_LogList.m_ShowMask|=CGit::LOG_INFO_FIRST_PARENT;
3214 else
3215 m_LogList.m_ShowMask&=~CGit::LOG_INFO_FIRST_PARENT;
3217 OnRefresh();
3219 FillLogMessageCtrl(false);
3223 void CLogDlg::OnBnClickShowWholeProject()
3225 this->UpdateData();
3227 if(this->m_bWholeProject)
3229 m_LogList.m_Path.Reset();
3230 SetWindowText(m_sTitle + _T(" - ") + CString(_T("Whole Project")));
3232 else
3234 m_LogList.m_Path=m_path;
3235 if(!m_path.IsEmpty())
3236 SetWindowText(m_sTitle + _T(" - ") + m_path.GetGitPathString());
3239 OnRefresh();
3241 FillLogMessageCtrl(false);