BrowseRefs: Context menu enhancements
[TortoiseGit.git] / src / TortoiseProc / FileDiffDlg.cpp
blob0bbf7fdadecc56a933d74300194a805acfd01e99
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2008 - TortoiseGit
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 #include "stdafx.h"
20 #include "TortoiseProc.h"
21 #include "UnicodeUtils.h"
22 #include "MessageBox.h"
23 #include "AppUtils.h"
24 #include "TempFile.h"
25 #include "ProgressDlg.h"
26 #include "SysImageList.h"
27 //#include "GitProperties.h"
28 #include "StringUtils.h"
29 #include "PathUtils.h"
30 #include "BrowseFolder.h"
31 #include "RevisionDlg.h"
32 #include ".\filediffdlg.h"
33 #include "gitdiff.h"
34 #include "CommonResource.h"
36 #define ID_COMPARE 1
37 #define ID_BLAME 2
38 #define ID_SAVEAS 3
39 #define ID_EXPORT 4
40 #define ID_CLIPBOARD 5
42 BOOL CFileDiffDlg::m_bAscending = FALSE;
43 int CFileDiffDlg::m_nSortedColumn = -1;
46 IMPLEMENT_DYNAMIC(CFileDiffDlg, CResizableStandAloneDialog)
47 CFileDiffDlg::CFileDiffDlg(CWnd* pParent /*=NULL*/)
48 : CResizableStandAloneDialog(CFileDiffDlg::IDD, pParent),
49 m_bBlame(false),
50 m_pProgDlg(NULL),
51 m_bCancelled(false)
55 CFileDiffDlg::~CFileDiffDlg()
57 DestroyIcon(m_hSwitchIcon);
60 void CFileDiffDlg::DoDataExchange(CDataExchange* pDX)
62 CResizableStandAloneDialog::DoDataExchange(pDX);
63 DDX_Control(pDX, IDC_FILELIST, m_cFileList);
64 DDX_Control(pDX, IDC_SWITCHLEFTRIGHT, m_SwitchButton);
65 DDX_Control(pDX, IDC_REV1BTN, m_cRev1Btn);
66 DDX_Control(pDX, IDC_REV2BTN, m_cRev2Btn);
67 DDX_Control(pDX, IDC_FILTER, m_cFilter);
71 BEGIN_MESSAGE_MAP(CFileDiffDlg, CResizableStandAloneDialog)
72 ON_NOTIFY(NM_DBLCLK, IDC_FILELIST, OnNMDblclkFilelist)
73 ON_NOTIFY(LVN_GETINFOTIP, IDC_FILELIST, OnLvnGetInfoTipFilelist)
74 ON_NOTIFY(NM_CUSTOMDRAW, IDC_FILELIST, OnNMCustomdrawFilelist)
75 ON_WM_CONTEXTMENU()
76 ON_WM_SETCURSOR()
77 ON_EN_SETFOCUS(IDC_SECONDURL, &CFileDiffDlg::OnEnSetfocusSecondurl)
78 ON_EN_SETFOCUS(IDC_FIRSTURL, &CFileDiffDlg::OnEnSetfocusFirsturl)
79 ON_BN_CLICKED(IDC_SWITCHLEFTRIGHT, &CFileDiffDlg::OnBnClickedSwitchleftright)
80 ON_NOTIFY(HDN_ITEMCLICK, 0, &CFileDiffDlg::OnHdnItemclickFilelist)
81 ON_BN_CLICKED(IDC_REV1BTN, &CFileDiffDlg::OnBnClickedRev1btn)
82 ON_BN_CLICKED(IDC_REV2BTN, &CFileDiffDlg::OnBnClickedRev2btn)
83 ON_MESSAGE(WM_FILTEREDIT_CANCELCLICKED, OnClickedCancelFilter)
84 ON_EN_CHANGE(IDC_FILTER, &CFileDiffDlg::OnEnChangeFilter)
85 ON_WM_TIMER()
86 END_MESSAGE_MAP()
89 void CFileDiffDlg::SetDiff(CTGitPath * path, GitRev rev1, GitRev rev2)
91 if(path!=NULL)
93 m_path1 = *path;
94 m_path2 = *path;
96 m_rev1 = rev1;
97 m_rev2 = rev2;
100 void CFileDiffDlg::SetDiff(CTGitPath * path, CString &hash1, CString &hash2)
102 if(path!=NULL)
104 m_path1 = *path;
105 m_path2 = *path;
108 BYTE_VECTOR logout;
110 g_Git.GetLog(logout,hash1,path,1,0);
111 m_rev1.ParserFromLog(logout);
113 logout.clear();
115 g_Git.GetLog(logout,hash2,path,1,0);
116 m_rev2.ParserFromLog(logout);
118 void CFileDiffDlg::SetDiff(CTGitPath * path, GitRev rev1)
120 if(path!=NULL)
122 m_path1 = *path;
123 m_path2 = *path;
125 m_rev1 = rev1;
126 m_rev2.m_CommitHash = _T("");
127 m_rev2.m_Subject = _T("Previou Version");
129 //this->GetDlgItem()->EnableWindow(FALSE);
134 BOOL CFileDiffDlg::OnInitDialog()
136 CResizableStandAloneDialog::OnInitDialog();
137 CString temp;
139 m_tooltips.Create(this);
140 m_tooltips.AddTool(IDC_SWITCHLEFTRIGHT, IDS_FILEDIFF_SWITCHLEFTRIGHT_TT);
142 m_cFileList.SetRedraw(false);
143 m_cFileList.DeleteAllItems();
144 DWORD exStyle = LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER | LVS_EX_INFOTIP;
145 m_cFileList.SetExtendedStyle(exStyle);
147 m_nIconFolder = SYS_IMAGE_LIST().GetDirIconIndex();
148 m_cFileList.SetImageList(&SYS_IMAGE_LIST(), LVSIL_SMALL);
150 m_hSwitchIcon = (HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_SWITCHLEFTRIGHT), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR);
151 m_SwitchButton.SetIcon(m_hSwitchIcon);
153 m_cFilter.SetCancelBitmaps(IDI_CANCELNORMAL, IDI_CANCELPRESSED);
154 m_cFilter.SetInfoIcon(IDI_FILTEREDIT);
155 temp.LoadString(IDS_FILEDIFF_FILTERCUE);
156 temp = _T(" ")+temp;
157 m_cFilter.SetCueBanner(temp);
159 int c = ((CHeaderCtrl*)(m_cFileList.GetDlgItem(0)))->GetItemCount()-1;
160 while (c>=0)
161 m_cFileList.DeleteColumn(c--);
164 temp.LoadString(IDS_FILEDIFF_FILE);
165 m_cFileList.InsertColumn(0, temp);
166 temp.LoadString(IDS_FILEDIFF_ACTION);
167 m_cFileList.InsertColumn(1, temp);
169 temp.LoadString(IDS_FILEDIFF_STATADD);
170 m_cFileList.InsertColumn(2, temp);
171 temp.LoadString(IDS_FILEDIFF_STATDEL);
172 m_cFileList.InsertColumn(3, temp);
174 int mincol = 0;
175 int maxcol = ((CHeaderCtrl*)(m_cFileList.GetDlgItem(0)))->GetItemCount()-1;
176 int col;
177 for (col = mincol; col <= maxcol; col++)
179 m_cFileList.SetColumnWidth(col,LVSCW_AUTOSIZE_USEHEADER);
182 m_cFileList.SetRedraw(true);
184 AddAnchor(IDC_DIFFSTATIC1, TOP_LEFT, TOP_RIGHT);
185 AddAnchor(IDC_SWITCHLEFTRIGHT, TOP_RIGHT);
186 AddAnchor(IDC_FIRSTURL, TOP_LEFT, TOP_RIGHT);
187 AddAnchor(IDC_REV1BTN, TOP_RIGHT);
188 AddAnchor(IDC_DIFFSTATIC2, TOP_LEFT, TOP_RIGHT);
189 AddAnchor(IDC_SECONDURL, TOP_LEFT, TOP_RIGHT);
190 AddAnchor(IDC_REV2BTN, TOP_RIGHT);
191 AddAnchor(IDC_FILTER, TOP_LEFT, TOP_RIGHT);
192 AddAnchor(IDC_FILELIST, TOP_LEFT, BOTTOM_RIGHT);
194 SetURLLabels();
196 EnableSaveRestore(_T("FileDiffDlg"));
198 InterlockedExchange(&m_bThreadRunning, TRUE);
199 if (AfxBeginThread(DiffThreadEntry, this)==NULL)
201 InterlockedExchange(&m_bThreadRunning, FALSE);
202 CMessageBox::Show(NULL, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
205 // Start with focus on file list
206 GetDlgItem(IDC_FILELIST)->SetFocus();
208 if(m_rev2.m_CommitHash.IsEmpty())
209 m_SwitchButton.EnableWindow(FALSE);
210 return FALSE;
213 #if 0
214 svn_error_t* CFileDiffDlg::DiffSummarizeCallback(const CTGitPath& path,
215 svn_client_diff_summarize_kind_t kind,
216 bool propchanged, svn_node_kind_t node)
218 CTGitPath* fd;
219 fd.path = path;
220 fd.kind = kind;
221 fd.node = node;
222 fd.propchanged = propchanged;
223 m_arFileList.push_back(fd);
224 return Git_NO_ERROR;
226 #endif
228 UINT CFileDiffDlg::DiffThreadEntry(LPVOID pVoid)
230 return ((CFileDiffDlg*)pVoid)->DiffThread();
233 UINT CFileDiffDlg::DiffThread()
235 bool bSuccess = true;
236 RefreshCursor();
237 m_cFileList.ShowText(CString(MAKEINTRESOURCE(IDS_FILEDIFF_WAIT)));
238 m_arFileList.Clear();
239 #if 0
240 if (m_bDoPegDiff)
242 // bSuccess = DiffSummarizePeg(m_path1, m_peg, m_rev1, m_rev2, m_depth, m_bIgnoreancestry);
244 else
246 // bSuccess = DiffSummarize(m_path1, m_rev1, m_path2, m_rev2, m_depth, m_bIgnoreancestry);
248 // if (!bSuccess)
249 // {
250 // m_cFileList.ShowText(GetLastErrorMessage());
251 // InterlockedExchange(&m_bThreadRunning, FALSE);
252 // return 0;
253 // }
254 #endif
255 CString cmd;
256 CString rev1=m_rev1.m_CommitHash;
257 if(this->m_rev1.m_CommitHash == GIT_REV_ZERO || this->m_rev2.m_CommitHash == GIT_REV_ZERO)
259 rev1=+_T("");
260 if(this->m_rev1.m_CommitHash == GIT_REV_ZERO)
261 cmd.Format(_T("git.exe diff -r --raw -C -M --numstat -z %s"),m_rev2.m_CommitHash);
262 else
263 cmd.Format(_T("git.exe diff -r -R --raw -C -M --numstat -z %s"),m_rev1.m_CommitHash);
264 }else
266 cmd.Format(_T("git.exe diff-tree -r --raw -C -M --numstat -z %s %s"),m_rev2.m_CommitHash,rev1);
269 BYTE_VECTOR out;
270 g_Git.Run(cmd,&out);
271 this->m_arFileList.ParserFromLog(out);
273 CString sFilterText;
274 m_cFilter.GetWindowText(sFilterText);
275 m_cFileList.SetRedraw(false);
276 Filter(sFilterText);
277 if (m_arFileList.GetCount()>0)
279 // Highlight first entry in file list
280 m_cFileList.SetSelectionMark(0);
281 m_cFileList.SetItemState(0, LVIS_SELECTED, LVIS_SELECTED);
284 int mincol = 0;
285 int maxcol = ((CHeaderCtrl*)(m_cFileList.GetDlgItem(0)))->GetItemCount()-1;
286 int col;
287 for (col = mincol; col <= maxcol; col++)
289 m_cFileList.SetColumnWidth(col,LVSCW_AUTOSIZE_USEHEADER);
292 m_cFileList.ClearText();
293 m_cFileList.SetRedraw(true);
295 InterlockedExchange(&m_bThreadRunning, FALSE);
296 InvalidateRect(NULL);
297 RefreshCursor();
298 return 0;
301 int CFileDiffDlg::AddEntry(const CTGitPath * fd)
303 int ret = -1;
304 if (fd)
306 int index = m_cFileList.GetItemCount();
308 int icon_idx = 0;
309 // if (fd->node == svn_node_dir)
310 // icon_idx = m_nIconFolder;
311 // else
313 icon_idx = SYS_IMAGE_LIST().GetPathIconIndex(fd->GetGitPathString());
316 ret = m_cFileList.InsertItem(index, fd->GetGitPathString(), icon_idx);
317 m_cFileList.SetItemText(index, 1, ((CTGitPath*)fd)->GetActionName());
318 m_cFileList.SetItemText(index, 2, ((CTGitPath*)fd)->m_StatAdd);
319 m_cFileList.SetItemText(index, 3, ((CTGitPath*)fd)->m_StatDel);
321 return ret;
324 void CFileDiffDlg::DoDiff(int selIndex, bool blame)
327 CGitDiff diff;
328 CTGitPath* fd = m_arFilteredList[selIndex];
329 diff.Diff(fd, fd,this->m_rev1.m_CommitHash, this->m_rev2.m_CommitHash, blame, FALSE);
331 #if 0
332 CFileDiffDlg::CTGitPath* fd = m_arFilteredList[selIndex];
334 CTGitPath url1 = CTGitPath(m_path1.GetGitPathString() + _T("/") + fd.path.GetGitPathString());
335 CTGitPath url2 = m_bDoPegDiff ? url1 : CTGitPath(m_path2.GetGitPathString() + _T("/") + fd.path.GetGitPathString());
337 if (fd.kind == svn_client_diff_summarize_kind_deleted)
339 if (!PathIsURL(url1))
340 url1 = CTGitPath(GetURLFromPath(m_path1) + _T("/") + fd.path.GetGitPathString());
341 if (!PathIsURL(url2))
342 url2 = m_bDoPegDiff ? url1 : CTGitPath(GetURLFromPath(m_path2) + _T("/") + fd.path.GetGitPathString());
345 if (fd.propchanged)
347 DiffProps(selIndex);
349 if (fd.node == svn_node_dir)
350 return;
352 CTGitPath tempfile = CTempFiles::Instance().GetTempFilePath(false, m_path1, m_rev1);
353 CString sTemp;
354 CProgressDlg progDlg;
355 progDlg.SetTitle(IDS_PROGRESSWAIT);
356 progDlg.SetAnimation(IDR_DOWNLOAD);
357 progDlg.ShowModeless(this);
358 progDlg.FormatPathLine(1, IDS_PROGRESSGETFILE, (LPCTSTR)m_path1.GetUIPathString());
359 progDlg.FormatNonPathLine(2, IDS_PROGRESSREVISIONTEXT, (LPCTSTR)m_rev1.ToString());
361 if ((fd.kind != svn_client_diff_summarize_kind_added)&&(!blame)&&(!Cat(url1, m_bDoPegDiff ? m_peg : m_rev1, m_rev1, tempfile)))
363 if ((!m_bDoPegDiff)||(!Cat(url1, m_rev1, m_rev1, tempfile)))
365 CMessageBox::Show(NULL, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
366 return;
369 else if ((fd.kind != svn_client_diff_summarize_kind_added)&&(blame)&&(!m_blamer.BlameToFile(url1, 1, m_rev1, m_bDoPegDiff ? m_peg : m_rev1, tempfile, _T(""), TRUE, TRUE)))
371 if ((!m_bDoPegDiff)||(!m_blamer.BlameToFile(url1, 1, m_rev1, m_rev1, tempfile, _T(""), TRUE, TRUE)))
373 CMessageBox::Show(NULL, m_blamer.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
374 return;
377 SetFileAttributes(tempfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
378 progDlg.SetProgress(1, 2);
379 progDlg.FormatPathLine(1, IDS_PROGRESSGETFILE, (LPCTSTR)url2.GetUIPathString());
380 progDlg.FormatNonPathLine(2, IDS_PROGRESSREVISIONTEXT, (LPCTSTR)m_rev2.ToString());
381 CTGitPath tempfile2 = CTempFiles::Instance().GetTempFilePath(false, url2, m_rev2);
382 if ((fd.kind != svn_client_diff_summarize_kind_deleted)&&(!blame)&&(!Cat(url2, m_bDoPegDiff ? m_peg : m_rev2, m_rev2, tempfile2)))
384 if ((!m_bDoPegDiff)||(!Cat(url2, m_rev2, m_rev2, tempfile2)))
386 CMessageBox::Show(NULL, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
387 return;
390 else if ((fd.kind != svn_client_diff_summarize_kind_deleted)&&(blame)&&(!m_blamer.BlameToFile(url2, 1, m_bDoPegDiff ? m_peg : m_rev2, m_rev2, tempfile2, _T(""), TRUE, TRUE)))
392 if ((!m_bDoPegDiff)||(!m_blamer.BlameToFile(url2, 1, m_rev2, m_rev2, tempfile2, _T(""), TRUE, TRUE)))
394 CMessageBox::Show(NULL, m_blamer.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
395 return;
398 SetFileAttributes(tempfile2.GetWinPath(), FILE_ATTRIBUTE_READONLY);
399 progDlg.SetProgress(2,2);
400 progDlg.Stop();
402 CString rev1name, rev2name;
403 if (m_bDoPegDiff)
405 rev1name.Format(_T("%s Revision %ld"), (LPCTSTR)fd.path.GetGitPathString(), (LONG)m_rev1);
406 rev2name.Format(_T("%s Revision %ld"), (LPCTSTR)fd.path.GetGitPathString(), (LONG)m_rev2);
408 else
410 rev1name = m_path1.GetGitPathString() + _T("/") + fd.path.GetGitPathString();
411 rev2name = m_path2.GetGitPathString() + _T("/") + fd.path.GetGitPathString();
413 CAppUtils::DiffFlags flags;
414 flags.AlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
415 flags.Blame(blame);
416 CAppUtils::StartExtDiff(
417 tempfile, tempfile2, rev1name, rev2name, flags);
418 #endif
421 #if 0
422 void CFileDiffDlg::DiffProps(int selIndex)
424 CFileDiffDlg::CTGitPath* fd = m_arFilteredList[selIndex];
426 CTGitPath url1 = CTGitPath(m_path1.GetGitPathString() + _T("/") + fd.path.GetGitPathString());
427 CTGitPath url2 = m_bDoPegDiff ? url1 : CTGitPath(m_path2.GetGitPathString() + _T("/") + fd.path.GetGitPathString());
429 GitProperties propsurl1(url1, m_rev1, false);
430 GitProperties propsurl2(url2, m_rev2, false);
432 // collect the properties of both revisions in a set
433 std::set<stdstring> properties;
434 for (int wcindex = 0; wcindex < propsurl1.GetCount(); ++wcindex)
436 stdstring urlname = propsurl1.GetItemName(wcindex);
437 if ( properties.find(urlname) == properties.end() )
439 properties.insert(urlname);
442 for (int wcindex = 0; wcindex < propsurl2.GetCount(); ++wcindex)
444 stdstring urlname = propsurl2.GetItemName(wcindex);
445 if ( properties.find(urlname) == properties.end() )
447 properties.insert(urlname);
451 // iterate over all properties and diff the properties
452 for (std::set<stdstring>::iterator iter = properties.begin(), end = properties.end(); iter != end; ++iter)
454 stdstring url1name = *iter;
456 stdstring url1value = _T(""); // CUnicodeUtils::StdGetUnicode((char *)propsurl1.GetItemValue(wcindex).c_str());
457 for (int url1index = 0; url1index < propsurl1.GetCount(); ++url1index)
459 if (propsurl1.GetItemName(url1index).compare(url1name)==0)
461 url1value = CString((char *)propsurl1.GetItemValue(url1index).c_str());
465 stdstring url2value = _T("");
466 for (int url2index = 0; url2index < propsurl2.GetCount(); ++url2index)
468 if (propsurl2.GetItemName(url2index).compare(url1name)==0)
470 url2value = CString((char *)propsurl2.GetItemValue(url2index).c_str());
474 if (url2value.compare(url1value)!=0)
476 // write both property values to temporary files
477 CTGitPath url1propfile = CTempFiles::Instance().GetTempFilePath(false);
478 CTGitPath url2propfile = CTempFiles::Instance().GetTempFilePath(false);
479 FILE * pFile;
480 _tfopen_s(&pFile, url1propfile.GetWinPath(), _T("wb"));
481 if (pFile)
483 fputs(CUnicodeUtils::StdGetUTF8(url1value).c_str(), pFile);
484 fclose(pFile);
485 FILE * pFile;
486 _tfopen_s(&pFile, url2propfile.GetWinPath(), _T("wb"));
487 if (pFile)
489 fputs(CUnicodeUtils::StdGetUTF8(url2value).c_str(), pFile);
490 fclose(pFile);
492 else
493 return;
495 else
496 return;
497 SetFileAttributes(url1propfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
498 SetFileAttributes(url2propfile.GetWinPath(), FILE_ATTRIBUTE_READONLY);
499 CString n1, n2;
500 if (m_rev1.IsWorking())
501 n1.Format(IDS_DIFF_WCNAME, url1name.c_str());
502 if (m_rev1.IsBase())
503 n1.Format(IDS_DIFF_BASENAME, url1name.c_str());
504 if (m_rev1.IsHead() || m_rev1.IsNumber())
506 if (m_bDoPegDiff)
508 n1.Format(_T("%s : %s Revision %ld"), url1name.c_str(), (LPCTSTR)fd.path.GetGitPathString(), (LONG)m_rev1);
510 else
512 CString sTemp = url1name.c_str();
513 sTemp += _T(" : ");
514 n1 = sTemp + m_path1.GetGitPathString() + _T("/") + fd.path.GetGitPathString();
517 if (m_rev2.IsWorking())
518 n2.Format(IDS_DIFF_WCNAME, url1name.c_str());
519 if (m_rev2.IsBase())
520 n2.Format(IDS_DIFF_BASENAME, url1name.c_str());
521 if (m_rev2.IsHead() || m_rev2.IsNumber())
523 if (m_bDoPegDiff)
525 n2.Format(_T("%s : %s Revision %ld"), url1name.c_str(), (LPCTSTR)fd.path.GetGitPathString(), (LONG)m_rev2);
527 else
529 CString sTemp = url1name.c_str();
530 sTemp += _T(" : ");
531 n2 = sTemp + m_path2.GetGitPathString() + _T("/") + fd.path.GetGitPathString();
534 CAppUtils::StartExtDiffProps(url1propfile, url2propfile, n1, n2, TRUE);
538 #endif
539 void CFileDiffDlg::OnNMDblclkFilelist(NMHDR *pNMHDR, LRESULT *pResult)
541 *pResult = 0;
542 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
543 int selIndex = pNMLV->iItem;
544 if (selIndex < 0)
545 return;
546 if (selIndex >= (int)m_arFilteredList.size())
547 return;
549 DoDiff(selIndex, m_bBlame);
552 void CFileDiffDlg::OnLvnGetInfoTipFilelist(NMHDR *pNMHDR, LRESULT *pResult)
555 LPNMLVGETINFOTIP pGetInfoTip = reinterpret_cast<LPNMLVGETINFOTIP>(pNMHDR);
556 if (pGetInfoTip->iItem >= (int)m_arFilteredList.size())
557 return;
559 CString path = m_path1.GetGitPathString() + _T("/") + m_arFilteredList[pGetInfoTip->iItem]->GetGitPathString();
560 if (pGetInfoTip->cchTextMax > path.GetLength())
561 _tcsncpy_s(pGetInfoTip->pszText, pGetInfoTip->cchTextMax, path, pGetInfoTip->cchTextMax);
563 *pResult = 0;
566 void CFileDiffDlg::OnNMCustomdrawFilelist(NMHDR *pNMHDR, LRESULT *pResult)
568 NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>( pNMHDR );
569 // Take the default processing unless we set this to something else below.
570 *pResult = CDRF_DODEFAULT;
572 // First thing - check the draw stage. If it's the control's prepaint
573 // stage, then tell Windows we want messages for every item.
575 if ( CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage )
577 *pResult = CDRF_NOTIFYITEMDRAW;
579 else if ( CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage )
581 // This is the prepaint stage for an item. Here's where we set the
582 // item's text color. Our return value will tell Windows to draw the
583 // item itself, but it will use the new color we set here.
585 // Tell Windows to paint the control itself.
586 *pResult = CDRF_DODEFAULT;
588 COLORREF crText = GetSysColor(COLOR_WINDOWTEXT);
590 if (m_arFilteredList.size() > pLVCD->nmcd.dwItemSpec)
592 CTGitPath * fd = m_arFilteredList[pLVCD->nmcd.dwItemSpec];
593 switch (fd->m_Action)
595 case CTGitPath::LOGACTIONS_ADDED:
596 crText = m_colors.GetColor(CColors::Added);
597 break;
598 case CTGitPath::LOGACTIONS_DELETED:
599 crText = m_colors.GetColor(CColors::Deleted);
600 break;
601 case CTGitPath::LOGACTIONS_MODIFIED:
602 crText = m_colors.GetColor(CColors::Modified);
603 break;
604 //case svn_client_diff_summarize_kind_normal:
605 default:
606 //if (fd.propchanged)
607 crText = m_colors.GetColor(CColors::PropertyChanged);
608 break;
611 // Store the color back in the NMLVCUSTOMDRAW struct.
612 pLVCD->clrText = crText;
616 void CFileDiffDlg::OnContextMenu(CWnd* pWnd, CPoint point)
618 if ((pWnd==0)||(pWnd != &m_cFileList))
619 return;
620 if (m_cFileList.GetSelectedCount() == 0)
621 return;
622 // if the context menu is invoked through the keyboard, we have to use
623 // a calculated position on where to anchor the menu on
624 if ((point.x == -1) && (point.y == -1))
626 CRect rect;
627 m_cFileList.GetItemRect(m_cFileList.GetSelectionMark(), &rect, LVIR_LABEL);
628 m_cFileList.ClientToScreen(&rect);
629 point = rect.CenterPoint();
631 CMenu popup;
632 if (popup.CreatePopupMenu())
634 CString temp;
635 temp.LoadString(IDS_LOG_POPUP_COMPARETWO);
636 popup.AppendMenu(MF_STRING | MF_ENABLED, ID_COMPARE, temp);
637 temp.LoadString(IDS_FILEDIFF_POPBLAME);
638 //popup.AppendMenu(MF_STRING | MF_ENABLED, ID_BLAME, temp);
639 popup.AppendMenu(MF_SEPARATOR, NULL);
640 temp.LoadString(IDS_FILEDIFF_POPSAVELIST);
641 popup.AppendMenu(MF_STRING | MF_ENABLED, ID_SAVEAS, temp);
642 temp.LoadString(IDS_FILEDIFF_POPCLIPBOARD);
643 popup.AppendMenu(MF_STRING | MF_ENABLED, ID_CLIPBOARD, temp);
644 temp.LoadString(IDS_FILEDIFF_POPEXPORT);
645 //popup.AppendMenu(MF_STRING | MF_ENABLED, ID_EXPORT, temp);
646 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
647 m_bCancelled = false;
648 switch (cmd)
650 case ID_COMPARE:
652 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
653 while (pos)
655 int index = m_cFileList.GetNextSelectedItem(pos);
656 DoDiff(index, false);
659 break;
660 case ID_BLAME:
662 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
663 while (pos)
665 int index = m_cFileList.GetNextSelectedItem(pos);
666 DoDiff(index, true);
669 break;
670 case ID_SAVEAS:
672 if (m_cFileList.GetSelectedCount() > 0)
674 CString temp;
675 CTGitPath savePath;
676 CString pathSave;
677 if (!CAppUtils::FileOpenSave(pathSave, NULL, IDS_REPOBROWSE_SAVEAS, IDS_COMMONFILEFILTER, false, m_hWnd))
679 break;
681 savePath = CTGitPath(pathSave);
683 // now open the selected file for writing
686 CStdioFile file(savePath.GetWinPathString(), CFile::typeBinary | CFile::modeReadWrite | CFile::modeCreate);
687 // temp.Format(IDS_FILEDIFF_CHANGEDLISTINTRO, (LPCTSTR)m_path1.GetGitPathString(), (LPCTSTR)m_rev1.ToString(), (LPCTSTR)m_path2.GetGitPathString(), (LPCTSTR)m_rev2.ToString());
688 file.WriteString(temp + _T("\n"));
689 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
690 while (pos)
692 int index = m_cFileList.GetNextSelectedItem(pos);
693 CTGitPath* fd = m_arFilteredList[index];
694 file.WriteString(fd->GetGitPathString());
695 file.WriteString(_T("\n"));
697 file.Close();
699 catch (CFileException* pE)
701 pE->ReportError();
705 break;
706 case ID_CLIPBOARD:
708 CopySelectionToClipboard();
710 break;
711 case ID_EXPORT:
713 #if 0 //this funcation seem no useful
714 // export all changed files to a folder
715 CBrowseFolder browseFolder;
716 browseFolder.m_style = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS;
717 if (browseFolder.Show(GetSafeHwnd(), m_strExportDir) == CBrowseFolder::OK)
719 m_arSelectedFileList.RemoveAll();
720 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
721 while (pos)
723 int index = m_cFileList.GetNextSelectedItem(pos);
724 CTGitPath* fd = m_arFilteredList[index];
725 m_arSelectedFileList.Add(fd);
727 m_pProgDlg = new CProgressDlg();
728 InterlockedExchange(&m_bThreadRunning, TRUE);
729 if (AfxBeginThread(ExportThreadEntry, this)==NULL)
731 InterlockedExchange(&m_bThreadRunning, FALSE);
732 CMessageBox::Show(NULL, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
735 #endif;
738 break;
744 UINT CFileDiffDlg::ExportThreadEntry(LPVOID pVoid)
746 return ((CFileDiffDlg*)pVoid)->ExportThread();
749 UINT CFileDiffDlg::ExportThread()
751 #if 0
752 RefreshCursor();
753 // if (m_pProgDlg == NULL)
754 // return 1;
755 long count = 0;
756 // SetAndClearProgressInfo(m_pProgDlg, false);
757 m_pProgDlg->SetTitle(IDS_PROGRESSWAIT);
758 m_pProgDlg->SetAnimation(AfxGetResourceHandle(), IDR_DOWNLOAD);
759 m_pProgDlg->ShowModeless(this);
760 for (INT_PTR i=0; (i<m_arSelectedFileList.GetCount())&&(!m_pProgDlg->HasUserCancelled()); ++i)
762 CTGitPath* fd = m_arSelectedFileList[i];
763 // CTGitPath url1 = CTGitPath(m_path1.GetGitPathString() + _T("/") + fd.path.GetGitPathString());
764 // CTGitPath url2 = m_bDoPegDiff ? url1 : CTGitPath(m_path2.GetGitPathString() + _T("/") + fd.path.GetGitPathString());
765 // if ((fd.node == svn_node_dir)&&(fd.kind != svn_client_diff_summarize_kind_added))
766 // {
767 // just create the directory
768 // CreateDirectoryEx(NULL, m_strExportDir+_T("\\")+CPathUtils::PathUnescape(fd.path.GetWinPathString()), NULL);
769 // continue;
770 // }
772 CString sTemp;
773 m_pProgDlg->FormatPathLine(1, IDS_PROGRESSGETFILE, (LPCTSTR)url1.GetGitPathString());
775 CTGitPath savepath = CTGitPath(m_strExportDir);
776 savepath.AppendPathString(_T("\\") + CPathUtils::PathUnescape(fd.path.GetWinPathString()));
777 CPathUtils::MakeSureDirectoryPathExists(fd.node == svn_node_file ? savepath.GetContainingDirectory().GetWinPath() : savepath.GetDirectory().GetWinPath());
778 if (fd.node == svn_node_dir)
780 // exporting a folder requires calling Git::Export() so we also export all
781 // children of that added folder.
782 if ((fd.kind == svn_client_diff_summarize_kind_added)&&(!Export(url2, savepath, m_bDoPegDiff ? m_peg : m_rev2, m_rev2, true, true)))
784 if ((!m_bDoPegDiff)||(!Export(url2, savepath, m_rev2, m_rev2, true, true)))
786 delete m_pProgDlg;
787 m_pProgDlg = NULL;
788 CMessageBox::Show(NULL, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
789 InterlockedExchange(&m_bThreadRunning, FALSE);
790 RefreshCursor();
791 return 1;
795 else
797 // exporting a file requires calling Git::Cat(), since Git::Export() only works
798 // with folders.
799 if ((fd.kind != svn_client_diff_summarize_kind_deleted)&&(!Cat(url2, m_bDoPegDiff ? m_peg : m_rev2, m_rev2, savepath)))
801 if ((!m_bDoPegDiff)||(!Cat(url2, m_rev2, m_rev2, savepath)))
803 delete m_pProgDlg;
804 m_pProgDlg = NULL;
805 CMessageBox::Show(NULL, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR);
806 InterlockedExchange(&m_bThreadRunning, FALSE);
807 RefreshCursor();
808 return 1;
812 count++;
813 m_pProgDlg->SetProgress (count, static_cast<DWORD>(m_arSelectedFileList.GetCount()));
815 m_pProgDlg->Stop();
816 SetAndClearProgressInfo(NULL, false);
817 delete m_pProgDlg;
818 m_pProgDlg = NULL;
819 InterlockedExchange(&m_bThreadRunning, FALSE);
820 RefreshCursor();
821 #endif
822 return 0;
825 BOOL CFileDiffDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
827 if (pWnd != &m_cFileList)
828 return CResizableStandAloneDialog::OnSetCursor(pWnd, nHitTest, message);
829 if (m_bThreadRunning == 0)
831 HCURSOR hCur = LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW));
832 SetCursor(hCur);
833 return CResizableStandAloneDialog::OnSetCursor(pWnd, nHitTest, message);
835 HCURSOR hCur = LoadCursor(NULL, MAKEINTRESOURCE(IDC_WAIT));
836 SetCursor(hCur);
837 return TRUE;
840 void CFileDiffDlg::OnEnSetfocusFirsturl()
842 GetDlgItem(IDC_FIRSTURL)->HideCaret();
845 void CFileDiffDlg::OnEnSetfocusSecondurl()
847 GetDlgItem(IDC_SECONDURL)->HideCaret();
851 void CFileDiffDlg::OnBnClickedSwitchleftright()
854 if (m_bThreadRunning)
855 return;
856 CString sFilterString;
857 m_cFilter.GetWindowText(sFilterString);
859 m_cFileList.SetRedraw(false);
860 m_cFileList.DeleteAllItems();
861 for (int i=0; i<(int)m_arFileList.GetCount(); ++i)
863 CTGitPath fd = m_arFileList[i];
864 if (fd.m_Action == CTGitPath::LOGACTIONS_ADDED)
865 fd.m_Action = CTGitPath::LOGACTIONS_DELETED;
866 else if (fd.m_Action == CTGitPath::LOGACTIONS_DELETED)
867 fd.m_Action = CTGitPath::LOGACTIONS_ADDED;
868 ( CTGitPath)m_arFileList[i] = ( CTGitPath)fd;
870 Filter(sFilterString);
872 m_cFileList.SetRedraw(true);
873 CTGitPath path = m_path1;
874 m_path1 = m_path2;
875 m_path2 = path;
876 GitRev rev = m_rev1;
877 m_rev1 = m_rev2;
878 m_rev2 = rev;
879 SetURLLabels();
883 void CFileDiffDlg::SetURLLabels()
886 m_cRev1Btn.SetWindowText(m_rev1.m_CommitHash.Left(6));
887 m_cRev2Btn.SetWindowText(m_rev2.m_CommitHash.Left(6));
889 SetDlgItemText(IDC_FIRSTURL, m_rev1.m_Subject+CString(_T("\r\n"))+m_rev1.m_CommitHash);
890 SetDlgItemText(IDC_SECONDURL,m_rev2.m_Subject+CString(_T("\r\n"))+m_rev2.m_CommitHash);
892 m_tooltips.AddTool(IDC_FIRSTURL,
893 CAppUtils::FormatDateAndTime( m_rev1.m_AuthorDate, DATE_SHORTDATE, false )+_T(" ")+m_rev1.m_AuthorName);
894 m_tooltips.AddTool(IDC_SECONDURL,
895 CAppUtils::FormatDateAndTime( m_rev2.m_AuthorDate, DATE_SHORTDATE, false )+_T(" ")+m_rev2.m_AuthorName);
899 BOOL CFileDiffDlg::PreTranslateMessage(MSG* pMsg)
901 m_tooltips.RelayEvent(pMsg);
902 if (pMsg->message == WM_KEYDOWN)
904 switch (pMsg->wParam)
906 case 'A':
908 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
910 // select all entries
911 for (int i=0; i<m_cFileList.GetItemCount(); ++i)
913 m_cFileList.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
915 return TRUE;
918 break;
919 case 'C':
920 case VK_INSERT:
922 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
924 CopySelectionToClipboard();
925 return TRUE;
928 break;
929 case '\r':
931 if (GetFocus() == GetDlgItem(IDC_FILELIST))
933 // Return pressed in file list. Show diff, as for double click
934 int selIndex = m_cFileList.GetSelectionMark();
935 if ((selIndex >= 0) && (selIndex < (int)m_arFileList.GetCount()))
936 DoDiff(selIndex, m_bBlame);
937 return TRUE;
940 break;
943 return __super::PreTranslateMessage(pMsg);
946 void CFileDiffDlg::OnCancel()
948 if (m_bThreadRunning)
950 m_bCancelled = true;
951 return;
953 __super::OnCancel();
956 void CFileDiffDlg::OnHdnItemclickFilelist(NMHDR *pNMHDR, LRESULT *pResult)
958 LPNMHEADER phdr = reinterpret_cast<LPNMHEADER>(pNMHDR);
959 if (m_bThreadRunning)
960 return;
962 if (m_nSortedColumn == phdr->iItem)
963 m_bAscending = !m_bAscending;
964 else
965 m_bAscending = TRUE;
966 m_nSortedColumn = phdr->iItem;
967 m_arSelectedFileList.RemoveAll();
968 Sort();
970 CString temp;
971 m_cFileList.SetRedraw(FALSE);
972 m_cFileList.DeleteAllItems();
973 m_cFilter.GetWindowText(temp);
974 Filter(temp);
976 CHeaderCtrl * pHeader = m_cFileList.GetHeaderCtrl();
977 HDITEM HeaderItem = {0};
978 HeaderItem.mask = HDI_FORMAT;
979 for (int i=0; i<pHeader->GetItemCount(); ++i)
981 pHeader->GetItem(i, &HeaderItem);
982 HeaderItem.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
983 pHeader->SetItem(i, &HeaderItem);
985 pHeader->GetItem(m_nSortedColumn, &HeaderItem);
986 HeaderItem.fmt |= (m_bAscending ? HDF_SORTUP : HDF_SORTDOWN);
987 pHeader->SetItem(m_nSortedColumn, &HeaderItem);
989 m_cFileList.SetRedraw(TRUE);
991 *pResult = 0;
994 void CFileDiffDlg::Sort()
996 if(m_arFileList.GetCount() < 2)
998 return;
1001 // std::sort(m_arFileList.begin(), m_arFileList.end(), &CFileDiffDlg::SortCompare);
1003 #if 0
1004 bool CFileDiffDlg::SortCompare(const CTGitPath*& Data1, const CTGitPath*& Data2)
1006 int result = 0;
1007 switch (m_nSortedColumn)
1009 case 0: //path column
1010 result = Data1.path.GetWinPathString().Compare(Data2.path.GetWinPathString());
1011 break;
1012 case 1: //action column
1013 result = Data1.kind - Data2.kind;
1014 break;
1015 default:
1016 break;
1019 if (!m_bAscending)
1020 result = -result;
1021 return result < 0;
1023 #endif
1025 void CFileDiffDlg::OnBnClickedRev1btn()
1027 #if 0
1028 if (m_bThreadRunning)
1029 return; // do nothing as long as the thread is still running
1031 // show a dialog where the user can enter a revision
1032 CRevisionDlg dlg(this);
1033 dlg.AllowWCRevs(false);
1034 *((GitRev*)&dlg) = m_rev1;
1036 if (dlg.DoModal() == IDOK)
1038 m_rev1 = dlg;
1039 m_cRev1Btn.SetWindowText(m_rev1.ToString());
1040 m_cFileList.DeleteAllItems();
1041 // start a new thread to re-fetch the diff
1042 InterlockedExchange(&m_bThreadRunning, TRUE);
1043 if (AfxBeginThread(DiffThreadEntry, this)==NULL)
1045 InterlockedExchange(&m_bThreadRunning, FALSE);
1046 CMessageBox::Show(NULL, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
1049 #endif
1052 void CFileDiffDlg::OnBnClickedRev2btn()
1054 #if 0
1055 if (m_bThreadRunning)
1056 return; // do nothing as long as the thread is still running
1058 // show a dialog where the user can enter a revision
1059 CRevisionDlg dlg(this);
1060 dlg.AllowWCRevs(false);
1061 *((GitRev*)&dlg) = m_rev2;
1063 if (dlg.DoModal() == IDOK)
1065 m_rev2 = dlg;
1066 m_cRev2Btn.SetWindowText(m_rev2.ToString());
1067 m_cFileList.DeleteAllItems();
1068 // start a new thread to re-fetch the diff
1069 InterlockedExchange(&m_bThreadRunning, TRUE);
1070 if (AfxBeginThread(DiffThreadEntry, this)==NULL)
1072 InterlockedExchange(&m_bThreadRunning, FALSE);
1073 CMessageBox::Show(NULL, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
1076 #endif
1079 LRESULT CFileDiffDlg::OnClickedCancelFilter(WPARAM /*wParam*/, LPARAM /*lParam*/)
1081 if (m_bThreadRunning)
1083 SetTimer(IDT_FILTER, 1000, NULL);
1084 return 0L;
1087 KillTimer(IDT_FILTER);
1089 m_cFileList.SetRedraw(FALSE);
1090 m_arFilteredList.clear();
1091 m_cFileList.DeleteAllItems();
1093 Filter(_T(""));
1095 m_cFileList.SetRedraw(TRUE);
1096 return 0L;
1099 void CFileDiffDlg::OnEnChangeFilter()
1101 SetTimer(IDT_FILTER, 1000, NULL);
1104 void CFileDiffDlg::OnTimer(UINT_PTR nIDEvent)
1106 if (m_bThreadRunning)
1107 return;
1109 CString sFilterText;
1110 KillTimer(IDT_FILTER);
1111 m_cFilter.GetWindowText(sFilterText);
1113 m_cFileList.SetRedraw(FALSE);
1114 m_cFileList.DeleteAllItems();
1116 Filter(sFilterText);
1118 m_cFileList.SetRedraw(TRUE);
1120 __super::OnTimer(nIDEvent);
1123 void CFileDiffDlg::Filter(CString sFilterText)
1126 sFilterText.MakeLower();
1128 m_arFilteredList.clear();
1130 for (int i=0;i<m_arFileList.GetCount();i++)
1132 CString sPath = m_arFileList[i].GetGitPathString();
1133 sPath.MakeLower();
1134 if (sPath.Find(sFilterText) >= 0)
1136 m_arFilteredList.push_back((CTGitPath*)&(m_arFileList[i]));
1139 for (std::vector<CTGitPath*>::const_iterator it = m_arFilteredList.begin(); it != m_arFilteredList.end(); ++it)
1141 AddEntry(*it);
1146 void CFileDiffDlg::CopySelectionToClipboard()
1148 // copy all selected paths to the clipboard
1149 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
1150 int index;
1151 CString sTextForClipboard;
1152 while ((index = m_cFileList.GetNextSelectedItem(pos)) >= 0)
1154 sTextForClipboard += m_cFileList.GetItemText(index, 0);
1155 sTextForClipboard += _T("\t");
1156 sTextForClipboard += m_cFileList.GetItemText(index, 1);
1157 sTextForClipboard += _T("\r\n");
1159 CStringUtils::WriteAsciiStringToClipboard(sTextForClipboard);