Fixed issue #1213: cannot diff renamed files with Revision Diff Dialog
[TortoiseGit.git] / src / TortoiseProc / FileDiffDlg.cpp
blob62fa6fb7138b35c1fa04ec127a8502ac6054f8d6
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2012 - TortoiseGit
4 // Copyright (C) 2003-2008 - TortoiseSVN
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "stdafx.h"
21 #include "TortoiseProc.h"
22 #include "UnicodeUtils.h"
23 #include "MessageBox.h"
24 #include "AppUtils.h"
25 #include "TempFile.h"
26 #include "SysImageList.h"
27 #include "IconMenu.h"
28 //#include "GitProperties.h"
29 #include "StringUtils.h"
30 #include "PathUtils.h"
31 #include "BrowseFolder.h"
32 #include ".\filediffdlg.h"
33 #include "gitdiff.h"
34 #include "LoglistCommonResource.h"
35 #include "LoglistUtils.h"
36 #include "BrowseRefsDlg.h"
37 #include "LogDlg.h"
38 #include "RefLogDlg.h"
39 #include "GitStatusListCtrl.h"
40 #include "FormatMessageWrapper.h"
42 #define ID_COMPARE 1
43 #define ID_BLAME 2
44 #define ID_SAVEAS 3
45 #define ID_EXPORT 4
46 #define ID_CLIPBOARD_PATH 5
47 #define ID_CLIPBOARD_ALL 6
48 #define ID_LOG 7
50 BOOL CFileDiffDlg::m_bAscending = FALSE;
51 int CFileDiffDlg::m_nSortedColumn = -1;
54 IMPLEMENT_DYNAMIC(CFileDiffDlg, CResizableStandAloneDialog)
55 CFileDiffDlg::CFileDiffDlg(CWnd* pParent /*=NULL*/)
56 : CResizableStandAloneDialog(CFileDiffDlg::IDD, pParent),
57 m_bBlame(false),
58 m_pProgDlg(NULL),
59 m_bCancelled(false)
61 m_bLoadingRef=FALSE;
64 CFileDiffDlg::~CFileDiffDlg()
68 void CFileDiffDlg::DoDataExchange(CDataExchange* pDX)
70 CResizableStandAloneDialog::DoDataExchange(pDX);
71 DDX_Control(pDX, IDC_FILELIST, m_cFileList);
72 DDX_Control(pDX, IDC_SWITCHLEFTRIGHT, m_SwitchButton);
73 DDX_Control(pDX, IDC_REV1BTN, m_cRev1Btn);
74 DDX_Control(pDX, IDC_REV2BTN, m_cRev2Btn);
75 DDX_Control(pDX, IDC_FILTER, m_cFilter);
76 DDX_Control(pDX, IDC_REV1EDIT, m_ctrRev1Edit);
77 DDX_Control(pDX, IDC_REV2EDIT, m_ctrRev2Edit);
81 BEGIN_MESSAGE_MAP(CFileDiffDlg, CResizableStandAloneDialog)
82 ON_NOTIFY(NM_DBLCLK, IDC_FILELIST, OnNMDblclkFilelist)
83 ON_NOTIFY(LVN_GETINFOTIP, IDC_FILELIST, OnLvnGetInfoTipFilelist)
84 ON_NOTIFY(NM_CUSTOMDRAW, IDC_FILELIST, OnNMCustomdrawFilelist)
85 ON_WM_CONTEXTMENU()
86 ON_WM_SETCURSOR()
87 ON_EN_SETFOCUS(IDC_SECONDURL, &CFileDiffDlg::OnEnSetfocusSecondurl)
88 ON_EN_SETFOCUS(IDC_FIRSTURL, &CFileDiffDlg::OnEnSetfocusFirsturl)
89 ON_BN_CLICKED(IDC_SWITCHLEFTRIGHT, &CFileDiffDlg::OnBnClickedSwitchleftright)
90 ON_NOTIFY(HDN_ITEMCLICK, 0, &CFileDiffDlg::OnHdnItemclickFilelist)
91 ON_BN_CLICKED(IDC_REV1BTN, &CFileDiffDlg::OnBnClickedRev1btn)
92 ON_BN_CLICKED(IDC_REV2BTN, &CFileDiffDlg::OnBnClickedRev2btn)
93 ON_MESSAGE(WM_FILTEREDIT_CANCELCLICKED, OnClickedCancelFilter)
94 ON_EN_CHANGE(IDC_FILTER, &CFileDiffDlg::OnEnChangeFilter)
95 ON_WM_TIMER()
96 ON_MESSAGE(ENAC_UPDATE, &CFileDiffDlg::OnEnUpdate)
97 ON_MESSAGE(MSG_REF_LOADED, OnRefLoad)
98 END_MESSAGE_MAP()
101 void CFileDiffDlg::SetDiff(CTGitPath * path, GitRev rev1, GitRev rev2)
103 if(path!=NULL)
105 m_path1 = *path;
106 m_path2 = *path;
107 m_sFilter = path->GetGitPathString();
109 m_rev1 = rev1;
110 m_rev2 = rev2;
113 void CFileDiffDlg::SetDiff(CTGitPath * path, CString &hash1, CString &hash2)
115 if(path!=NULL)
117 m_path1 = *path;
118 m_path2 = *path;
119 m_sFilter = path->GetGitPathString();
122 BYTE_VECTOR logout;
124 if(hash1 == GIT_REV_ZERO)
126 m_rev1.m_CommitHash.Empty();
127 m_rev1.GetSubject() = CString(MAKEINTRESOURCE(IDS_git_DEPTH_WORKING));
129 else
131 m_rev1.GetCommit(hash1);
134 logout.clear();
136 if(hash2 == GIT_REV_ZERO)
138 m_rev2.m_CommitHash.Empty();
139 m_rev2.GetSubject() = CString(MAKEINTRESOURCE(IDS_git_DEPTH_WORKING));
141 else
143 m_rev2.GetCommit(hash2);
147 void CFileDiffDlg::SetDiff(CTGitPath * path, GitRev rev1)
149 if(path!=NULL)
151 m_path1 = *path;
152 m_path2 = *path;
153 m_sFilter = path->GetGitPathString();
155 m_rev1 = rev1;
156 m_rev2.m_CommitHash.Empty();
157 m_rev2.GetSubject() = CString(MAKEINTRESOURCE(IDS_PROC_PREVIOUSVERSION));
159 //this->GetDlgItem()->EnableWindow(FALSE);
162 BOOL CFileDiffDlg::OnInitDialog()
164 CResizableStandAloneDialog::OnInitDialog();
165 CString temp;
167 CString sWindowTitle;
168 GetWindowText(sWindowTitle);
169 CAppUtils::SetWindowTitle(m_hWnd, g_Git.m_CurrentDir, sWindowTitle);
171 this->m_ctrRev1Edit.Init();
172 this->m_ctrRev2Edit.Init();
174 m_tooltips.Create(this);
175 m_tooltips.AddTool(IDC_SWITCHLEFTRIGHT, IDS_FILEDIFF_SWITCHLEFTRIGHT_TT);
177 m_cFileList.SetRedraw(false);
178 m_cFileList.DeleteAllItems();
179 DWORD exStyle = LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER | LVS_EX_INFOTIP;
180 m_cFileList.SetExtendedStyle(exStyle);
182 m_nIconFolder = SYS_IMAGE_LIST().GetDirIconIndex();
183 m_cFileList.SetImageList(&SYS_IMAGE_LIST(), LVSIL_SMALL);
185 m_SwitchButton.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_SWITCHLEFTRIGHT), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR));
186 m_SwitchButton.Invalidate();
188 m_cFilter.SetCancelBitmaps(IDI_CANCELNORMAL, IDI_CANCELPRESSED);
189 m_cFilter.SetInfoIcon(IDI_FILTEREDIT);
190 temp.LoadString(IDS_FILEDIFF_FILTERCUE);
191 temp = _T(" ")+temp;
192 m_cFilter.SetCueBanner(temp);
193 if (!m_sFilter.IsEmpty())
194 m_cFilter.SetWindowText(m_sFilter);
196 int c = ((CHeaderCtrl*)(m_cFileList.GetDlgItem(0)))->GetItemCount()-1;
197 while (c>=0)
198 m_cFileList.DeleteColumn(c--);
200 temp.LoadString(IDS_FILEDIFF_FILE);
201 m_cFileList.InsertColumn(0, temp);
202 temp.LoadString(IDS_FILEDIFF_ACTION);
203 m_cFileList.InsertColumn(1, temp);
205 temp.LoadString(IDS_FILEDIFF_STATADD);
206 m_cFileList.InsertColumn(2, temp);
207 temp.LoadString(IDS_FILEDIFF_STATDEL);
208 m_cFileList.InsertColumn(3, temp);
210 int mincol = 0;
211 int maxcol = ((CHeaderCtrl*)(m_cFileList.GetDlgItem(0)))->GetItemCount()-1;
212 int col;
213 for (col = mincol; col <= maxcol; col++)
215 m_cFileList.SetColumnWidth(col,LVSCW_AUTOSIZE_USEHEADER);
218 m_cFileList.SetRedraw(true);
220 AddAnchor(IDC_DIFFSTATIC1, TOP_LEFT, TOP_RIGHT);
221 AddAnchor(IDC_SWITCHLEFTRIGHT, TOP_RIGHT);
222 AddAnchor(IDC_FIRSTURL, TOP_LEFT, TOP_RIGHT);
223 AddAnchor(IDC_REV1BTN, TOP_RIGHT);
224 //AddAnchor(IDC_DIFFSTATIC2, TOP_LEFT, TOP_RIGHT);
225 AddAnchor(IDC_SECONDURL, TOP_LEFT, TOP_RIGHT);
226 AddAnchor(IDC_REV2BTN, TOP_RIGHT);
227 AddAnchor(IDC_FILTER, TOP_LEFT, TOP_RIGHT);
228 AddAnchor(IDC_FILELIST, TOP_LEFT, BOTTOM_RIGHT);
229 AddAnchor(IDC_REV1GROUP,TOP_LEFT,TOP_RIGHT);
230 AddAnchor(IDC_REV2GROUP,TOP_LEFT,TOP_RIGHT);
231 AddAnchor(IDC_REV1EDIT,TOP_LEFT);
232 AddAnchor(IDC_REV2EDIT,TOP_LEFT);
234 EnableSaveRestore(_T("FileDiffDlg"));
236 if(this->m_strRev1.IsEmpty())
237 this->m_ctrRev1Edit.SetWindowText(this->m_rev1.m_CommitHash.ToString());
238 else
240 if(m_rev1.GetCommit(this->m_strRev1))
242 CString msg;
243 msg.Format(IDS_PROC_REFINVALID, this->m_strRev1);
244 this->m_FileListText += msg;
247 this->m_ctrRev1Edit.SetWindowText(m_strRev1);
250 if(this->m_strRev2.IsEmpty())
251 this->m_ctrRev2Edit.SetWindowText(this->m_rev2.m_CommitHash.ToString());
252 else
254 if(m_rev2.GetCommit(this->m_strRev2))
256 CString msg;
257 msg.Format(IDS_PROC_REFINVALID, this->m_strRev2);
258 this->m_FileListText += msg;
261 this->m_ctrRev2Edit.SetWindowText(m_strRev2);
264 SetURLLabels();
266 InterlockedExchange(&m_bThreadRunning, TRUE);
267 if (AfxBeginThread(DiffThreadEntry, this)==NULL)
269 InterlockedExchange(&m_bThreadRunning, FALSE);
270 CMessageBox::Show(NULL, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
273 InterlockedExchange(&m_bLoadingRef, TRUE);
274 if (AfxBeginThread(LoadRefThreadEntry, this)==NULL)
276 InterlockedExchange(&m_bLoadingRef, FALSE);
277 CMessageBox::Show(NULL, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
280 this->m_cRev1Btn.AddEntry(CString(MAKEINTRESOURCE(IDS_REFBROWSE)));
281 this->m_cRev1Btn.AddEntry(CString(MAKEINTRESOURCE(IDS_LOG)));
282 this->m_cRev1Btn.AddEntry(CString(MAKEINTRESOURCE(IDS_REFLOG)));
284 this->m_cRev2Btn.AddEntry(CString(MAKEINTRESOURCE(IDS_REFBROWSE)));
285 this->m_cRev2Btn.AddEntry(CString(MAKEINTRESOURCE(IDS_LOG)));
286 this->m_cRev2Btn.AddEntry(CString(MAKEINTRESOURCE(IDS_REFLOG)));
288 // Start with focus on file list
289 GetDlgItem(IDC_FILELIST)->SetFocus();
291 if(m_rev2.m_CommitHash.IsEmpty())
292 m_SwitchButton.EnableWindow(FALSE);
294 KillTimer(IDT_INPUT);
295 return FALSE;
298 #if 0
299 svn_error_t* CFileDiffDlg::DiffSummarizeCallback(const CTGitPath& path,
300 svn_client_diff_summarize_kind_t kind,
301 bool propchanged, svn_node_kind_t node)
303 CTGitPath* fd;
304 fd.path = path;
305 fd.kind = kind;
306 fd.node = node;
307 fd.propchanged = propchanged;
308 m_arFileList.push_back(fd);
309 return Git_NO_ERROR;
311 #endif
313 UINT CFileDiffDlg::DiffThreadEntry(LPVOID pVoid)
315 return ((CFileDiffDlg*)pVoid)->DiffThread();
318 UINT CFileDiffDlg::DiffThread()
320 RefreshCursor();
321 m_cFileList.ShowText(CString(MAKEINTRESOURCE(IDS_FILEDIFF_WAIT)));
322 m_cFileList.DeleteAllItems();
323 m_arFileList.Clear();
324 EnableInputControl(false);
325 #if 0
326 bool bSuccess = true;
327 if (m_bDoPegDiff)
329 // bSuccess = DiffSummarizePeg(m_path1, m_peg, m_rev1, m_rev2, m_depth, m_bIgnoreancestry);
331 else
333 // bSuccess = DiffSummarize(m_path1, m_rev1, m_path2, m_rev2, m_depth, m_bIgnoreancestry);
335 // if (!bSuccess)
336 // {
337 // m_cFileList.ShowText(GetLastErrorMessage());
338 // InterlockedExchange(&m_bThreadRunning, FALSE);
339 // return 0;
340 // }
341 #endif
343 if( m_rev1.m_CommitHash.IsEmpty() || m_rev2.m_CommitHash.IsEmpty())
344 g_Git.RefreshGitIndex();
346 g_Git.GetCommitDiffList(m_rev1.m_CommitHash.ToString(),m_rev2.m_CommitHash.ToString(),m_arFileList);
348 CString sFilterText;
349 m_cFilter.GetWindowText(sFilterText);
350 m_cFileList.SetRedraw(false);
351 Filter(sFilterText);
352 if (m_arFileList.GetCount()>0)
354 // Highlight first entry in file list
355 m_cFileList.SetSelectionMark(0);
356 m_cFileList.SetItemState(0, LVIS_SELECTED, LVIS_SELECTED);
359 int mincol = 0;
360 int maxcol = ((CHeaderCtrl*)(m_cFileList.GetDlgItem(0)))->GetItemCount()-1;
361 int col;
362 for (col = mincol; col <= maxcol; col++)
364 m_cFileList.SetColumnWidth(col,LVSCW_AUTOSIZE_USEHEADER);
367 m_cFileList.ClearText();
368 m_cFileList.SetRedraw(true);
370 InterlockedExchange(&m_bThreadRunning, FALSE);
371 InvalidateRect(NULL);
372 RefreshCursor();
373 EnableInputControl(true);
374 return 0;
377 int CFileDiffDlg::AddEntry(const CTGitPath * fd)
379 int ret = -1;
380 if (fd)
382 int index = m_cFileList.GetItemCount();
384 int icon_idx = 0;
385 // if (fd->node == svn_node_dir)
386 // icon_idx = m_nIconFolder;
387 // else
389 icon_idx = SYS_IMAGE_LIST().GetPathIconIndex(fd->GetGitPathString());
392 ret = m_cFileList.InsertItem(index, fd->GetGitPathString(), icon_idx);
393 m_cFileList.SetItemText(index, 1, ((CTGitPath*)fd)->GetActionName());
394 m_cFileList.SetItemText(index, 2, ((CTGitPath*)fd)->m_StatAdd);
395 m_cFileList.SetItemText(index, 3, ((CTGitPath*)fd)->m_StatDel);
397 return ret;
400 void CFileDiffDlg::EnableInputControl(bool b)
402 this->m_ctrRev1Edit.EnableWindow(b);
403 this->m_ctrRev2Edit.EnableWindow(b);
404 this->m_cRev1Btn.EnableWindow(b);
405 this->m_cRev2Btn.EnableWindow(b);
406 m_cFilter.EnableWindow(b);
407 m_SwitchButton.EnableWindow(b);
410 void CFileDiffDlg::DoDiff(int selIndex, bool blame)
412 CGitDiff diff;
413 CTGitPath* fd2 = m_arFilteredList[selIndex];
414 CTGitPath* fd1 = fd2;
415 if (fd2->m_Action & CTGitPath::LOGACTIONS_REPLACED)
416 fd1 = new CTGitPath(fd2->GetGitOldPathString());
417 diff.Diff(fd2, fd1, this->m_rev1.m_CommitHash.ToString(), this->m_rev2.m_CommitHash.ToString(), blame, FALSE);
421 void CFileDiffDlg::OnNMDblclkFilelist(NMHDR *pNMHDR, LRESULT *pResult)
423 *pResult = 0;
424 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
425 int selIndex = pNMLV->iItem;
426 if (selIndex < 0)
427 return;
428 if (selIndex >= (int)m_arFilteredList.size())
429 return;
431 DoDiff(selIndex, m_bBlame);
434 void CFileDiffDlg::OnLvnGetInfoTipFilelist(NMHDR *pNMHDR, LRESULT *pResult)
437 LPNMLVGETINFOTIP pGetInfoTip = reinterpret_cast<LPNMLVGETINFOTIP>(pNMHDR);
438 if (pGetInfoTip->iItem >= (int)m_arFilteredList.size())
439 return;
441 CString path = m_path1.GetGitPathString() + _T("/") + m_arFilteredList[pGetInfoTip->iItem]->GetGitPathString();
442 if (pGetInfoTip->cchTextMax > path.GetLength())
443 _tcsncpy_s(pGetInfoTip->pszText, pGetInfoTip->cchTextMax, path, pGetInfoTip->cchTextMax);
445 *pResult = 0;
448 void CFileDiffDlg::OnNMCustomdrawFilelist(NMHDR *pNMHDR, LRESULT *pResult)
450 NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>( pNMHDR );
451 // Take the default processing unless we set this to something else below.
452 *pResult = CDRF_DODEFAULT;
454 // First thing - check the draw stage. If it's the control's prepaint
455 // stage, then tell Windows we want messages for every item.
457 if ( CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage )
459 *pResult = CDRF_NOTIFYITEMDRAW;
461 else if ( CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage )
463 // This is the prepaint stage for an item. Here's where we set the
464 // item's text color. Our return value will tell Windows to draw the
465 // item itself, but it will use the new color we set here.
467 // Tell Windows to paint the control itself.
468 *pResult = CDRF_DODEFAULT;
470 COLORREF crText = GetSysColor(COLOR_WINDOWTEXT);
472 if (m_arFilteredList.size() > pLVCD->nmcd.dwItemSpec)
474 CTGitPath * fd = m_arFilteredList[pLVCD->nmcd.dwItemSpec];
475 switch (fd->m_Action)
477 case CTGitPath::LOGACTIONS_ADDED:
478 crText = m_colors.GetColor(CColors::Added);
479 break;
480 case CTGitPath::LOGACTIONS_DELETED:
481 crText = m_colors.GetColor(CColors::Deleted);
482 break;
483 case CTGitPath::LOGACTIONS_MODIFIED:
484 crText = m_colors.GetColor(CColors::Modified);
485 break;
486 //case svn_client_diff_summarize_kind_normal:
487 default:
488 //if (fd.propchanged)
489 crText = m_colors.GetColor(CColors::PropertyChanged);
490 break;
493 // Store the color back in the NMLVCUSTOMDRAW struct.
494 pLVCD->clrText = crText;
498 UINT CFileDiffDlg::LoadRefThread()
500 g_Git.GetBranchList(m_Reflist,NULL,CGit::BRANCH_ALL_F);
501 g_Git.GetTagList(m_Reflist);
503 this->PostMessage(MSG_REF_LOADED);
504 InterlockedExchange(&m_bLoadingRef, FALSE);
505 return 0;
508 void CFileDiffDlg::OnContextMenu(CWnd* pWnd, CPoint point)
510 if ((pWnd==0)||(pWnd != &m_cFileList))
511 return;
512 if (m_cFileList.GetSelectedCount() == 0)
513 return;
514 // if the context menu is invoked through the keyboard, we have to use
515 // a calculated position on where to anchor the menu on
516 if ((point.x == -1) && (point.y == -1))
518 CRect rect;
519 m_cFileList.GetItemRect(m_cFileList.GetSelectionMark(), &rect, LVIR_LABEL);
520 m_cFileList.ClientToScreen(&rect);
521 point = rect.CenterPoint();
523 CIconMenu popup;
524 if (popup.CreatePopupMenu())
526 popup.AppendMenuIcon(ID_COMPARE, IDS_LOG_POPUP_COMPARETWO, IDI_DIFF);
527 popup.AppendMenuIcon(ID_BLAME, IDS_FILEDIFF_POPBLAME, IDI_BLAME);
528 popup.AppendMenuIcon(ID_LOG, IDS_FILEDIFF_LOG, IDI_LOG);
529 popup.AppendMenu(MF_SEPARATOR, NULL);
530 popup.AppendMenuIcon(ID_EXPORT, IDS_FILEDIFF_POPEXPORT, IDI_EXPORT);
531 popup.AppendMenu(MF_SEPARATOR, NULL);
532 popup.AppendMenuIcon(ID_SAVEAS, IDS_FILEDIFF_POPSAVELIST, IDI_SAVEAS);
533 popup.AppendMenuIcon(ID_CLIPBOARD_PATH, IDS_STATUSLIST_CONTEXT_COPY, IDI_COPYCLIP);
534 popup.AppendMenuIcon(ID_CLIPBOARD_ALL, IDS_STATUSLIST_CONTEXT_COPYEXT, IDI_COPYCLIP);
536 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
537 m_bCancelled = false;
538 switch (cmd)
540 case ID_COMPARE:
542 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
543 while (pos)
545 int index = m_cFileList.GetNextSelectedItem(pos);
546 DoDiff(index, false);
549 break;
550 case ID_BLAME:
552 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
553 while (pos)
555 int index = m_cFileList.GetNextSelectedItem(pos);
556 CAppUtils::LaunchTortoiseBlame(m_arFilteredList[index]->GetWinPathString(), m_rev1.m_CommitHash.ToString());
559 break;
560 case ID_LOG:
562 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
563 while (pos)
565 int index = m_cFileList.GetNextSelectedItem(pos);
566 CString cmd = _T("/command:log");
567 cmd += _T(" /path:\"")+m_arFilteredList[index]->GetWinPathString()+_T("\" ");
568 cmd += _T(" /endrev:")+m_rev1.m_CommitHash.ToString();
569 CAppUtils::RunTortoiseProc(cmd);
572 break;
573 case ID_SAVEAS:
575 if (m_cFileList.GetSelectedCount() > 0)
577 CString temp;
578 CTGitPath savePath;
579 CString pathSave;
580 if (!CAppUtils::FileOpenSave(pathSave, NULL, IDS_REPOBROWSE_SAVEAS, IDS_COMMONFILEFILTER, false, m_hWnd))
582 break;
584 savePath = CTGitPath(pathSave);
586 // now open the selected file for writing
589 CStdioFile file(savePath.GetWinPathString(), CFile::typeBinary | CFile::modeReadWrite | CFile::modeCreate);
590 // temp.Format(IDS_FILEDIFF_CHANGEDLISTINTRO, (LPCTSTR)m_path1.GetGitPathString(), (LPCTSTR)m_rev1.ToString(), (LPCTSTR)m_path2.GetGitPathString(), (LPCTSTR)m_rev2.ToString());
591 file.WriteString(temp + _T("\n"));
592 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
593 while (pos)
595 int index = m_cFileList.GetNextSelectedItem(pos);
596 CTGitPath* fd = m_arFilteredList[index];
597 file.WriteString(fd->GetGitPathString());
598 file.WriteString(_T("\n"));
600 file.Close();
602 catch (CFileException* pE)
604 pE->ReportError();
608 break;
609 case ID_CLIPBOARD_PATH:
611 CopySelectionToClipboard();
613 break;
615 case ID_CLIPBOARD_ALL:
617 CopySelectionToClipboard(TRUE);
619 break;
620 case ID_EXPORT:
622 // export all changed files to a folder
623 CBrowseFolder browseFolder;
624 browseFolder.m_style = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS;
625 if (browseFolder.Show(GetSafeHwnd(), m_strExportDir) == CBrowseFolder::OK)
627 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
628 while (pos)
630 int index = m_cFileList.GetNextSelectedItem(pos);
631 CTGitPath* fd = m_arFilteredList[index];
632 // we cannot export directories or folders
633 if (fd->m_Action == CTGitPath::LOGACTIONS_DELETED || fd->IsDirectory())
634 continue;
635 CAppUtils::CreateMultipleDirectory(m_strExportDir + _T("\\") + fd->GetDirectory().GetWinPathString());
636 CString filename = m_strExportDir + _T("\\") + fd->GetWinPathString();
637 if(m_rev1.m_CommitHash.ToString() == GIT_REV_ZERO)
639 if(!CopyFile(g_Git.m_CurrentDir + _T("\\") + fd->GetWinPath(), filename, false))
641 MessageBox(CFormatMessageWrapper(), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
642 return;
645 else
647 if(g_Git.GetOneFile(m_rev1.m_CommitHash, *fd, filename))
649 CString out;
650 out.Format(IDS_STATUSLIST_CHECKOUTFILEFAILED, fd->GetGitPathString(), m_rev1.m_CommitHash.ToString(), filename);
651 if (CMessageBox::Show(NULL, out, _T("TortoiseGit"), 2, IDI_WARNING, CString(MAKEINTRESOURCE(IDS_IGNOREBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
652 return;
659 break;
665 BOOL CFileDiffDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
667 if (pWnd != &m_cFileList)
668 return CResizableStandAloneDialog::OnSetCursor(pWnd, nHitTest, message);
669 if (m_bThreadRunning == 0)
671 HCURSOR hCur = LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW));
672 SetCursor(hCur);
673 return CResizableStandAloneDialog::OnSetCursor(pWnd, nHitTest, message);
675 HCURSOR hCur = LoadCursor(NULL, MAKEINTRESOURCE(IDC_WAIT));
676 SetCursor(hCur);
677 return TRUE;
680 void CFileDiffDlg::OnEnSetfocusFirsturl()
682 GetDlgItem(IDC_FIRSTURL)->HideCaret();
685 void CFileDiffDlg::OnEnSetfocusSecondurl()
687 GetDlgItem(IDC_SECONDURL)->HideCaret();
691 void CFileDiffDlg::OnBnClickedSwitchleftright()
694 if (m_bThreadRunning)
695 return;
697 #if 0
698 CString sFilterString;
699 m_cFilter.GetWindowText(sFilterString);
701 m_cFileList.SetRedraw(false);
702 m_cFileList.DeleteAllItems();
703 for (int i=0; i<(int)m_arFileList.GetCount(); ++i)
705 CTGitPath fd = m_arFileList[i];
706 if (fd.m_Action == CTGitPath::LOGACTIONS_ADDED)
707 fd.m_Action = CTGitPath::LOGACTIONS_DELETED;
708 else if (fd.m_Action == CTGitPath::LOGACTIONS_DELETED)
709 fd.m_Action = CTGitPath::LOGACTIONS_ADDED;
710 std::swap(fd.m_StatAdd, fd.m_StatDel);
711 (CTGitPath&)m_arFileList[i] = fd;
713 Filter(sFilterString);
714 #endif
716 m_cFileList.SetRedraw(true);
717 CTGitPath path = m_path1;
718 m_path1 = m_path2;
719 m_path2 = path;
720 GitRev rev = m_rev1;
721 m_rev1 = m_rev2;
722 m_rev2 = rev;
724 CString str1,str2;
725 this->m_ctrRev1Edit.GetWindowText(str1);
726 this->m_ctrRev2Edit.GetWindowText(str2);
728 this->m_ctrRev1Edit.SetWindowText(str2);
729 this->m_ctrRev2Edit.SetWindowText(str1);
731 SetURLLabels();
732 //KillTimer(IDT_INPUT);
735 void CFileDiffDlg::SetURLLabels(int mask)
738 // m_cRev1Btn.SetWindowText(m_rev1.m_CommitHash.ToString().Left(6));
739 // m_cRev2Btn.SetWindowText(m_rev2.m_CommitHash.ToString().Left(6));
741 if(mask &0x1)
743 SetDlgItemText(IDC_FIRSTURL, m_rev1.m_CommitHash.ToString().Left(8)+_T(": ")+m_rev1.GetSubject());
744 m_tooltips.AddTool(IDC_FIRSTURL,
745 CLoglistUtils::FormatDateAndTime(m_rev1.GetAuthorDate(), DATE_SHORTDATE, false) + _T(" ") + m_rev1.GetAuthorName());
749 if(mask &0x2)
751 SetDlgItemText(IDC_SECONDURL,m_rev2.m_CommitHash.ToString().Left(8)+_T(": ")+m_rev2.GetSubject());
753 m_tooltips.AddTool(IDC_SECONDURL,
754 CLoglistUtils::FormatDateAndTime(m_rev2.GetAuthorDate(), DATE_SHORTDATE, false) + _T(" ") + m_rev2.GetAuthorName());
757 this->GetDlgItem(IDC_REV2GROUP)->SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_FILEDIFF_VERSION2BASE)));
758 this->GetDlgItem(IDC_REV1GROUP)->SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_FILEDIFF_VERSION1)));
760 if( (mask&0x3) == 0x3)
761 if(m_rev2.GetCommitterDate() > m_rev1.GetCommitterDate())
763 this->GetDlgItem(IDC_REV2GROUP)->SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_FILEDIFF_VERSION2BASENEWER)));
765 else
767 this->GetDlgItem(IDC_REV1GROUP)->SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_FILEDIFF_VERSION1NEWER)));
771 void CFileDiffDlg::ClearURLabels(int mask)
773 if(mask&0x1)
775 SetDlgItemText(IDC_FIRSTURL, _T(""));
776 m_tooltips.AddTool(IDC_FIRSTURL, _T(""));
779 if(mask&0x2)
781 SetDlgItemText(IDC_SECONDURL, _T(""));
782 m_tooltips.AddTool(IDC_SECONDURL, _T(""));
785 BOOL CFileDiffDlg::PreTranslateMessage(MSG* pMsg)
787 m_tooltips.RelayEvent(pMsg);
788 if (pMsg->message == WM_KEYDOWN)
790 switch (pMsg->wParam)
792 case 'A':
794 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
796 // select all entries
797 for (int i=0; i<m_cFileList.GetItemCount(); ++i)
799 m_cFileList.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
801 return TRUE;
804 break;
805 case 'C':
806 case VK_INSERT:
808 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
810 CopySelectionToClipboard();
811 return TRUE;
814 break;
815 case '\r':
817 if (GetFocus() == GetDlgItem(IDC_FILELIST))
819 // Return pressed in file list. Show diff, as for double click
820 int selIndex = m_cFileList.GetSelectionMark();
821 if ((selIndex >= 0) && (selIndex < (int)m_arFileList.GetCount()))
822 DoDiff(selIndex, m_bBlame);
823 return TRUE;
826 break;
829 return __super::PreTranslateMessage(pMsg);
832 void CFileDiffDlg::OnCancel()
834 if (m_bThreadRunning)
836 m_bCancelled = true;
837 return;
839 __super::OnCancel();
842 void CFileDiffDlg::OnHdnItemclickFilelist(NMHDR *pNMHDR, LRESULT *pResult)
844 LPNMHEADER phdr = reinterpret_cast<LPNMHEADER>(pNMHDR);
845 if (m_bThreadRunning)
846 return;
848 if (m_nSortedColumn == phdr->iItem)
849 m_bAscending = !m_bAscending;
850 else
851 m_bAscending = TRUE;
852 m_nSortedColumn = phdr->iItem;
853 Sort();
855 CString temp;
856 m_cFileList.SetRedraw(FALSE);
857 m_cFileList.DeleteAllItems();
858 m_cFilter.GetWindowText(temp);
859 Filter(temp);
861 CHeaderCtrl * pHeader = m_cFileList.GetHeaderCtrl();
862 HDITEM HeaderItem = {0};
863 HeaderItem.mask = HDI_FORMAT;
864 for (int i=0; i<pHeader->GetItemCount(); ++i)
866 pHeader->GetItem(i, &HeaderItem);
867 HeaderItem.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
868 pHeader->SetItem(i, &HeaderItem);
870 pHeader->GetItem(m_nSortedColumn, &HeaderItem);
871 HeaderItem.fmt |= (m_bAscending ? HDF_SORTUP : HDF_SORTDOWN);
872 pHeader->SetItem(m_nSortedColumn, &HeaderItem);
874 m_cFileList.SetRedraw(TRUE);
876 *pResult = 0;
879 void CFileDiffDlg::Sort()
881 if(m_arFileList.GetCount() < 2)
883 return;
886 std::sort(m_arFileList.m_paths.begin(), m_arFileList.m_paths.end(), &CFileDiffDlg::SortCompare);
889 bool CFileDiffDlg::SortCompare(const CTGitPath& Data1, const CTGitPath& Data2)
891 int result = 0;
892 int d1, d2;
893 switch (m_nSortedColumn)
895 case 0: //path column
896 result = Data1.GetWinPathString().Compare(Data2.GetWinPathString());
897 break;
898 case 1: //action column
899 result = Data1.m_Action - Data2.m_Action;
900 break;
901 case 2:
902 d1 = CSorter::A2L(Data1.m_StatAdd);
903 d2 = CSorter::A2L(Data2.m_StatAdd);
904 result = d1 - d2;
905 break;
906 case 3:
907 d1 = CSorter::A2L(Data1.m_StatDel);;
908 d2 = CSorter::A2L(Data2.m_StatDel);
909 result = d1 - d2;
910 break;
911 default:
912 break;
915 if (!m_bAscending)
916 result = -result;
917 return result < 0;
921 void CFileDiffDlg::OnBnClickedRev1btn()
923 ClickRevButton(&this->m_cRev1Btn,&this->m_rev1, &this->m_ctrRev1Edit);
926 void CFileDiffDlg::ClickRevButton(CMenuButton *button, GitRev *rev, CACEdit *edit)
928 int entry=button->GetCurrentEntry();
929 if(entry == 0) /* Browse Refence*/
932 CString str = CBrowseRefsDlg::PickRef();
933 if(str.IsEmpty())
934 return;
936 if(FillRevFromString(rev,str))
937 return;
939 edit->SetWindowText(str);
943 if(entry == 1) /*Log*/
945 CLogDlg dlg;
946 dlg.SetSelect(true);
947 if(dlg.DoModal() == IDOK)
949 if( dlg.GetSelectedHash().IsEmpty() )
950 return;
952 if(FillRevFromString(rev,dlg.GetSelectedHash()))
953 return;
955 edit->SetWindowText(dlg.GetSelectedHash());
958 else
959 return;
962 if(entry == 2) /*RefLog*/
964 CRefLogDlg dlg;
965 if(dlg.DoModal() == IDOK)
967 if(FillRevFromString(rev,dlg.m_SelectedHash))
968 return;
970 edit->SetWindowText(dlg.m_SelectedHash);
973 else
974 return;
977 SetURLLabels();
979 InterlockedExchange(&m_bThreadRunning, TRUE);
980 if (AfxBeginThread(DiffThreadEntry, this)==NULL)
982 InterlockedExchange(&m_bThreadRunning, FALSE);
983 CMessageBox::Show(NULL, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
985 KillTimer(IDT_INPUT);
988 void CFileDiffDlg::OnBnClickedRev2btn()
990 ClickRevButton(&this->m_cRev2Btn,&this->m_rev2, &this->m_ctrRev2Edit);
993 LRESULT CFileDiffDlg::OnClickedCancelFilter(WPARAM /*wParam*/, LPARAM /*lParam*/)
995 if (m_bThreadRunning)
997 SetTimer(IDT_FILTER, 1000, NULL);
998 return 0L;
1001 KillTimer(IDT_FILTER);
1003 m_cFileList.SetRedraw(FALSE);
1004 m_arFilteredList.clear();
1005 m_cFileList.DeleteAllItems();
1007 Filter(_T(""));
1009 m_cFileList.SetRedraw(TRUE);
1010 return 0L;
1013 void CFileDiffDlg::OnEnChangeFilter()
1015 SetTimer(IDT_FILTER, 1000, NULL);
1018 void CFileDiffDlg::OnTimer(UINT_PTR nIDEvent)
1020 if (m_bThreadRunning)
1021 return;
1023 if( nIDEvent == IDT_FILTER)
1025 CString sFilterText;
1026 KillTimer(IDT_FILTER);
1027 m_cFilter.GetWindowText(sFilterText);
1029 m_cFileList.SetRedraw(FALSE);
1030 m_cFileList.DeleteAllItems();
1032 Filter(sFilterText);
1034 m_cFileList.SetRedraw(TRUE);
1036 __super::OnTimer(nIDEvent);
1039 if( nIDEvent == IDT_INPUT)
1041 KillTimer(IDT_INPUT);
1042 TRACE(_T("Input Timer\r\n"));
1044 GitRev gitrev;
1045 CString str;
1046 int mask = 0;
1047 this->m_ctrRev1Edit.GetWindowText(str);
1048 if( !gitrev.GetCommit(str) )
1050 this->m_rev1=gitrev;
1051 mask |= 0x1;
1054 this->m_ctrRev2Edit.GetWindowText(str);
1056 if( !gitrev.GetCommit(str) )
1058 this->m_rev2=gitrev;
1059 mask |= 0x2;
1062 this->SetURLLabels(mask);
1064 if(mask == 0x3)
1067 InterlockedExchange(&m_bThreadRunning, TRUE);
1068 if (AfxBeginThread(DiffThreadEntry, this)==NULL)
1070 InterlockedExchange(&m_bThreadRunning, FALSE);
1071 CMessageBox::Show(NULL, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
1077 void CFileDiffDlg::Filter(CString sFilterText)
1079 sFilterText.MakeLower();
1081 m_arFilteredList.clear();
1083 for (int i=0;i<m_arFileList.GetCount();i++)
1085 CString sPath = m_arFileList[i].GetGitPathString();
1086 sPath.MakeLower();
1087 if (sPath.Find(sFilterText) >= 0)
1089 m_arFilteredList.push_back((CTGitPath*)&(m_arFileList[i]));
1092 for (std::vector<CTGitPath*>::const_iterator it = m_arFilteredList.begin(); it != m_arFilteredList.end(); ++it)
1094 AddEntry(*it);
1098 void CFileDiffDlg::CopySelectionToClipboard(BOOL isFull)
1100 // copy all selected paths to the clipboard
1101 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
1102 int index;
1103 CString sTextForClipboard;
1104 while ((index = m_cFileList.GetNextSelectedItem(pos)) >= 0)
1106 sTextForClipboard += m_cFileList.GetItemText(index, 0);
1107 sTextForClipboard += _T("\t");
1109 if(!isFull)
1111 sTextForClipboard += _T("\r\n");
1114 else
1116 sTextForClipboard += m_cFileList.GetItemText(index, 1);
1117 sTextForClipboard += _T("\t");
1118 sTextForClipboard += m_cFileList.GetItemText(index, 2);
1119 sTextForClipboard += _T("\t");
1120 sTextForClipboard += m_cFileList.GetItemText(index, 3);
1121 sTextForClipboard += _T("\r\n");
1124 CStringUtils::WriteAsciiStringToClipboard(sTextForClipboard);
1128 LRESULT CFileDiffDlg::OnRefLoad(WPARAM wParam, LPARAM lParam)
1130 UNREFERENCED_PARAMETER(wParam);
1131 UNREFERENCED_PARAMETER(lParam);
1132 for(int i=0;i<m_Reflist.size();i++)
1134 CString str=m_Reflist[i];
1136 if(str.Find(_T("remotes/")) == 0)
1137 str=str.Mid(8);
1139 m_ctrRev1Edit.AddSearchString(str);
1140 m_ctrRev2Edit.AddSearchString(str);
1142 return 0;
1145 BOOL CFileDiffDlg::DestroyWindow()
1147 return CResizableStandAloneDialog::DestroyWindow();
1150 LRESULT CFileDiffDlg::OnEnUpdate(WPARAM /*wParam*/, LPARAM lParam)
1152 if(lParam == IDC_REV1EDIT)
1154 OnTextUpdate(&this->m_ctrRev1Edit);
1155 ClearURLabels(1);
1157 if(lParam == IDC_REV2EDIT)
1159 OnTextUpdate(&this->m_ctrRev2Edit);
1160 ClearURLabels(1<<1);
1162 return 0;
1165 void CFileDiffDlg::OnTextUpdate(CACEdit * /*pEdit*/)
1167 SetTimer(IDT_INPUT, 1000, NULL);
1168 this->m_cFileList.ShowText(_T("Wait For input validate version"));