OSDN Git Service

RebaseDlg: Implement Pick squash and edit by context menu
[tortoisegit/TortoiseGitJp.git] / src / TortoiseProc / GitLogListBase.cpp
1 // GitLogList.cpp : implementation file\r
2 //\r
3 /*\r
4         Description: qgit revision list view\r
5 \r
6         Author: Marco Costalba (C) 2005-2007\r
7 \r
8         Copyright: See COPYING file that comes with this distribution\r
9 \r
10 */\r
11 #include "stdafx.h"\r
12 #include "GitLogListBase.h"\r
13 #include "GitRev.h"\r
14 //#include "VssStyle.h"\r
15 #include "IconMenu.h"\r
16 // CGitLogListBase\r
17 #include "cursor.h"\r
18 #include "InputDlg.h"\r
19 #include "PropDlg.h"\r
20 #include "SVNProgressDlg.h"\r
21 #include "ProgressDlg.h"\r
22 //#include "RepositoryBrowser.h"\r
23 //#include "CopyDlg.h"\r
24 //#include "StatGraphDlg.h"\r
25 #include "Logdlg.h"\r
26 #include "MessageBox.h"\r
27 #include "Registry.h"\r
28 #include "AppUtils.h"\r
29 #include "PathUtils.h"\r
30 #include "StringUtils.h"\r
31 #include "UnicodeUtils.h"\r
32 #include "TempFile.h"\r
33 //#include "GitInfo.h"\r
34 //#include "GitDiff.h"\r
35 #include "IconMenu.h"\r
36 //#include "RevisionRangeDlg.h"\r
37 //#include "BrowseFolder.h"\r
38 //#include "BlameDlg.h"\r
39 //#include "Blame.h"\r
40 //#include "GitHelpers.h"\r
41 #include "GitStatus.h"\r
42 //#include "LogDlgHelper.h"\r
43 //#include "CachedLogInfo.h"\r
44 //#include "RepositoryInfo.h"\r
45 //#include "EditPropertiesDlg.h"\r
46 #include "FileDiffDlg.h"\r
47 #include "..\\TortoiseShell\\Resource.h"\r
48 \r
49 \r
50 IMPLEMENT_DYNAMIC(CGitLogListBase, CHintListCtrl)\r
51 \r
52 CGitLogListBase::CGitLogListBase():CHintListCtrl()\r
53         ,m_regMaxBugIDColWidth(_T("Software\\TortoiseGit\\MaxBugIDColWidth"), 200)\r
54         ,m_nSearchIndex(0)\r
55         ,m_bNoDispUpdates(FALSE)\r
56         , m_bThreadRunning(FALSE)\r
57         , m_bStrictStopped(false)\r
58         , m_pStoreSelection(NULL)\r
59         , m_nSelectedFilter(LOGFILTER_ALL)\r
60 {\r
61         // use the default GUI font, create a copy of it and\r
62         // change the copy to BOLD (leave the rest of the font\r
63         // the same)\r
64         HFONT hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);\r
65         LOGFONT lf = {0};\r
66         GetObject(hFont, sizeof(LOGFONT), &lf);\r
67         lf.lfWeight = FW_BOLD;\r
68         m_boldFont = CreateFontIndirect(&lf);\r
69         \r
70         m_bShowBugtraqColumn=0;\r
71 \r
72         m_IsIDReplaceAction=FALSE;\r
73 \r
74         m_wcRev.m_CommitHash=GIT_REV_ZERO;\r
75         m_wcRev.m_Subject=_T("Working Copy");\r
76 \r
77         m_hModifiedIcon = (HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_ACTIONMODIFIED), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);\r
78         m_hReplacedIcon = (HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_ACTIONREPLACED), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);\r
79         m_hAddedIcon    =  (HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_ACTIONADDED), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);\r
80         m_hDeletedIcon = (HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_ACTIONDELETED), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);\r
81 \r
82         m_bFilterWithRegex = !!CRegDWORD(_T("Software\\TortoiseGit\\UseRegexFilter"), TRUE);\r
83 \r
84         g_Git.GetMapHashToFriendName(m_HashMap);\r
85         m_CurrentBranch=g_Git.GetCurrentBranch();\r
86 \r
87         m_From=CTime(1970,1,2,0,0,0);\r
88         m_To=CTime::GetCurrentTime();\r
89     m_ShowMask = 0;\r
90         m_LoadingThread = NULL;\r
91 \r
92         m_bExitThread=FALSE;\r
93         m_IsOldFirst = FALSE;\r
94         m_IsRebaseReplaceGraph = FALSE;\r
95 \r
96         for(int i=0;i<Lanes::COLORS_NUM;i++)\r
97         {\r
98                 m_LineColors[i] = m_Colors.GetColor((CColors::Colors)(CColors::BranchLine1+i));\r
99         }\r
100         // get short/long datetime setting from registry\r
101         DWORD RegUseShortDateFormat = CRegDWORD(_T("Software\\TortoiseGit\\LogDateFormat"), TRUE);\r
102         if ( RegUseShortDateFormat )\r
103         {\r
104                 m_DateFormat = DATE_SHORTDATE;\r
105         }\r
106         else\r
107         {\r
108                 m_DateFormat = DATE_LONGDATE;\r
109         }\r
110         // get relative time display setting from registry\r
111         DWORD regRelativeTimes = CRegDWORD(_T("Software\\TortoiseGit\\RelativeTimes"), FALSE);\r
112         m_bRelativeTimes = (regRelativeTimes != 0);\r
113 }\r
114 \r
115 CGitLogListBase::~CGitLogListBase()\r
116 {\r
117         InterlockedExchange(&m_bNoDispUpdates, TRUE);\r
118 \r
119         DestroyIcon(m_hModifiedIcon);\r
120         DestroyIcon(m_hReplacedIcon);\r
121         DestroyIcon(m_hAddedIcon);\r
122         DestroyIcon(m_hDeletedIcon);\r
123         m_logEntries.ClearAll();\r
124 \r
125         if (m_boldFont)\r
126                 DeleteObject(m_boldFont);\r
127 \r
128         if ( m_pStoreSelection )\r
129         {\r
130                 delete m_pStoreSelection;\r
131                 m_pStoreSelection = NULL;\r
132         }\r
133 \r
134         if(this->m_bThreadRunning)\r
135         {\r
136                 m_bExitThread=true;\r
137                 WaitForSingleObject(m_LoadingThread->m_hThread,1000);\r
138                 TerminateThread();\r
139         }\r
140 }\r
141 \r
142 \r
143 BEGIN_MESSAGE_MAP(CGitLogListBase, CHintListCtrl)\r
144         ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnNMCustomdrawLoglist)\r
145         ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnLvnGetdispinfoLoglist)\r
146         ON_WM_CONTEXTMENU()\r
147         ON_NOTIFY_REFLECT(NM_DBLCLK, OnNMDblclkLoglist)\r
148         ON_NOTIFY_REFLECT(LVN_ODFINDITEM,OnLvnOdfinditemLoglist)\r
149         ON_WM_CREATE()\r
150         ON_WM_DESTROY()\r
151         ON_MESSAGE(MSG_LOADED,OnLoad)\r
152 END_MESSAGE_MAP()\r
153 \r
154 int CGitLogListBase:: OnCreate(LPCREATESTRUCT lpCreateStruct)\r
155 {\r
156         PreSubclassWindow();\r
157         return CHintListCtrl::OnCreate(lpCreateStruct);\r
158 }\r
159 \r
160 void CGitLogListBase::PreSubclassWindow()\r
161 {\r
162         SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_SUBITEMIMAGES);\r
163         // load the icons for the action columns\r
164         m_Theme.SetWindowTheme(GetSafeHwnd(), L"Explorer", NULL);\r
165         CHintListCtrl::PreSubclassWindow();\r
166 }\r
167 \r
168 void CGitLogListBase::InsertGitColumn()\r
169 {\r
170         CString temp;\r
171 \r
172         int c = ((CHeaderCtrl*)(GetDlgItem(0)))->GetItemCount()-1;\r
173         \r
174         while (c>=0)\r
175                 DeleteColumn(c--);\r
176         temp.LoadString(IDS_LOG_GRAPH);\r
177 \r
178         if(m_IsRebaseReplaceGraph)\r
179         {\r
180                 temp=_T("Rebase");\r
181         }\r
182         else\r
183         {\r
184                 temp.LoadString(IDS_LOG_GRAPH);\r
185         }\r
186         InsertColumn(this->LOGLIST_GRAPH, temp);\r
187         \r
188 #if 0   \r
189         // make the revision column right aligned\r
190         LVCOLUMN Column;\r
191         Column.mask = LVCF_FMT;\r
192         Column.fmt = LVCFMT_RIGHT;\r
193         SetColumn(0, &Column); \r
194 #endif  \r
195 //      CString log;\r
196 //      g_Git.GetLog(log);\r
197 \r
198         if(m_IsIDReplaceAction)\r
199         {\r
200                 temp.LoadString(IDS_LOG_ID);\r
201                 InsertColumn(this->LOGLIST_ACTION, temp);\r
202         }\r
203         else\r
204         {\r
205                 temp.LoadString(IDS_LOG_ACTIONS);\r
206                 InsertColumn(this->LOGLIST_ACTION, temp);\r
207         }\r
208         temp.LoadString(IDS_LOG_MESSAGE);\r
209         InsertColumn(this->LOGLIST_MESSAGE, temp);\r
210         \r
211         temp.LoadString(IDS_LOG_AUTHOR);\r
212         InsertColumn(this->LOGLIST_AUTHOR, temp);\r
213         \r
214         temp.LoadString(IDS_LOG_DATE);\r
215         InsertColumn(this->LOGLIST_DATE, temp);\r
216         \r
217 \r
218         if (m_bShowBugtraqColumn)\r
219         {\r
220 //              temp = m_ProjectProperties.sLabel;\r
221                 if (temp.IsEmpty())\r
222                         temp.LoadString(IDS_LOG_BUGIDS);\r
223                 InsertColumn(this->LOGLIST_BUG, temp);\r
224 \r
225         }\r
226         \r
227         SetRedraw(false);\r
228         ResizeAllListCtrlCols();\r
229         SetRedraw(true);\r
230 \r
231 }\r
232 \r
233 /**\r
234  * Resizes all columns in a list control to values in registry.\r
235  */\r
236 void CGitLogListBase::ResizeAllListCtrlCols()\r
237 {\r
238         // column max and min widths to allow\r
239         static const int nMinimumWidth = 10;\r
240         static const int nMaximumWidth = 1000;\r
241         CHeaderCtrl* pHdrCtrl = (CHeaderCtrl*)(GetDlgItem(0));\r
242         if (pHdrCtrl)\r
243         {\r
244                 int numcols = pHdrCtrl->GetItemCount();\r
245                 for (int col = 0; col < numcols; col++)\r
246                 {\r
247                         // get width for this col last time from registry\r
248                         CString regentry;\r
249                         regentry.Format( _T("Software\\TortoiseGit\\log\\ColWidth%d"), col);\r
250                         CRegDWORD regwidth(regentry, 0);\r
251                         int cx = regwidth;\r
252                         if ( cx == 0 )\r
253                         {\r
254                                 // no saved value, setup sensible defaults\r
255                                 if (col == this->LOGLIST_MESSAGE)\r
256                                 {\r
257                                         cx = LOGLIST_MESSAGE_MIN;\r
258                                 }\r
259                                 else\r
260                                 {\r
261                                         cx = ICONITEMBORDER+16*4;\r
262                                 }\r
263                         }\r
264                         if (cx < nMinimumWidth)\r
265                         {\r
266                                 cx = nMinimumWidth;\r
267                         } else if (cx > nMaximumWidth)\r
268                         {\r
269                                 cx = nMaximumWidth;\r
270                         }\r
271 \r
272                         SetColumnWidth(col, cx);\r
273                 }\r
274         }\r
275 \r
276 }\r
277 \r
278 \r
279 BOOL CGitLogListBase::GetShortName(CString ref, CString &shortname,CString prefix)\r
280 {\r
281         TRACE(_T("%s %s\r\n"),ref,prefix);\r
282         if(ref.Left(prefix.GetLength()) ==  prefix)\r
283         {\r
284                 shortname = ref.Right(ref.GetLength()-prefix.GetLength());\r
285                 if(shortname.Right(3)==_T("^{}"))\r
286                         shortname=shortname.Left(shortname.GetLength()-3);\r
287                 return TRUE;\r
288         }\r
289         return FALSE;\r
290 }\r
291 void CGitLogListBase::FillBackGround(HDC hdc, int Index,CRect &rect)\r
292 {       \r
293 //      HBRUSH brush;\r
294         LVITEM   rItem;\r
295         SecureZeroMemory(&rItem, sizeof(LVITEM));\r
296         rItem.mask  = LVIF_STATE;\r
297         rItem.iItem = Index;\r
298         rItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED;\r
299         GetItem(&rItem);\r
300 \r
301         if (m_Theme.IsAppThemed() && m_bVista)\r
302         {\r
303                 m_Theme.Open(m_hWnd, L"Explorer");\r
304                 int state = LISS_NORMAL;\r
305                 if (rItem.state & LVIS_SELECTED)\r
306                 {\r
307                         if (::GetFocus() == m_hWnd)\r
308                                 state |= LISS_SELECTED;\r
309                         else\r
310                                 state |= LISS_SELECTEDNOTFOCUS;\r
311                 }\r
312                 else\r
313                 {\r
314 #if 0\r
315                         if (pLogEntry->bCopiedSelf)\r
316                         {\r
317                                 // unfortunately, the pLVCD->nmcd.uItemState does not contain valid\r
318                                 // information at this drawing stage. But we can check the whether the\r
319                                 // previous stage changed the background color of the item\r
320                                 if (pLVCD->clrTextBk == GetSysColor(COLOR_MENU))\r
321                                 {\r
322                                         HBRUSH brush;\r
323                                         brush = ::CreateSolidBrush(::GetSysColor(COLOR_MENU));\r
324                                         if (brush)\r
325                                         {\r
326                                                 ::FillRect(pLVCD->nmcd.hdc, &rect, brush);\r
327                                                 ::DeleteObject(brush);\r
328                                         }\r
329                                 }\r
330                         }\r
331 #endif\r
332                 }\r
333 \r
334                 if (m_Theme.IsBackgroundPartiallyTransparent(LVP_LISTDETAIL, state))\r
335                         m_Theme.DrawParentBackground(m_hWnd, hdc, &rect);\r
336 \r
337                         m_Theme.DrawBackground(hdc, LVP_LISTDETAIL, state, &rect, NULL);\r
338         }\r
339         else\r
340         {\r
341                 HBRUSH brush;\r
342                 if (rItem.state & LVIS_SELECTED)\r
343                 {\r
344                         if (::GetFocus() == m_hWnd)\r
345                                 brush = ::CreateSolidBrush(::GetSysColor(COLOR_HIGHLIGHT));\r
346                         else\r
347                                 brush = ::CreateSolidBrush(::GetSysColor(COLOR_BTNFACE));\r
348                 }\r
349                 else\r
350                 {\r
351                         //if (pLogEntry->bCopiedSelf)\r
352                         //      brush = ::CreateSolidBrush(::GetSysColor(COLOR_MENU));\r
353                         //else\r
354                                 brush = ::CreateSolidBrush(::GetSysColor(COLOR_WINDOW));\r
355                 }\r
356                 if (brush == NULL)\r
357                         return;\r
358 \r
359                 ::FillRect(hdc, &rect, brush);\r
360                 ::DeleteObject(brush);\r
361                 \r
362         }\r
363 }\r
364 \r
365 void CGitLogListBase::DrawTagBranch(HDC hdc,CRect &rect,INT_PTR index)\r
366 {\r
367         GitRev* data = (GitRev*)m_arShownList.GetAt(index);\r
368         CRect rt=rect;\r
369         LVITEM   rItem;\r
370         SecureZeroMemory(&rItem, sizeof(LVITEM));\r
371         rItem.mask  = LVIF_STATE;\r
372         rItem.iItem = index;\r
373         rItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED;\r
374         GetItem(&rItem);\r
375 \r
376         for(unsigned int i=0;i<m_HashMap[data->m_CommitHash].size();i++)\r
377         {\r
378                 CString str;\r
379                 str=m_HashMap[data->m_CommitHash][i];\r
380                 \r
381                 CString shortname;\r
382                 HBRUSH brush=0;\r
383                 shortname=_T("");\r
384                 if(GetShortName(str,shortname,_T("refs/heads/")))\r
385                 {\r
386                         if( shortname == m_CurrentBranch )\r
387                                 brush = ::CreateSolidBrush(m_Colors.GetColor(CColors::CurrentBranch));\r
388                         else\r
389                                 brush = ::CreateSolidBrush(m_Colors.GetColor(CColors::LocalBranch));\r
390 \r
391                 }else if(GetShortName(str,shortname,_T("refs/remotes/")))\r
392                 {\r
393                         brush = ::CreateSolidBrush(m_Colors.GetColor(CColors::RemoteBranch));\r
394                 }\r
395                 else if(GetShortName(str,shortname,_T("refs/tags/")))\r
396                 {\r
397                         brush = ::CreateSolidBrush(m_Colors.GetColor(CColors::Tag));\r
398                 }\r
399 \r
400                 if(!shortname.IsEmpty())\r
401                 {\r
402                         SIZE size;\r
403                         memset(&size,0,sizeof(SIZE));\r
404                         GetTextExtentPoint32(hdc, shortname,shortname.GetLength(),&size);\r
405                 \r
406                         rt.SetRect(rt.left,rt.top,rt.left+size.cx,rt.bottom);\r
407                         rt.right+=4;\r
408                         ::FillRect(hdc, &rt, brush);\r
409                         if (rItem.state & LVIS_SELECTED)\r
410                         {\r
411                                 COLORREF   clrOld   = ::SetTextColor(hdc,::GetSysColor(COLOR_HIGHLIGHTTEXT));   \r
412                                 ::DrawText(hdc,shortname,shortname.GetLength(),&rt,DT_CENTER);\r
413                                 ::SetTextColor(hdc,clrOld);   \r
414                         }else\r
415                         {\r
416                                 ::DrawText(hdc,shortname,shortname.GetLength(),&rt,DT_CENTER);\r
417                         }\r
418 \r
419                         \r
420                         ::MoveToEx(hdc,rt.left,rt.top,NULL);\r
421                         ::LineTo(hdc,rt.right,rt.top);\r
422                         ::LineTo(hdc,rt.right,rt.bottom);\r
423                         ::LineTo(hdc,rt.left,rt.bottom);\r
424                         ::LineTo(hdc,rt.left,rt.top);\r
425                                 \r
426                         rt.left=rt.right+3;\r
427                 }\r
428                 if(brush)\r
429                         ::DeleteObject(brush);\r
430         }               \r
431         rt.right=rect.right;\r
432 \r
433         if (rItem.state & LVIS_SELECTED)\r
434         {\r
435                 COLORREF   clrOld   = ::SetTextColor(hdc,::GetSysColor(COLOR_HIGHLIGHTTEXT));   \r
436                 ::DrawText(hdc,data->m_Subject,data->m_Subject.GetLength(),&rt,DT_LEFT);\r
437                 ::SetTextColor(hdc,clrOld);   \r
438         }else\r
439         {\r
440                 ::DrawText(hdc,data->m_Subject,data->m_Subject.GetLength(),&rt,DT_LEFT);\r
441         }\r
442         \r
443 }\r
444 \r
445 void CGitLogListBase::paintGraphLane(HDC hdc, int laneHeight,int type, int x1, int x2,\r
446                                       const COLORREF& col,int top\r
447                                                                           )  \r
448 {\r
449         int h = laneHeight / 2;\r
450         int m = (x1 + x2) / 2;\r
451         int r = (x2 - x1) / 3;\r
452         int d =  2 * r;\r
453 \r
454         #define P_CENTER m , h+top\r
455         #define P_0      x2, h+top\r
456         #define P_90     m , 0+top\r
457         #define P_180    x1, h+top\r
458         #define P_270    m , 2 * h+top\r
459         #define R_CENTER m - r, h - r+top, m - r+d, h - r+top+d\r
460 \r
461         //static QPen myPen(Qt::black, 2); // fast path here\r
462         CPen pen;\r
463         pen.CreatePen(PS_SOLID,2,col);\r
464         //myPen.setColor(col);\r
465         HPEN oldpen=(HPEN)::SelectObject(hdc,(HPEN)pen);\r
466 \r
467         //p->setPen(myPen);\r
468 \r
469         // vertical line\r
470         switch (type) {\r
471         case Lanes::ACTIVE:\r
472         case Lanes::NOT_ACTIVE:\r
473         case Lanes::MERGE_FORK:\r
474         case Lanes::MERGE_FORK_R:\r
475         case Lanes::MERGE_FORK_L:\r
476         case Lanes::JOIN:\r
477         case Lanes::JOIN_R:\r
478         case Lanes::JOIN_L:\r
479                 DrawLine(hdc,P_90,P_270);\r
480                 //p->drawLine(P_90, P_270);\r
481                 break;\r
482         case Lanes::HEAD:\r
483         case Lanes::HEAD_R:\r
484         case Lanes::HEAD_L:\r
485         case Lanes::BRANCH:\r
486                 DrawLine(hdc,P_CENTER,P_270);\r
487                 //p->drawLine(P_CENTER, P_270);\r
488                 break;\r
489         case Lanes::TAIL:\r
490         case Lanes::TAIL_R:\r
491         case Lanes::TAIL_L:\r
492         case Lanes::INITIAL:\r
493         case Lanes::BOUNDARY:\r
494         case Lanes::BOUNDARY_C:\r
495         case Lanes::BOUNDARY_R:\r
496         case Lanes::BOUNDARY_L:\r
497                 DrawLine(hdc,P_90, P_CENTER);\r
498                 //p->drawLine(P_90, P_CENTER);\r
499                 break;\r
500         default:\r
501                 break;\r
502         }\r
503 \r
504         // horizontal line\r
505         switch (type) {\r
506         case Lanes::MERGE_FORK:\r
507         case Lanes::JOIN:\r
508         case Lanes::HEAD:\r
509         case Lanes::TAIL:\r
510         case Lanes::CROSS:\r
511         case Lanes::CROSS_EMPTY:\r
512         case Lanes::BOUNDARY_C:\r
513                 DrawLine(hdc,P_180,P_0);\r
514                 //p->drawLine(P_180, P_0);\r
515                 break;\r
516         case Lanes::MERGE_FORK_R:\r
517         case Lanes::JOIN_R:\r
518         case Lanes::HEAD_R:\r
519         case Lanes::TAIL_R:\r
520         case Lanes::BOUNDARY_R:\r
521                 DrawLine(hdc,P_180,P_CENTER);\r
522                 //p->drawLine(P_180, P_CENTER);\r
523                 break;\r
524         case Lanes::MERGE_FORK_L:\r
525         case Lanes::JOIN_L:\r
526         case Lanes::HEAD_L:\r
527         case Lanes::TAIL_L:\r
528         case Lanes::BOUNDARY_L:\r
529                 DrawLine(hdc,P_CENTER,P_0);\r
530                 //p->drawLine(P_CENTER, P_0);\r
531                 break;\r
532         default:\r
533                 break;\r
534         }\r
535 \r
536         CBrush brush;\r
537         brush.CreateSolidBrush(col);\r
538         HBRUSH oldbrush=(HBRUSH)::SelectObject(hdc,(HBRUSH)brush);\r
539         // center symbol, e.g. rect or ellipse\r
540         switch (type) {\r
541         case Lanes::ACTIVE:\r
542         case Lanes::INITIAL:\r
543         case Lanes::BRANCH:\r
544 \r
545                 //p->setPen(Qt::NoPen);\r
546                 //p->setBrush(col);\r
547                 ::Ellipse(hdc, R_CENTER);\r
548                 //p->drawEllipse(R_CENTER);\r
549                 break;\r
550         case Lanes::MERGE_FORK:\r
551         case Lanes::MERGE_FORK_R:\r
552         case Lanes::MERGE_FORK_L:\r
553                 //p->setPen(Qt::NoPen);\r
554                 //p->setBrush(col);\r
555                 //p->drawRect(R_CENTER);\r
556                 Rectangle(hdc,R_CENTER);\r
557                 break;\r
558         case Lanes::UNAPPLIED:\r
559                 // Red minus sign\r
560                 //p->setPen(Qt::NoPen);\r
561                 //p->setBrush(Qt::red);\r
562                 //p->drawRect(m - r, h - 1, d, 2);\r
563                 ::Rectangle(hdc,m-r,h-1,d,2);\r
564                 break;\r
565         case Lanes::APPLIED:\r
566                 // Green plus sign\r
567                 //p->setPen(Qt::NoPen);\r
568                 //p->setBrush(DARK_GREEN);\r
569                 //p->drawRect(m - r, h - 1, d, 2);\r
570                 //p->drawRect(m - 1, h - r, 2, d);\r
571                 ::Rectangle(hdc,m-r,h-1,d,2);\r
572                 ::Rectangle(hdc,m-1,h-r,2,d);\r
573                 break;\r
574         case Lanes::BOUNDARY:\r
575                 //p->setBrush(back);\r
576                 //p->drawEllipse(R_CENTER);\r
577                 ::Ellipse(hdc, R_CENTER);\r
578                 break;\r
579         case Lanes::BOUNDARY_C:\r
580         case Lanes::BOUNDARY_R:\r
581         case Lanes::BOUNDARY_L:\r
582                 //p->setBrush(back);\r
583                 //p->drawRect(R_CENTER);\r
584                 ::Rectangle(hdc,R_CENTER);\r
585                 break;\r
586         default:\r
587                 break;\r
588         }\r
589 \r
590         ::SelectObject(hdc,oldpen);\r
591         ::SelectObject(hdc,oldbrush);\r
592         #undef P_CENTER\r
593         #undef P_0\r
594         #undef P_90\r
595         #undef P_180\r
596         #undef P_270\r
597         #undef R_CENTER\r
598 }\r
599 \r
600 void CGitLogListBase::DrawGraph(HDC hdc,CRect &rect,INT_PTR index)\r
601 {\r
602         //todo unfinished\r
603 //      return;\r
604         GitRev* data = (GitRev*)m_arShownList.GetAt(index);\r
605         CRect rt=rect;\r
606         LVITEM   rItem;\r
607         SecureZeroMemory(&rItem, sizeof(LVITEM));\r
608         rItem.mask  = LVIF_STATE;\r
609         rItem.iItem = index;\r
610         rItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED;\r
611         GetItem(&rItem);\r
612 \r
613         \r
614 //      p->translate(QPoint(opt.rect.left(), opt.rect.top()));\r
615 \r
616 \r
617 \r
618         if (data->m_Lanes.size() == 0)\r
619                 m_logEntries.setLane(data->m_CommitHash);\r
620 \r
621         std::vector<int>& lanes=data->m_Lanes;\r
622         UINT laneNum = lanes.size();\r
623         UINT mergeLane = 0;\r
624         for (UINT i = 0; i < laneNum; i++)\r
625                 if (Lanes::isMerge(lanes[i])) {\r
626                         mergeLane = i;\r
627                         break;\r
628                 }\r
629 \r
630         int x1 = 0, x2 = 0;\r
631         int maxWidth = rect.Width();\r
632         int lw = 3 * rect.Height() / 4; //laneWidth() \r
633         for (UINT i = 0; i < laneNum && x2 < maxWidth; i++) {\r
634 \r
635                 x1 = x2;\r
636                 x2 += lw;\r
637 \r
638                 int ln = lanes[i];\r
639                 if (ln == Lanes::EMPTY)\r
640                         continue;\r
641 \r
642                 UINT col = (  Lanes:: isHead(ln) ||Lanes:: isTail(ln) || Lanes::isJoin(ln)\r
643                             || ln ==Lanes:: CROSS_EMPTY) ? mergeLane : i;\r
644 \r
645                 if (ln == Lanes::CROSS) {\r
646                         paintGraphLane(hdc, rect.Height(),Lanes::NOT_ACTIVE, x1, x2, m_LineColors[col % Lanes::COLORS_NUM],rect.top);\r
647                         paintGraphLane(hdc, rect.Height(),Lanes::CROSS, x1, x2, m_LineColors[mergeLane % Lanes::COLORS_NUM],rect.top);\r
648                 } else\r
649                         paintGraphLane(hdc, rect.Height(),ln, x1, x2, m_LineColors[col % Lanes::COLORS_NUM],rect.top);\r
650         }\r
651 \r
652         TRACE(_T("index %d %d\r\n"),index,data->m_Lanes.size());\r
653 }\r
654 \r
655 void CGitLogListBase::OnNMCustomdrawLoglist(NMHDR *pNMHDR, LRESULT *pResult)\r
656 {\r
657 \r
658         NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>( pNMHDR );\r
659         // Take the default processing unless we set this to something else below.\r
660         *pResult = CDRF_DODEFAULT;\r
661 \r
662         if (m_bNoDispUpdates)\r
663                 return;\r
664 \r
665 \r
666 \r
667         switch (pLVCD->nmcd.dwDrawStage)\r
668         {\r
669         case CDDS_PREPAINT:\r
670                 {\r
671                         *pResult = CDRF_NOTIFYITEMDRAW;\r
672                         return;\r
673                 }\r
674                 break;\r
675         case CDDS_ITEMPREPAINT:\r
676                 {\r
677                         // This is the prepaint stage for an item. Here's where we set the\r
678                         // item's text color. \r
679                         \r
680                         // Tell Windows to send draw notifications for each subitem.\r
681                         *pResult = CDRF_NOTIFYSUBITEMDRAW;\r
682 \r
683                         COLORREF crText = GetSysColor(COLOR_WINDOWTEXT);\r
684 \r
685                         if (m_arShownList.GetCount() > (INT_PTR)pLVCD->nmcd.dwItemSpec)\r
686                         {\r
687                                 GitRev* data = (GitRev*)m_arShownList.GetAt(pLVCD->nmcd.dwItemSpec);\r
688                                 if (data)\r
689                                 {\r
690 #if 0\r
691                                         if (data->bCopiedSelf)\r
692                                         {\r
693                                                 // only change the background color if the item is not 'hot' (on vista with m_Themes enabled)\r
694                                                 if (!m_Theme.IsAppm_Themed() || !m_bVista || ((pLVCD->nmcd.uItemState & CDIS_HOT)==0))\r
695                                                         pLVCD->clrTextBk = GetSysColor(COLOR_MENU);\r
696                                         }\r
697 \r
698                                         if (data->bCopies)\r
699                                                 crText = m_Colors.GetColor(CColors::Modified);\r
700 #endif\r
701 //                                      if ((data->childStackDepth)||(m_mergedRevs.find(data->Rev) != m_mergedRevs.end()))\r
702 //                                              crText = GetSysColor(COLOR_GRAYTEXT);\r
703 //                                      if (data->Rev == m_wcRev)\r
704 //                                      {\r
705 //                                              SelectObject(pLVCD->nmcd.hdc, m_boldFont);\r
706                                                 // We changed the font, so we're returning CDRF_NEWFONT. This\r
707                                                 // tells the control to recalculate the extent of the text.\r
708 //                                              *pResult = CDRF_NOTIFYSUBITEMDRAW | CDRF_NEWFONT;\r
709 //                                      }\r
710                                 }\r
711                         }\r
712                         if (m_arShownList.GetCount() == (INT_PTR)pLVCD->nmcd.dwItemSpec)\r
713                         {\r
714                                 if (m_bStrictStopped)\r
715                                         crText = GetSysColor(COLOR_GRAYTEXT);\r
716                         }\r
717                         // Store the color back in the NMLVCUSTOMDRAW struct.\r
718                         pLVCD->clrText = crText;\r
719                         return;\r
720                 }\r
721                 break;\r
722         case CDDS_ITEMPREPAINT|CDDS_ITEM|CDDS_SUBITEM:\r
723                 {\r
724                         if ((m_bStrictStopped)&&(m_arShownList.GetCount() == (INT_PTR)pLVCD->nmcd.dwItemSpec))\r
725                         {\r
726                                 pLVCD->nmcd.uItemState &= ~(CDIS_SELECTED|CDIS_FOCUS);\r
727                         }\r
728 \r
729                         if (pLVCD->iSubItem == LOGLIST_GRAPH)\r
730                         {\r
731                                 if (m_arShownList.GetCount() > (INT_PTR)pLVCD->nmcd.dwItemSpec && (!this->m_IsRebaseReplaceGraph) )\r
732                                 {\r
733                                         CRect rect;\r
734                                         GetSubItemRect(pLVCD->nmcd.dwItemSpec, pLVCD->iSubItem, LVIR_BOUNDS, rect);\r
735                                         \r
736                                         FillBackGround(pLVCD->nmcd.hdc, (INT_PTR)pLVCD->nmcd.dwItemSpec,rect);\r
737                                         DrawGraph(pLVCD->nmcd.hdc,rect,pLVCD->nmcd.dwItemSpec);\r
738 \r
739                                         *pResult = CDRF_SKIPDEFAULT;\r
740                                         return;\r
741                                 \r
742                                 }\r
743                         }\r
744 \r
745                         if (pLVCD->iSubItem == LOGLIST_MESSAGE)\r
746                         {\r
747                                 if (m_arShownList.GetCount() > (INT_PTR)pLVCD->nmcd.dwItemSpec)\r
748                                 {\r
749                                         GitRev* data = (GitRev*)m_arShownList.GetAt(pLVCD->nmcd.dwItemSpec);\r
750                                         //if(!data->m_IsFull)\r
751                                         //{\r
752                                                 //if(data->SafeFetchFullInfo(&g_Git))\r
753                                                 //      this->Invalidate();\r
754                                                 //TRACE(_T("Update ... %d\r\n"),pLVCD->nmcd.dwItemSpec);\r
755                                         //}\r
756 \r
757                                         if(m_HashMap[data->m_CommitHash].size()!=0)\r
758                                         {\r
759                                                 CRect rect;\r
760 \r
761                                                 GetSubItemRect(pLVCD->nmcd.dwItemSpec, pLVCD->iSubItem, LVIR_BOUNDS, rect);\r
762                                         \r
763                                                 FillBackGround(pLVCD->nmcd.hdc, (INT_PTR)pLVCD->nmcd.dwItemSpec,rect);\r
764                                                 DrawTagBranch(pLVCD->nmcd.hdc,rect,pLVCD->nmcd.dwItemSpec);\r
765 \r
766                                                 *pResult = CDRF_SKIPDEFAULT;\r
767                                                 return;\r
768 \r
769                                         }\r
770                                 }\r
771                         }\r
772                         \r
773                         if (pLVCD->iSubItem == 1)\r
774                         {\r
775                                 if(this->m_IsIDReplaceAction)\r
776                                 {\r
777                                         *pResult = CDRF_DODEFAULT;\r
778                                         return;\r
779                                 }\r
780                                 *pResult = CDRF_DODEFAULT;\r
781 \r
782                                 if (m_arShownList.GetCount() <= (INT_PTR)pLVCD->nmcd.dwItemSpec)\r
783                                         return;\r
784 \r
785                                 int             nIcons = 0;\r
786                                 int             iconwidth = ::GetSystemMetrics(SM_CXSMICON);\r
787                                 int             iconheight = ::GetSystemMetrics(SM_CYSMICON);\r
788 \r
789                                 GitRev* pLogEntry = reinterpret_cast<GitRev *>(m_arShownList.GetAt(pLVCD->nmcd.dwItemSpec));\r
790                                 CRect rect;\r
791                                 GetSubItemRect(pLVCD->nmcd.dwItemSpec, pLVCD->iSubItem, LVIR_BOUNDS, rect);\r
792                                 // Get the selected state of the\r
793                                 // item being drawn.                                                    \r
794 \r
795                                 // Fill the background\r
796                                 FillBackGround(pLVCD->nmcd.hdc, (INT_PTR)pLVCD->nmcd.dwItemSpec,rect);\r
797                                 \r
798                                 // Draw the icon(s) into the compatible DC\r
799                                 if (pLogEntry->m_Action & CTGitPath::LOGACTIONS_MODIFIED)\r
800                                         ::DrawIconEx(pLVCD->nmcd.hdc, rect.left + ICONITEMBORDER, rect.top, m_hModifiedIcon, iconwidth, iconheight, 0, NULL, DI_NORMAL);\r
801                                 nIcons++;\r
802 \r
803                                 if (pLogEntry->m_Action & (CTGitPath::LOGACTIONS_ADDED|CTGitPath::LOGACTIONS_COPY) )\r
804                                         ::DrawIconEx(pLVCD->nmcd.hdc, rect.left+nIcons*iconwidth + ICONITEMBORDER, rect.top, m_hAddedIcon, iconwidth, iconheight, 0, NULL, DI_NORMAL);\r
805                                 nIcons++;\r
806 \r
807                                 if (pLogEntry->m_Action & CTGitPath::LOGACTIONS_DELETED)\r
808                                         ::DrawIconEx(pLVCD->nmcd.hdc, rect.left+nIcons*iconwidth + ICONITEMBORDER, rect.top, m_hDeletedIcon, iconwidth, iconheight, 0, NULL, DI_NORMAL);\r
809                                 nIcons++;\r
810 \r
811                                 if (pLogEntry->m_Action & CTGitPath::LOGACTIONS_REPLACED)\r
812                                         ::DrawIconEx(pLVCD->nmcd.hdc, rect.left+nIcons*iconwidth + ICONITEMBORDER, rect.top, m_hReplacedIcon, iconwidth, iconheight, 0, NULL, DI_NORMAL);\r
813                                 nIcons++;\r
814                                 *pResult = CDRF_SKIPDEFAULT;\r
815                                 return;\r
816                         }\r
817                 }\r
818                 break;\r
819         }\r
820         *pResult = CDRF_DODEFAULT;\r
821 \r
822 }\r
823 \r
824 // CGitLogListBase message handlers\r
825 \r
826 void CGitLogListBase::OnLvnGetdispinfoLoglist(NMHDR *pNMHDR, LRESULT *pResult)\r
827 {\r
828         NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);\r
829 \r
830         // Create a pointer to the item\r
831         LV_ITEM* pItem = &(pDispInfo)->item;\r
832 \r
833         // Do the list need text information?\r
834         if (!(pItem->mask & LVIF_TEXT))\r
835                 return;\r
836 \r
837         // By default, clear text buffer.\r
838         lstrcpyn(pItem->pszText, _T(""), pItem->cchTextMax);\r
839 \r
840         bool bOutOfRange = pItem->iItem >= ShownCountWithStopped();\r
841         \r
842         *pResult = 0;\r
843         if (m_bNoDispUpdates || bOutOfRange)\r
844                 return;\r
845 \r
846         // Which item number?\r
847         int itemid = pItem->iItem;\r
848         GitRev * pLogEntry = NULL;\r
849         if (itemid < m_arShownList.GetCount())\r
850                 pLogEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(pItem->iItem));\r
851 \r
852         CString temp;\r
853         if(m_IsOldFirst)\r
854         {\r
855                 temp.Format(_T("%d"),pItem->iItem+1);\r
856 \r
857         }else\r
858         {\r
859                 temp.Format(_T("%d"),m_arShownList.GetCount()-pItem->iItem);\r
860         }\r
861             \r
862         // Which column?\r
863         switch (pItem->iSubItem)\r
864         {\r
865         case this->LOGLIST_GRAPH:       //Graphic\r
866                 if (pLogEntry)\r
867                 {\r
868                         if(this->m_IsRebaseReplaceGraph)\r
869                         {\r
870                                 CTGitPath path;\r
871                                 path.m_Action=pLogEntry->m_Action&CTGitPath::LOGACTIONS_REBASE_MODE_MASK;\r
872 \r
873                                 lstrcpyn(pItem->pszText,path.GetActionName(), pItem->cchTextMax);\r
874                         }\r
875                 }\r
876                 break;\r
877         case this->LOGLIST_ACTION: //action -- no text in the column\r
878                 if(this->m_IsIDReplaceAction)\r
879                         lstrcpyn(pItem->pszText, temp, pItem->cchTextMax);\r
880                 break;\r
881         case this->LOGLIST_MESSAGE: //Message\r
882                 if (pLogEntry)\r
883                         lstrcpyn(pItem->pszText, (LPCTSTR)pLogEntry->m_Subject, pItem->cchTextMax);\r
884                 break;\r
885         case this->LOGLIST_AUTHOR: //Author\r
886                 if (pLogEntry)\r
887                         lstrcpyn(pItem->pszText, (LPCTSTR)pLogEntry->m_AuthorName, pItem->cchTextMax);\r
888                 break;\r
889         case this->LOGLIST_DATE: //Date\r
890                 if (pLogEntry)\r
891                         lstrcpyn(pItem->pszText,\r
892                                 CAppUtils::FormatDateAndTime( pLogEntry->m_AuthorDate, m_DateFormat, true, m_bRelativeTimes ), \r
893                                 pItem->cchTextMax);\r
894                 break;\r
895                 \r
896         case 5:\r
897 \r
898                 break;\r
899         default:\r
900                 ASSERT(false);\r
901         }\r
902 }\r
903 \r
904 void CGitLogListBase::OnContextMenu(CWnd* pWnd, CPoint point)\r
905 {\r
906 \r
907         int selIndex = GetSelectionMark();\r
908         if (selIndex < 0)\r
909                 return; // nothing selected, nothing to do with a context menu\r
910 \r
911         // if the user selected the info text telling about not all revisions shown due to\r
912         // the "stop on copy/rename" option, we also don't show the context menu\r
913         if ((m_bStrictStopped)&&(selIndex == m_arShownList.GetCount()))\r
914                 return;\r
915 \r
916         // if the context menu is invoked through the keyboard, we have to use\r
917         // a calculated position on where to anchor the menu on\r
918         if ((point.x == -1) && (point.y == -1))\r
919         {\r
920                 CRect rect;\r
921                 GetItemRect(selIndex, &rect, LVIR_LABEL);\r
922                 ClientToScreen(&rect);\r
923                 point = rect.CenterPoint();\r
924         }\r
925         m_nSearchIndex = selIndex;\r
926         m_bCancelled = FALSE;\r
927 \r
928         // calculate some information the context menu commands can use\r
929 //      CString pathURL = GetURLFromPath(m_path);\r
930 \r
931         POSITION pos = GetFirstSelectedItemPosition();\r
932         int indexNext = GetNextSelectedItem(pos);\r
933         if (indexNext < 0)\r
934                 return;\r
935 \r
936         GitRev* pSelLogEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(indexNext));\r
937 #if 0\r
938         GitRev revSelected = pSelLogEntry->Rev;\r
939         GitRev revPrevious = git_revnum_t(revSelected)-1;\r
940         if ((pSelLogEntry->pArChangedPaths)&&(pSelLogEntry->pArChangedPaths->GetCount() <= 2))\r
941         {\r
942                 for (int i=0; i<pSelLogEntry->pArChangedPaths->GetCount(); ++i)\r
943                 {\r
944                         LogChangedPath * changedpath = (LogChangedPath *)pSelLogEntry->pArChangedPaths->GetAt(i);\r
945                         if (changedpath->lCopyFromRev)\r
946                                 revPrevious = changedpath->lCopyFromRev;\r
947                 }\r
948         }\r
949         GitRev revSelected2;\r
950         if (pos)\r
951         {\r
952                 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(GetNextSelectedItem(pos)));\r
953                 revSelected2 = pLogEntry->Rev;\r
954         }\r
955         bool bAllFromTheSameAuthor = true;\r
956         CString firstAuthor;\r
957         CLogDataVector selEntries;\r
958         GitRev revLowest, revHighest;\r
959         GitRevRangeArray revisionRanges;\r
960         {\r
961                 POSITION pos = GetFirstSelectedItemPosition();\r
962                 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(GetNextSelectedItem(pos)));\r
963                 revisionRanges.AddRevision(pLogEntry->Rev);\r
964                 selEntries.push_back(pLogEntry);\r
965                 firstAuthor = pLogEntry->sAuthor;\r
966                 revLowest = pLogEntry->Rev;\r
967                 revHighest = pLogEntry->Rev;\r
968                 while (pos)\r
969                 {\r
970                         pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(GetNextSelectedItem(pos)));\r
971                         revisionRanges.AddRevision(pLogEntry->Rev);\r
972                         selEntries.push_back(pLogEntry);\r
973                         if (firstAuthor.Compare(pLogEntry->sAuthor))\r
974                                 bAllFromTheSameAuthor = false;\r
975                         revLowest = (git_revnum_t(pLogEntry->Rev) > git_revnum_t(revLowest) ? revLowest : pLogEntry->Rev);\r
976                         revHighest = (git_revnum_t(pLogEntry->Rev) < git_revnum_t(revHighest) ? revHighest : pLogEntry->Rev);\r
977                 }\r
978         }\r
979 \r
980 #endif\r
981 \r
982         int FirstSelect=-1, LastSelect=-1;\r
983         pos = GetFirstSelectedItemPosition();\r
984         FirstSelect = GetNextSelectedItem(pos);\r
985         while(pos)\r
986         {\r
987                 LastSelect = GetNextSelectedItem(pos);\r
988         }\r
989         //entry is selected, now show the popup menu\r
990         CIconMenu popup;\r
991         if (popup.CreatePopupMenu())\r
992         {\r
993 \r
994                 if(this->m_IsRebaseReplaceGraph)\r
995                 {\r
996                         popup.AppendMenuIcon(ID_REBASE_PICK,   _T("Pick"),   IDI_OPEN);\r
997                         popup.AppendMenuIcon(ID_REBASE_SQUASH, _T("Squash"), IDI_OPEN);\r
998                         popup.AppendMenuIcon(ID_REBASE_EDIT,   _T("Edit"),   IDI_OPEN);\r
999                 }\r
1000 \r
1001                 if (GetSelectedCount() == 1)\r
1002                 {\r
1003 #if 0\r
1004                         if (!m_path.IsDirectory())\r
1005                         {\r
1006                                 if (m_hasWC)\r
1007                                 {\r
1008                                         popup.AppendMenuIcon(ID_COMPARE, IDS_LOG_POPUP_COMPARE, IDI_DIFF);\r
1009                                         popup.AppendMenuIcon(ID_BLAMECOMPARE, IDS_LOG_POPUP_BLAMECOMPARE, IDI_BLAME);\r
1010                                 }\r
1011                                 popup.AppendMenuIcon(ID_GNUDIFF1, IDS_LOG_POPUP_GNUDIFF_CH, IDI_DIFF);\r
1012                                 popup.AppendMenuIcon(ID_COMPAREWITHPREVIOUS, IDS_LOG_POPUP_COMPAREWITHPREVIOUS, IDI_DIFF);\r
1013                                 popup.AppendMenu(MF_SEPARATOR, NULL);\r
1014                                 popup.AppendMenuIcon(ID_SAVEAS, IDS_LOG_POPUP_SAVE, IDI_SAVEAS);\r
1015                                 popup.AppendMenuIcon(ID_OPEN, IDS_LOG_POPUP_OPEN, IDI_OPEN);\r
1016                                 popup.AppendMenuIcon(ID_OPENWITH, IDS_LOG_POPUP_OPENWITH, IDI_OPEN);\r
1017                                 popup.AppendMenuIcon(ID_BLAME, IDS_LOG_POPUP_BLAME, IDI_BLAME);\r
1018                                 popup.AppendMenu(MF_SEPARATOR, NULL);\r
1019                         }\r
1020                         else\r
1021 #endif \r
1022                         {\r
1023                                 if (m_hasWC)\r
1024                                 {\r
1025                                         popup.AppendMenuIcon(ID_COMPARE, IDS_LOG_POPUP_COMPARE, IDI_DIFF);\r
1026                                         // TODO:\r
1027                                         // TortoiseMerge could be improved to take a /blame switch\r
1028                                         // and then not 'cat' the files from a unified diff but\r
1029                                         // blame then.\r
1030                                         // But until that's implemented, the context menu entry for\r
1031                                         // this feature is commented out.\r
1032                                         //popup.AppendMenu(ID_BLAMECOMPARE, IDS_LOG_POPUP_BLAMECOMPARE, IDI_BLAME);\r
1033                                 }\r
1034                                 popup.AppendMenuIcon(ID_GNUDIFF1, IDS_LOG_POPUP_GNUDIFF_CH, IDI_DIFF);\r
1035                                 popup.AppendMenuIcon(ID_COMPAREWITHPREVIOUS, IDS_LOG_POPUP_COMPAREWITHPREVIOUS, IDI_DIFF);\r
1036                                 //popup.AppendMenuIcon(ID_BLAMEWITHPREVIOUS, IDS_LOG_POPUP_BLAMEWITHPREVIOUS, IDI_BLAME);\r
1037                                 popup.AppendMenu(MF_SEPARATOR, NULL);\r
1038                         }\r
1039 \r
1040 //                      if (!m_ProjectProperties.sWebViewerRev.IsEmpty())\r
1041 //                      {\r
1042 //                              popup.AppendMenuIcon(ID_VIEWREV, IDS_LOG_POPUP_VIEWREV);\r
1043 //                      }\r
1044 //                      if (!m_ProjectProperties.sWebViewerPathRev.IsEmpty())\r
1045 //                      {\r
1046 //                              popup.AppendMenuIcon(ID_VIEWPATHREV, IDS_LOG_POPUP_VIEWPATHREV);\r
1047 //                      }\r
1048 //                      if ((!m_ProjectProperties.sWebViewerPathRev.IsEmpty())||\r
1049 //                              (!m_ProjectProperties.sWebViewerRev.IsEmpty()))\r
1050 //                      {\r
1051 //                              popup.AppendMenu(MF_SEPARATOR, NULL);\r
1052 //                      }\r
1053 \r
1054                         //if (m_hasWC)\r
1055                         //      popup.AppendMenuIcon(ID_REVERTTOREV, IDS_LOG_POPUP_REVERTTOREV, IDI_REVERT);\r
1056                         //if (m_hasWC)\r
1057                         //      popup.AppendMenuIcon(ID_REVERTREV, IDS_LOG_POPUP_REVERTREV, IDI_REVERT);\r
1058                         //if (m_hasWC)\r
1059                         //      popup.AppendMenuIcon(ID_MERGEREV, IDS_LOG_POPUP_MERGEREV, IDI_MERGE);\r
1060                         \r
1061                         CString str;\r
1062                         str.Format(_T("Reset %s to this"),g_Git.GetCurrentBranch());\r
1063                         popup.AppendMenuIcon(ID_RESET,str,IDI_REVERT);\r
1064                         popup.AppendMenuIcon(ID_SWITCHTOREV, _T("Switch/Checkout to this") , IDI_SWITCH);\r
1065                         popup.AppendMenuIcon(ID_CREATE_BRANCH, _T("Create Branch at this version") , IDI_COPY);\r
1066                         popup.AppendMenuIcon(ID_CREATE_TAG, _T("Create Tag at this version"), IDI_COPY);\r
1067                         popup.AppendMenuIcon(ID_CHERRY_PICK, _T("Cherry Pick this version"), IDI_EXPORT);\r
1068                         popup.AppendMenuIcon(ID_EXPORT, _T("Export this version"), IDI_EXPORT);\r
1069                         \r
1070 \r
1071                         popup.AppendMenu(MF_SEPARATOR, NULL);\r
1072                 }\r
1073                 else if (GetSelectedCount() >= 2)\r
1074                 {\r
1075                         bool bAddSeparator = false;\r
1076                         if (IsSelectionContinuous() || (GetSelectedCount() == 2))\r
1077                         {\r
1078                                 popup.AppendMenuIcon(ID_COMPARETWO, IDS_LOG_POPUP_COMPARETWO, IDI_DIFF);\r
1079                         }\r
1080                         if (GetSelectedCount() == 2)\r
1081                         {\r
1082                                 //popup.AppendMenuIcon(ID_BLAMETWO, IDS_LOG_POPUP_BLAMEREVS, IDI_BLAME);\r
1083                                 popup.AppendMenuIcon(ID_GNUDIFF2, IDS_LOG_POPUP_GNUDIFF, IDI_DIFF);\r
1084                                 bAddSeparator = true;\r
1085                         }\r
1086                         if (m_hasWC)\r
1087                         {\r
1088                                 //popup.AppendMenuIcon(ID_REVERTREV, IDS_LOG_POPUP_REVERTREVS, IDI_REVERT);\r
1089 //                              if (m_hasWC)\r
1090 //                                      popup.AppendMenuIcon(ID_MERGEREV, IDS_LOG_POPUP_MERGEREVS, IDI_MERGE);\r
1091                                 bAddSeparator = true;\r
1092                         }\r
1093                         if (bAddSeparator)\r
1094                                 popup.AppendMenu(MF_SEPARATOR, NULL);\r
1095                 }\r
1096 #if 0\r
1097 //              if ((selEntries.size() > 0)&&(bAllFromTheSameAuthor))\r
1098 //              {\r
1099 //                      popup.AppendMenuIcon(ID_EDITAUTHOR, IDS_LOG_POPUP_EDITAUTHOR);\r
1100 //              }\r
1101 //              if (GetSelectedCount() == 1)\r
1102 //              {\r
1103 //                      popup.AppendMenuIcon(ID_EDITLOG, IDS_LOG_POPUP_EDITLOG);\r
1104 //                      popup.AppendMenuIcon(ID_REVPROPS, IDS_REPOBROWSE_SHOWREVPROP, IDI_PROPERTIES); // "Show Revision Properties"\r
1105 //                      popup.AppendMenu(MF_SEPARATOR, NULL);\r
1106 //              }\r
1107 #endif\r
1108 \r
1109                 \r
1110                 if (GetSelectedCount() == 1)\r
1111                 {\r
1112                         popup.AppendMenuIcon(ID_COPYHASH, _T("Copy Commit Hash"));\r
1113                 }\r
1114                 if (GetSelectedCount() != 0)\r
1115                 {\r
1116                         popup.AppendMenuIcon(ID_COPYCLIPBOARD, IDS_LOG_POPUP_COPYTOCLIPBOARD);\r
1117                 }\r
1118                 popup.AppendMenuIcon(ID_FINDENTRY, IDS_LOG_POPUP_FIND);\r
1119 \r
1120                 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);\r
1121 //              DialogEnableWindow(IDOK, FALSE);\r
1122 //              SetPromptApp(&theApp);\r
1123         \r
1124                 this->ContextMenuAction(cmd, FirstSelect, LastSelect);\r
1125                 \r
1126 //              EnableOKButton();\r
1127         } // if (popup.CreatePopupMenu())\r
1128 \r
1129 }\r
1130 \r
1131 bool CGitLogListBase::IsSelectionContinuous()\r
1132 {\r
1133         if ( GetSelectedCount()==1 )\r
1134         {\r
1135                 // if only one revision is selected, the selection is of course\r
1136                 // continuous\r
1137                 return true;\r
1138         }\r
1139 \r
1140         POSITION pos = GetFirstSelectedItemPosition();\r
1141         bool bContinuous = (m_arShownList.GetCount() == (INT_PTR)m_logEntries.size());\r
1142         if (bContinuous)\r
1143         {\r
1144                 int itemindex = GetNextSelectedItem(pos);\r
1145                 while (pos)\r
1146                 {\r
1147                         int nextindex = GetNextSelectedItem(pos);\r
1148                         if (nextindex - itemindex > 1)\r
1149                         {\r
1150                                 bContinuous = false;\r
1151                                 break;\r
1152                         }\r
1153                         itemindex = nextindex;\r
1154                 }\r
1155         }\r
1156         return bContinuous;\r
1157 }\r
1158 \r
1159 void CGitLogListBase::CopySelectionToClipBoard(bool HashOnly)\r
1160 {\r
1161 \r
1162         CString sClipdata;\r
1163         POSITION pos = GetFirstSelectedItemPosition();\r
1164         if (pos != NULL)\r
1165         {\r
1166                 CString sRev;\r
1167                 sRev.LoadString(IDS_LOG_REVISION);\r
1168                 CString sAuthor;\r
1169                 sAuthor.LoadString(IDS_LOG_AUTHOR);\r
1170                 CString sDate;\r
1171                 sDate.LoadString(IDS_LOG_DATE);\r
1172                 CString sMessage;\r
1173                 sMessage.LoadString(IDS_LOG_MESSAGE);\r
1174                 while (pos)\r
1175                 {\r
1176                         CString sLogCopyText;\r
1177                         CString sPaths;\r
1178                         GitRev * pLogEntry = reinterpret_cast<GitRev *>(m_arShownList.GetAt(GetNextSelectedItem(pos)));\r
1179 \r
1180                         if(!HashOnly)\r
1181                         {\r
1182                                 //pLogEntry->m_Files\r
1183                                 //LogChangedPathArray * cpatharray = pLogEntry->pArChangedPaths;\r
1184                         \r
1185                                 for (int cpPathIndex = 0; cpPathIndex<pLogEntry->m_Files.GetCount(); ++cpPathIndex)\r
1186                                 {\r
1187                                         sPaths += ((CTGitPath&)pLogEntry->m_Files[cpPathIndex]).GetActionName() + _T(" : ") + pLogEntry->m_Files[cpPathIndex].GetGitPathString();\r
1188                                         sPaths += _T("\r\n");\r
1189                                 }\r
1190                                 sPaths.Trim();\r
1191                                 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"),\r
1192                                         (LPCTSTR)sRev, pLogEntry->m_CommitHash,\r
1193                                         (LPCTSTR)sAuthor, (LPCTSTR)pLogEntry->m_AuthorName,\r
1194                                         (LPCTSTR)sDate, \r
1195                                         (LPCTSTR)CAppUtils::FormatDateAndTime( pLogEntry->m_AuthorDate, m_DateFormat, true, m_bRelativeTimes ),\r
1196                                         (LPCTSTR)sMessage, pLogEntry->m_Subject+_T("\r\n")+pLogEntry->m_Body,\r
1197                                         (LPCTSTR)sPaths);\r
1198                                 sClipdata +=  sLogCopyText;\r
1199                         }else\r
1200                         {\r
1201                                 sClipdata += pLogEntry->m_CommitHash;\r
1202                                 break;\r
1203                         }\r
1204 \r
1205                 }\r
1206                 CStringUtils::WriteAsciiStringToClipboard(sClipdata, GetSafeHwnd());\r
1207         }\r
1208 \r
1209 }\r
1210 \r
1211 void CGitLogListBase::DiffSelectedRevWithPrevious()\r
1212 {\r
1213 #if 0\r
1214         if (m_bThreadRunning)\r
1215                 return;\r
1216         UpdateLogInfoLabel();\r
1217         int selIndex = m_LogList.GetSelectionMark();\r
1218         if (selIndex < 0)\r
1219                 return;\r
1220         int selCount = m_LogList.GetSelectedCount();\r
1221         if (selCount != 1)\r
1222                 return;\r
1223 \r
1224         // Find selected entry in the log list\r
1225         POSITION pos = m_LogList.GetFirstSelectedItemPosition();\r
1226         PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(m_LogList.GetNextSelectedItem(pos)));\r
1227         long rev1 = pLogEntry->Rev;\r
1228         long rev2 = rev1-1;\r
1229         CTGitPath path = m_path;\r
1230 \r
1231         // See how many files under the relative root were changed in selected revision\r
1232         int nChanged = 0;\r
1233         LogChangedPath * changed = NULL;\r
1234         for (INT_PTR c = 0; c < pLogEntry->pArChangedPaths->GetCount(); ++c)\r
1235         {\r
1236                 LogChangedPath * cpath = pLogEntry->pArChangedPaths->GetAt(c);\r
1237                 if (cpath  &&  cpath -> sPath.Left(m_sRelativeRoot.GetLength()).Compare(m_sRelativeRoot)==0)\r
1238                 {\r
1239                         ++nChanged;\r
1240                         changed = cpath;\r
1241                 }\r
1242         }\r
1243 \r
1244         if (m_path.IsDirectory() && nChanged == 1) \r
1245         {\r
1246                 // We're looking at the log for a directory and only one file under dir was changed in the revision\r
1247                 // Do diff on that file instead of whole directory\r
1248                 path.AppendPathString(changed->sPath.Mid(m_sRelativeRoot.GetLength()));\r
1249         } \r
1250 \r
1251         m_bCancelled = FALSE;\r
1252         DialogEnableWindow(IDOK, FALSE);\r
1253         SetPromptApp(&theApp);\r
1254         theApp.DoWaitCursor(1);\r
1255 \r
1256         if (PromptShown())\r
1257         {\r
1258                 GitDiff diff(this, m_hWnd, true);\r
1259                 diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));\r
1260                 diff.SetHEADPeg(m_LogRevision);\r
1261                 diff.ShowCompare(path, rev2, path, rev1);\r
1262         }\r
1263         else\r
1264         {\r
1265                 CAppUtils::StartShowCompare(m_hWnd, path, rev2, path, rev1, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));\r
1266         }\r
1267 \r
1268         theApp.DoWaitCursor(-1);\r
1269         EnableOKButton();\r
1270 #endif\r
1271 }\r
1272 \r
1273 void CGitLogListBase::OnLvnOdfinditemLoglist(NMHDR *pNMHDR, LRESULT *pResult)\r
1274 {\r
1275         LPNMLVFINDITEM pFindInfo = reinterpret_cast<LPNMLVFINDITEM>(pNMHDR);\r
1276         *pResult = -1;\r
1277         \r
1278         if (pFindInfo->lvfi.flags & LVFI_PARAM)\r
1279                 return; \r
1280         if ((pFindInfo->iStart < 0)||(pFindInfo->iStart >= m_arShownList.GetCount()))\r
1281                 return;\r
1282         if (pFindInfo->lvfi.psz == 0)\r
1283                 return;\r
1284 #if 0\r
1285         CString sCmp = pFindInfo->lvfi.psz;\r
1286         CString sRev;   \r
1287         for (int i=pFindInfo->iStart; i<m_arShownList.GetCount(); ++i)\r
1288         {\r
1289                 GitRev * pLogEntry = reinterpret_cast<GitRev*>(m_arShownList.GetAt(i));\r
1290                 sRev.Format(_T("%ld"), pLogEntry->Rev);\r
1291                 if (pFindInfo->lvfi.flags & LVFI_PARTIAL)\r
1292                 {\r
1293                         if (sCmp.Compare(sRev.Left(sCmp.GetLength()))==0)\r
1294                         {\r
1295                                 *pResult = i;\r
1296                                 return;\r
1297                         }\r
1298                 }\r
1299                 else\r
1300                 {\r
1301                         if (sCmp.Compare(sRev)==0)\r
1302                         {\r
1303                                 *pResult = i;\r
1304                                 return;\r
1305                         }\r
1306                 }\r
1307         }\r
1308         if (pFindInfo->lvfi.flags & LVFI_WRAP)\r
1309         {\r
1310                 for (int i=0; i<pFindInfo->iStart; ++i)\r
1311                 {\r
1312                         PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.GetAt(i));\r
1313                         sRev.Format(_T("%ld"), pLogEntry->Rev);\r
1314                         if (pFindInfo->lvfi.flags & LVFI_PARTIAL)\r
1315                         {\r
1316                                 if (sCmp.Compare(sRev.Left(sCmp.GetLength()))==0)\r
1317                                 {\r
1318                                         *pResult = i;\r
1319                                         return;\r
1320                                 }\r
1321                         }\r
1322                         else\r
1323                         {\r
1324                                 if (sCmp.Compare(sRev)==0)\r
1325                                 {\r
1326                                         *pResult = i;\r
1327                                         return;\r
1328                                 }\r
1329                         }\r
1330                 }\r
1331         }\r
1332 #endif\r
1333         *pResult = -1;\r
1334 }\r
1335 \r
1336 int CGitLogListBase::FillGitLog(CTGitPath *path,int info,CString *from,CString *to)\r
1337 {\r
1338         ClearText();\r
1339 \r
1340         this->m_logEntries.ClearAll();\r
1341         this->m_logEntries.ParserFromLog(path,-1,info,from,to);\r
1342 \r
1343         //this->m_logEntries.ParserFromLog();\r
1344         SetItemCountEx(this->m_logEntries.size());\r
1345 \r
1346         this->m_arShownList.RemoveAll();\r
1347 \r
1348         for(unsigned int i=0;i<m_logEntries.size();i++)\r
1349         {\r
1350                 if(m_IsOldFirst)\r
1351                 {\r
1352                         m_logEntries[m_logEntries.size()-i-1].m_IsFull=TRUE;\r
1353                         this->m_arShownList.Add(&m_logEntries[m_logEntries.size()-i-1]);\r
1354                 \r
1355                 }else\r
1356                 {\r
1357                         m_logEntries[i].m_IsFull=TRUE;\r
1358                         this->m_arShownList.Add(&m_logEntries[i]);\r
1359                 }\r
1360         }\r
1361 \r
1362     if(path)\r
1363         m_Path=*path;\r
1364         return 0;\r
1365 \r
1366 }\r
1367 \r
1368 int CGitLogListBase::FillGitShortLog()\r
1369 {\r
1370         ClearText();\r
1371 \r
1372         this->m_logEntries.ClearAll();\r
1373 \r
1374         m_LogCache.FetchCacheIndex(g_Git.m_CurrentDir);\r
1375 \r
1376     CTGitPath *path;\r
1377     if(this->m_Path.IsEmpty())\r
1378         path=NULL;\r
1379     else\r
1380         path=&this->m_Path;\r
1381 \r
1382         CString hash;\r
1383         int mask;\r
1384         mask = CGit::LOG_INFO_ONLY_HASH | CGit::LOG_INFO_BOUNDARY;\r
1385 //      if(this->m_bAllBranch)\r
1386         mask |= m_ShowMask;\r
1387 \r
1388         this->m_logEntries.ParserShortLog(path,hash,-1,mask);\r
1389         \r
1390 \r
1391         //this->m_logEntries.ParserFromLog();\r
1392         if(IsInWorkingThread())\r
1393                 PostMessage(LVM_SETITEMCOUNT, (WPARAM) this->m_logEntries.size(),(LPARAM) LVSICF_NOINVALIDATEALL);\r
1394         else\r
1395                 SetItemCountEx(this->m_logEntries.size());\r
1396 \r
1397         this->m_arShownList.RemoveAll();\r
1398 \r
1399         for(unsigned int i=0;i<m_logEntries.size();i++)\r
1400         {\r
1401                 if(this->m_IsOldFirst)\r
1402                 {\r
1403                         this->m_arShownList.Add(&m_logEntries[m_logEntries.size()-1-i]);\r
1404 \r
1405                 }else\r
1406                 {\r
1407                         this->m_arShownList.Add(&m_logEntries[i]);\r
1408                 }\r
1409         }\r
1410         return 0;\r
1411 }\r
1412 \r
1413 BOOL CGitLogListBase::PreTranslateMessage(MSG* pMsg)\r
1414 {\r
1415         // Skip Ctrl-C when copying text out of the log message or search filter\r
1416         BOOL bSkipAccelerator = ( pMsg->message == WM_KEYDOWN && pMsg->wParam=='C' && (GetFocus()==GetDlgItem(IDC_MSGVIEW) || GetFocus()==GetDlgItem(IDC_SEARCHEDIT) ) && GetKeyState(VK_CONTROL)&0x8000 );\r
1417         if (pMsg->message == WM_KEYDOWN && pMsg->wParam=='\r')\r
1418         {\r
1419                 //if (GetFocus()==GetDlgItem(IDC_LOGLIST))\r
1420                 {\r
1421                         if (CRegDWORD(_T("Software\\TortoiseGit\\DiffByDoubleClickInLog"), FALSE))\r
1422                         {\r
1423                                 DiffSelectedRevWithPrevious();\r
1424                                 return TRUE;\r
1425                         }\r
1426                 }\r
1427 #if 0\r
1428                 if (GetFocus()==GetDlgItem(IDC_LOGMSG))\r
1429                 {\r
1430                         DiffSelectedFile();\r
1431                         return TRUE;\r
1432                 }\r
1433 #endif\r
1434         }\r
1435 \r
1436 #if 0\r
1437         if (m_hAccel && !bSkipAccelerator)\r
1438         {\r
1439                 int ret = TranslateAccelerator(m_hWnd, m_hAccel, pMsg);\r
1440                 if (ret)\r
1441                         return TRUE;\r
1442         }\r
1443         \r
1444 #endif\r
1445         //m_tooltips.RelayEvent(pMsg);\r
1446         return __super::PreTranslateMessage(pMsg);\r
1447 }\r
1448 \r
1449 void CGitLogListBase::OnNMDblclkLoglist(NMHDR * /*pNMHDR*/, LRESULT *pResult)\r
1450 {\r
1451         // a double click on an entry in the revision list has happened\r
1452         *pResult = 0;\r
1453 \r
1454   if (CRegDWORD(_T("Software\\TortoiseGit\\DiffByDoubleClickInLog"), FALSE))\r
1455           DiffSelectedRevWithPrevious();\r
1456 }\r
1457 \r
1458 int CGitLogListBase::FetchLogAsync(void * data)\r
1459 {\r
1460         m_ProcData=data;\r
1461         m_bExitThread=FALSE;\r
1462         InterlockedExchange(&m_bThreadRunning, TRUE);\r
1463         InterlockedExchange(&m_bNoDispUpdates, TRUE);\r
1464         m_LoadingThread = AfxBeginThread(LogThreadEntry, this);\r
1465         if (m_LoadingThread ==NULL)\r
1466         {\r
1467                 InterlockedExchange(&m_bThreadRunning, FALSE);\r
1468                 InterlockedExchange(&m_bNoDispUpdates, FALSE);\r
1469                 CMessageBox::Show(NULL, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);\r
1470                 return -1;\r
1471         }\r
1472         return 0;\r
1473 }\r
1474 \r
1475 //this is the thread function which calls the subversion function\r
1476 UINT CGitLogListBase::LogThreadEntry(LPVOID pVoid)\r
1477 {\r
1478         return ((CGitLogListBase*)pVoid)->LogThread();\r
1479 }\r
1480 \r
1481 void CGitLogListBase::GetTimeRange(CTime &oldest, CTime &latest)\r
1482 {\r
1483         //CTime time;\r
1484         oldest=CTime::GetCurrentTime();\r
1485         latest=CTime(1971,1,2,0,0,0);\r
1486         for(unsigned int i=0;i<m_logEntries.size();i++)\r
1487         {\r
1488                 if(m_logEntries[i].m_AuthorDate.GetTime() < oldest.GetTime())\r
1489                         oldest = m_logEntries[i].m_AuthorDate.GetTime();\r
1490 \r
1491                 if(m_logEntries[i].m_AuthorDate.GetTime() > latest.GetTime())\r
1492                         latest = m_logEntries[i].m_AuthorDate.GetTime();\r
1493 \r
1494         }\r
1495 }\r
1496 \r
1497 //Helper class for FetchFullLogInfo()\r
1498 class CGitCall_FetchFullLogInfo : public CGitCall\r
1499 {\r
1500 public:\r
1501         CGitCall_FetchFullLogInfo(CGitLogListBase* ploglist):m_ploglist(ploglist),m_CollectedCount(0){}\r
1502         virtual bool OnOutputData(const BYTE* data, size_t size)\r
1503         {\r
1504                 if(size==0)\r
1505                         return m_ploglist->m_bExitThread;\r
1506                 //Add received data to byte collector\r
1507                 m_ByteCollector.append(data,size);\r
1508 \r
1509                 //Find loginfo endmarker\r
1510                 static const BYTE dataToFind[]={0,0};\r
1511                 int found=m_ByteCollector.findData(dataToFind,2);\r
1512                 if(found<0)\r
1513                         return m_ploglist->m_bExitThread;//Not found\r
1514                 found+=2;//Position after loginfo end-marker\r
1515 \r
1516                 //Prepare data for OnLogInfo and call it\r
1517                 BYTE_VECTOR logInfo;\r
1518                 logInfo.append(&*m_ByteCollector.begin(),found);\r
1519                 OnLogInfo(logInfo);\r
1520 \r
1521                 //Remove loginfo from bytecollector\r
1522                 m_ByteCollector.erase(m_ByteCollector.begin(),m_ByteCollector.begin()+found);\r
1523 \r
1524                 return m_ploglist->m_bExitThread;\r
1525         }\r
1526         virtual void OnEnd()\r
1527         {\r
1528                 //Rest should be a complete log line.\r
1529                 if(!m_ByteCollector.empty())\r
1530                         OnLogInfo(m_ByteCollector);\r
1531         }\r
1532 \r
1533 \r
1534         void OnLogInfo(BYTE_VECTOR& logInfo)\r
1535         {\r
1536                 GitRev fullRev;\r
1537                 fullRev.ParserFromLog(logInfo);\r
1538                 MAP_HASH_REV::iterator itRev=m_ploglist->m_logEntries.m_HashMap.find(fullRev.m_CommitHash);\r
1539                 if(itRev==m_ploglist->m_logEntries.m_HashMap.end())\r
1540                 {\r
1541                         //Should not occur, only when Git-tree updated in the mean time. (Race condition)\r
1542                         return;//Ignore\r
1543                 }\r
1544                 //Set updating\r
1545                 int rev=itRev->second;\r
1546                 GitRev* revInVector=&m_ploglist->m_logEntries[rev];\r
1547 \r
1548 \r
1549 //              fullRev.m_IsUpdateing=TRUE;\r
1550 //              fullRev.m_IsFull=TRUE;\r
1551 \r
1552 \r
1553                 if(InterlockedExchange(&revInVector->m_IsUpdateing,TRUE))\r
1554                         return;//Cannot update this row now. Ignore.\r
1555                 TCHAR oldmark=revInVector->m_Mark;\r
1556                 GIT_REV_LIST oldlist=revInVector->m_ParentHash;\r
1557 //              CString oldhash=m_CommitHash;\r
1558 \r
1559                 //Parse new rev info\r
1560                 revInVector->ParserFromLog(logInfo);\r
1561 \r
1562                 if(oldmark!=0)\r
1563                         revInVector->m_Mark=oldmark;  //parser full log will cause old mark overwrited. \r
1564                                                                //So we need keep old bound mark.\r
1565                 revInVector->m_ParentHash=oldlist;\r
1566 \r
1567                 //Reset updating\r
1568                 InterlockedExchange(&revInVector->m_IsFull,TRUE);\r
1569                 InterlockedExchange(&revInVector->m_IsUpdateing,FALSE);\r
1570 \r
1571                 //Notify listcontrol and update progress bar\r
1572                 ++m_CollectedCount;\r
1573 \r
1574                 ::PostMessage(m_ploglist->m_hWnd,MSG_LOADED,(WPARAM)rev,0);\r
1575 \r
1576                 DWORD percent=m_CollectedCount*98/m_ploglist->m_logEntries.size() + GITLOG_START+1;\r
1577                 if(percent == GITLOG_END)\r
1578                         percent = GITLOG_END -1;\r
1579                 \r
1580                 ::PostMessage(m_ploglist->GetParent()->m_hWnd,MSG_LOAD_PERCENTAGE,(WPARAM) percent,0);\r
1581         }\r
1582 \r
1583         CGitLogListBase*        m_ploglist;\r
1584         BYTE_VECTOR                     m_ByteCollector;\r
1585         int                                     m_CollectedCount;\r
1586 \r
1587 };\r
1588 \r
1589 void CGitLogListBase::FetchFullLogInfo()\r
1590 {\r
1591         CGitCall_FetchFullLogInfo fetcher(this);\r
1592         int mask=\r
1593                 CGit::LOG_INFO_STAT|\r
1594                 CGit::LOG_INFO_FILESTATE|\r
1595                 CGit::LOG_INFO_DETECT_COPYRENAME|\r
1596                 m_ShowMask;\r
1597         g_Git.GetLog(&fetcher,CString(),NULL,-1,mask);\r
1598 }\r
1599 \r
1600 void CGitLogListBase::FetchFullLogInfoOrig()\r
1601 {\r
1602         unsigned int updated=0;\r
1603         int percent=0;\r
1604         CRect rect;\r
1605         while(1)\r
1606         {\r
1607                 for(unsigned int i=0;i<m_logEntries.size();i++)\r
1608                 {\r
1609                         if(m_LogCache.GetCacheData(m_logEntries[i]))\r
1610                         {\r
1611                                 if(!m_logEntries.FetchFullInfo(i))\r
1612                                 {\r
1613                                         updated++;\r
1614                                 }\r
1615                                 m_LogCache.AddCacheEntry(m_logEntries[i]);\r
1616 \r
1617                         }else\r
1618                         {\r
1619                                 updated++;\r
1620                                 InterlockedExchange(&m_logEntries[i].m_IsUpdateing,FALSE);\r
1621                                 InterlockedExchange(&m_logEntries[i].m_IsFull,TRUE);\r
1622                         }\r
1623                         \r
1624                         ::PostMessage(m_hWnd,MSG_LOADED,(WPARAM)i,0);\r
1625 \r
1626                         if(m_bExitThread)\r
1627                         {\r
1628                                 InterlockedExchange(&m_bThreadRunning, FALSE);\r
1629                                 InterlockedExchange(&m_bNoDispUpdates, FALSE);\r
1630                                 return;\r
1631                         }\r
1632 \r
1633                         percent=updated*98/m_logEntries.size() + GITLOG_START+1;\r
1634                         if(percent == GITLOG_END)\r
1635                                 percent = GITLOG_END -1;\r
1636                         \r
1637                         ::PostMessage(this->GetParent()->m_hWnd,MSG_LOAD_PERCENTAGE,(WPARAM) percent,0);\r
1638 \r
1639                         \r
1640                 }\r
1641                 if(updated==m_logEntries.size())\r
1642                         break;\r
1643         }\r
1644 }\r
1645 \r
1646 UINT CGitLogListBase::LogThread()\r
1647 {\r
1648 \r
1649 //      if(m_ProcCallBack)\r
1650 //              m_ProcCallBack(m_ProcData,GITLOG_START);\r
1651         ::PostMessage(this->GetParent()->m_hWnd,MSG_LOAD_PERCENTAGE,(WPARAM) GITLOG_START,0);\r
1652 \r
1653         InterlockedExchange(&m_bThreadRunning, TRUE);\r
1654         InterlockedExchange(&m_bNoDispUpdates, TRUE);\r
1655 \r
1656     //does the user force the cache to refresh (shift or control key down)?\r
1657     bool refresh =    (GetKeyState (VK_CONTROL) < 0) \r
1658                    || (GetKeyState (VK_SHIFT) < 0);\r
1659 \r
1660         //disable the "Get All" button while we're receiving\r
1661         //log messages.\r
1662 \r
1663         FillGitShortLog();\r
1664         \r
1665         if(this->m_bExitThread)\r
1666         {\r
1667                 InterlockedExchange(&m_bThreadRunning, FALSE);\r
1668                 InterlockedExchange(&m_bNoDispUpdates, FALSE);\r
1669                 return 0;\r
1670         }\r
1671 #if 0\r
1672         RedrawItems(0, m_arShownList.GetCount());\r
1673 //      SetRedraw(false);\r
1674 //      ResizeAllListCtrlCols();\r
1675 //      SetRedraw(true);\r
1676 \r
1677         if ( m_pStoreSelection )\r
1678         {\r
1679                 // Deleting the instance will restore the\r
1680                 // selection of the CLogDlg.\r
1681                 delete m_pStoreSelection;\r
1682                 m_pStoreSelection = NULL;\r
1683         }\r
1684         else\r
1685         {\r
1686                 // If no selection has been set then this must be the first time\r
1687                 // the revisions are shown. Let's preselect the topmost revision.\r
1688                 if ( GetItemCount()>0 )\r
1689                 {\r
1690                         SetSelectionMark(0);\r
1691                         SetItemState(0, LVIS_SELECTED, LVIS_SELECTED);\r
1692                 }\r
1693         }\r
1694 #endif\r
1695         InterlockedExchange(&m_bNoDispUpdates, FALSE);\r
1696 \r
1697 \r
1698         //FetchFullLogInfo();\r
1699         FetchFullLogInfoOrig();\r
1700         //RefreshCursor();\r
1701         // make sure the filter is applied (if any) now, after we refreshed/fetched\r
1702         // the log messages\r
1703 \r
1704         ::PostMessage(this->GetParent()->m_hWnd,MSG_LOAD_PERCENTAGE,(WPARAM) GITLOG_END,0);\r
1705 \r
1706         InterlockedExchange(&m_bThreadRunning, FALSE);\r
1707 \r
1708         return 0;\r
1709 }\r
1710 \r
1711 void CGitLogListBase::Refresh()\r
1712 {       \r
1713         m_bExitThread=TRUE;\r
1714         DWORD ret =::WaitForSingleObject(m_LoadingThread->m_hThread,20000);\r
1715         if(ret == WAIT_TIMEOUT)\r
1716                 TerminateThread();\r
1717 \r
1718         this->Clear();\r
1719 \r
1720         //Update branch and Tag info\r
1721         ReloadHashMap();\r
1722         //Assume Thread have exited\r
1723         //if(!m_bThreadRunning)\r
1724         {\r
1725                 this->SetItemCountEx(0);\r
1726                 m_logEntries.clear();\r
1727                 m_bExitThread=FALSE;\r
1728                 InterlockedExchange(&m_bThreadRunning, TRUE);\r
1729                 InterlockedExchange(&m_bNoDispUpdates, TRUE);\r
1730                 if (AfxBeginThread(LogThreadEntry, this)==NULL)\r
1731                 {\r
1732                         InterlockedExchange(&m_bThreadRunning, FALSE);\r
1733                         InterlockedExchange(&m_bNoDispUpdates, FALSE);\r
1734                         CMessageBox::Show(NULL, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);\r
1735                 }\r
1736                 m_sFilterText.Empty();\r
1737                 m_From=CTime(1970,1,2,0,0,0);\r
1738                 m_To=CTime::GetCurrentTime();\r
1739         }\r
1740 }\r
1741 bool CGitLogListBase::ValidateRegexp(LPCTSTR regexp_str, tr1::wregex& pat, bool bMatchCase /* = false */)\r
1742 {\r
1743         try\r
1744         {\r
1745                 tr1::regex_constants::syntax_option_type type = tr1::regex_constants::ECMAScript;\r
1746                 if (!bMatchCase)\r
1747                         type |= tr1::regex_constants::icase;\r
1748                 pat = tr1::wregex(regexp_str, type);\r
1749                 return true;\r
1750         }\r
1751         catch (exception) {}\r
1752         return false;\r
1753 }\r
1754 \r
1755 void CGitLogListBase::RecalculateShownList(CPtrArray * pShownlist)\r
1756 {\r
1757 \r
1758         pShownlist->RemoveAll();\r
1759         tr1::wregex pat;//(_T("Remove"), tr1::regex_constants::icase);\r
1760         bool bRegex = false;\r
1761         if (m_bFilterWithRegex)\r
1762                 bRegex = ValidateRegexp(m_sFilterText, pat, false);\r
1763 \r
1764         tr1::regex_constants::match_flag_type flags = tr1::regex_constants::match_any;\r
1765         CString sRev;\r
1766         for (DWORD i=0; i<m_logEntries.size(); ++i)\r
1767         {\r
1768                 if ((bRegex)&&(m_bFilterWithRegex))\r
1769                 {\r
1770 #if 0\r
1771                         if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_BUGID))\r
1772                         {\r
1773                                 ATLTRACE(_T("bugID = \"%s\"\n"), (LPCTSTR)m_logEntries[i]->sBugIDs);\r
1774                                 if (regex_search(wstring((LPCTSTR)m_logEntries[i]->sBugIDs), pat, flags)&&IsEntryInDateRange(i))\r
1775                                 {\r
1776                                         pShownlist->Add(m_logEntries[i]);\r
1777                                         continue;\r
1778                                 }\r
1779                         }\r
1780 #endif\r
1781                         if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_MESSAGES))\r
1782                         {\r
1783                                 ATLTRACE(_T("messge = \"%s\"\n"),m_logEntries[i].m_Subject);\r
1784                                 if (regex_search(wstring((LPCTSTR)m_logEntries[i].m_Subject), pat, flags)&&IsEntryInDateRange(i))\r
1785                                 {\r
1786                                         pShownlist->Add(&m_logEntries[i]);\r
1787                                         continue;\r
1788                                 }\r
1789 \r
1790                                 ATLTRACE(_T("messge = \"%s\"\n"),m_logEntries[i].m_Body);\r
1791                                 if (regex_search(wstring((LPCTSTR)m_logEntries[i].m_Body), pat, flags)&&IsEntryInDateRange(i))\r
1792                                 {\r
1793                                         pShownlist->Add(&m_logEntries[i]);\r
1794                                         continue;\r
1795                                 }\r
1796                         }\r
1797 #if 0\r
1798                         if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_PATHS))\r
1799                         {\r
1800                                 LogChangedPathArray * cpatharray = m_logEntries[i]->pArChangedPaths;\r
1801 \r
1802                                 bool bGoing = true;\r
1803                                 for (INT_PTR cpPathIndex = 0; cpPathIndex<cpatharray->GetCount() && bGoing; ++cpPathIndex)\r
1804                                 {\r
1805                                         LogChangedPath * cpath = cpatharray->GetAt(cpPathIndex);\r
1806                                         if (regex_search(wstring((LPCTSTR)cpath->sCopyFromPath), pat, flags)&&IsEntryInDateRange(i))\r
1807                                         {\r
1808                                                 pShownlist->Add(m_logEntries[i]);\r
1809                                                 bGoing = false;\r
1810                                                 continue;\r
1811                                         }\r
1812                                         if (regex_search(wstring((LPCTSTR)cpath->sPath), pat, flags)&&IsEntryInDateRange(i))\r
1813                                         {\r
1814                                                 pShownlist->Add(m_logEntries[i]);\r
1815                                                 bGoing = false;\r
1816                                                 continue;\r
1817                                         }\r
1818                                         if (regex_search(wstring((LPCTSTR)cpath->GetAction()), pat, flags)&&IsEntryInDateRange(i))\r
1819                                         {\r
1820                                                 pShownlist->Add(m_logEntries[i]);\r
1821                                                 bGoing = false;\r
1822                                                 continue;\r
1823                                         }\r
1824                                 }\r
1825                                 if (!bGoing)\r
1826                                         continue;\r
1827                         }\r
1828 #endif\r
1829                         if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_AUTHORS))\r
1830                         {\r
1831                                 if (regex_search(wstring((LPCTSTR)m_logEntries[i].m_AuthorName), pat, flags)&&IsEntryInDateRange(i))\r
1832                                 {\r
1833                                         pShownlist->Add(&m_logEntries[i]);\r
1834                                         continue;\r
1835                                 }\r
1836                         }\r
1837                         if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_REVS))\r
1838                         {\r
1839                                 sRev.Format(_T("%s"), m_logEntries[i].m_CommitHash);\r
1840                                 if (regex_search(wstring((LPCTSTR)sRev), pat, flags)&&IsEntryInDateRange(i))\r
1841                                 {\r
1842                                         pShownlist->Add(&m_logEntries[i]);\r
1843                                         continue;\r
1844                                 }\r
1845                         }\r
1846                 } // if (bRegex)\r
1847                 else\r
1848                 {\r
1849                         CString find = m_sFilterText;\r
1850                         find.MakeLower();\r
1851 #if 0\r
1852                         if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_BUGID))\r
1853                         {\r
1854                                 CString sBugIDs = m_logEntries[i]->sBugIDs;\r
1855 \r
1856                                 sBugIDs = sBugIDs.MakeLower();\r
1857                                 if ((sBugIDs.Find(find) >= 0)&&(IsEntryInDateRange(i)))\r
1858                                 {\r
1859                                         pShownlist->Add(m_logEntries[i]);\r
1860                                         continue;\r
1861                                 }\r
1862                         }\r
1863 #endif\r
1864                         if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_MESSAGES))\r
1865                         {\r
1866                                 CString msg = m_logEntries[i].m_Subject;\r
1867 \r
1868                                 msg = msg.MakeLower();\r
1869                                 if ((msg.Find(find) >= 0)&&(IsEntryInDateRange(i)))\r
1870                                 {\r
1871                                         pShownlist->Add(&m_logEntries[i]);\r
1872                                         continue;\r
1873                                 }\r
1874                                 msg = m_logEntries[i].m_Body;\r
1875 \r
1876                                 msg = msg.MakeLower();\r
1877                                 if ((msg.Find(find) >= 0)&&(IsEntryInDateRange(i)))\r
1878                                 {\r
1879                                         pShownlist->Add(&m_logEntries[i]);\r
1880                                         continue;\r
1881                                 }\r
1882                         }\r
1883 #if 0\r
1884                         if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_PATHS))\r
1885                         {\r
1886                                 LogChangedPathArray * cpatharray = m_logEntries[i]->pArChangedPaths;\r
1887 \r
1888                                 bool bGoing = true;\r
1889                                 for (INT_PTR cpPathIndex = 0; cpPathIndex<cpatharray->GetCount() && bGoing; ++cpPathIndex)\r
1890                                 {\r
1891                                         LogChangedPath * cpath = cpatharray->GetAt(cpPathIndex);\r
1892                                         CString path = cpath->sCopyFromPath;\r
1893                                         path.MakeLower();\r
1894                                         if ((path.Find(find)>=0)&&(IsEntryInDateRange(i)))\r
1895                                         {\r
1896                                                 pShownlist->Add(m_logEntries[i]);\r
1897                                                 bGoing = false;\r
1898                                                 continue;\r
1899                                         }\r
1900                                         path = cpath->sPath;\r
1901                                         path.MakeLower();\r
1902                                         if ((path.Find(find)>=0)&&(IsEntryInDateRange(i)))\r
1903                                         {\r
1904                                                 pShownlist->Add(m_logEntries[i]);\r
1905                                                 bGoing = false;\r
1906                                                 continue;\r
1907                                         }\r
1908                                         path = cpath->GetAction();\r
1909                                         path.MakeLower();\r
1910                                         if ((path.Find(find)>=0)&&(IsEntryInDateRange(i)))\r
1911                                         {\r
1912                                                 pShownlist->Add(m_logEntries[i]);\r
1913                                                 bGoing = false;\r
1914                                                 continue;\r
1915                                         }\r
1916                                 }\r
1917                         }\r
1918 #endif\r
1919                         if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_AUTHORS))\r
1920                         {\r
1921                                 CString msg = m_logEntries[i].m_AuthorName;\r
1922                                 msg = msg.MakeLower();\r
1923                                 if ((msg.Find(find) >= 0)&&(IsEntryInDateRange(i)))\r
1924                                 {\r
1925                                         pShownlist->Add(&m_logEntries[i]);\r
1926                                         continue;\r
1927                                 }\r
1928                         }\r
1929                         if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_REVS))\r
1930                         {\r
1931                                 sRev.Format(_T("%s"), m_logEntries[i].m_CommitHash);\r
1932                                 if ((sRev.Find(find) >= 0)&&(IsEntryInDateRange(i)))\r
1933                                 {\r
1934                                         pShownlist->Add(&m_logEntries[i]);\r
1935                                         continue;\r
1936                                 }\r
1937                         }\r
1938                 } // else (from if (bRegex))    \r
1939         } // for (DWORD i=0; i<m_logEntries.size(); ++i) \r
1940 \r
1941 }\r
1942 \r
1943 BOOL CGitLogListBase::IsEntryInDateRange(int i)\r
1944 {\r
1945         __time64_t time = m_logEntries[i].m_AuthorDate.GetTime();\r
1946         if ((time >= m_From.GetTime())&&(time <= m_To.GetTime()))\r
1947                 return TRUE;\r
1948 \r
1949         return FALSE;\r
1950 \r
1951 //      return TRUE;\r
1952 }\r
1953 void CGitLogListBase::StartFilter()\r
1954 {\r
1955         InterlockedExchange(&m_bNoDispUpdates, TRUE);\r
1956         RecalculateShownList(&m_arShownList);\r
1957         InterlockedExchange(&m_bNoDispUpdates, FALSE);\r
1958 \r
1959 \r
1960         DeleteAllItems();\r
1961         SetItemCountEx(ShownCountWithStopped());\r
1962         RedrawItems(0, ShownCountWithStopped());\r
1963         SetRedraw(false);\r
1964         //ResizeAllListCtrlCols();\r
1965         SetRedraw(true);\r
1966         Invalidate();\r
1967 \r
1968 }\r
1969 void CGitLogListBase::RemoveFilter()\r
1970 {\r
1971 \r
1972         InterlockedExchange(&m_bNoDispUpdates, TRUE);\r
1973 \r
1974         m_arShownList.RemoveAll();\r
1975 \r
1976         // reset the time filter too\r
1977 #if 0\r
1978         m_timFrom = (__time64_t(m_tFrom));\r
1979         m_timTo = (__time64_t(m_tTo));\r
1980         m_DateFrom.SetTime(&m_timFrom);\r
1981         m_DateTo.SetTime(&m_timTo);\r
1982         m_DateFrom.SetRange(&m_timFrom, &m_timTo);\r
1983         m_DateTo.SetRange(&m_timFrom, &m_timTo);\r
1984 #endif\r
1985 \r
1986         for (DWORD i=0; i<m_logEntries.size(); ++i)\r
1987         {\r
1988                 if(this->m_IsOldFirst)\r
1989                 {\r
1990                         m_arShownList.Add(&m_logEntries[m_logEntries.size()-i-1]);\r
1991                 }else\r
1992                 {\r
1993                         m_arShownList.Add(&m_logEntries[i]);\r
1994                 }\r
1995         }\r
1996 //      InterlockedExchange(&m_bNoDispUpdates, FALSE);\r
1997         DeleteAllItems();\r
1998         SetItemCountEx(ShownCountWithStopped());\r
1999         RedrawItems(0, ShownCountWithStopped());\r
2000 //      SetRedraw(false);\r
2001 //      ResizeAllListCtrlCols();\r
2002 //      SetRedraw(true);\r
2003 \r
2004         InterlockedExchange(&m_bNoDispUpdates, FALSE);\r
2005 }\r
2006 \r
2007 void CGitLogListBase::Clear()\r
2008 {\r
2009         m_arShownList.RemoveAll();\r
2010         m_logEntries.clear();\r
2011         m_logEntries.m_HashMap.clear();\r
2012         DeleteAllItems();\r
2013         m_logEntries.m_Lns.clear();\r
2014 \r
2015         m_logEntries.m_FirstFreeLane=0;\r
2016         m_logEntries.m_Lns.clear();\r
2017 \r
2018 }\r
2019 \r
2020 void CGitLogListBase::OnDestroy()\r
2021 {\r
2022         // save the column widths to the registry\r
2023         SaveColumnWidths();\r
2024 \r
2025         if(this->m_bThreadRunning)\r
2026         {\r
2027                 this->m_bExitThread=true;\r
2028                 DWORD ret =::WaitForSingleObject(m_LoadingThread->m_hThread,20000);\r
2029                 if(ret == WAIT_TIMEOUT)\r
2030                         TerminateThread();\r
2031         }\r
2032         while(m_LogCache.SaveCache())\r
2033         {\r
2034                 if(CMessageBox::Show(NULL,_T("Cannot Save Log Cache to Disk. To retry click yes. To give up click no."),_T("TortoiseGit"),\r
2035                                                         MB_YESNO) == IDNO)\r
2036                                                         break;\r
2037         }\r
2038         CHintListCtrl::OnDestroy();\r
2039 }\r
2040 \r
2041 LRESULT CGitLogListBase::OnLoad(WPARAM wParam,LPARAM lParam)\r
2042 {\r
2043         CRect rect;\r
2044         int i=(int)wParam;\r
2045         this->GetItemRect(i,&rect,LVIR_BOUNDS);\r
2046         this->InvalidateRect(rect);\r
2047         return 0;\r
2048 }\r
2049 \r
2050 /**\r
2051  * Save column widths to the registry\r
2052  */\r
2053 void CGitLogListBase::SaveColumnWidths()\r
2054 {\r
2055         CHeaderCtrl* pHdrCtrl = (CHeaderCtrl*)(GetDlgItem(0));\r
2056         if (pHdrCtrl)\r
2057         {\r
2058                 int numcols = pHdrCtrl->GetItemCount();\r
2059                 for (int col = 0; col < numcols; col++)\r
2060                 {\r
2061                         int width = GetColumnWidth( col );\r
2062                         CString regentry;\r
2063                         regentry.Format( _T("Software\\TortoiseGit\\log\\ColWidth%d"), col);\r
2064                         CRegDWORD regwidth(regentry, 0);\r
2065                         regwidth = width;       // this writes it to reg\r
2066                 }\r
2067         }\r
2068 }\r
2069 \r