Merge branch 'restructure-tree'
[TortoiseGit.git] / src / TortoiseProc / GitLogListBase.cpp
bloba83150a8fba971915ee255cd45720b335b5b16b5
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2011 - TortoiseGit
4 // Copyright (C) 2005-2007 Marco Costalba
5 // Copyright (C) 2011 - Sven Strickroth <email@cs-ware.de>
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License
9 // as published by the Free Software Foundation; either version 2
10 // of the License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software Foundation,
19 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 // GitLogList.cpp : implementation file
23 #include "stdafx.h"
24 #include "resource.h"
25 #include "GitLogListBase.h"
26 #include "GitRev.h"
27 //#include "VssStyle.h"
28 #include "IconMenu.h"
29 // CGitLogListBase
30 #include "cursor.h"
31 #include "InputDlg.h"
32 #include "PropDlg.h"
33 #include "SVNProgressDlg.h"
34 #include "ProgressDlg.h"
35 //#include "RepositoryBrowser.h"
36 //#include "CopyDlg.h"
37 //#include "StatGraphDlg.h"
38 #include "Logdlg.h"
39 #include "MessageBox.h"
40 #include "Registry.h"
41 #include "AppUtils.h"
42 #include "PathUtils.h"
43 #include "StringUtils.h"
44 #include "UnicodeUtils.h"
45 #include "TempFile.h"
46 //#include "GitInfo.h"
47 //#include "GitDiff.h"
48 #include "IconMenu.h"
49 //#include "RevisionRangeDlg.h"
50 //#include "BrowseFolder.h"
51 //#include "BlameDlg.h"
52 //#include "Blame.h"
53 //#include "GitHelpers.h"
54 #include "GitStatus.h"
55 //#include "LogDlgHelper.h"
56 //#include "CachedLogInfo.h"
57 //#include "RepositoryInfo.h"
58 //#include "EditPropertiesDlg.h"
59 #include "FileDiffDlg.h"
60 #include "..\\TortoiseShell\\Resource.h"
61 #include "FindDlg.h"
63 const UINT CGitLogListBase::m_FindDialogMessage = RegisterWindowMessage(FINDMSGSTRING);
65 IMPLEMENT_DYNAMIC(CGitLogListBase, CHintListCtrl)
67 CGitLogListBase::CGitLogListBase():CHintListCtrl()
68 ,m_regMaxBugIDColWidth(_T("Software\\TortoiseGit\\MaxBugIDColWidth"), 200)
69 ,m_nSearchIndex(0)
70 ,m_bNoDispUpdates(FALSE)
71 , m_bThreadRunning(FALSE)
72 , m_bStrictStopped(false)
73 , m_pStoreSelection(NULL)
74 , m_nSelectedFilter(LOGFILTER_ALL)
75 , m_bVista(false)
76 , m_bShowWC(false)
77 , m_logEntries(&m_LogCache)
78 , m_pFindDialog(NULL)
79 , m_ColumnManager(this)
80 , m_dwDefaultColumns(0)
81 , m_arShownList(&m_critSec)
83 // use the default GUI font, create a copy of it and
84 // change the copy to BOLD (leave the rest of the font
85 // the same)
86 HFONT hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
87 LOGFONT lf = {0};
88 GetObject(hFont, sizeof(LOGFONT), &lf);
89 lf.lfWeight = FW_BOLD;
90 m_boldFont = CreateFontIndirect(&lf);
92 m_bShowBugtraqColumn=false;
94 m_IsIDReplaceAction=FALSE;
96 this->m_critSec.Init();
97 m_wcRev.m_CommitHash.Empty();
98 m_wcRev.GetSubject()=_T("Working dir changes");
99 m_wcRev.m_ParentHash.clear();
100 m_wcRev.m_Mark=_T('-');
101 m_wcRev.m_IsUpdateing=FALSE;
102 m_wcRev.m_IsFull = TRUE;
104 m_hModifiedIcon = (HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_ACTIONMODIFIED), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
105 m_hReplacedIcon = (HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_ACTIONREPLACED), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
106 m_hAddedIcon = (HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_ACTIONADDED), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
107 m_hDeletedIcon = (HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_ACTIONDELETED), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
108 m_hFetchIcon = (HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_ACTIONFETCHING), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
110 m_bFilterWithRegex = !!CRegDWORD(_T("Software\\TortoiseGit\\UseRegexFilter"), TRUE);
112 g_Git.GetMapHashToFriendName(m_HashMap);
113 m_CurrentBranch=g_Git.GetCurrentBranch();
114 this->m_HeadHash=g_Git.GetHash(_T("HEAD"));
116 m_From=-1;;
117 m_To=-1;
119 m_ShowMask = 0;
120 m_LoadingThread = NULL;
122 InterlockedExchange(&m_bExitThread,FALSE);
123 m_IsOldFirst = FALSE;
124 m_IsRebaseReplaceGraph = FALSE;
127 for(int i=0;i<Lanes::COLORS_NUM;i++)
129 m_LineColors[i] = m_Colors.GetColor((CColors::Colors)(CColors::BranchLine1+i));
131 // get short/long datetime setting from registry
132 DWORD RegUseShortDateFormat = CRegDWORD(_T("Software\\TortoiseGit\\LogDateFormat"), TRUE);
133 if ( RegUseShortDateFormat )
135 m_DateFormat = DATE_SHORTDATE;
137 else
139 m_DateFormat = DATE_LONGDATE;
141 // get relative time display setting from registry
142 DWORD regRelativeTimes = CRegDWORD(_T("Software\\TortoiseGit\\RelativeTimes"), FALSE);
143 m_bRelativeTimes = (regRelativeTimes != 0);
144 m_ContextMenuMask = 0xFFFFFFFFFFFFFFFF;
146 m_ContextMenuMask &= ~GetContextMenuBit(ID_REBASE_PICK);
147 m_ContextMenuMask &= ~GetContextMenuBit(ID_REBASE_SQUASH);
148 m_ContextMenuMask &= ~GetContextMenuBit(ID_REBASE_EDIT);
149 m_ContextMenuMask &= ~GetContextMenuBit(ID_REBASE_SKIP);
150 m_ContextMenuMask &= ~GetContextMenuBit(ID_LOG);
151 m_ContextMenuMask &= ~GetContextMenuBit(ID_BLAME);
153 OSVERSIONINFOEX inf;
154 SecureZeroMemory(&inf, sizeof(OSVERSIONINFOEX));
155 inf.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
156 GetVersionEx((OSVERSIONINFO *)&inf);
157 WORD fullver = MAKEWORD(inf.dwMinorVersion, inf.dwMajorVersion);
158 m_bVista = (fullver >= 0x0600);
160 m_ColumnRegKey=_T("log");
162 m_AsyncThreadExit = FALSE;
163 m_AsyncDiffEvent = ::CreateEvent(NULL,FALSE,TRUE,NULL);
164 m_AsynDiffListLock.Init();
166 m_DiffingThread = AfxBeginThread(AsyncThread, this, THREAD_PRIORITY_BELOW_NORMAL);
167 if (m_DiffingThread ==NULL)
169 CMessageBox::Show(NULL, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
170 return;
175 int CGitLogListBase::AsyncDiffThread()
177 m_AsyncThreadExited = false;
178 while(!m_AsyncThreadExit)
180 ::WaitForSingleObject(m_AsyncDiffEvent, INFINITE);
182 GitRev *pRev = NULL;
183 while(!m_AsyncThreadExit && m_AsynDiffList.size() > 0)
185 m_AsynDiffListLock.Lock();
186 pRev = m_AsynDiffList.back();
187 m_AsynDiffList.pop_back();
188 m_AsynDiffListLock.Unlock();
190 if( pRev->m_CommitHash.IsEmpty() )
192 if(pRev->m_IsDiffFiles)
193 continue;
195 pRev->GetFiles(this).Clear();
196 pRev->m_ParentHash.clear();
197 pRev->m_ParentHash.push_back(m_HeadHash);
198 if(g_Git.IsInitRepos())
200 g_Git.GetInitAddList(pRev->GetFiles(this));
202 }else
204 g_Git.GetCommitDiffList(pRev->m_CommitHash.ToString(),this->m_HeadHash.ToString(), pRev->GetFiles(this));
206 pRev->GetAction(this) = 0;
208 for(int j=0;j< pRev->GetFiles(this).GetCount();j++)
209 pRev->GetAction(this) |= pRev->GetFiles(this)[j].m_Action;
211 InterlockedExchange(&pRev->m_IsDiffFiles, TRUE);
212 InterlockedExchange(&pRev->m_IsFull, TRUE);
214 pRev->GetBody().Format(_T("%d files changed"),pRev->GetFiles(this).GetCount());
215 ::PostMessage(m_hWnd,MSG_LOADED,(WPARAM)0,0);
216 this->GetParent()->PostMessage(WM_COMMAND, MSG_FETCHED_DIFF, 0);
219 if(!pRev->CheckAndDiff())
220 { // fetch change file list
221 for(int i=GetTopIndex(); !m_AsyncThreadExit && i <= GetTopIndex()+GetCountPerPage(); i++)
223 if(i < m_arShownList.GetCount())
225 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(i);
226 if(data->m_CommitHash == pRev->m_CommitHash)
228 ::PostMessage(m_hWnd,MSG_LOADED,(WPARAM)i,0);
229 break;
234 if(!m_AsyncThreadExit && GetSelectedCount() == 1)
236 POSITION pos = GetFirstSelectedItemPosition();
237 int nItem = GetNextSelectedItem(pos);
239 if(nItem>=0)
241 GitRev* data = (GitRev*)m_arShownList[nItem];
242 if(data)
243 if(data->m_CommitHash == pRev->m_CommitHash)
245 this->GetParent()->PostMessage(WM_COMMAND, MSG_FETCHED_DIFF, 0);
252 m_AsyncThreadExited = true;
253 return 0;
255 void CGitLogListBase::hideFromContextMenu(unsigned __int64 hideMask, bool exclusivelyShow)
257 if (exclusivelyShow) {
258 m_ContextMenuMask &= hideMask;
259 } else {
260 m_ContextMenuMask &= ~hideMask;
264 CGitLogListBase::~CGitLogListBase()
266 InterlockedExchange(&m_bNoDispUpdates, TRUE);
267 this->m_arShownList.SafeRemoveAll();
269 DestroyIcon(m_hModifiedIcon);
270 DestroyIcon(m_hReplacedIcon);
271 DestroyIcon(m_hAddedIcon);
272 DestroyIcon(m_hDeletedIcon);
273 m_logEntries.ClearAll();
275 if (m_boldFont)
276 DeleteObject(m_boldFont);
278 if ( m_pStoreSelection )
280 delete m_pStoreSelection;
281 m_pStoreSelection = NULL;
284 SafeTerminateThread();
285 SafeTerminateAsyncDiffThread();
287 if(m_AsyncDiffEvent)
288 CloseHandle(m_AsyncDiffEvent);
292 BEGIN_MESSAGE_MAP(CGitLogListBase, CHintListCtrl)
293 ON_REGISTERED_MESSAGE(m_FindDialogMessage, OnFindDialogMessage)
294 ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnNMCustomdrawLoglist)
295 ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnLvnGetdispinfoLoglist)
296 ON_WM_CONTEXTMENU()
297 ON_NOTIFY_REFLECT(NM_DBLCLK, OnNMDblclkLoglist)
298 ON_NOTIFY_REFLECT(LVN_ODFINDITEM,OnLvnOdfinditemLoglist)
299 ON_WM_CREATE()
300 ON_WM_DESTROY()
301 ON_MESSAGE(MSG_LOADED,OnLoad)
302 ON_WM_MEASUREITEM()
303 ON_WM_MEASUREITEM_REFLECT()
304 ON_NOTIFY(HDN_BEGINTRACKA, 0, OnHdnBegintrack)
305 ON_NOTIFY(HDN_BEGINTRACKW, 0, OnHdnBegintrack)
306 ON_NOTIFY(HDN_ITEMCHANGINGA, 0, OnHdnItemchanging)
307 ON_NOTIFY(HDN_ITEMCHANGINGW, 0, OnHdnItemchanging)
308 ON_NOTIFY(HDN_ENDTRACK, 0, OnColumnResized)
309 ON_NOTIFY(HDN_ENDDRAG, 0, OnColumnMoved)
310 END_MESSAGE_MAP()
312 void CGitLogListBase::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)
314 //if (m_nRowHeight>0)
316 lpMeasureItemStruct->itemHeight = 50;
320 int CGitLogListBase:: OnCreate(LPCREATESTRUCT lpCreateStruct)
322 PreSubclassWindow();
323 return CHintListCtrl::OnCreate(lpCreateStruct);
326 void CGitLogListBase::PreSubclassWindow()
328 SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_SUBITEMIMAGES);
329 // load the icons for the action columns
330 // m_Theme.Open(m_hWnd, L"ListView");
331 m_Theme.Open(m_hWnd, L"Explorer::ListView;ListView");
332 m_Theme.SetWindowTheme(m_hWnd, L"Explorer", NULL);
333 CHintListCtrl::PreSubclassWindow();
336 void CGitLogListBase::InsertGitColumn()
338 CString temp;
340 CRegDWORD regFullRowSelect(_T("Software\\TortoiseGit\\FullRowSelect"), TRUE);
341 DWORD exStyle = LVS_EX_HEADERDRAGDROP | LVS_EX_DOUBLEBUFFER | LVS_EX_INFOTIP | LVS_EX_SUBITEMIMAGES;
342 if (DWORD(regFullRowSelect))
343 exStyle |= LVS_EX_FULLROWSELECT;
344 SetExtendedStyle(exStyle);
346 UpdateProjectProperties();
348 static UINT normal[] =
350 IDS_LOG_GRAPH,
351 IDS_LOG_REBASE,
352 IDS_LOG_ID,
353 IDS_LOG_HASH,
354 IDS_LOG_ACTIONS,
355 IDS_LOG_MESSAGE,
356 IDS_LOG_AUTHOR,
357 IDS_LOG_DATE,
358 IDS_LOG_EMAIL,
359 IDS_LOG_COMMIT_NAME,
360 IDS_LOG_COMMIT_EMAIL,
361 IDS_LOG_COMMIT_DATE,
362 IDS_LOG_BUGIDS,
365 static int with[] =
367 ICONITEMBORDER+16*4,
368 ICONITEMBORDER+16*4,
369 ICONITEMBORDER+16*4,
370 ICONITEMBORDER+16*4,
371 ICONITEMBORDER+16*4,
372 LOGLIST_MESSAGE_MIN,
373 ICONITEMBORDER+16*4,
374 ICONITEMBORDER+16*4,
375 ICONITEMBORDER+16*4,
376 ICONITEMBORDER+16*4,
377 ICONITEMBORDER+16*4,
378 ICONITEMBORDER+16*4,
379 ICONITEMBORDER+16*4,
381 m_dwDefaultColumns = GIT_LOG_GRAPH|GIT_LOG_ACTIONS|GIT_LOG_MESSAGE|GIT_LOG_AUTHOR|GIT_LOG_DATE;
383 DWORD hideColumns = 0;
384 if(this->m_IsRebaseReplaceGraph)
386 hideColumns |= GIT_LOG_GRAPH;
387 m_dwDefaultColumns |= GIT_LOG_REBASE;
388 } else {
389 hideColumns |= GIT_LOG_REBASE;
392 if(this->m_IsIDReplaceAction)
394 hideColumns |= GIT_LOG_ACTIONS;
395 m_dwDefaultColumns |= GIT_LOG_ID;
396 m_dwDefaultColumns |= GIT_LOG_HASH;
397 } else {
398 hideColumns |= GIT_LOG_ID;
400 if(this->m_bShowBugtraqColumn)
402 m_dwDefaultColumns |= GIT_LOGLIST_BUG;
403 } else {
404 hideColumns |= GIT_LOGLIST_BUG;
406 SetRedraw(false);
408 m_ColumnManager.SetNames(normal, sizeof(normal)/sizeof(UINT));
409 m_ColumnManager.ReadSettings(m_dwDefaultColumns, hideColumns, m_ColumnRegKey+_T("loglist"), sizeof(normal)/sizeof(UINT), with);
411 SetRedraw(true);
416 * Resizes all columns in a list control to values in registry.
418 void CGitLogListBase::ResizeAllListCtrlCols()
420 // column max and min widths to allow
421 static const int nMinimumWidth = 10;
422 static const int nMaximumWidth = 1000;
423 CHeaderCtrl* pHdrCtrl = (CHeaderCtrl*)(GetDlgItem(0));
424 if (pHdrCtrl)
426 int numcols = pHdrCtrl->GetItemCount();
427 for (int col = 0; col < numcols; col++)
429 // get width for this col last time from registry
430 CString regentry;
431 regentry.Format( _T("Software\\TortoiseGit\\%s\\ColWidth%d"),m_ColumnRegKey, col);
432 CRegDWORD regwidth(regentry, 0);
433 int cx = regwidth;
434 if ( cx == 0 )
436 // no saved value, setup sensible defaults
437 if (col == this->LOGLIST_MESSAGE)
439 cx = LOGLIST_MESSAGE_MIN;
441 else
443 cx = ICONITEMBORDER+16*4;
446 if (cx < nMinimumWidth)
448 cx = nMinimumWidth;
449 } else if (cx > nMaximumWidth)
451 cx = nMaximumWidth;
454 SetColumnWidth(col, cx);
461 BOOL CGitLogListBase::GetShortName(CString ref, CString &shortname,CString prefix)
463 //TRACE(_T("%s %s\r\n"),ref,prefix);
464 if(ref.Left(prefix.GetLength()) == prefix)
466 shortname = ref.Right(ref.GetLength()-prefix.GetLength());
467 if(shortname.Right(3)==_T("^{}"))
468 shortname=shortname.Left(shortname.GetLength()-3);
469 return TRUE;
471 return FALSE;
474 void CGitLogListBase::FillBackGround(HDC hdc, int Index,CRect &rect)
476 // HBRUSH brush;
477 LVITEM rItem;
478 SecureZeroMemory(&rItem, sizeof(LVITEM));
479 rItem.mask = LVIF_STATE;
480 rItem.iItem = Index;
481 rItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
482 GetItem(&rItem);
484 GitRev* pLogEntry = (GitRev*)m_arShownList.SafeGetAt(Index);
485 HBRUSH brush = NULL;
487 if (m_Theme.IsAppThemed() && m_bVista)
489 int state = LISS_NORMAL;
490 if (rItem.state & LVIS_SELECTED)
492 if (::GetFocus() == m_hWnd)
493 state |= LISS_SELECTED;
494 else
495 state |= LISS_SELECTEDNOTFOCUS;
497 else
499 if(pLogEntry->GetAction(this)&CTGitPath::LOGACTIONS_REBASE_SQUASH)
500 brush = ::CreateSolidBrush(RGB(156,156,156));
501 else if(pLogEntry->GetAction(this)&CTGitPath::LOGACTIONS_REBASE_EDIT)
502 brush = ::CreateSolidBrush(RGB(200,200,128));
505 if (brush != NULL)
507 ::FillRect(hdc, &rect, brush);
508 ::DeleteObject(brush);
510 else
512 if (m_Theme.IsBackgroundPartiallyTransparent(LVP_LISTITEM, state))
513 m_Theme.DrawParentBackground(m_hWnd, hdc, &rect);
515 CRect rectDraw = rect;
516 if(rItem.state & LVIS_SELECTED)
517 rectDraw.InflateRect(1,0);
518 else
519 rectDraw.InflateRect(1,1);
521 m_Theme.DrawBackground(hdc, LVP_LISTITEM, state, rectDraw, &rect);
524 else
526 if (rItem.state & LVIS_SELECTED)
528 if (::GetFocus() == m_hWnd)
529 brush = ::CreateSolidBrush(::GetSysColor(COLOR_HIGHLIGHT));
530 else
531 brush = ::CreateSolidBrush(::GetSysColor(COLOR_BTNFACE));
533 else
535 //if (pLogEntry->bCopiedSelf)
536 // brush = ::CreateSolidBrush(::GetSysColor(COLOR_MENU));
537 //else
538 if(pLogEntry->GetAction(this)&CTGitPath::LOGACTIONS_REBASE_SQUASH)
539 brush = ::CreateSolidBrush(RGB(156,156,156));
540 else if(pLogEntry->GetAction(this)&CTGitPath::LOGACTIONS_REBASE_EDIT)
541 brush = ::CreateSolidBrush(RGB(200,200,128));
542 else
543 brush = ::CreateSolidBrush(::GetSysColor(COLOR_WINDOW));
545 if (brush == NULL)
546 return;
548 ::FillRect(hdc, &rect, brush);
549 ::DeleteObject(brush);
553 void CGitLogListBase::DrawTagBranch(HDC hdc,CRect &rect,INT_PTR index)
555 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(index);
556 CRect rt=rect;
557 LVITEM rItem;
558 SecureZeroMemory(&rItem, sizeof(LVITEM));
559 rItem.mask = LVIF_STATE;
560 rItem.iItem = index;
561 rItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
562 GetItem(&rItem);
564 CDC W_Dc;
565 W_Dc.Attach(hdc);
567 for(unsigned int i=0;i<m_HashMap[data->m_CommitHash].size();i++)
569 CString str;
570 str=m_HashMap[data->m_CommitHash][i];
572 CString shortname;
573 HBRUSH brush = 0;
574 shortname = _T("");
575 COLORREF colRef = 0;
577 //Determine label color
578 if(GetShortName(str,shortname,_T("refs/heads/")))
580 if( shortname == m_CurrentBranch )
581 colRef = m_Colors.GetColor(CColors::CurrentBranch);
582 else
583 colRef = m_Colors.GetColor(CColors::LocalBranch);
585 }else if(GetShortName(str,shortname,_T("refs/remotes/")))
587 colRef = m_Colors.GetColor(CColors::RemoteBranch);
589 else if(GetShortName(str,shortname,_T("refs/tags/")))
591 colRef = m_Colors.GetColor(CColors::Tag);
593 else if(GetShortName(str,shortname,_T("refs/stash")))
595 colRef = m_Colors.GetColor(CColors::Stash);
596 shortname=_T("stash");
597 }else if(GetShortName(str,shortname,_T("refs/bisect/")))
599 if(shortname.Find(_T("good")) == 0)
601 colRef = m_Colors.GetColor(CColors::BisectGood);
602 shortname = _T("good");
605 if(shortname.Find(_T("bad")) == 0)
607 colRef = m_Colors.GetColor(CColors::BisectBad);
608 shortname = _T("bad");
612 //When row selected, ajust label color
613 if (!(m_Theme.IsAppThemed() && m_bVista))
614 if (rItem.state & LVIS_SELECTED)
615 colRef = CColors::MixColors(colRef, ::GetSysColor(COLOR_HIGHLIGHT), 150);
617 brush = ::CreateSolidBrush(colRef);
619 if(!shortname.IsEmpty() && (rt.left<rect.right) )
621 SIZE size;
622 memset(&size,0,sizeof(SIZE));
623 GetTextExtentPoint32(hdc, shortname,shortname.GetLength(),&size);
625 rt.SetRect(rt.left,rt.top,rt.left+size.cx,rt.bottom);
626 rt.right+=8;
628 int textpos = DT_CENTER;
630 if(rt.right > rect.right)
632 rt.right = rect.right;
633 textpos =0;
636 //Fill interior of ref label
637 ::FillRect(hdc, &rt, brush);
639 //Draw edge of label
641 CRect rectEdge = rt;
643 W_Dc.Draw3dRect(rectEdge, m_Colors.Lighten(colRef,100), m_Colors.Darken(colRef,100));
644 rectEdge.DeflateRect(1,1);
645 W_Dc.Draw3dRect(rectEdge, m_Colors.Lighten(colRef,50), m_Colors.Darken(colRef,50));
647 //Draw text inside label
648 if (m_Theme.IsAppThemed() && m_bVista)
650 int txtState = LISS_NORMAL;
651 if (rItem.state & LVIS_SELECTED)
652 txtState = LISS_SELECTED;
654 m_Theme.DrawText(hdc, LVP_LISTITEM, txtState, shortname, -1, textpos | DT_SINGLELINE | DT_VCENTER, 0, &rt);
656 else
658 W_Dc.SetBkMode(TRANSPARENT);
659 if (rItem.state & LVIS_SELECTED)
661 COLORREF clrNew = ::GetSysColor(COLOR_HIGHLIGHTTEXT);
662 COLORREF clrOld = ::SetTextColor(hdc,clrNew);
663 ::DrawText(hdc,shortname,shortname.GetLength(),&rt,textpos | DT_SINGLELINE | DT_VCENTER);
664 ::SetTextColor(hdc,clrOld);
665 }else
667 ::DrawText(hdc,shortname,shortname.GetLength(),&rt,textpos | DT_SINGLELINE | DT_VCENTER);
671 //::MoveToEx(hdc,rt.left,rt.top,NULL);
672 //::LineTo(hdc,rt.right,rt.top);
673 //::LineTo(hdc,rt.right,rt.bottom);
674 //::LineTo(hdc,rt.left,rt.bottom);
675 //::LineTo(hdc,rt.left,rt.top);
677 rt.left=rt.right+1;
679 if(brush)
680 ::DeleteObject(brush);
682 rt.right=rect.right;
684 if (m_Theme.IsAppThemed() && m_bVista)
686 int txtState = LISS_NORMAL;
687 if (rItem.state & LVIS_SELECTED)
688 txtState = LISS_SELECTED;
690 m_Theme.DrawText(hdc, LVP_LISTITEM, txtState, data->GetSubject(), -1, DT_NOPREFIX | DT_LEFT | DT_SINGLELINE | DT_VCENTER, 0, &rt);
692 else
694 if (rItem.state & LVIS_SELECTED)
696 COLORREF clrOld = ::SetTextColor(hdc,::GetSysColor(COLOR_HIGHLIGHTTEXT));
697 ::DrawText(hdc,data->GetSubject(),data->GetSubject().GetLength(),&rt,DT_NOPREFIX | DT_LEFT | DT_SINGLELINE | DT_VCENTER);
698 ::SetTextColor(hdc,clrOld);
699 }else
701 ::DrawText(hdc,data->GetSubject(),data->GetSubject().GetLength(),&rt,DT_NOPREFIX | DT_LEFT | DT_SINGLELINE | DT_VCENTER);
705 W_Dc.Detach();
708 static COLORREF blend(const COLORREF& col1, const COLORREF& col2, int amount = 128) {
710 // Returns ((256 - amount)*col1 + amount*col2) / 256;
711 return RGB(((256 - amount)*GetRValue(col1) + amount*GetRValue(col2) ) / 256,
712 ((256 - amount)*GetGValue(col1) + amount*GetGValue(col2) ) / 256,
713 ((256 - amount)*GetBValue(col1) + amount*GetBValue(col2) ) / 256);
716 Gdiplus::Color GetGdiColor(COLORREF col)
718 return Gdiplus::Color(GetRValue(col),GetGValue(col),GetBValue(col));
720 void CGitLogListBase::paintGraphLane(HDC hdc, int laneHeight,int type, int x1, int x2,
721 const COLORREF& col,const COLORREF& activeColor, int top
724 int h = laneHeight / 2;
725 int m = (x1 + x2) / 2;
726 int r = (x2 - x1) / 3;
727 int d = 2 * r;
729 #define P_CENTER m , h+top
730 #define P_0 x2, h+top
731 #define P_90 m , 0+top-1
732 #define P_180 x1, h+top
733 #define P_270 m , 2 * h+top +1
734 #define R_CENTER m - r, h - r+top, d, d
737 #define DELTA_UR_B 2*(x1 - m), 2*h +top
738 #define DELTA_UR_E 0*16, 90*16 +top // -,
740 #define DELTA_DR_B 2*(x1 - m), 2*-h +top
741 #define DELTA_DR_E 270*16, 90*16 +top // -'
743 #define DELTA_UL_B 2*(x2 - m), 2*h +top
744 #define DELTA_UL_E 90*16, 90*16 +top // ,-
746 #define DELTA_DL_B 2*(x2 - m),2*-h +top
747 #define DELTA_DL_E 180*16, 90*16 // '-
749 #define CENTER_UR x1, 2*h, 225
750 #define CENTER_DR x1, 0 , 135
751 #define CENTER_UL x2, 2*h, 315
752 #define CENTER_DL x2, 0 , 45
755 Gdiplus::Graphics graphics( hdc );
757 graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
759 // arc
760 switch (type) {
761 case Lanes::JOIN:
762 case Lanes::JOIN_R:
763 case Lanes::HEAD:
764 case Lanes::HEAD_R:
766 Gdiplus::LinearGradientBrush gradient(
767 Gdiplus::Point(x1-2, h+top-2),
768 Gdiplus::Point(P_270),
769 GetGdiColor(activeColor),GetGdiColor(col));
772 Gdiplus::Pen mypen(&gradient,2);
773 //Gdiplus::Pen mypen(Gdiplus::Color(0,0,0),2);
775 //graphics.DrawRectangle(&mypen,x1-(x2-x1)/2,top+h, x2-x1,laneHeight);
776 graphics.DrawArc(&mypen,x1-(x2-x1)/2-1,top+h-1, x2-x1,laneHeight,270,90);
777 //graphics.DrawLine(&mypen,x1-1,h+top,P_270);
779 break;
781 case Lanes::JOIN_L:
784 Gdiplus::LinearGradientBrush gradient(
785 Gdiplus::Point(P_270),
786 Gdiplus::Point(x2+1, h+top-1),
787 GetGdiColor(col),GetGdiColor(activeColor));
790 Gdiplus::Pen mypen(&gradient,2);
791 //Gdiplus::Pen mypen(Gdiplus::Color(0,0,0),2);
793 //graphics.DrawRectangle(&mypen,x1-(x2-x1)/2,top+h, x2-x1,laneHeight);
794 graphics.DrawArc(&mypen,x1+(x2-x1)/2,top+h-1, x2-x1,laneHeight,180,90);
795 //graphics.DrawLine(&mypen,x1-1,h+top,P_270);
798 break;
800 case Lanes::TAIL:
801 case Lanes::TAIL_R:
804 Gdiplus::LinearGradientBrush gradient(
805 Gdiplus::Point(x1-2, h+top-2),
806 Gdiplus::Point(P_90),
807 GetGdiColor(activeColor),GetGdiColor(col));
809 Gdiplus::Pen mypen(&gradient,2);
811 graphics.DrawArc(&mypen,x1-(x2-x1)/2-1,top-h-1, x2-x1,laneHeight,0,90);
813 #if 0
814 QConicalGradient gradient(CENTER_DR);
815 gradient.setColorAt(0.375, activeCol);
816 gradient.setColorAt(0.625, col);
817 myPen.setBrush(gradient);
818 p->setPen(myPen);
819 p->drawArc(P_CENTER, DELTA_DR);
820 #endif
821 break;
823 default:
824 break;
828 //static QPen myPen(Qt::black, 2); // fast path here
829 CPen pen;
830 pen.CreatePen(PS_SOLID,2,col);
831 //myPen.setColor(col);
832 HPEN oldpen=(HPEN)::SelectObject(hdc,(HPEN)pen);
834 Gdiplus::Pen myPen(GetGdiColor(col),2);
836 graphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
838 //p->setPen(myPen);
840 // vertical line
841 switch (type) {
842 case Lanes::ACTIVE:
843 case Lanes::NOT_ACTIVE:
844 case Lanes::MERGE_FORK:
845 case Lanes::MERGE_FORK_R:
846 case Lanes::MERGE_FORK_L:
847 case Lanes::JOIN:
848 case Lanes::JOIN_R:
849 case Lanes::JOIN_L:
850 case Lanes::CROSS:
851 //DrawLine(hdc,P_90,P_270);
852 graphics.DrawLine(&myPen,P_90,P_270);
853 //p->drawLine(P_90, P_270);
854 break;
855 case Lanes::HEAD_L:
856 case Lanes::BRANCH:
857 //DrawLine(hdc,P_CENTER,P_270);
858 graphics.DrawLine(&myPen,P_CENTER,P_270);
859 //p->drawLine(P_CENTER, P_270);
860 break;
861 case Lanes::TAIL_L:
862 case Lanes::INITIAL:
863 case Lanes::BOUNDARY:
864 case Lanes::BOUNDARY_C:
865 case Lanes::BOUNDARY_R:
866 case Lanes::BOUNDARY_L:
867 //DrawLine(hdc,P_90, P_CENTER);
868 graphics.DrawLine(&myPen,P_90,P_CENTER);
869 //p->drawLine(P_90, P_CENTER);
870 break;
871 default:
872 break;
875 myPen.SetColor(GetGdiColor(activeColor));
877 // horizontal line
878 switch (type) {
879 case Lanes::MERGE_FORK:
880 case Lanes::JOIN:
881 case Lanes::HEAD:
882 case Lanes::TAIL:
883 case Lanes::CROSS:
884 case Lanes::CROSS_EMPTY:
885 case Lanes::BOUNDARY_C:
886 //DrawLine(hdc,P_180,P_0);
887 graphics.DrawLine(&myPen,P_180,P_0);
888 //p->drawLine(P_180, P_0);
889 break;
890 case Lanes::MERGE_FORK_R:
891 case Lanes::BOUNDARY_R:
892 //DrawLine(hdc,P_180,P_CENTER);
893 graphics.DrawLine(&myPen,P_180,P_CENTER);
894 //p->drawLine(P_180, P_CENTER);
895 break;
896 case Lanes::MERGE_FORK_L:
897 case Lanes::HEAD_L:
898 case Lanes::TAIL_L:
899 case Lanes::BOUNDARY_L:
900 //DrawLine(hdc,P_CENTER,P_0);
901 graphics.DrawLine(&myPen,P_CENTER,P_0);
902 //p->drawLine(P_CENTER, P_0);
903 break;
904 default:
905 break;
908 graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
910 CBrush brush;
911 brush.CreateSolidBrush(col);
912 HBRUSH oldbrush=(HBRUSH)::SelectObject(hdc,(HBRUSH)brush);
914 Gdiplus::SolidBrush myBrush(GetGdiColor(col));
915 // center symbol, e.g. rect or ellipse
916 switch (type) {
917 case Lanes::ACTIVE:
918 case Lanes::INITIAL:
919 case Lanes::BRANCH:
921 //p->setPen(Qt::NoPen);
922 //p->setBrush(col);
923 graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
924 graphics.FillEllipse(&myBrush, R_CENTER);
925 //p->drawEllipse(R_CENTER);
926 break;
927 case Lanes::MERGE_FORK:
928 case Lanes::MERGE_FORK_R:
929 case Lanes::MERGE_FORK_L:
930 //p->setPen(Qt::NoPen);
931 //p->setBrush(col);
932 //p->drawRect(R_CENTER);
933 graphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
934 graphics.FillRectangle(&myBrush, R_CENTER);
935 break;
936 case Lanes::UNAPPLIED:
937 // Red minus sign
938 //p->setPen(Qt::NoPen);
939 //p->setBrush(Qt::red);
940 //p->drawRect(m - r, h - 1, d, 2);
941 graphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
942 graphics.FillRectangle(&myBrush,m-r,h-1,d,2);
943 break;
944 case Lanes::APPLIED:
945 // Green plus sign
946 //p->setPen(Qt::NoPen);
947 //p->setBrush(DARK_GREEN);
948 //p->drawRect(m - r, h - 1, d, 2);
949 //p->drawRect(m - 1, h - r, 2, d);
950 graphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
951 graphics.FillRectangle(&myBrush,m-r,h-1,d,2);
952 graphics.FillRectangle(&myBrush,m-1,h-r,2,d);
953 break;
954 case Lanes::BOUNDARY:
955 //p->setBrush(back);
956 //p->drawEllipse(R_CENTER);
957 graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
958 graphics.DrawEllipse(&myPen, R_CENTER);
959 break;
960 case Lanes::BOUNDARY_C:
961 case Lanes::BOUNDARY_R:
962 case Lanes::BOUNDARY_L:
963 //p->setBrush(back);
964 //p->drawRect(R_CENTER);
965 graphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
966 graphics.FillRectangle(&myBrush,R_CENTER);
967 break;
968 default:
969 break;
972 ::SelectObject(hdc,oldpen);
973 ::SelectObject(hdc,oldbrush);
974 #undef P_CENTER
975 #undef P_0
976 #undef P_90
977 #undef P_180
978 #undef P_270
979 #undef R_CENTER
982 void CGitLogListBase::DrawGraph(HDC hdc,CRect &rect,INT_PTR index)
984 // TODO: unfinished
985 // return;
986 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(index);
987 if(data->m_CommitHash.IsEmpty())
988 return;
990 CRect rt=rect;
991 LVITEM rItem;
992 SecureZeroMemory(&rItem, sizeof(LVITEM));
993 rItem.mask = LVIF_STATE;
994 rItem.iItem = index;
995 rItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
996 GetItem(&rItem);
998 // p->translate(QPoint(opt.rect.left(), opt.rect.top()));
1000 if (data->m_Lanes.size() == 0)
1001 m_logEntries.setLane(data->m_CommitHash);
1003 std::vector<int>& lanes=data->m_Lanes;
1004 UINT laneNum = lanes.size();
1005 UINT activeLane = 0;
1006 for (UINT i = 0; i < laneNum; i++)
1007 if (Lanes::isMerge(lanes[i])) {
1008 activeLane = i;
1009 break;
1012 int x1 = 0, x2 = 0;
1013 int maxWidth = rect.Width();
1014 int lw = 3 * rect.Height() / 4; //laneWidth()
1016 COLORREF activeColor = m_LineColors[activeLane % Lanes::COLORS_NUM];
1017 //if (opt.state & QStyle::State_Selected)
1018 // activeColor = blend(activeColor, opt.palette.highlightedText().color(), 208);
1020 for (unsigned int i = 0; i < laneNum && x2 < maxWidth; i++)
1023 x1 = x2;
1024 x2 += lw;
1026 int ln = lanes[i];
1027 if (ln == Lanes::EMPTY)
1028 continue;
1030 COLORREF color = i == activeLane ? activeColor : m_LineColors[i % Lanes::COLORS_NUM];
1031 paintGraphLane(hdc, rect.Height(),ln, x1+rect.left, x2+rect.left, color,activeColor, rect.top);
1034 #if 0
1035 for (UINT i = 0; i < laneNum && x2 < maxWidth; i++) {
1037 x1 = x2;
1038 x2 += lw;
1040 int ln = lanes[i];
1041 if (ln == Lanes::EMPTY)
1042 continue;
1044 UINT col = ( Lanes:: isHead(ln) ||Lanes:: isTail(ln) || Lanes::isJoin(ln)
1045 || ln ==Lanes:: CROSS_EMPTY) ? activeLane : i;
1047 if (ln == Lanes::CROSS) {
1048 paintGraphLane(hdc, rect.Height(),Lanes::NOT_ACTIVE, x1, x2, m_LineColors[col % Lanes::COLORS_NUM],rect.top);
1049 paintGraphLane(hdc, rect.Height(),Lanes::CROSS, x1, x2, m_LineColors[activeLane % Lanes::COLORS_NUM],rect.top);
1050 } else
1051 paintGraphLane(hdc, rect.Height(),ln, x1, x2, m_LineColors[col % Lanes::COLORS_NUM],rect.top);
1053 #endif
1057 void CGitLogListBase::OnNMCustomdrawLoglist(NMHDR *pNMHDR, LRESULT *pResult)
1060 NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>( pNMHDR );
1061 // Take the default processing unless we set this to something else below.
1062 *pResult = CDRF_DODEFAULT;
1064 if (m_bNoDispUpdates)
1065 return;
1069 switch (pLVCD->nmcd.dwDrawStage)
1071 case CDDS_PREPAINT:
1073 *pResult = CDRF_NOTIFYITEMDRAW;
1074 return;
1076 break;
1077 case CDDS_ITEMPREPAINT:
1079 // This is the prepaint stage for an item. Here's where we set the
1080 // item's text color.
1082 // Tell Windows to send draw notifications for each subitem.
1083 *pResult = CDRF_NOTIFYSUBITEMDRAW;
1085 COLORREF crText = GetSysColor(COLOR_WINDOWTEXT);
1087 if (m_arShownList.GetCount() > (INT_PTR)pLVCD->nmcd.dwItemSpec)
1089 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(pLVCD->nmcd.dwItemSpec);
1090 if (data)
1092 #if 0
1093 if (data->bCopiedSelf)
1095 // only change the background color if the item is not 'hot' (on vista with m_Themes enabled)
1096 if (!m_Theme.IsAppm_Themed() || !m_bVista || ((pLVCD->nmcd.uItemState & CDIS_HOT)==0))
1097 pLVCD->clrTextBk = GetSysColor(COLOR_MENU);
1100 if (data->bCopies)
1101 crText = m_Colors.GetColor(CColors::Modified);
1102 #endif
1103 if (data->GetAction(this)& (CTGitPath::LOGACTIONS_REBASE_DONE| CTGitPath::LOGACTIONS_REBASE_SKIP) )
1104 crText = RGB(128,128,128);
1106 if(data->GetAction(this)&CTGitPath::LOGACTIONS_REBASE_SQUASH)
1107 pLVCD->clrTextBk = RGB(156,156,156);
1108 else if(data->GetAction(this)&CTGitPath::LOGACTIONS_REBASE_EDIT)
1109 pLVCD->clrTextBk = RGB(200,200,128);
1110 else
1111 pLVCD->clrTextBk = ::GetSysColor(COLOR_WINDOW);
1113 if(data->GetAction(this)&CTGitPath::LOGACTIONS_REBASE_CURRENT)
1115 SelectObject(pLVCD->nmcd.hdc, m_boldFont);
1116 *pResult = CDRF_NOTIFYSUBITEMDRAW | CDRF_NEWFONT;
1119 if(data->m_CommitHash.ToString() == m_HeadHash)
1121 SelectObject(pLVCD->nmcd.hdc, m_boldFont);
1122 *pResult = CDRF_NOTIFYSUBITEMDRAW | CDRF_NEWFONT;
1125 // if ((data->childStackDepth)||(m_mergedRevs.find(data->Rev) != m_mergedRevs.end()))
1126 // crText = GetSysColor(COLOR_GRAYTEXT);
1128 if (data->m_CommitHash.IsEmpty())
1130 //crText = GetSysColor(RGB(200,200,0));
1131 //SelectObject(pLVCD->nmcd.hdc, m_boldFont);
1132 // We changed the font, so we're returning CDRF_NEWFONT. This
1133 // tells the control to recalculate the extent of the text.
1134 *pResult = CDRF_NOTIFYSUBITEMDRAW | CDRF_NEWFONT;
1138 if (m_arShownList.GetCount() == (INT_PTR)pLVCD->nmcd.dwItemSpec)
1140 if (m_bStrictStopped)
1141 crText = GetSysColor(COLOR_GRAYTEXT);
1143 // Store the color back in the NMLVCUSTOMDRAW struct.
1144 pLVCD->clrText = crText;
1145 return;
1147 break;
1148 case CDDS_ITEMPREPAINT|CDDS_ITEM|CDDS_SUBITEM:
1150 if ((m_bStrictStopped)&&(m_arShownList.GetCount() == (INT_PTR)pLVCD->nmcd.dwItemSpec))
1152 pLVCD->nmcd.uItemState &= ~(CDIS_SELECTED|CDIS_FOCUS);
1155 if (pLVCD->iSubItem == LOGLIST_GRAPH)
1157 if (m_arShownList.GetCount() > (INT_PTR)pLVCD->nmcd.dwItemSpec && (!this->m_IsRebaseReplaceGraph) )
1159 CRect rect;
1160 GetSubItemRect(pLVCD->nmcd.dwItemSpec, pLVCD->iSubItem, LVIR_LABEL, rect);
1162 //TRACE(_T("A Graphic left %d right %d\r\n"),rect.left,rect.right);
1163 FillBackGround(pLVCD->nmcd.hdc, (INT_PTR)pLVCD->nmcd.dwItemSpec,rect);
1165 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(pLVCD->nmcd.dwItemSpec);
1166 if( !data ->m_CommitHash.IsEmpty())
1167 DrawGraph(pLVCD->nmcd.hdc,rect,pLVCD->nmcd.dwItemSpec);
1169 *pResult = CDRF_SKIPDEFAULT;
1170 return;
1174 if (pLVCD->iSubItem == LOGLIST_MESSAGE)
1176 if (m_arShownList.GetCount() > (INT_PTR)pLVCD->nmcd.dwItemSpec)
1178 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(pLVCD->nmcd.dwItemSpec);
1179 //if(!data->m_IsFull)
1181 //if(data->SafeFetchFullInfo(&g_Git))
1182 // this->Invalidate();
1183 //TRACE(_T("Update ... %d\r\n"),pLVCD->nmcd.dwItemSpec);
1186 if(m_HashMap[data->m_CommitHash].size()!=0)
1188 CRect rect;
1190 GetSubItemRect(pLVCD->nmcd.dwItemSpec, pLVCD->iSubItem, LVIR_BOUNDS, rect);
1192 FillBackGround(pLVCD->nmcd.hdc, (INT_PTR)pLVCD->nmcd.dwItemSpec,rect);
1193 DrawTagBranch(pLVCD->nmcd.hdc,rect,pLVCD->nmcd.dwItemSpec);
1195 *pResult = CDRF_SKIPDEFAULT;
1196 return;
1203 if (pLVCD->iSubItem == LOGLIST_ACTION)
1205 if(this->m_IsIDReplaceAction)
1207 *pResult = CDRF_DODEFAULT;
1208 return;
1210 *pResult = CDRF_DODEFAULT;
1212 if (m_arShownList.GetCount() <= (INT_PTR)pLVCD->nmcd.dwItemSpec)
1213 return;
1215 int nIcons = 0;
1216 int iconwidth = ::GetSystemMetrics(SM_CXSMICON);
1217 int iconheight = ::GetSystemMetrics(SM_CYSMICON);
1219 GitRev* pLogEntry = reinterpret_cast<GitRev *>(m_arShownList.SafeGetAt(pLVCD->nmcd.dwItemSpec));
1220 CRect rect;
1221 GetSubItemRect(pLVCD->nmcd.dwItemSpec, pLVCD->iSubItem, LVIR_BOUNDS, rect);
1222 //TRACE(_T("Action left %d right %d\r\n"),rect.left,rect.right);
1223 // Get the selected state of the
1224 // item being drawn.
1226 // Fill the background
1227 FillBackGround(pLVCD->nmcd.hdc, (INT_PTR)pLVCD->nmcd.dwItemSpec,rect);
1229 // Draw the icon(s) into the compatible DC
1230 pLogEntry->GetAction(this);
1232 if (!pLogEntry->m_IsDiffFiles)
1233 ::DrawIconEx(pLVCD->nmcd.hdc, rect.left + ICONITEMBORDER, rect.top, m_hFetchIcon, iconwidth, iconheight, 0, NULL, DI_NORMAL);
1235 if (pLogEntry->GetAction(this) & CTGitPath::LOGACTIONS_MODIFIED)
1236 ::DrawIconEx(pLVCD->nmcd.hdc, rect.left + ICONITEMBORDER, rect.top, m_hModifiedIcon, iconwidth, iconheight, 0, NULL, DI_NORMAL);
1237 nIcons++;
1239 if (pLogEntry->GetAction(this) & (CTGitPath::LOGACTIONS_ADDED|CTGitPath::LOGACTIONS_COPY) )
1240 ::DrawIconEx(pLVCD->nmcd.hdc, rect.left+nIcons*iconwidth + ICONITEMBORDER, rect.top, m_hAddedIcon, iconwidth, iconheight, 0, NULL, DI_NORMAL);
1241 nIcons++;
1243 if (pLogEntry->GetAction(this) & CTGitPath::LOGACTIONS_DELETED)
1244 ::DrawIconEx(pLVCD->nmcd.hdc, rect.left+nIcons*iconwidth + ICONITEMBORDER, rect.top, m_hDeletedIcon, iconwidth, iconheight, 0, NULL, DI_NORMAL);
1245 nIcons++;
1247 if (pLogEntry->GetAction(this) & CTGitPath::LOGACTIONS_REPLACED)
1248 ::DrawIconEx(pLVCD->nmcd.hdc, rect.left+nIcons*iconwidth + ICONITEMBORDER, rect.top, m_hReplacedIcon, iconwidth, iconheight, 0, NULL, DI_NORMAL);
1249 nIcons++;
1250 *pResult = CDRF_SKIPDEFAULT;
1251 return;
1254 break;
1256 *pResult = CDRF_DODEFAULT;
1259 // CGitLogListBase message handlers
1261 void CGitLogListBase::OnLvnGetdispinfoLoglist(NMHDR *pNMHDR, LRESULT *pResult)
1263 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1265 // Create a pointer to the item
1266 LV_ITEM* pItem = &(pDispInfo)->item;
1268 // Do the list need text information?
1269 if (!(pItem->mask & LVIF_TEXT))
1270 return;
1272 // By default, clear text buffer.
1273 lstrcpyn(pItem->pszText, _T(""), pItem->cchTextMax);
1275 bool bOutOfRange = pItem->iItem >= ShownCountWithStopped();
1277 *pResult = 0;
1278 if (m_bNoDispUpdates || bOutOfRange)
1279 return;
1281 // Which item number?
1282 int itemid = pItem->iItem;
1283 GitRev * pLogEntry = NULL;
1284 if (itemid < m_arShownList.GetCount())
1285 pLogEntry = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(pItem->iItem));
1287 CString temp;
1288 if(m_IsOldFirst)
1290 temp.Format(_T("%d"),pItem->iItem+1);
1292 }else
1294 temp.Format(_T("%d"),m_arShownList.GetCount()-pItem->iItem);
1297 // Which column?
1298 switch (pItem->iSubItem)
1300 case this->LOGLIST_GRAPH: //Graphic
1301 break;
1302 case this->LOGLIST_REBASE:
1304 if(this->m_IsRebaseReplaceGraph)
1306 CTGitPath path;
1307 path.m_Action=pLogEntry->GetAction(this)&CTGitPath::LOGACTIONS_REBASE_MODE_MASK;
1308 lstrcpyn(pItem->pszText,path.GetActionName(), pItem->cchTextMax);
1311 break;
1312 case this->LOGLIST_ACTION: //action -- no text in the column
1313 break;
1314 case this->LOGLIST_HASH:
1315 if(pLogEntry)
1316 lstrcpyn(pItem->pszText, pLogEntry->m_CommitHash.ToString(), pItem->cchTextMax);
1317 break;
1318 case this->LOGLIST_ID:
1319 if(this->m_IsIDReplaceAction)
1320 lstrcpyn(pItem->pszText, temp, pItem->cchTextMax);
1321 break;
1322 case this->LOGLIST_MESSAGE: //Message
1323 if (pLogEntry)
1324 lstrcpyn(pItem->pszText, (LPCTSTR)pLogEntry->GetSubject(), pItem->cchTextMax);
1325 break;
1326 case this->LOGLIST_AUTHOR: //Author
1327 if (pLogEntry)
1328 lstrcpyn(pItem->pszText, (LPCTSTR)pLogEntry->GetAuthorName(), pItem->cchTextMax);
1329 break;
1330 case this->LOGLIST_DATE: //Date
1331 if ( pLogEntry && (!pLogEntry->m_CommitHash.IsEmpty()) )
1332 lstrcpyn(pItem->pszText,
1333 CAppUtils::FormatDateAndTime( pLogEntry->GetAuthorDate(), m_DateFormat, true, m_bRelativeTimes ),
1334 pItem->cchTextMax);
1335 break;
1337 case this->LOGLIST_EMAIL:
1338 if (pLogEntry)
1339 lstrcpyn(pItem->pszText, (LPCTSTR)pLogEntry->GetAuthorEmail(), pItem->cchTextMax);
1340 break;
1342 case this->LOGLIST_COMMIT_NAME: //Commit
1343 if (pLogEntry)
1344 lstrcpyn(pItem->pszText, (LPCTSTR)pLogEntry->GetCommitterName(), pItem->cchTextMax);
1345 break;
1347 case this->LOGLIST_COMMIT_EMAIL: //Commit Email
1348 if (pLogEntry)
1349 lstrcpyn(pItem->pszText, (LPCTSTR)pLogEntry->GetCommitterEmail(), pItem->cchTextMax);
1350 break;
1352 case this->LOGLIST_COMMIT_DATE: //Commit Date
1353 if (pLogEntry)
1354 lstrcpyn(pItem->pszText,
1355 CAppUtils::FormatDateAndTime( pLogEntry->GetCommitterDate(), m_DateFormat, true, m_bRelativeTimes ),
1356 pItem->cchTextMax);
1357 break;
1358 case this->LOGLIST_BUG: //Bug ID
1359 if(pLogEntry)
1360 lstrcpyn(pItem->pszText, (LPCTSTR)this->m_ProjectProperties.FindBugID(pLogEntry->GetSubject()), pItem->cchTextMax);
1361 break;
1363 default:
1364 ASSERT(false);
1368 void CGitLogListBase::OnContextMenu(CWnd* pWnd, CPoint point)
1371 if (pWnd == GetHeaderCtrl())
1373 return m_ColumnManager.OnContextMenuHeader(pWnd,point,!!IsGroupViewEnabled());
1376 int selIndex = GetSelectionMark();
1377 if (selIndex < 0)
1378 return; // nothing selected, nothing to do with a context menu
1380 // if the user selected the info text telling about not all revisions shown due to
1381 // the "stop on copy/rename" option, we also don't show the context menu
1382 if ((m_bStrictStopped)&&(selIndex == m_arShownList.GetCount()))
1383 return;
1385 // if the context menu is invoked through the keyboard, we have to use
1386 // a calculated position on where to anchor the menu on
1387 if ((point.x == -1) && (point.y == -1))
1389 CRect rect;
1390 GetItemRect(selIndex, &rect, LVIR_LABEL);
1391 ClientToScreen(&rect);
1392 point = rect.CenterPoint();
1394 m_nSearchIndex = selIndex;
1395 m_bCancelled = FALSE;
1397 // calculate some information the context menu commands can use
1398 // CString pathURL = GetURLFromPath(m_path);
1400 POSITION pos = GetFirstSelectedItemPosition();
1401 int indexNext = GetNextSelectedItem(pos);
1402 if (indexNext < 0)
1403 return;
1405 GitRev* pSelLogEntry = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(indexNext));
1406 #if 0
1407 GitRev revSelected = pSelLogEntry->Rev;
1408 GitRev revPrevious = git_revnum_t(revSelected)-1;
1409 if ((pSelLogEntry->pArChangedPaths)&&(pSelLogEntry->pArChangedPaths->GetCount() <= 2))
1411 for (int i=0; i<pSelLogEntry->pArChangedPaths->GetCount(); ++i)
1413 LogChangedPath * changedpath = (LogChangedPath *)pSelLogEntry->pArChangedPaths->SafeGetAt(i);
1414 if (changedpath->lCopyFromRev)
1415 revPrevious = changedpath->lCopyFromRev;
1418 GitRev revSelected2;
1419 if (pos)
1421 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(GetNextSelectedItem(pos)));
1422 revSelected2 = pLogEntry->Rev;
1424 bool bAllFromTheSameAuthor = true;
1425 CString firstAuthor;
1426 CLogDataVector selEntries;
1427 GitRev revLowest, revHighest;
1428 GitRevRangeArray revisionRanges;
1430 POSITION pos = GetFirstSelectedItemPosition();
1431 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(GetNextSelectedItem(pos)));
1432 revisionRanges.AddRevision(pLogEntry->Rev);
1433 selEntries.push_back(pLogEntry);
1434 firstAuthor = pLogEntry->sAuthor;
1435 revLowest = pLogEntry->Rev;
1436 revHighest = pLogEntry->Rev;
1437 while (pos)
1439 pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(GetNextSelectedItem(pos)));
1440 revisionRanges.AddRevision(pLogEntry->Rev);
1441 selEntries.push_back(pLogEntry);
1442 if (firstAuthor.Compare(pLogEntry->sAuthor))
1443 bAllFromTheSameAuthor = false;
1444 revLowest = (git_revnum_t(pLogEntry->Rev) > git_revnum_t(revLowest) ? revLowest : pLogEntry->Rev);
1445 revHighest = (git_revnum_t(pLogEntry->Rev) < git_revnum_t(revHighest) ? revHighest : pLogEntry->Rev);
1449 #endif
1451 int FirstSelect=-1, LastSelect=-1;
1452 pos = GetFirstSelectedItemPosition();
1453 FirstSelect = GetNextSelectedItem(pos);
1454 while(pos)
1456 LastSelect = GetNextSelectedItem(pos);
1458 //entry is selected, now show the popup menu
1459 CIconMenu popup;
1460 CIconMenu subbranchmenu, submenu, gnudiffmenu,diffmenu;
1462 if (popup.CreatePopupMenu())
1465 if(m_ContextMenuMask&GetContextMenuBit(ID_REBASE_PICK))
1466 popup.AppendMenuIcon(ID_REBASE_PICK, IDS_REBASE_PICK, IDI_PICK);
1468 if(m_ContextMenuMask&GetContextMenuBit(ID_REBASE_SQUASH))
1469 popup.AppendMenuIcon(ID_REBASE_SQUASH, IDS_REBASE_SQUASH, IDI_SQUASH);
1471 if(m_ContextMenuMask&GetContextMenuBit(ID_REBASE_EDIT))
1472 popup.AppendMenuIcon(ID_REBASE_EDIT, IDS_REBASE_EDIT, IDI_EDIT);
1474 if(m_ContextMenuMask&GetContextMenuBit(ID_REBASE_SKIP))
1475 popup.AppendMenuIcon(ID_REBASE_SKIP, IDS_REBASE_SKIP, IDI_SKIP);
1477 if(m_ContextMenuMask&(GetContextMenuBit(ID_REBASE_SKIP)|GetContextMenuBit(ID_REBASE_EDIT)|
1478 GetContextMenuBit(ID_REBASE_SQUASH)|GetContextMenuBit(ID_REBASE_PICK)))
1479 popup.AppendMenu(MF_SEPARATOR, NULL);
1481 if (GetSelectedCount() == 1)
1485 if( !pSelLogEntry->m_CommitHash.IsEmpty())
1487 if(m_ContextMenuMask&GetContextMenuBit(ID_COMPARE))
1488 popup.AppendMenuIcon(ID_COMPARE, IDS_LOG_POPUP_COMPARE, IDI_DIFF);
1489 // TODO:
1490 // TortoiseMerge could be improved to take a /blame switch
1491 // and then not 'cat' the files from a unified diff but
1492 // blame then.
1493 // But until that's implemented, the context menu entry for
1494 // this feature is commented out.
1495 //popup.AppendMenu(ID_BLAMECOMPARE, IDS_LOG_POPUP_BLAMECOMPARE, IDI_BLAME);
1496 }else
1498 if(m_ContextMenuMask&GetContextMenuBit(ID_COMMIT))
1499 popup.AppendMenuIcon(ID_COMMIT, IDS_LOG_POPUP_COMMIT, IDI_COMMIT);
1501 if(m_ContextMenuMask&GetContextMenuBit(ID_GNUDIFF1))
1503 GitRev *pRev=pSelLogEntry;
1504 if(pSelLogEntry->m_ParentHash.size()==0)
1506 pRev->GetParentFromHash(pRev->m_CommitHash);
1508 if(pRev->m_ParentHash.size()<=1)
1510 popup.AppendMenuIcon(ID_GNUDIFF1, IDS_LOG_POPUP_GNUDIFF_CH, IDI_DIFF);
1512 }else
1514 gnudiffmenu.CreatePopupMenu();
1515 popup.AppendMenuIcon(ID_GNUDIFF1,IDS_LOG_POPUP_GNUDIFF_PARENT, IDI_DIFF, gnudiffmenu.m_hMenu);
1517 gnudiffmenu.AppendMenuIcon(ID_GNUDIFF1+(0xFFFF<<16),_T("All Parents"));
1518 gnudiffmenu.AppendMenuIcon(ID_GNUDIFF1+(0xFFFE<<16),_T("Only Merged Files"));
1520 for(int i=0;i<pRev->m_ParentHash.size();i++)
1522 CString str;
1523 str.Format(_T("%d parent"), i+1);
1524 gnudiffmenu.AppendMenuIcon(ID_GNUDIFF1+((i+1)<<16),str);
1529 if(m_ContextMenuMask&GetContextMenuBit(ID_COMPAREWITHPREVIOUS))
1532 GitRev *pRev=pSelLogEntry;
1533 if(pSelLogEntry->m_ParentHash.size()==0)
1535 pRev->GetParentFromHash(pRev->m_CommitHash);
1537 if(pRev->m_ParentHash.size()<=1)
1539 popup.AppendMenuIcon(ID_COMPAREWITHPREVIOUS, IDS_LOG_POPUP_COMPAREWITHPREVIOUS, IDI_DIFF);
1542 else
1544 diffmenu.CreatePopupMenu();
1545 popup.AppendMenuIcon(ID_COMPAREWITHPREVIOUS, IDS_LOG_POPUP_COMPAREWITHPREVIOUS, IDI_DIFF, diffmenu.m_hMenu);
1546 for(int i=0;i<pRev->m_ParentHash.size();i++)
1548 CString str;
1549 str.Format(_T("%d Parent"), i+1);
1550 diffmenu.AppendMenuIcon(ID_COMPAREWITHPREVIOUS +((i+1)<<16),str);
1555 if(m_ContextMenuMask&GetContextMenuBit(ID_BLAME))
1556 popup.AppendMenuIcon(ID_BLAME, IDS_LOG_POPUP_BLAME, IDI_BLAME);
1558 //popup.AppendMenuIcon(ID_BLAMEWITHPREVIOUS, IDS_LOG_POPUP_BLAMEWITHPREVIOUS, IDI_BLAME);
1559 popup.AppendMenu(MF_SEPARATOR, NULL);
1562 // if (!m_ProjectProperties.sWebViewerRev.IsEmpty())
1563 // {
1564 // popup.AppendMenuIcon(ID_VIEWREV, IDS_LOG_POPUP_VIEWREV);
1565 // }
1566 // if (!m_ProjectProperties.sWebViewerPathRev.IsEmpty())
1567 // {
1568 // popup.AppendMenuIcon(ID_VIEWPATHREV, IDS_LOG_POPUP_VIEWPATHREV);
1569 // }
1570 // if ((!m_ProjectProperties.sWebViewerPathRev.IsEmpty())||
1571 // (!m_ProjectProperties.sWebViewerRev.IsEmpty()))
1572 // {
1573 // popup.AppendMenu(MF_SEPARATOR, NULL);
1574 // }
1576 CString str,format;
1577 //if (m_hasWC)
1578 // popup.AppendMenuIcon(ID_REVERTTOREV, IDS_LOG_POPUP_REVERTTOREV, IDI_REVERT);
1580 if(!pSelLogEntry->m_CommitHash.IsEmpty())
1582 if((m_ContextMenuMask&GetContextMenuBit(ID_LOG)) &&
1583 GetSelectedCount() == 1)
1584 popup.AppendMenuIcon(ID_LOG, IDS_LOG_POPUP_LOG, IDI_LOG);
1586 format.LoadString(IDS_LOG_POPUP_MERGEREV);
1587 str.Format(format,g_Git.GetCurrentBranch());
1589 if (m_ContextMenuMask&GetContextMenuBit(ID_MERGEREV))
1590 popup.AppendMenuIcon(ID_MERGEREV, str, IDI_MERGE);
1592 format.LoadString(IDS_RESET_TO_THIS_FORMAT);
1593 str.Format(format,g_Git.GetCurrentBranch());
1595 if(m_ContextMenuMask&GetContextMenuBit(ID_RESET))
1596 popup.AppendMenuIcon(ID_RESET,str,IDI_REVERT);
1599 // Add Switch Branch express Menu
1600 if( this->m_HashMap.find(pSelLogEntry->m_CommitHash) != m_HashMap.end()
1601 && (m_ContextMenuMask&GetContextMenuBit(ID_SWITCHBRANCH))
1604 std::vector<CString *> branchs;
1605 CString ref;
1607 for(int i=0;i<m_HashMap[pSelLogEntry->m_CommitHash].size();i++)
1609 ref = m_HashMap[pSelLogEntry->m_CommitHash][i];
1610 if(ref.Find(_T("refs/heads/")) == 0)
1612 branchs.push_back(&m_HashMap[pSelLogEntry->m_CommitHash][i]);
1616 CString str;
1617 str.LoadString(IDS_SWITCH_BRANCH);
1619 if(branchs.size() == 1)
1621 str+=_T(" ");
1622 str+= _T('"') + branchs[0]->Mid(11) + _T('"');
1623 popup.AppendMenuIcon(ID_SWITCHBRANCH,str,IDI_SWITCH);
1625 popup.SetMenuItemData(ID_SWITCHBRANCH,(ULONG_PTR)branchs[0]);
1628 else if( branchs.size() > 1 )
1630 subbranchmenu.CreatePopupMenu();
1631 for(int i=0;i<branchs.size();i++)
1633 subbranchmenu.AppendMenuIcon(ID_SWITCHBRANCH+(i<<16), branchs[i]->Mid(11));
1634 subbranchmenu.SetMenuItemData(ID_SWITCHBRANCH+(i<<16), (ULONG_PTR) branchs[i]);
1637 popup.AppendMenuIcon(ID_SWITCHBRANCH, str, IDI_SWITCH, subbranchmenu.m_hMenu);
1641 if(m_ContextMenuMask&GetContextMenuBit(ID_SWITCHTOREV))
1642 popup.AppendMenuIcon(ID_SWITCHTOREV, IDS_SWITCH_TO_THIS , IDI_SWITCH);
1644 if(m_ContextMenuMask&GetContextMenuBit(ID_CREATE_BRANCH))
1645 popup.AppendMenuIcon(ID_CREATE_BRANCH, IDS_CREATE_BRANCH_AT_THIS , IDI_COPY);
1647 if(m_ContextMenuMask&GetContextMenuBit(ID_CREATE_TAG))
1648 popup.AppendMenuIcon(ID_CREATE_TAG,IDS_CREATE_TAG_AT_THIS , IDI_TAG);
1650 format.LoadString(IDS_REBASE_THIS_FORMAT);
1651 str.Format(format,g_Git.GetCurrentBranch());
1653 if(pSelLogEntry->m_CommitHash != m_HeadHash)
1654 if(m_ContextMenuMask&GetContextMenuBit(ID_REBASE_TO_VERSION))
1655 popup.AppendMenuIcon(ID_REBASE_TO_VERSION, str , IDI_REBASE);
1657 if(m_ContextMenuMask&GetContextMenuBit(ID_EXPORT))
1658 popup.AppendMenuIcon(ID_EXPORT,IDS_EXPORT_TO_THIS, IDI_EXPORT);
1660 if (m_ContextMenuMask&GetContextMenuBit(ID_REVERTREV))
1661 popup.AppendMenuIcon(ID_REVERTREV, IDS_LOG_POPUP_REVERTREV, IDI_REVERT);
1663 if (m_ContextMenuMask&GetContextMenuBit(ID_EDITNOTE))
1664 popup.AppendMenuIcon(ID_EDITNOTE, IDS_EDIT_NOTES, IDI_EDIT);
1666 popup.AppendMenu(MF_SEPARATOR, NULL);
1671 if(!pSelLogEntry->m_Ref.IsEmpty() && GetSelectedCount() == 1)
1673 popup.AppendMenuIcon(ID_REFLOG_DEL, IDS_REFLOG_DEL, IDI_DELETE);
1674 popup.AppendMenuIcon(ID_STASH_APPLY, IDS_MENUSTASHAPPLY, IDI_RELOCATE);
1675 popup.AppendMenu(MF_SEPARATOR, NULL);
1678 if (GetSelectedCount() >= 2)
1680 bool bAddSeparator = false;
1681 if (IsSelectionContinuous() || (GetSelectedCount() == 2))
1683 if(m_ContextMenuMask&GetContextMenuBit(ID_COMPARETWO))
1684 popup.AppendMenuIcon(ID_COMPARETWO, IDS_LOG_POPUP_COMPARETWO, IDI_DIFF);
1687 if (GetSelectedCount() == 2)
1689 //popup.AppendMenuIcon(ID_BLAMETWO, IDS_LOG_POPUP_BLAMEREVS, IDI_BLAME);
1690 if(m_ContextMenuMask&GetContextMenuBit(ID_GNUDIFF2))
1691 popup.AppendMenuIcon(ID_GNUDIFF2, IDS_LOG_POPUP_GNUDIFF, IDI_DIFF);
1692 bAddSeparator = true;
1695 if (m_hasWC)
1697 //popup.AppendMenuIcon(ID_REVERTREV, IDS_LOG_POPUP_REVERTREVS, IDI_REVERT);
1698 // if (m_hasWC)
1699 // popup.AppendMenuIcon(ID_MERGEREV, IDS_LOG_POPUP_MERGEREVS, IDI_MERGE);
1700 bAddSeparator = true;
1703 if (m_ContextMenuMask&GetContextMenuBit(ID_REVERTREV))
1704 popup.AppendMenuIcon(ID_REVERTREV, IDS_LOG_POPUP_REVERTREVS, IDI_REVERT);
1706 if (bAddSeparator)
1707 popup.AppendMenu(MF_SEPARATOR, NULL);
1710 if ( GetSelectedCount() >0 && (!pSelLogEntry->m_CommitHash.IsEmpty()))
1712 bool bAddSeparator = false;
1713 if ( IsSelectionContinuous() && GetSelectedCount() >= 2 )
1715 if(m_ContextMenuMask&GetContextMenuBit(ID_COMBINE_COMMIT))
1717 CString head;
1718 int headindex;
1719 headindex = this->GetHeadIndex();
1720 if(headindex>=0)
1722 head.Format(_T("HEAD~%d"),LastSelect-headindex);
1723 CGitHash hash=g_Git.GetHash(head);
1724 GitRev* pLastEntry = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(LastSelect));
1725 if(pLastEntry->m_CommitHash == hash) {
1726 popup.AppendMenuIcon(ID_COMBINE_COMMIT,IDS_COMBINE_TO_ONE,IDI_COMBINE);
1727 bAddSeparator = true;
1732 if(m_ContextMenuMask&GetContextMenuBit(ID_CHERRY_PICK)) {
1733 popup.AppendMenuIcon(ID_CHERRY_PICK, IDS_CHERRY_PICK_VERSION, IDI_EXPORT);
1734 bAddSeparator = true;
1737 if(GetSelectedCount()<=2 || (IsSelectionContinuous() && GetSelectedCount() > 0))
1738 if(m_ContextMenuMask&GetContextMenuBit(ID_CREATE_PATCH)) {
1739 popup.AppendMenuIcon(ID_CREATE_PATCH, IDS_CREATE_PATCH, IDI_PATCH);
1740 bAddSeparator = true;
1743 if (bAddSeparator)
1744 popup.AppendMenu(MF_SEPARATOR, NULL);
1747 #if 0
1748 // if ((selEntries.size() > 0)&&(bAllFromTheSameAuthor))
1749 // {
1750 // popup.AppendMenuIcon(ID_EDITAUTHOR, IDS_LOG_POPUP_EDITAUTHOR);
1751 // }
1752 // if (GetSelectedCount() == 1)
1753 // {
1754 // popup.AppendMenuIcon(ID_EDITLOG, IDS_LOG_POPUP_EDITLOG);
1755 // popup.AppendMenuIcon(ID_REVPROPS, IDS_REPOBROWSE_SHOWREVPROP, IDI_PROPERTIES); // "Show Revision Properties"
1756 // popup.AppendMenu(MF_SEPARATOR, NULL);
1757 // }
1758 #endif
1760 if (GetSelectedCount() == 1)
1762 bool bAddSeparator = false;
1763 if(m_ContextMenuMask&GetContextMenuBit(ID_PUSH) && m_HashMap[pSelLogEntry->m_CommitHash].size() >= 1)
1765 // show the push-option only if the log entry has an associated local branch
1766 bool isLocal = false;
1767 for(int i=0; isLocal == false && i < m_HashMap[pSelLogEntry->m_CommitHash].size(); i++)
1769 if (m_HashMap[pSelLogEntry->m_CommitHash][i].Find(_T("refs/heads/")) == 0)
1770 isLocal = true;
1772 if (isLocal)
1774 popup.AppendMenuIcon(ID_PUSH, IDS_LOG_PUSH, IDI_PUSH);
1775 bAddSeparator = true;
1779 if(m_ContextMenuMask &GetContextMenuBit(ID_DELETE))
1781 if( this->m_HashMap.find(pSelLogEntry->m_CommitHash) != m_HashMap.end() )
1783 CString str;
1784 if( m_HashMap[pSelLogEntry->m_CommitHash].size() == 1 )
1786 str.LoadString(IDS_DELETE_BRANCHTAG_SHORT);
1787 str+=_T(" ");
1788 str+=m_HashMap[pSelLogEntry->m_CommitHash].at(0);
1789 popup.AppendMenuIcon(ID_DELETE, str, IDI_DELETE);
1790 bAddSeparator = true;
1792 else if( m_HashMap[pSelLogEntry->m_CommitHash].size() > 1 )
1794 str.LoadString(IDS_DELETE_BRANCHTAG);
1795 submenu.CreatePopupMenu();
1796 for(int i=0;i<m_HashMap[pSelLogEntry->m_CommitHash].size();i++)
1798 submenu.AppendMenuIcon(ID_DELETE+(i<<16),m_HashMap[pSelLogEntry->m_CommitHash][i]);
1801 popup.AppendMenuIcon(ID_DELETE,str, IDI_DELETE, submenu.m_hMenu);
1802 bAddSeparator = true;
1805 } // m_ContextMenuMask &GetContextMenuBit(ID_DELETE)
1806 if (bAddSeparator)
1807 popup.AppendMenu(MF_SEPARATOR, NULL);
1808 } // GetSelectedCount() == 1
1810 if (GetSelectedCount() == 1)
1812 if(m_ContextMenuMask&GetContextMenuBit(ID_COPYHASH))
1813 popup.AppendMenuIcon(ID_COPYHASH, IDS_COPY_COMMIT_HASH, IDI_COPYCLIP);
1815 if (GetSelectedCount() != 0)
1817 if(m_ContextMenuMask&GetContextMenuBit(ID_COPYCLIPBOARD))
1818 popup.AppendMenuIcon(ID_COPYCLIPBOARD, IDS_LOG_POPUP_COPYTOCLIPBOARD, IDI_COPYCLIP);
1821 if(m_ContextMenuMask&GetContextMenuBit(ID_FINDENTRY))
1822 popup.AppendMenuIcon(ID_FINDENTRY, IDS_LOG_POPUP_FIND, IDI_FILTEREDIT);
1824 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
1825 // DialogEnableWindow(IDOK, FALSE);
1826 // SetPromptApp(&theApp);
1828 this->ContextMenuAction(cmd, FirstSelect, LastSelect, &popup);
1830 // EnableOKButton();
1831 } // if (popup.CreatePopupMenu())
1835 bool CGitLogListBase::IsSelectionContinuous()
1837 if ( GetSelectedCount()==1 )
1839 // if only one revision is selected, the selection is of course
1840 // continuous
1841 return true;
1844 POSITION pos = GetFirstSelectedItemPosition();
1845 bool bContinuous = (m_arShownList.GetCount() == (INT_PTR)m_logEntries.size());
1846 if (bContinuous)
1848 int itemindex = GetNextSelectedItem(pos);
1849 while (pos)
1851 int nextindex = GetNextSelectedItem(pos);
1852 if (nextindex - itemindex > 1)
1854 bContinuous = false;
1855 break;
1857 itemindex = nextindex;
1860 return bContinuous;
1863 void CGitLogListBase::CopySelectionToClipBoard(bool HashOnly)
1866 CString sClipdata;
1867 POSITION pos = GetFirstSelectedItemPosition();
1868 if (pos != NULL)
1870 CString sRev;
1871 sRev.LoadString(IDS_LOG_REVISION);
1872 CString sAuthor;
1873 sAuthor.LoadString(IDS_LOG_AUTHOR);
1874 CString sDate;
1875 sDate.LoadString(IDS_LOG_DATE);
1876 CString sMessage;
1877 sMessage.LoadString(IDS_LOG_MESSAGE);
1878 while (pos)
1880 CString sLogCopyText;
1881 CString sPaths;
1882 GitRev * pLogEntry = reinterpret_cast<GitRev *>(m_arShownList.SafeGetAt(GetNextSelectedItem(pos)));
1884 if(!HashOnly)
1886 //pLogEntry->GetFiles(this)
1887 //LogChangedPathArray * cpatharray = pLogEntry->pArChangedPaths;
1889 for (int cpPathIndex = 0; cpPathIndex<pLogEntry->GetFiles(this).GetCount(); ++cpPathIndex)
1891 sPaths += ((CTGitPath&)pLogEntry->GetFiles(this)[cpPathIndex]).GetActionName() + _T(" : ") + pLogEntry->GetFiles(this)[cpPathIndex].GetGitPathString();
1892 sPaths += _T("\r\n");
1894 sPaths.Trim();
1895 CString body = pLogEntry->GetBody();
1896 body.Replace(_T("\n"), _T("\r\n"));
1897 sLogCopyText.Format(_T("%s: %s\r\n%s: %s\r\n%s: %s\r\n%s:\r\n%s\r\n----\r\n%s\r\n\r\n"),
1898 (LPCTSTR)sRev, pLogEntry->m_CommitHash.ToString(),
1899 (LPCTSTR)sAuthor, (LPCTSTR)pLogEntry->GetAuthorName(),
1900 (LPCTSTR)sDate,
1901 (LPCTSTR)CAppUtils::FormatDateAndTime( pLogEntry->GetAuthorDate(), m_DateFormat, true, m_bRelativeTimes ),
1902 (LPCTSTR)sMessage, pLogEntry->GetSubject().Trim() + _T("\r\n\r\n") + body.Trim(),
1903 (LPCTSTR)sPaths);
1904 sClipdata += sLogCopyText;
1905 }else
1907 sClipdata += pLogEntry->m_CommitHash;
1908 break;
1912 CStringUtils::WriteAsciiStringToClipboard(sClipdata, GetSafeHwnd());
1917 void CGitLogListBase::DiffSelectedRevWithPrevious()
1919 if (m_bThreadRunning)
1920 return;
1922 int FirstSelect=-1, LastSelect=-1;
1923 POSITION pos = GetFirstSelectedItemPosition();
1924 FirstSelect = GetNextSelectedItem(pos);
1925 while(pos)
1927 LastSelect = GetNextSelectedItem(pos);
1930 ContextMenuAction(ID_COMPAREWITHPREVIOUS,FirstSelect,LastSelect, NULL);
1932 #if 0
1933 UpdateLogInfoLabel();
1934 int selIndex = m_LogList.GetSelectionMark();
1935 if (selIndex < 0)
1936 return;
1937 int selCount = m_LogList.GetSelectedCount();
1938 if (selCount != 1)
1939 return;
1941 // Find selected entry in the log list
1942 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
1943 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(m_LogList.GetNextSelectedItem(pos)));
1944 long rev1 = pLogEntry->Rev;
1945 long rev2 = rev1-1;
1946 CTGitPath path = m_path;
1948 // See how many files under the relative root were changed in selected revision
1949 int nChanged = 0;
1950 LogChangedPath * changed = NULL;
1951 for (INT_PTR c = 0; c < pLogEntry->pArChangedPaths->GetCount(); ++c)
1953 LogChangedPath * cpath = pLogEntry->pArChangedPaths->SafeGetAt(c);
1954 if (cpath && cpath -> sPath.Left(m_sRelativeRoot.GetLength()).Compare(m_sRelativeRoot)==0)
1956 ++nChanged;
1957 changed = cpath;
1961 if (m_path.IsDirectory() && nChanged == 1)
1963 // We're looking at the log for a directory and only one file under dir was changed in the revision
1964 // Do diff on that file instead of whole directory
1965 path.AppendPathString(changed->sPath.Mid(m_sRelativeRoot.GetLength()));
1968 m_bCancelled = FALSE;
1969 DialogEnableWindow(IDOK, FALSE);
1970 SetPromptApp(&theApp);
1971 theApp.DoWaitCursor(1);
1973 if (PromptShown())
1975 GitDiff diff(this, m_hWnd, true);
1976 diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
1977 diff.SetHEADPeg(m_LogRevision);
1978 diff.ShowCompare(path, rev2, path, rev1);
1980 else
1982 CAppUtils::StartShowCompare(m_hWnd, path, rev2, path, rev1, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
1985 theApp.DoWaitCursor(-1);
1986 EnableOKButton();
1987 #endif
1990 void CGitLogListBase::OnLvnOdfinditemLoglist(NMHDR *pNMHDR, LRESULT *pResult)
1992 LPNMLVFINDITEM pFindInfo = reinterpret_cast<LPNMLVFINDITEM>(pNMHDR);
1993 *pResult = -1;
1995 if (pFindInfo->lvfi.flags & LVFI_PARAM)
1996 return;
1997 if ((pFindInfo->iStart < 0)||(pFindInfo->iStart >= m_arShownList.GetCount()))
1998 return;
1999 if (pFindInfo->lvfi.psz == 0)
2000 return;
2001 #if 0
2002 CString sCmp = pFindInfo->lvfi.psz;
2003 CString sRev;
2004 for (int i=pFindInfo->iStart; i<m_arShownList.GetCount(); ++i)
2006 GitRev * pLogEntry = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(i));
2007 sRev.Format(_T("%ld"), pLogEntry->Rev);
2008 if (pFindInfo->lvfi.flags & LVFI_PARTIAL)
2010 if (sCmp.Compare(sRev.Left(sCmp.GetLength()))==0)
2012 *pResult = i;
2013 return;
2016 else
2018 if (sCmp.Compare(sRev)==0)
2020 *pResult = i;
2021 return;
2025 if (pFindInfo->lvfi.flags & LVFI_WRAP)
2027 for (int i=0; i<pFindInfo->iStart; ++i)
2029 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(i));
2030 sRev.Format(_T("%ld"), pLogEntry->Rev);
2031 if (pFindInfo->lvfi.flags & LVFI_PARTIAL)
2033 if (sCmp.Compare(sRev.Left(sCmp.GetLength()))==0)
2035 *pResult = i;
2036 return;
2039 else
2041 if (sCmp.Compare(sRev)==0)
2043 *pResult = i;
2044 return;
2049 #endif
2050 *pResult = -1;
2053 int CGitLogListBase::FillGitLog(CTGitPath *path,int info,CString *from,CString *to)
2055 ClearText();
2058 this->m_arShownList.SafeRemoveAll();
2060 this->m_logEntries.ClearAll();
2061 this->m_logEntries.ParserFromLog(path,-1,info,from,to);
2063 //this->m_logEntries.ParserFromLog();
2064 SetItemCountEx(this->m_logEntries.size());
2066 for(unsigned int i=0;i<m_logEntries.size();i++)
2068 if(m_IsOldFirst)
2070 m_logEntries.GetGitRevAt(m_logEntries.size()-i-1).m_IsFull=TRUE;
2071 this->m_arShownList.SafeAdd(&m_logEntries.GetGitRevAt(m_logEntries.size()-i-1));
2073 }else
2075 m_logEntries.GetGitRevAt(i).m_IsFull=TRUE;
2076 this->m_arShownList.SafeAdd(&m_logEntries.GetGitRevAt(i));
2080 if(path)
2081 m_Path=*path;
2082 return 0;
2086 int CGitLogListBase::BeginFetchLog()
2088 ClearText();
2090 this->m_arShownList.SafeRemoveAll();
2092 this->m_logEntries.ClearAll();
2093 git_init();
2095 this->m_LogCache.ClearAllParent();
2097 m_LogCache.FetchCacheIndex(g_Git.m_CurrentDir);
2099 CTGitPath *path;
2100 if(this->m_Path.IsEmpty())
2101 path=NULL;
2102 else
2103 path=&this->m_Path;
2105 CString hash;
2106 int mask;
2107 mask = CGit::LOG_INFO_ONLY_HASH | CGit::LOG_INFO_BOUNDARY;
2108 // if(this->m_bAllBranch)
2109 mask |= m_ShowMask ;
2111 if(m_bShowWC)
2113 this->m_logEntries.insert(m_logEntries.begin(),this->m_wcRev.m_CommitHash);
2114 ResetWcRev();
2115 this->m_LogCache.m_HashMap[m_wcRev.m_CommitHash]=m_wcRev;
2118 CString *pFrom, *pTo;
2119 pFrom = pTo = NULL;
2120 CString head(_T("HEAD"));
2121 if(!this->m_startrev.IsEmpty())
2123 pFrom = &this->m_startrev;
2124 if(!this->m_endrev.IsEmpty())
2125 pTo = &this->m_endrev;
2126 else
2127 pTo = &head;
2130 CFilterData data;
2131 data.m_From = m_From;
2132 data.m_To =m_To;
2134 #if 0 /* use tortoiegit filter */
2135 if(this->m_nSelectedFilter == LOGFILTER_ALL || m_nSelectedFilter == LOGFILTER_AUTHORS)
2136 data.m_Author = this->m_sFilterText;
2138 if(this->m_nSelectedFilter == LOGFILTER_ALL || m_nSelectedFilter == LOGFILTER_MESSAGES)
2139 data.m_MessageFilter = this->m_sFilterText;
2141 data.m_IsRegex = m_bFilterWithRegex;
2142 #endif
2144 CString cmd=g_Git.GetLogCmd(m_StartRef,path,-1,mask,pFrom,pTo,true,&data);
2146 //this->m_logEntries.ParserFromLog();
2147 if(IsInWorkingThread())
2149 PostMessage(LVM_SETITEMCOUNT, (WPARAM) this->m_logEntries.size(),(LPARAM) LVSICF_NOINVALIDATEALL);
2151 else
2153 SetItemCountEx(this->m_logEntries.size());
2156 git_init();
2158 if(g_Git.IsInitRepos())
2159 return 0;
2161 if(git_open_log(&m_DllGitLog,CUnicodeUtils::GetMulti(cmd,CP_ACP).GetBuffer()))
2163 return -1;
2166 return 0;
2169 BOOL CGitLogListBase::PreTranslateMessage(MSG* pMsg)
2171 if (pMsg->message == WM_KEYDOWN && pMsg->wParam=='\r')
2173 //if (GetFocus()==GetDlgItem(IDC_LOGLIST))
2175 if (CRegDWORD(_T("Software\\TortoiseGit\\DiffByDoubleClickInLog"), FALSE))
2177 DiffSelectedRevWithPrevious();
2178 return TRUE;
2181 #if 0
2182 if (GetFocus()==GetDlgItem(IDC_LOGMSG))
2184 DiffSelectedFile();
2185 return TRUE;
2187 #endif
2190 #if 0
2191 if (m_hAccel && !bSkipAccelerator)
2193 int ret = TranslateAccelerator(m_hWnd, m_hAccel, pMsg);
2194 if (ret)
2195 return TRUE;
2198 #endif
2199 //m_tooltips.RelayEvent(pMsg);
2200 return __super::PreTranslateMessage(pMsg);
2203 void CGitLogListBase::OnNMDblclkLoglist(NMHDR * /*pNMHDR*/, LRESULT *pResult)
2205 // a double click on an entry in the revision list has happened
2206 *pResult = 0;
2208 if (CRegDWORD(_T("Software\\TortoiseGit\\DiffByDoubleClickInLog"), FALSE))
2209 DiffSelectedRevWithPrevious();
2212 int CGitLogListBase::FetchLogAsync(void * data)
2214 m_ProcData=data;
2215 m_bExitThread=FALSE;
2216 InterlockedExchange(&m_bThreadRunning, TRUE);
2217 InterlockedExchange(&m_bNoDispUpdates, TRUE);
2218 m_LoadingThread = AfxBeginThread(LogThreadEntry, this, THREAD_PRIORITY_LOWEST);
2219 if (m_LoadingThread ==NULL)
2221 InterlockedExchange(&m_bThreadRunning, FALSE);
2222 InterlockedExchange(&m_bNoDispUpdates, FALSE);
2223 CMessageBox::Show(NULL, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
2224 return -1;
2226 return 0;
2229 //this is the thread function which calls the subversion function
2230 UINT CGitLogListBase::LogThreadEntry(LPVOID pVoid)
2232 return ((CGitLogListBase*)pVoid)->LogThread();
2235 void CGitLogListBase::GetTimeRange(CTime &oldest, CTime &latest)
2237 //CTime time;
2238 oldest=CTime::GetCurrentTime();
2239 latest=CTime(1971,1,2,0,0,0);
2240 for(unsigned int i=0;i<m_logEntries.size();i++)
2242 if(m_logEntries[i].IsEmpty())
2243 continue;
2245 if(m_logEntries.GetGitRevAt(i).GetAuthorDate().GetTime() < oldest.GetTime())
2246 oldest = m_logEntries.GetGitRevAt(i).GetAuthorDate().GetTime();
2248 if(m_logEntries.GetGitRevAt(i).GetAuthorDate().GetTime() > latest.GetTime())
2249 latest = m_logEntries.GetGitRevAt(i).GetAuthorDate().GetTime();
2253 if(latest<oldest)
2254 latest=oldest;
2257 UINT CGitLogListBase::LogThread()
2259 ::PostMessage(this->GetParent()->m_hWnd,MSG_LOAD_PERCENTAGE,(WPARAM) GITLOG_START,0);
2261 InterlockedExchange(&m_bThreadRunning, TRUE);
2262 InterlockedExchange(&m_bNoDispUpdates, TRUE);
2264 ULONGLONG t1,t2;
2266 if(BeginFetchLog())
2268 InterlockedExchange(&m_bThreadRunning, FALSE);
2269 InterlockedExchange(&m_bNoDispUpdates, FALSE);
2271 return -1;
2274 tr1::wregex pat;//(_T("Remove"), tr1::regex_constants::icase);
2275 bool bRegex = false;
2276 if (m_bFilterWithRegex)
2277 bRegex = ValidateRegexp(m_sFilterText, pat, false);
2279 TRACE(_T("\n===Begin===\n"));
2280 //Update work copy item;
2282 if( m_logEntries.size() > 0)
2284 GitRev *pRev = &m_logEntries.GetGitRevAt(0);
2286 m_arShownList.SafeAdd(pRev);
2290 InterlockedExchange(&m_bNoDispUpdates, FALSE);
2292 if(!g_Git.IsInitRepos())
2294 g_Git.m_critGitDllSec.Lock();
2295 git_get_log_firstcommit(m_DllGitLog);
2296 int total = git_get_log_estimate_commit_count(m_DllGitLog);
2297 g_Git.m_critGitDllSec.Unlock();
2299 GIT_COMMIT commit;
2300 t2=t1=GetTickCount();
2301 int oldprecentage = 0;
2302 int oldsize=m_logEntries.size();
2303 int ret=0;
2304 while( ret== 0)
2306 g_Git.m_critGitDllSec.Lock();
2307 ret=git_get_log_nextcommit(this->m_DllGitLog,&commit);
2308 g_Git.m_critGitDllSec.Unlock();
2310 if(ret)
2311 break;
2313 //printf("%s\r\n",commit.GetSubject());
2314 if(m_bExitThread)
2315 break;
2317 CGitHash hash = (char*)commit.m_hash ;
2319 GitRev *pRev = m_LogCache.GetCacheData(hash);
2320 pRev->m_GitCommit = commit;
2321 InterlockedExchange(&pRev->m_IsCommitParsed, FALSE);
2323 char *note=NULL;
2324 g_Git.m_critGitDllSec.Lock();
2325 git_get_notes(commit.m_hash,&note);
2326 g_Git.m_critGitDllSec.Unlock();
2328 if(note)
2330 pRev->m_Notes.Empty();
2331 g_Git.StringAppend(&pRev->m_Notes,(BYTE*)note);
2334 if(!pRev->m_IsDiffFiles)
2336 pRev->m_CallDiffAsync = DiffAsync;
2339 pRev->ParserParentFromCommit(&commit);
2341 #ifdef DEBUG
2342 pRev->DbgPrint();
2343 TRACE(_T("\n"));
2344 #endif
2346 if(!m_sFilterText.IsEmpty())
2348 if(!IsMatchFilter(bRegex,pRev,pat))
2349 continue;
2351 this->m_critSec.Lock();
2352 m_logEntries.push_back(hash);
2353 m_arShownList.SafeAdd(pRev);
2354 this->m_critSec.Unlock();
2356 t2=GetTickCount();
2358 if(t2-t1>500 || (m_logEntries.size()-oldsize >100))
2360 //update UI
2361 int percent=m_logEntries.size()*100/total + GITLOG_START+1;
2362 if(percent > 99)
2363 percent =99;
2364 if(percent < GITLOG_START)
2365 percent = GITLOG_START +1;
2367 oldsize = m_logEntries.size();
2368 PostMessage(LVM_SETITEMCOUNT, (WPARAM) this->m_logEntries.size(),(LPARAM) LVSICF_NOINVALIDATEALL|LVSICF_NOSCROLL);
2370 //if( percent > oldprecentage )
2372 ::PostMessage(this->GetParent()->m_hWnd,MSG_LOAD_PERCENTAGE,(WPARAM) percent,0);
2373 oldprecentage = percent;
2375 t1 = t2;
2378 g_Git.m_critGitDllSec.Lock();
2379 git_close_log(m_DllGitLog);
2380 g_Git.m_critGitDllSec.Unlock();
2383 //Update UI;
2384 PostMessage(LVM_SETITEMCOUNT, (WPARAM) this->m_logEntries.size(),(LPARAM) LVSICF_NOINVALIDATEALL|LVSICF_NOSCROLL);
2385 ::PostMessage(this->GetParent()->m_hWnd,MSG_LOAD_PERCENTAGE,(WPARAM) GITLOG_END,0);
2387 InterlockedExchange(&m_bThreadRunning, FALSE);
2389 return 0;
2392 void CGitLogListBase::Refresh(BOOL IsCleanFilter)
2394 SafeTerminateThread();
2396 this->SetItemCountEx(0);
2397 this->Clear();
2399 ResetWcRev();
2401 //Update branch and Tag info
2402 ReloadHashMap();
2403 //Assume Thread have exited
2404 //if(!m_bThreadRunning)
2406 m_logEntries.clear();
2408 if(IsCleanFilter)
2410 m_sFilterText.Empty();
2411 m_From=-1;
2412 m_To=-1;
2415 InterlockedExchange(&m_bExitThread,FALSE);
2417 InterlockedExchange(&m_bThreadRunning, TRUE);
2418 InterlockedExchange(&m_bNoDispUpdates, TRUE);
2419 if ( (m_LoadingThread=AfxBeginThread(LogThreadEntry, this)) ==NULL)
2421 InterlockedExchange(&m_bThreadRunning, FALSE);
2422 InterlockedExchange(&m_bNoDispUpdates, FALSE);
2423 CMessageBox::Show(NULL, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
2427 bool CGitLogListBase::ValidateRegexp(LPCTSTR regexp_str, tr1::wregex& pat, bool bMatchCase /* = false */)
2431 tr1::regex_constants::syntax_option_type type = tr1::regex_constants::ECMAScript;
2432 if (!bMatchCase)
2433 type |= tr1::regex_constants::icase;
2434 pat = tr1::wregex(regexp_str, type);
2435 return true;
2437 catch (exception) {}
2438 return false;
2440 BOOL CGitLogListBase::IsMatchFilter(bool bRegex, GitRev *pRev, tr1::wregex &pat)
2443 tr1::regex_constants::match_flag_type flags = tr1::regex_constants::match_any;
2444 CString sRev;
2446 if ((bRegex)&&(m_bFilterWithRegex))
2448 if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_BUGID))
2450 if(this->m_bShowBugtraqColumn)
2452 CString sBugIds = m_ProjectProperties.FindBugID(pRev->GetSubject());
2454 ATLTRACE(_T("bugID = \"%s\"\n"), sBugIds);
2455 if (regex_search(wstring(sBugIds), pat, flags))
2457 return TRUE;
2462 if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_MESSAGES))
2464 ATLTRACE(_T("messge = \"%s\"\n"), pRev->GetSubject());
2465 if (regex_search(wstring((LPCTSTR)pRev->GetSubject()), pat, flags))
2467 return TRUE;
2470 ATLTRACE(_T("messge = \"%s\"\n"),pRev->GetBody());
2471 if (regex_search(wstring((LPCTSTR)pRev->GetBody()), pat, flags))
2473 return TRUE;
2477 if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_AUTHORS))
2479 if (regex_search(wstring(pRev->GetAuthorName()), pat, flags))
2481 return TRUE;
2484 if (regex_search(wstring(pRev->GetCommitterName()), pat, flags))
2486 return TRUE;
2490 if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_REVS))
2492 sRev.Format(_T("%s"), pRev->m_CommitHash.ToString());
2493 if (regex_search(wstring((LPCTSTR)sRev), pat, flags))
2495 return TRUE;
2499 if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_PATHS))
2501 CTGitPathList *pathList=NULL;
2502 if( pRev->m_IsDiffFiles)
2503 pathList = &pRev->GetFiles(this);
2504 else
2506 if(!pRev->m_IsSimpleListReady)
2507 pRev->SafeGetSimpleList(&g_Git);
2510 if(pathList)
2511 for (INT_PTR cpPathIndex = 0; cpPathIndex < pathList->GetCount(); ++cpPathIndex)
2513 if (regex_search(wstring((LPCTSTR)pathList->m_paths.at(cpPathIndex).GetGitOldPathString()), pat, flags))
2515 return true;
2517 if (regex_search(wstring((LPCTSTR)pathList->m_paths.at(cpPathIndex).GetGitPathString()), pat, flags))
2519 return true;
2523 for(INT_PTR i=0;i<pRev->m_SimpleFileList.size();i++)
2525 if (regex_search(wstring((LPCTSTR)pRev->m_SimpleFileList[i]), pat, flags))
2527 return true;
2532 else
2534 CString find = m_sFilterText;
2535 find.MakeLower();
2537 if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_BUGID))
2539 if(this->m_bShowBugtraqColumn)
2541 CString sBugIds = m_ProjectProperties.FindBugID(pRev->GetSubject());
2543 sBugIds.MakeLower();
2544 if ((sBugIds.Find(find) >= 0))
2546 return TRUE;
2551 if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_MESSAGES))
2553 CString msg = pRev->GetSubject();
2555 msg = msg.MakeLower();
2556 if ((msg.Find(find) >= 0))
2558 return TRUE;
2561 msg = pRev->GetBody();
2563 msg = msg.MakeLower();
2564 if ((msg.Find(find) >= 0))
2566 return TRUE;
2570 if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_AUTHORS))
2572 CString msg = pRev->GetAuthorName();
2573 msg = msg.MakeLower();
2574 if ((msg.Find(find) >= 0))
2576 return TRUE;
2580 if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_REVS))
2582 sRev.Format(_T("%s"), pRev->m_CommitHash.ToString());
2583 if ((sRev.Find(find) >= 0))
2585 return TRUE;
2589 if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_PATHS))
2591 CTGitPathList *pathList=NULL;
2592 if( pRev->m_IsDiffFiles)
2593 pathList = &pRev->GetFiles(this);
2594 else
2596 if(!pRev->m_IsSimpleListReady)
2597 pRev->SafeGetSimpleList(&g_Git);
2599 if(pathList)
2600 for (INT_PTR cpPathIndex = 0; cpPathIndex < pathList->GetCount() ; ++cpPathIndex)
2602 CTGitPath *cpath = &pathList->m_paths.at(cpPathIndex);
2603 CString path = cpath->GetGitOldPathString();
2604 path.MakeLower();
2605 if ((path.Find(find)>=0))
2607 return true;
2609 path = cpath->GetGitPathString();
2610 path.MakeLower();
2611 if ((path.Find(find)>=0))
2613 return true;
2617 for (INT_PTR i=0;i<pRev->m_SimpleFileList.size();i++)
2619 CString path = pRev->m_SimpleFileList[i];
2620 path.MakeLower();
2621 if ((path.Find(find)>=0))
2623 return true;
2627 } // else (from if (bRegex))
2628 return FALSE;
2632 void CGitLogListBase::RecalculateShownList(CThreadSafePtrArray * pShownlist)
2635 pShownlist->SafeRemoveAll();
2637 tr1::wregex pat;//(_T("Remove"), tr1::regex_constants::icase);
2638 bool bRegex = false;
2639 if (m_bFilterWithRegex)
2640 bRegex = ValidateRegexp(m_sFilterText, pat, false);
2642 tr1::regex_constants::match_flag_type flags = tr1::regex_constants::match_any;
2643 CString sRev;
2644 for (DWORD i=0; i<m_logEntries.size(); ++i)
2646 if ((bRegex)&&(m_bFilterWithRegex))
2648 #if 0
2649 if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_BUGID))
2651 ATLTRACE(_T("bugID = \"%s\"\n"), (LPCTSTR)m_logEntries[i]->sBugIDs);
2652 if (regex_search(wstring((LPCTSTR)m_logEntries[i]->sBugIDs), pat, flags)&&IsEntryInDateRange(i))
2654 pShownlist->SafeAdd(m_logEntries[i]);
2655 continue;
2658 #endif
2659 if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_MESSAGES))
2661 ATLTRACE(_T("messge = \"%s\"\n"),m_logEntries.GetGitRevAt(i).GetSubject());
2662 if (regex_search(wstring((LPCTSTR)m_logEntries.GetGitRevAt(i).GetSubject()), pat, flags)&&IsEntryInDateRange(i))
2664 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
2665 continue;
2668 ATLTRACE(_T("messge = \"%s\"\n"),m_logEntries.GetGitRevAt(i).GetBody());
2669 if (regex_search(wstring((LPCTSTR)m_logEntries.GetGitRevAt(i).GetBody()), pat, flags)&&IsEntryInDateRange(i))
2671 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
2672 continue;
2675 if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_PATHS))
2677 CTGitPathList pathList = m_logEntries.GetGitRevAt(i).GetFiles(this);
2679 bool bGoing = true;
2680 for (INT_PTR cpPathIndex = 0; cpPathIndex < pathList.GetCount() && bGoing; ++cpPathIndex)
2682 CTGitPath cpath = pathList[cpPathIndex];
2683 if (regex_search(wstring((LPCTSTR)cpath.GetGitOldPathString()), pat, flags)&&IsEntryInDateRange(i))
2685 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
2686 bGoing = false;
2687 continue;
2689 if (regex_search(wstring((LPCTSTR)cpath.GetGitPathString()), pat, flags)&&IsEntryInDateRange(i))
2691 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
2692 bGoing = false;
2693 continue;
2695 if (regex_search(wstring((LPCTSTR)cpath.GetActionName()), pat, flags)&&IsEntryInDateRange(i))
2697 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
2698 bGoing = false;
2699 continue;
2703 if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_AUTHORS))
2705 if (regex_search(wstring((LPCTSTR)m_logEntries.GetGitRevAt(i).GetAuthorName()), pat, flags)&&IsEntryInDateRange(i))
2707 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
2708 continue;
2711 if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_REVS))
2713 sRev.Format(_T("%s"), m_logEntries.GetGitRevAt(i).m_CommitHash.ToString());
2714 if (regex_search(wstring((LPCTSTR)sRev), pat, flags)&&IsEntryInDateRange(i))
2716 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
2717 continue;
2720 } // if (bRegex)
2721 else
2723 CString find = m_sFilterText;
2724 find.MakeLower();
2725 #if 0
2726 if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_BUGID))
2728 CString sBugIDs = m_logEntries[i]->sBugIDs;
2730 sBugIDs = sBugIDs.MakeLower();
2731 if ((sBugIDs.Find(find) >= 0)&&(IsEntryInDateRange(i)))
2733 pShownlist->SafeAdd(m_logEntries[i]);
2734 continue;
2737 #endif
2738 if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_MESSAGES))
2740 CString msg = m_logEntries.GetGitRevAt(i).GetSubject();
2742 msg = msg.MakeLower();
2743 if ((msg.Find(find) >= 0)&&(IsEntryInDateRange(i)))
2745 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
2746 continue;
2748 msg = m_logEntries.GetGitRevAt(i).GetBody();
2750 msg = msg.MakeLower();
2751 if ((msg.Find(find) >= 0)&&(IsEntryInDateRange(i)))
2753 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
2754 continue;
2757 if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_PATHS))
2759 CTGitPathList pathList = m_logEntries.GetGitRevAt(i).GetFiles(this);
2761 bool bGoing = true;
2762 for (INT_PTR cpPathIndex = 0; cpPathIndex < pathList.GetCount() && bGoing; ++cpPathIndex)
2764 CTGitPath cpath = pathList[cpPathIndex];
2765 CString path = cpath.GetGitOldPathString();
2766 path.MakeLower();
2767 if ((path.Find(find)>=0)&&(IsEntryInDateRange(i)))
2769 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
2770 bGoing = false;
2771 continue;
2773 path = cpath.GetGitPathString();
2774 path.MakeLower();
2775 if ((path.Find(find)>=0)&&(IsEntryInDateRange(i)))
2777 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
2778 bGoing = false;
2779 continue;
2781 path = cpath.GetActionName();
2782 path.MakeLower();
2783 if ((path.Find(find)>=0)&&(IsEntryInDateRange(i)))
2785 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
2786 bGoing = false;
2787 continue;
2791 if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_AUTHORS))
2793 CString msg = m_logEntries.GetGitRevAt(i).GetAuthorName();
2794 msg = msg.MakeLower();
2795 if ((msg.Find(find) >= 0)&&(IsEntryInDateRange(i)))
2797 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
2798 continue;
2801 if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_REVS))
2803 sRev.Format(_T("%s"), m_logEntries.GetGitRevAt(i).m_CommitHash.ToString());
2804 if ((sRev.Find(find) >= 0)&&(IsEntryInDateRange(i)))
2806 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
2807 continue;
2810 } // else (from if (bRegex))
2811 } // for (DWORD i=0; i<m_logEntries.size(); ++i)
2815 BOOL CGitLogListBase::IsEntryInDateRange(int /*i*/)
2818 __time64_t time = m_logEntries.GetGitRevAt(i).GetAuthorDate().GetTime();
2820 if(m_From == -1)
2821 if(m_To == -1)
2822 return true;
2823 else
2824 return time <= m_To;
2825 else
2826 if(m_To == -1)
2827 return time >= m_From;
2828 else
2829 return ((time >= m_From)&&(time <= m_To));
2831 return TRUE; /* git dll will filter time range */
2833 // return TRUE;
2835 void CGitLogListBase::StartFilter()
2837 InterlockedExchange(&m_bNoDispUpdates, TRUE);
2838 RecalculateShownList(&m_arShownList);
2839 InterlockedExchange(&m_bNoDispUpdates, FALSE);
2842 DeleteAllItems();
2843 SetItemCountEx(ShownCountWithStopped());
2844 RedrawItems(0, ShownCountWithStopped());
2845 Invalidate();
2848 void CGitLogListBase::RemoveFilter()
2851 InterlockedExchange(&m_bNoDispUpdates, TRUE);
2853 m_arShownList.SafeRemoveAll();
2855 // reset the time filter too
2856 #if 0
2857 m_timFrom = (__time64_t(m_tFrom));
2858 m_timTo = (__time64_t(m_tTo));
2859 m_DateFrom.SetTime(&m_timFrom);
2860 m_DateTo.SetTime(&m_timTo);
2861 m_DateFrom.SetRange(&m_timFrom, &m_timTo);
2862 m_DateTo.SetRange(&m_timFrom, &m_timTo);
2863 #endif
2865 for (DWORD i=0; i<m_logEntries.size(); ++i)
2867 if(this->m_IsOldFirst)
2869 m_arShownList.SafeAdd(&m_logEntries.GetGitRevAt(m_logEntries.size()-i-1));
2870 }else
2872 m_arShownList.SafeAdd(&m_logEntries.GetGitRevAt(i));
2875 // InterlockedExchange(&m_bNoDispUpdates, FALSE);
2876 DeleteAllItems();
2877 SetItemCountEx(ShownCountWithStopped());
2878 RedrawItems(0, ShownCountWithStopped());
2880 InterlockedExchange(&m_bNoDispUpdates, FALSE);
2883 void CGitLogListBase::Clear()
2885 m_arShownList.SafeRemoveAll();
2886 DeleteAllItems();
2888 m_logEntries.ClearAll();
2892 void CGitLogListBase::OnDestroy()
2894 // save the column widths to the registry
2895 SaveColumnWidths();
2897 SafeTerminateThread();
2898 SafeTerminateAsyncDiffThread();
2900 int retry = 0;
2901 while(m_LogCache.SaveCache())
2903 if(retry > 5)
2904 break;
2905 Sleep(1000);
2907 retry++;
2909 //if(CMessageBox::Show(NULL,_T("Cannot Save Log Cache to Disk. To retry click yes. To give up click no."),_T("TortoiseGit"),
2910 // MB_YESNO) == IDNO)
2911 // break;
2914 CHintListCtrl::OnDestroy();
2917 LRESULT CGitLogListBase::OnLoad(WPARAM wParam,LPARAM lParam)
2919 UNREFERENCED_PARAMETER(lParam);
2920 CRect rect;
2921 int i=(int)wParam;
2922 this->GetItemRect(i,&rect,LVIR_BOUNDS);
2923 this->InvalidateRect(rect);
2925 return 0;
2929 * Save column widths to the registry
2931 void CGitLogListBase::SaveColumnWidths()
2933 int maxcol = m_ColumnManager.GetColumnCount();
2935 for (int col = 0; col < maxcol; col++)
2936 if (m_ColumnManager.IsVisible (col))
2937 m_ColumnManager.ColumnResized (col);
2939 m_ColumnManager.WriteSettings();
2942 int CGitLogListBase::GetHeadIndex()
2944 if(m_HeadHash.IsEmpty())
2945 return -1;
2947 for(int i=0;i<m_arShownList.GetCount();i++)
2949 GitRev *pRev = (GitRev*)m_arShownList[i];
2950 if(pRev)
2952 if(pRev->m_CommitHash.ToString() == m_HeadHash )
2953 return i;
2956 return -1;
2958 void CGitLogListBase::OnFind()
2960 if (!m_pFindDialog)
2962 m_pFindDialog = new CFindDlg(this);
2963 m_pFindDialog->Create(this);
2966 void CGitLogListBase::OnHdnBegintrack(NMHDR *pNMHDR, LRESULT *pResult)
2968 m_ColumnManager.OnHdnBegintrack(pNMHDR, pResult);
2970 void CGitLogListBase::OnHdnItemchanging(NMHDR *pNMHDR, LRESULT *pResult)
2972 if(!m_ColumnManager.OnHdnItemchanging(pNMHDR, pResult))
2973 Default();
2975 LRESULT CGitLogListBase::OnFindDialogMessage(WPARAM /*wParam*/, LPARAM /*lParam*/)
2978 ASSERT(m_pFindDialog != NULL);
2979 bool bFound = false;
2980 int i=0;
2982 if (m_pFindDialog->IsTerminating())
2984 // invalidate the handle identifying the dialog box.
2985 m_pFindDialog = NULL;
2986 return 0;
2989 if(m_pFindDialog->IsRef())
2991 CString str;
2992 str=m_pFindDialog->GetFindString();
2994 CGitHash hash;
2996 if(!str.IsEmpty())
2997 hash = g_Git.GetHash(str);
2999 if(!hash.IsEmpty())
3001 for (i = 0; i<m_arShownList.GetCount(); i++)
3003 GitRev* pLogEntry = (GitRev*)m_arShownList.SafeGetAt(i);
3004 if(pLogEntry && pLogEntry->m_CommitHash == hash)
3006 bFound = true;
3007 break;
3014 if(m_pFindDialog->FindNext())
3016 //read data from dialog
3017 CString FindText = m_pFindDialog->GetFindString();
3018 bool bMatchCase = (m_pFindDialog->MatchCase() == TRUE);
3020 tr1::wregex pat;
3021 bool bRegex = ValidateRegexp(FindText, pat, bMatchCase);
3023 tr1::regex_constants::match_flag_type flags = tr1::regex_constants::match_not_null;
3026 for (i = this->m_nSearchIndex; i<m_arShownList.GetCount()&&!bFound; i++)
3028 GitRev* pLogEntry = (GitRev*)m_arShownList.SafeGetAt(i);
3030 CString str;
3031 str+=pLogEntry->m_CommitHash.ToString();
3032 str+=_T("\n");
3034 for(int j=0;j<this->m_HashMap[pLogEntry->m_CommitHash].size();j++)
3036 str+=m_HashMap[pLogEntry->m_CommitHash][j];
3037 str+=_T("\n");
3040 str+=pLogEntry->GetAuthorEmail();
3041 str+=_T("\n");
3042 str+=pLogEntry->GetAuthorName();
3043 str+=_T("\n");
3044 str+=pLogEntry->GetBody();
3045 str+=_T("\n");
3046 str+=pLogEntry->GetCommitterEmail();
3047 str+=_T("\n");
3048 str+=pLogEntry->GetCommitterName();
3049 str+=_T("\n");
3050 str+=pLogEntry->GetSubject();
3051 str+=_T("\n");
3054 /*Because changed files list is loaded on demand when gui show,
3055 files will empty when files have not fetched.
3057 we can add it back by using one-way diff(with outnumber changed and rename detect.
3058 here just need changed filename list. one-way is much quicker.
3060 if(pLogEntry->m_IsFull)
3062 for(int i=0;i<pLogEntry->GetFiles(this).GetCount();i++)
3064 str+=pLogEntry->GetFiles(this)[i].GetWinPath();
3065 str+=_T("\n");
3066 str+=pLogEntry->GetFiles(this)[i].GetGitOldPathString();
3067 str+=_T("\n");
3069 }else
3071 if(!pLogEntry->m_IsSimpleListReady)
3072 pLogEntry->SafeGetSimpleList(&g_Git);
3074 for(int i=0;i<pLogEntry->m_SimpleFileList.size();i++)
3076 str+=pLogEntry->m_SimpleFileList[i];
3077 str+=_T("\n");
3083 if (bRegex)
3085 if (regex_search(wstring(str), pat, flags))
3087 bFound = true;
3088 break;
3091 else
3093 if (bMatchCase)
3095 if (str.Find(FindText) >= 0)
3097 bFound = true;
3098 break;
3102 else
3104 CString msg = str;
3105 msg = msg.MakeLower();
3106 CString find = FindText.MakeLower();
3107 if (msg.Find(find) >= 0)
3109 bFound = TRUE;
3110 break;
3114 } // for (i = this->m_nSearchIndex; i<m_arShownList.GetItemCount()&&!bFound; i++)
3116 } // if(m_pFindDialog->FindNext())
3117 //UpdateLogInfoLabel();
3119 if (bFound)
3121 this->m_nSearchIndex = i;
3122 EnsureVisible(i, FALSE);
3123 SetItemState(GetSelectionMark(), 0, LVIS_SELECTED);
3124 SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
3125 SetSelectionMark(i);
3126 //FillLogMessageCtrl();
3127 UpdateData(FALSE);
3128 m_nSearchIndex++;
3129 if (m_nSearchIndex >= m_arShownList.GetCount())
3130 m_nSearchIndex = (int)m_arShownList.GetCount()-1;
3133 return 0;
3136 void CGitLogListBase::OnColumnResized(NMHDR *pNMHDR, LRESULT *pResult)
3138 m_ColumnManager.OnColumnResized(pNMHDR,pResult);
3140 *pResult = FALSE;
3143 void CGitLogListBase::OnColumnMoved(NMHDR *pNMHDR, LRESULT *pResult)
3145 m_ColumnManager.OnColumnMoved(pNMHDR, pResult);
3147 Invalidate(FALSE);