SyncDlg: Disallow in/out changes to include local context menu
[TortoiseGit.git] / src / TortoiseProc / FileDiffDlg.cpp
blob940112a70cba1cfdbeba2f393dac5e914ef6351a
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2017 - 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 "SysImageList.h"
26 #include "IconMenu.h"
27 #include "StringUtils.h"
28 #include "PathUtils.h"
29 #include "BrowseFolder.h"
30 #include "FileDiffDlg.h"
31 #include "GitDiff.h"
32 #include "LoglistCommonResource.h"
33 #include "LoglistUtils.h"
34 #include "BrowseRefsDlg.h"
35 #include "LogDlg.h"
36 #include "RefLogDlg.h"
37 #include "GitStatusListCtrl.h"
38 #include "FormatMessageWrapper.h"
39 #include "GitDataObject.h"
41 #define ID_COMPARE 1
42 #define ID_BLAME 2
43 #define ID_SAVEAS 3
44 #define ID_EXPORT 4
45 #define ID_CLIPBOARD_PATH 5
46 #define ID_CLIPBOARD_ALL 6
47 #define ID_LOG 7
48 #define ID_GNUDIFFCOMPARE 8
49 #define ID_REVERT1 9
50 #define ID_REVERT2 10
51 #define ID_LOGSUBMODULE 11
53 BOOL CFileDiffDlg::m_bAscending = TRUE;
54 int CFileDiffDlg::m_nSortedColumn = -1;
56 UINT CFileDiffDlg::WM_DISABLEBUTTONS = RegisterWindowMessage(L"TORTOISEGIT_FILEDIFF_DISABLEBUTTONS");
57 UINT CFileDiffDlg::WM_DIFFFINISHED = RegisterWindowMessage(L"TORTOISEGIT_FILEDIFF_DIFFFINISHED");
59 IMPLEMENT_DYNAMIC(CFileDiffDlg, CResizableStandAloneDialog)
60 CFileDiffDlg::CFileDiffDlg(CWnd* pParent /*=nullptr*/)
61 : CResizableStandAloneDialog(CFileDiffDlg::IDD, pParent)
62 , m_bBlame(false)
63 , m_bCancelled(false)
64 , m_nIconFolder(0)
65 , m_bThreadRunning(FALSE)
66 , m_bIgnoreSpaceAtEol(false)
67 , m_bIgnoreSpaceChange(false)
68 , m_bIgnoreAllSpace(false)
69 , m_bIgnoreBlankLines(false)
70 , m_bIsBare(false)
71 , m_bLoadingRef(FALSE)
75 CFileDiffDlg::~CFileDiffDlg()
79 void CFileDiffDlg::DoDataExchange(CDataExchange* pDX)
81 CResizableStandAloneDialog::DoDataExchange(pDX);
82 DDX_Control(pDX, IDC_FILELIST, m_cFileList);
83 DDX_Control(pDX, IDC_SWITCHLEFTRIGHT, m_SwitchButton);
84 DDX_Control(pDX, IDC_REV1BTN, m_cRev1Btn);
85 DDX_Control(pDX, IDC_REV2BTN, m_cRev2Btn);
86 DDX_Control(pDX, IDC_FILTER, m_cFilter);
87 DDX_Control(pDX, IDC_REV1EDIT, m_ctrRev1Edit);
88 DDX_Control(pDX, IDC_REV2EDIT, m_ctrRev2Edit);
89 DDX_Control(pDX, IDC_DIFFOPTION, m_cDiffOptionsBtn);
93 BEGIN_MESSAGE_MAP(CFileDiffDlg, CResizableStandAloneDialog)
94 ON_NOTIFY(NM_DBLCLK, IDC_FILELIST, OnNMDblclkFilelist)
95 ON_NOTIFY(LVN_GETINFOTIP, IDC_FILELIST, OnLvnGetInfoTipFilelist)
96 ON_NOTIFY(NM_CUSTOMDRAW, IDC_FILELIST, OnNMCustomdrawFilelist)
97 ON_WM_CONTEXTMENU()
98 ON_WM_SETCURSOR()
99 ON_EN_SETFOCUS(IDC_SECONDURL, &CFileDiffDlg::OnEnSetfocusSecondurl)
100 ON_EN_SETFOCUS(IDC_FIRSTURL, &CFileDiffDlg::OnEnSetfocusFirsturl)
101 ON_BN_CLICKED(IDC_SWITCHLEFTRIGHT, &CFileDiffDlg::OnBnClickedSwitchleftright)
102 ON_NOTIFY(HDN_ITEMCLICK, 0, &CFileDiffDlg::OnHdnItemclickFilelist)
103 ON_BN_CLICKED(IDC_REV1BTN, &CFileDiffDlg::OnBnClickedRev1btn)
104 ON_BN_CLICKED(IDC_REV2BTN, &CFileDiffDlg::OnBnClickedRev2btn)
105 ON_MESSAGE(WM_FILTEREDIT_CANCELCLICKED, OnClickedCancelFilter)
106 ON_EN_CHANGE(IDC_FILTER, &CFileDiffDlg::OnEnChangeFilter)
107 ON_WM_TIMER()
108 ON_MESSAGE(ENAC_UPDATE, &CFileDiffDlg::OnEnUpdate)
109 ON_MESSAGE(MSG_REF_LOADED, OnRefLoad)
110 ON_REGISTERED_MESSAGE(WM_DISABLEBUTTONS, OnDisableButtons)
111 ON_REGISTERED_MESSAGE(WM_DIFFFINISHED, OnDiffFinished)
112 ON_BN_CLICKED(IDC_DIFFOPTION, OnBnClickedDiffoption)
113 ON_BN_CLICKED(IDC_LOG, &CFileDiffDlg::OnBnClickedLog)
114 ON_NOTIFY(LVN_BEGINDRAG, IDC_FILELIST, OnLvnBegindrag)
115 END_MESSAGE_MAP()
118 void CFileDiffDlg::SetDiff(const CTGitPath* path, const GitRev& baseRev1, const GitRev& rev2)
120 if (path)
122 m_path1 = *path;
123 m_path2 = *path;
124 m_sFilter = path->GetGitPathString();
126 m_rev1 = baseRev1;
127 m_rev2 = rev2;
130 void CFileDiffDlg::SetDiff(const CTGitPath* path, const CString &baseRev1, const CString& hash2)
132 if (path)
134 m_path1 = *path;
135 m_path2 = *path;
136 m_sFilter = path->GetGitPathString();
139 BYTE_VECTOR logout;
141 if (baseRev1 == GIT_REV_ZERO)
143 m_rev1.m_CommitHash.Empty();
144 m_rev1.GetSubject().LoadString(IDS_git_DEPTH_WORKING);
146 else
148 if (m_rev1.GetCommit(baseRev1))
149 MessageBox(m_rev1.GetLastErr(), L"TortoiseGit", MB_ICONERROR);
152 logout.clear();
154 if(hash2 == GIT_REV_ZERO)
156 m_rev2.m_CommitHash.Empty();
157 m_rev2.GetSubject().LoadString(IDS_git_DEPTH_WORKING);
159 else
161 if (m_rev2.GetCommit(hash2))
162 MessageBox(m_rev2.GetLastErr(), L"TortoiseGit", MB_ICONERROR);
166 void CFileDiffDlg::SetDiff(const CTGitPath* path, const GitRev &baseRev1)
168 if (path)
170 m_path1 = *path;
171 m_path2 = *path;
172 m_sFilter = path->GetGitPathString();
174 m_rev1 = baseRev1;
175 m_rev2.m_CommitHash.Empty();
176 m_rev2.GetSubject().LoadString(IDS_PROC_PREVIOUSVERSION);
178 //this->GetDlgItem()->EnableWindow(FALSE);
181 BOOL CFileDiffDlg::OnInitDialog()
183 CResizableStandAloneDialog::OnInitDialog();
184 CString temp;
186 CString sWindowTitle;
187 GetWindowText(sWindowTitle);
188 CString pathText = g_Git.m_CurrentDir;
189 if (!m_path1.IsEmpty())
190 pathText = g_Git.CombinePath(m_path1);
191 CAppUtils::SetWindowTitle(m_hWnd, pathText, sWindowTitle);
193 this->m_ctrRev1Edit.Init();
194 this->m_ctrRev2Edit.Init();
196 m_tooltips.AddTool(IDC_SWITCHLEFTRIGHT, IDS_FILEDIFF_SWITCHLEFTRIGHT_TT);
198 m_cFileList.SetRedraw(false);
199 m_cFileList.DeleteAllItems();
200 DWORD exStyle = LVS_EX_DOUBLEBUFFER | LVS_EX_INFOTIP;
201 if (CRegDWORD(L"Software\\TortoiseGit\\FullRowSelect", TRUE))
202 exStyle |= LVS_EX_FULLROWSELECT;
203 m_cFileList.SetExtendedStyle(exStyle);
205 m_nIconFolder = SYS_IMAGE_LIST().GetDirIconIndex();
206 m_cFileList.SetImageList(&SYS_IMAGE_LIST(), LVSIL_SMALL);
208 int iconWidth = GetSystemMetrics(SM_CXSMICON);
209 int iconHeight = GetSystemMetrics(SM_CYSMICON);
210 m_SwitchButton.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_SWITCHLEFTRIGHT), IMAGE_ICON, iconWidth, iconHeight, LR_DEFAULTCOLOR));
211 m_SwitchButton.Invalidate();
213 m_cFilter.SetCancelBitmaps(IDI_CANCELNORMAL, IDI_CANCELPRESSED);
214 m_cFilter.SetInfoIcon(IDI_FILTEREDIT);
215 temp.LoadString(IDS_FILEDIFF_FILTERCUE);
216 temp = L" " + temp;
217 m_cFilter.SetCueBanner(temp);
218 if (!m_sFilter.IsEmpty())
219 m_cFilter.SetWindowText(m_sFilter);
221 int c = m_cFileList.GetHeaderCtrl()->GetItemCount() - 1;
222 while (c>=0)
223 m_cFileList.DeleteColumn(c--);
225 temp.LoadString(IDS_FILEDIFF_FILE);
226 m_cFileList.InsertColumn(0, temp);
227 temp.LoadString(IDS_FILEDIFF_EXT);
228 m_cFileList.InsertColumn(1, temp);
229 temp.LoadString(IDS_FILEDIFF_ACTION);
230 m_cFileList.InsertColumn(2, temp);
232 temp.LoadString(IDS_FILEDIFF_STATADD);
233 m_cFileList.InsertColumn(3, temp);
234 temp.LoadString(IDS_FILEDIFF_STATDEL);
235 m_cFileList.InsertColumn(4, temp);
237 int mincol = 0;
238 int maxcol = m_cFileList.GetHeaderCtrl()->GetItemCount() - 1;
239 int col;
240 for (col = mincol; col <= maxcol; col++)
241 m_cFileList.SetColumnWidth(col,LVSCW_AUTOSIZE_USEHEADER);
243 m_cFileList.SetRedraw(true);
245 AddAnchor(IDC_DIFFSTATIC1, TOP_LEFT, TOP_RIGHT);
246 AddAnchor(IDC_SWITCHLEFTRIGHT, TOP_RIGHT);
247 AddAnchor(IDC_FIRSTURL, TOP_LEFT, TOP_RIGHT);
248 AddAnchor(IDC_REV1BTN, TOP_RIGHT);
249 //AddAnchor(IDC_DIFFSTATIC2, TOP_LEFT, TOP_RIGHT);
250 AddAnchor(IDC_SECONDURL, TOP_LEFT, TOP_RIGHT);
251 AddAnchor(IDC_REV2BTN, TOP_RIGHT);
252 AddAnchor(IDC_FILTER, TOP_LEFT, TOP_RIGHT);
253 AddAnchor(IDC_FILELIST, TOP_LEFT, BOTTOM_RIGHT);
254 AddAnchor(IDC_REV1GROUP,TOP_LEFT,TOP_RIGHT);
255 AddAnchor(IDC_REV2GROUP,TOP_LEFT,TOP_RIGHT);
256 AddAnchor(IDC_REV1EDIT,TOP_LEFT);
257 AddAnchor(IDC_REV2EDIT,TOP_LEFT);
258 AddAnchor(IDC_DIFFOPTION, TOP_RIGHT);
259 AddAnchor(IDC_LOG, TOP_RIGHT);
261 EnableSaveRestore(L"FileDiffDlg");
263 m_bIsBare = GitAdminDir::IsBareRepo(g_Git.m_CurrentDir);
265 if(this->m_strRev1.IsEmpty())
266 this->m_ctrRev1Edit.SetWindowText(this->m_rev1.m_CommitHash.ToString());
267 else
269 if (m_rev1.GetCommit(m_strRev1))
271 CString msg;
272 msg.Format(IDS_PROC_REFINVALID, (LPCTSTR)m_strRev1);
273 m_cFileList.ShowText(msg + L'\n' + m_rev1.GetLastErr());
276 this->m_ctrRev1Edit.SetWindowText(m_strRev1);
279 if(this->m_strRev2.IsEmpty())
280 this->m_ctrRev2Edit.SetWindowText(this->m_rev2.m_CommitHash.ToString());
281 else
283 if (m_rev2.GetCommit(m_strRev2))
285 CString msg;
286 msg.Format(IDS_PROC_REFINVALID, (LPCTSTR)m_strRev2);
287 m_cFileList.ShowText(msg + L'\n' + m_rev1.GetLastErr());
290 this->m_ctrRev2Edit.SetWindowText(m_strRev2);
293 SetURLLabels();
295 InterlockedExchange(&m_bThreadRunning, TRUE);
296 if (!AfxBeginThread(DiffThreadEntry, this))
298 InterlockedExchange(&m_bThreadRunning, FALSE);
299 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
302 InterlockedExchange(&m_bLoadingRef, TRUE);
303 if (!AfxBeginThread(LoadRefThreadEntry, this))
305 InterlockedExchange(&m_bLoadingRef, FALSE);
306 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
309 this->m_cRev1Btn.AddEntry(CString(MAKEINTRESOURCE(IDS_REFBROWSE)));
310 this->m_cRev1Btn.AddEntry(CString(MAKEINTRESOURCE(IDS_LOG)));
311 this->m_cRev1Btn.AddEntry(CString(MAKEINTRESOURCE(IDS_REFLOG)));
313 this->m_cRev2Btn.AddEntry(CString(MAKEINTRESOURCE(IDS_REFBROWSE)));
314 this->m_cRev2Btn.AddEntry(CString(MAKEINTRESOURCE(IDS_LOG)));
315 this->m_cRev2Btn.AddEntry(CString(MAKEINTRESOURCE(IDS_REFLOG)));
317 // Start with focus on file list
318 GetDlgItem(IDC_FILELIST)->SetFocus();
320 if(m_rev2.m_CommitHash.IsEmpty())
321 m_SwitchButton.EnableWindow(FALSE);
323 m_cDiffOptionsBtn.m_bAlwaysShowArrow = true;
325 KillTimer(IDT_INPUT);
326 return FALSE;
329 UINT CFileDiffDlg::DiffThreadEntry(LPVOID pVoid)
331 return reinterpret_cast<CFileDiffDlg*>(pVoid)->DiffThread();
334 UINT CFileDiffDlg::DiffThread()
336 SendMessage(WM_DISABLEBUTTONS);
338 if( m_rev1.m_CommitHash.IsEmpty() || m_rev2.m_CommitHash.IsEmpty())
339 g_Git.RefreshGitIndex();
341 g_Git.GetCommitDiffList(m_rev2.m_CommitHash.ToString(), m_rev1.m_CommitHash.ToString(), m_arFileList, m_bIgnoreSpaceAtEol, m_bIgnoreSpaceChange, m_bIgnoreAllSpace, m_bIgnoreBlankLines);
342 Sort();
344 SendMessage(WM_DIFFFINISHED);
346 InterlockedExchange(&m_bThreadRunning, FALSE);
347 return 0;
350 LRESULT CFileDiffDlg::OnDisableButtons(WPARAM, LPARAM)
352 RefreshCursor();
353 m_cFileList.ShowText(CString(MAKEINTRESOURCE(IDS_FILEDIFF_WAIT)));
354 m_cFileList.DeleteAllItems();
355 m_arFileList.Clear();
356 EnableInputControl(false);
357 return 0;
360 LRESULT CFileDiffDlg::OnDiffFinished(WPARAM, LPARAM)
362 CString sFilterText;
363 m_cFilter.GetWindowText(sFilterText);
364 m_cFileList.SetRedraw(false);
365 Filter(sFilterText);
366 if (!m_arFileList.IsEmpty())
368 // Highlight first entry in file list
369 m_cFileList.SetSelectionMark(0);
370 m_cFileList.SetItemState(0, LVIS_SELECTED, LVIS_SELECTED);
373 int mincol = 0;
374 int maxcol = m_cFileList.GetHeaderCtrl()->GetItemCount() - 1;
375 int col;
376 for (col = mincol; col <= maxcol; ++col)
377 m_cFileList.SetColumnWidth(col, LVSCW_AUTOSIZE_USEHEADER);
379 m_cFileList.ClearText();
380 if (m_arFileList.IsEmpty())
381 m_cFileList.ShowText(CString(MAKEINTRESOURCE(IDS_COMPAREREV_NODIFF)));
382 m_cFileList.SetRedraw(true);
384 InvalidateRect(nullptr);
385 RefreshCursor();
386 EnableInputControl(true);
387 return 0;
390 int CFileDiffDlg::AddEntry(const CTGitPath * fd)
392 int ret = -1;
393 if (fd)
395 int index = m_cFileList.GetItemCount();
397 int icon_idx = 0;
398 if (fd->IsDirectory())
399 icon_idx = m_nIconFolder;
400 else
401 icon_idx = SYS_IMAGE_LIST().GetPathIconIndex(fd->GetGitPathString());
403 ret = m_cFileList.InsertItem(index, fd->GetGitPathString(), icon_idx);
404 m_cFileList.SetItemText(index, 1, fd->GetFileExtension());
405 m_cFileList.SetItemText(index, 2, fd->GetActionName());
406 m_cFileList.SetItemText(index, 3, fd->m_StatAdd);
407 m_cFileList.SetItemText(index, 4, fd->m_StatDel);
409 return ret;
412 void CFileDiffDlg::EnableInputControl(bool b)
414 this->m_ctrRev1Edit.EnableWindow(b);
415 this->m_ctrRev2Edit.EnableWindow(b);
416 this->m_cRev1Btn.EnableWindow(b);
417 this->m_cRev2Btn.EnableWindow(b);
418 m_cFilter.EnableWindow(b);
419 m_SwitchButton.EnableWindow(b);
420 GetDlgItem(IDC_LOG)->EnableWindow(b && !(m_rev1.m_CommitHash.IsEmpty() || m_rev2.m_CommitHash.IsEmpty()));
423 void CFileDiffDlg::DoDiff(int selIndex, bool blame)
425 CTGitPath* fd2 = m_arFilteredList[selIndex];
426 CTGitPath* fd1 = fd2;
427 if (m_rev2.m_CommitHash.IsEmpty() && g_Git.IsInitRepos())
429 CGitDiff::DiffNull(fd2, GIT_REV_ZERO);
430 return;
432 if (fd1->m_Action & CTGitPath::LOGACTIONS_ADDED)
434 CGitDiff::DiffNull(fd1, m_rev2.m_CommitHash.ToString(), true);
435 return;
437 if (fd1->m_Action & CTGitPath::LOGACTIONS_DELETED)
439 CGitDiff::DiffNull(fd1, m_rev1.m_CommitHash.ToString(), false);
440 return;
442 if (fd1->m_Action & CTGitPath::LOGACTIONS_REPLACED)
443 fd2 = new CTGitPath(fd1->GetGitOldPathString());
444 CGitDiff::Diff(fd1, fd2, m_rev2.m_CommitHash.ToString(), m_rev1.m_CommitHash.ToString(), blame, FALSE);
445 if (fd1 != fd2)
446 delete fd2;
450 void CFileDiffDlg::OnNMDblclkFilelist(NMHDR *pNMHDR, LRESULT *pResult)
452 *pResult = 0;
453 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
454 int selIndex = pNMLV->iItem;
455 if (selIndex < 0)
456 return;
457 if (selIndex >= (int)m_arFilteredList.size())
458 return;
460 DoDiff(selIndex, m_bBlame);
463 void CFileDiffDlg::OnLvnGetInfoTipFilelist(NMHDR *pNMHDR, LRESULT *pResult)
465 LPNMLVGETINFOTIP pGetInfoTip = reinterpret_cast<LPNMLVGETINFOTIP>(pNMHDR);
466 if (pGetInfoTip->iItem >= (int)m_arFilteredList.size())
467 return;
469 CString path = m_path1.GetGitPathString() + L'/' + m_arFilteredList[pGetInfoTip->iItem]->GetGitPathString();
470 if (pGetInfoTip->cchTextMax > path.GetLength())
471 wcsncpy_s(pGetInfoTip->pszText, pGetInfoTip->cchTextMax, path, pGetInfoTip->cchTextMax - 1);
473 *pResult = 0;
476 void CFileDiffDlg::OnNMCustomdrawFilelist(NMHDR *pNMHDR, LRESULT *pResult)
478 NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>( pNMHDR );
479 // Take the default processing unless we set this to something else below.
480 *pResult = CDRF_DODEFAULT;
482 // First thing - check the draw stage. If it's the control's prepaint
483 // stage, then tell Windows we want messages for every item.
485 if ( CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage )
487 *pResult = CDRF_NOTIFYITEMDRAW;
489 else if ( CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage )
491 // This is the prepaint stage for an item. Here's where we set the
492 // item's text color. Our return value will tell Windows to draw the
493 // item itself, but it will use the new color we set here.
495 // Tell Windows to paint the control itself.
496 *pResult = CDRF_DODEFAULT;
498 COLORREF crText = GetSysColor(COLOR_WINDOWTEXT);
500 if (m_arFilteredList.size() > pLVCD->nmcd.dwItemSpec)
502 CTGitPath * fd = m_arFilteredList[pLVCD->nmcd.dwItemSpec];
503 switch (fd->m_Action)
505 case CTGitPath::LOGACTIONS_ADDED:
506 crText = m_colors.GetColor(CColors::Added);
507 break;
508 case CTGitPath::LOGACTIONS_DELETED:
509 crText = m_colors.GetColor(CColors::Deleted);
510 break;
511 case CTGitPath::LOGACTIONS_MODIFIED:
512 crText = m_colors.GetColor(CColors::Modified);
513 break;
514 default:
515 crText = m_colors.GetColor(CColors::PropertyChanged);
516 break;
519 // Store the color back in the NMLVCUSTOMDRAW struct.
520 pLVCD->clrText = crText;
524 UINT CFileDiffDlg::LoadRefThread()
526 g_Git.GetBranchList(m_Reflist, nullptr, CGit::BRANCH_ALL_F);
527 g_Git.GetTagList(m_Reflist);
529 this->PostMessage(MSG_REF_LOADED);
530 InterlockedExchange(&m_bLoadingRef, FALSE);
531 return 0;
534 void CFileDiffDlg::OnContextMenu(CWnd* pWnd, CPoint point)
536 if (!pWnd || pWnd != &m_cFileList)
537 return;
538 if (m_cFileList.GetSelectedCount() == 0)
539 return;
540 // if the context menu is invoked through the keyboard, we have to use
541 // a calculated position on where to anchor the menu on
542 if ((point.x == -1) && (point.y == -1))
544 CRect rect;
545 m_cFileList.GetItemRect(m_cFileList.GetSelectionMark(), &rect, LVIR_LABEL);
546 m_cFileList.ClientToScreen(&rect);
547 point = rect.CenterPoint();
549 CIconMenu popup;
550 if (popup.CreatePopupMenu())
552 int firstEntry = -1;
553 POSITION firstPos = m_cFileList.GetFirstSelectedItemPosition();
554 if (firstPos)
555 firstEntry = m_cFileList.GetNextSelectedItem(firstPos);
557 CString menuText;
558 popup.AppendMenuIcon(ID_COMPARE, IDS_LOG_POPUP_COMPARETWO, IDI_DIFF);
559 popup.SetDefaultItem(ID_COMPARE, FALSE);
560 popup.AppendMenuIcon(ID_GNUDIFFCOMPARE, IDS_LOG_POPUP_GNUDIFF, IDI_DIFF);
561 popup.AppendMenu(MF_SEPARATOR, NULL);
562 if (!m_bIsBare)
564 if (!m_rev1.m_CommitHash.IsEmpty())
566 menuText.Format(IDS_FILEDIFF_POPREVERTTOREV, (LPCTSTR)m_rev1.m_CommitHash.ToString().Left(g_Git.GetShortHASHLength()));
567 popup.AppendMenuIcon(ID_REVERT1, menuText, IDI_REVERT);
569 if (!m_rev2.m_CommitHash.IsEmpty())
571 menuText.Format(IDS_FILEDIFF_POPREVERTTOREV, (LPCTSTR)m_rev2.m_CommitHash.ToString().Left(g_Git.GetShortHASHLength()));
572 popup.AppendMenuIcon(ID_REVERT2, menuText, IDI_REVERT);
574 popup.AppendMenu(MF_SEPARATOR, NULL);
576 popup.AppendMenuIcon(ID_LOG, IDS_FILEDIFF_LOG, IDI_LOG);
577 if (firstEntry >= 0 && !m_arFilteredList[firstEntry]->IsDirectory())
579 if (!m_bIsBare)
581 popup.AppendMenuIcon(ID_BLAME, IDS_FILEDIFF_POPBLAME, IDI_BLAME);
582 popup.AppendMenu(MF_SEPARATOR, NULL);
584 popup.AppendMenuIcon(ID_EXPORT, IDS_FILEDIFF_POPEXPORT, IDI_EXPORT);
586 else if (firstEntry >= 0)
587 popup.AppendMenuIcon(ID_LOGSUBMODULE, IDS_MENULOGSUBMODULE, IDI_LOG);
588 popup.AppendMenu(MF_SEPARATOR, NULL);
589 popup.AppendMenuIcon(ID_SAVEAS, IDS_FILEDIFF_POPSAVELIST, IDI_SAVEAS);
590 popup.AppendMenuIcon(ID_CLIPBOARD_PATH, IDS_STATUSLIST_CONTEXT_COPY, IDI_COPYCLIP);
591 popup.AppendMenuIcon(ID_CLIPBOARD_ALL, IDS_STATUSLIST_CONTEXT_COPYEXT, IDI_COPYCLIP);
593 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this);
594 m_bCancelled = false;
595 switch (cmd)
597 case ID_COMPARE:
599 if (!CheckMultipleDiffs())
600 break;
601 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
602 while (pos)
604 int index = m_cFileList.GetNextSelectedItem(pos);
605 DoDiff(index, false);
608 break;
609 case ID_GNUDIFFCOMPARE:
611 if (!CheckMultipleDiffs())
612 break;
613 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
614 while (pos)
616 CTGitPath *fd2 = m_arFilteredList[m_cFileList.GetNextSelectedItem(pos)];
617 CTGitPath *fd1 = fd2;
618 if (fd1->m_Action & CTGitPath::LOGACTIONS_REPLACED)
619 fd2 = new CTGitPath(fd2->GetGitOldPathString());
620 CAppUtils::StartShowUnifiedDiff(m_hWnd, *fd1, m_rev1.m_CommitHash.ToString(), *fd2, m_rev2.m_CommitHash.ToString(), !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
621 if (fd1 != fd2)
622 delete fd2;
625 break;
626 case ID_REVERT1:
627 RevertSelectedItemToVersion(m_rev1.m_CommitHash.ToString());
628 break;
629 case ID_REVERT2:
630 RevertSelectedItemToVersion(m_rev2.m_CommitHash.ToString());
631 break;
632 case ID_BLAME:
634 if (!CheckMultipleDiffs())
635 break;
636 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
637 while (pos)
639 int index = m_cFileList.GetNextSelectedItem(pos);
640 if (m_arFilteredList[index]->m_Action & CTGitPath::LOGACTIONS_DELETED)
642 if (!m_rev1.m_CommitHash.IsEmpty())
643 CAppUtils::LaunchTortoiseBlame(m_arFilteredList[index]->GetWinPathString(), m_rev1.m_CommitHash.ToString());
644 continue;
646 if (m_rev2.m_CommitHash.IsEmpty() && (m_arFilteredList[index]->m_Action & CTGitPath::LOGACTIONS_ADDED))
647 continue;
648 if (m_rev2.m_CommitHash.IsEmpty() && (m_arFilteredList[index]->m_Action & CTGitPath::LOGACTIONS_REPLACED))
650 CAppUtils::LaunchTortoiseBlame(m_arFilteredList[index]->GetGitOldPathString(), m_rev1.m_CommitHash.ToString());
651 continue;
653 CAppUtils::LaunchTortoiseBlame(m_arFilteredList[index]->GetWinPathString(), m_rev2.m_CommitHash.ToString());
656 break;
657 case ID_LOG:
658 case ID_LOGSUBMODULE:
660 if (!CheckMultipleDiffs())
661 break;
662 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
663 while (pos)
665 int index = m_cFileList.GetNextSelectedItem(pos);
666 CString sCmd = L"/command:log";
667 if (sCmd == ID_LOGSUBMODULE)
668 sCmd += L" /submodule";
669 sCmd += L" /path:\"" + m_arFilteredList[index]->GetWinPathString() + L"\" ";
670 sCmd += L" /endrev:" + m_rev2.m_CommitHash.ToString();
671 CAppUtils::RunTortoiseGitProc(sCmd);
674 break;
675 case ID_SAVEAS:
677 if (m_cFileList.GetSelectedCount() > 0)
679 CTGitPath savePath;
680 CString pathSave;
681 if (!CAppUtils::FileOpenSave(pathSave, nullptr, IDS_FILEDIFF_POPSAVELIST, IDS_TEXTFILEFILTER, false, m_hWnd, L"txt"))
682 break;
683 savePath = CTGitPath(pathSave);
685 // now open the selected file for writing
688 CStdioFile file(savePath.GetWinPathString(), CFile::typeBinary | CFile::modeReadWrite | CFile::modeCreate);
689 CString temp;
690 if (m_path1.IsEmpty() && m_path2.IsEmpty())
691 temp.Format(IDS_FILEDIFF_CHANGEDLISTINTROROOT, (LPCTSTR)m_rev1.m_CommitHash.ToString(), (LPCTSTR)m_rev2.m_CommitHash.ToString());
692 else
693 temp.Format(IDS_FILEDIFF_CHANGEDLISTINTRO, (LPCTSTR)m_path1.GetGitPathString(), (LPCTSTR)m_rev1.m_CommitHash.ToString(), (LPCTSTR)m_path2.GetGitPathString(), (LPCTSTR)m_rev2.m_CommitHash.ToString());
694 file.WriteString(temp + L"\r\n");
695 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
696 while (pos)
698 int index = m_cFileList.GetNextSelectedItem(pos);
699 CTGitPath* fd = m_arFilteredList[index];
700 file.WriteString(fd->GetGitPathString());
701 file.WriteString(L"\r\n");
703 file.Close();
705 catch (CFileException* pE)
707 pE->ReportError();
711 break;
712 case ID_CLIPBOARD_PATH:
714 CopySelectionToClipboard();
716 break;
718 case ID_CLIPBOARD_ALL:
720 CopySelectionToClipboard(TRUE);
722 break;
723 case ID_EXPORT:
725 // export all changed files to a folder
726 CBrowseFolder browseFolder;
727 browseFolder.m_style = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS;
728 if (browseFolder.Show(GetSafeHwnd(), m_strExportDir) == CBrowseFolder::OK)
730 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
731 while (pos)
733 int index = m_cFileList.GetNextSelectedItem(pos);
734 CTGitPath* fd = m_arFilteredList[index];
735 // we cannot export directories or folders
736 if (fd->m_Action == CTGitPath::LOGACTIONS_DELETED || fd->IsDirectory())
737 continue;
738 CPathUtils::MakeSureDirectoryPathExists(m_strExportDir + L'\\' + fd->GetContainingDirectory().GetWinPathString());
739 CString filename = m_strExportDir + L'\\' + fd->GetWinPathString();
740 if (m_rev2.m_CommitHash.ToString() == GIT_REV_ZERO)
742 if(!CopyFile(g_Git.CombinePath(fd), filename, false))
744 MessageBox(CFormatMessageWrapper(), L"TortoiseGit", MB_OK | MB_ICONERROR);
745 return;
748 else
750 if (g_Git.GetOneFile(m_rev2.m_CommitHash, *fd, filename))
752 CString out;
753 out.Format(IDS_STATUSLIST_CHECKOUTFILEFAILED, (LPCTSTR)fd->GetGitPathString(), (LPCTSTR)m_rev2.m_CommitHash.ToString(), (LPCTSTR)filename);
754 if (CMessageBox::Show(GetSafeHwnd(), g_Git.GetGitLastErr(out, CGit::GIT_CMD_GETONEFILE), L"TortoiseGit", 2, IDI_WARNING, CString(MAKEINTRESOURCE(IDS_IGNOREBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
755 return;
762 break;
768 BOOL CFileDiffDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
770 if (pWnd != &m_cFileList)
771 return CResizableStandAloneDialog::OnSetCursor(pWnd, nHitTest, message);
772 if (m_bThreadRunning == 0)
774 HCURSOR hCur = LoadCursor(nullptr, IDC_ARROW);
775 SetCursor(hCur);
776 return CResizableStandAloneDialog::OnSetCursor(pWnd, nHitTest, message);
778 HCURSOR hCur = LoadCursor(nullptr, IDC_WAIT);
779 SetCursor(hCur);
780 return TRUE;
783 void CFileDiffDlg::OnEnSetfocusFirsturl()
785 GetDlgItem(IDC_FIRSTURL)->HideCaret();
788 void CFileDiffDlg::OnEnSetfocusSecondurl()
790 GetDlgItem(IDC_SECONDURL)->HideCaret();
793 void CFileDiffDlg::OnBnClickedSwitchleftright()
795 if (m_bThreadRunning)
796 return;
798 #if 0
799 CString sFilterString;
800 m_cFilter.GetWindowText(sFilterString);
802 m_cFileList.SetRedraw(false);
803 m_cFileList.DeleteAllItems();
804 for (int i=0; i<(int)m_arFileList.GetCount(); ++i)
806 CTGitPath fd = m_arFileList[i];
807 if (fd.m_Action == CTGitPath::LOGACTIONS_ADDED)
808 fd.m_Action = CTGitPath::LOGACTIONS_DELETED;
809 else if (fd.m_Action == CTGitPath::LOGACTIONS_DELETED)
810 fd.m_Action = CTGitPath::LOGACTIONS_ADDED;
811 std::swap(fd.m_StatAdd, fd.m_StatDel);
812 (CTGitPath&)m_arFileList[i] = fd;
814 Filter(sFilterString);
815 #endif
817 m_cFileList.SetRedraw(true);
818 CTGitPath path = m_path1;
819 m_path1 = m_path2;
820 m_path2 = path;
821 GitRev rev = m_rev1;
822 m_rev1 = m_rev2;
823 m_rev2 = rev;
825 CString str1,str2;
826 this->m_ctrRev1Edit.GetWindowText(str1);
827 this->m_ctrRev2Edit.GetWindowText(str2);
829 this->m_ctrRev1Edit.SetWindowText(str2);
830 this->m_ctrRev2Edit.SetWindowText(str1);
832 SetURLLabels();
833 //KillTimer(IDT_INPUT);
836 void CFileDiffDlg::SetURLLabels(int mask)
838 if(mask &0x1)
840 SetDlgItemText(IDC_FIRSTURL, m_rev1.m_CommitHash.ToString().Left(8) + L": " + m_rev1.GetSubject());
841 if (!m_rev1.m_CommitHash.IsEmpty())
842 m_tooltips.AddTool(IDC_FIRSTURL,
843 CLoglistUtils::FormatDateAndTime(m_rev1.GetAuthorDate(), DATE_SHORTDATE) + L" " + m_rev1.GetAuthorName());
846 if(mask &0x2)
848 SetDlgItemText(IDC_SECONDURL,m_rev2.m_CommitHash.ToString().Left(8) + L": " + m_rev2.GetSubject());
849 if (!m_rev2.m_CommitHash.IsEmpty())
850 m_tooltips.AddTool(IDC_SECONDURL,
851 CLoglistUtils::FormatDateAndTime(m_rev2.GetAuthorDate(), DATE_SHORTDATE) + L" " + m_rev2.GetAuthorName());
854 this->GetDlgItem(IDC_REV1GROUP)->SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_FILEDIFF_VERSION1BASE)));
855 this->GetDlgItem(IDC_REV2GROUP)->SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_FILEDIFF_VERSION2)));
857 if ((mask & 0x3) == 0x3 && !m_rev1.m_CommitHash.IsEmpty() && !m_rev2.m_CommitHash.IsEmpty())
858 if(m_rev1.GetCommitterDate() > m_rev2.GetCommitterDate())
859 GetDlgItem(IDC_REV1GROUP)->SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_FILEDIFF_VERSION1BASENEWER)));
860 else if (m_rev1.GetCommitterDate() < m_rev2.GetCommitterDate())
861 GetDlgItem(IDC_REV2GROUP)->SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_FILEDIFF_VERSION2NEWER)));
864 void CFileDiffDlg::ClearURLabels(int mask)
866 if(mask&0x1)
868 SetDlgItemText(IDC_FIRSTURL, L"");
869 m_tooltips.AddTool(IDC_FIRSTURL, L"");
872 if(mask&0x2)
874 SetDlgItemText(IDC_SECONDURL, L"");
875 m_tooltips.AddTool(IDC_SECONDURL, L"");
878 BOOL CFileDiffDlg::PreTranslateMessage(MSG* pMsg)
880 if (pMsg->message == WM_KEYDOWN)
882 switch (pMsg->wParam)
884 case 'A':
886 if (GetFocus() != GetDlgItem(IDC_FILELIST))
887 break;
888 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
890 // select all entries
891 for (int i=0; i<m_cFileList.GetItemCount(); ++i)
892 m_cFileList.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
893 return TRUE;
896 break;
897 case 'C':
898 case VK_INSERT:
900 if (GetFocus() != GetDlgItem(IDC_FILELIST))
901 break;
902 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
904 CopySelectionToClipboard();
905 return TRUE;
908 break;
909 case '\r':
911 if (GetFocus() == GetDlgItem(IDC_FILELIST))
913 // Return pressed in file list. Show diff, as for double click
914 int selIndex = m_cFileList.GetSelectionMark();
915 if ((selIndex >= 0) && (selIndex < (int)m_arFileList.GetCount()))
916 DoDiff(selIndex, m_bBlame);
917 return TRUE;
920 break;
921 case VK_F5:
923 OnTimer(IDT_INPUT);
925 break;
926 case VK_ESCAPE:
927 if (GetFocus() == GetDlgItem(IDC_FILTER) && m_cFilter.GetWindowTextLength())
929 m_cFilter.SetWindowText(L"");
930 OnClickedCancelFilter(NULL, NULL);
931 return TRUE;
933 break;
936 return __super::PreTranslateMessage(pMsg);
939 void CFileDiffDlg::OnCancel()
941 if (m_bThreadRunning)
943 m_bCancelled = true;
944 return;
946 __super::OnCancel();
949 void CFileDiffDlg::OnHdnItemclickFilelist(NMHDR *pNMHDR, LRESULT *pResult)
951 LPNMHEADER phdr = reinterpret_cast<LPNMHEADER>(pNMHDR);
952 if (m_bThreadRunning)
953 return;
955 if (m_nSortedColumn == phdr->iItem)
956 m_bAscending = !m_bAscending;
957 else
958 m_bAscending = TRUE;
959 m_nSortedColumn = phdr->iItem;
960 Sort();
962 CString temp;
963 m_cFileList.SetRedraw(FALSE);
964 m_cFileList.DeleteAllItems();
965 m_cFilter.GetWindowText(temp);
966 Filter(temp);
968 CHeaderCtrl * pHeader = m_cFileList.GetHeaderCtrl();
969 HDITEM HeaderItem = {0};
970 HeaderItem.mask = HDI_FORMAT;
971 for (int i=0; i<pHeader->GetItemCount(); ++i)
973 pHeader->GetItem(i, &HeaderItem);
974 HeaderItem.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
975 pHeader->SetItem(i, &HeaderItem);
977 pHeader->GetItem(m_nSortedColumn, &HeaderItem);
978 HeaderItem.fmt |= (m_bAscending ? HDF_SORTUP : HDF_SORTDOWN);
979 pHeader->SetItem(m_nSortedColumn, &HeaderItem);
981 m_cFileList.SetRedraw(TRUE);
983 *pResult = 0;
986 void CFileDiffDlg::Sort()
988 if(m_arFileList.GetCount() < 2)
989 return;
991 std::sort(m_arFileList.m_paths.begin(), m_arFileList.m_paths.end(), &CFileDiffDlg::SortCompare);
994 bool CFileDiffDlg::SortCompare(const CTGitPath& Data1, const CTGitPath& Data2)
996 int result = 0;
997 int d1, d2;
998 switch (m_nSortedColumn)
1000 case 0: //path column
1001 result = Data1.GetWinPathString().Compare(Data2.GetWinPathString());
1002 break;
1003 case 1: //extension column
1004 result = Data1.GetFileExtension().Compare(Data2.GetFileExtension());
1005 break;
1006 case 2: //action column
1007 result = Data1.m_Action - Data2.m_Action;
1008 break;
1009 case 3:
1010 d1 = CSorter::A2L(Data1.m_StatAdd);
1011 d2 = CSorter::A2L(Data2.m_StatAdd);
1012 result = d1 - d2;
1013 break;
1014 case 4:
1015 d1 = CSorter::A2L(Data1.m_StatDel);;
1016 d2 = CSorter::A2L(Data2.m_StatDel);
1017 result = d1 - d2;
1018 break;
1019 default:
1020 break;
1022 // sort by path name as second priority
1023 if (m_nSortedColumn != 0 && result == 0)
1024 result = Data1.GetWinPathString().Compare(Data2.GetWinPathString());
1026 if (!m_bAscending)
1027 result = -result;
1028 return result < 0;
1032 void CFileDiffDlg::OnBnClickedRev1btn()
1034 ClickRevButton(&this->m_cRev1Btn,&this->m_rev1, &this->m_ctrRev1Edit);
1037 void CFileDiffDlg::ClickRevButton(CMenuButton *button, GitRev *rev, CACEdit *edit)
1039 INT_PTR entry=button->GetCurrentEntry();
1040 if(entry == 0) /* Browse Refence*/
1043 CString str = CBrowseRefsDlg::PickRef();
1044 if(str.IsEmpty())
1045 return;
1047 if(FillRevFromString(rev,str))
1048 return;
1050 edit->SetWindowText(str);
1054 if(entry == 1) /*Log*/
1056 CLogDlg dlg;
1057 CString revision;
1058 edit->GetWindowText(revision);
1059 dlg.SetParams(CTGitPath(), CTGitPath(), revision, revision, 0);
1060 dlg.SetSelect(true);
1061 if(dlg.DoModal() == IDOK)
1063 if (dlg.GetSelectedHash().empty())
1064 return;
1066 if (FillRevFromString(rev, dlg.GetSelectedHash().at(0).ToString()))
1067 return;
1069 edit->SetWindowText(dlg.GetSelectedHash().at(0).ToString());
1071 else
1072 return;
1075 if(entry == 2) /*RefLog*/
1077 CRefLogDlg dlg;
1078 if(dlg.DoModal() == IDOK)
1080 if(FillRevFromString(rev,dlg.m_SelectedHash))
1081 return;
1083 edit->SetWindowText(dlg.m_SelectedHash);
1085 else
1086 return;
1089 SetURLLabels();
1091 InterlockedExchange(&m_bThreadRunning, TRUE);
1092 if (!AfxBeginThread(DiffThreadEntry, this))
1094 InterlockedExchange(&m_bThreadRunning, FALSE);
1095 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
1097 KillTimer(IDT_INPUT);
1100 void CFileDiffDlg::OnBnClickedRev2btn()
1102 ClickRevButton(&this->m_cRev2Btn,&this->m_rev2, &this->m_ctrRev2Edit);
1105 LRESULT CFileDiffDlg::OnClickedCancelFilter(WPARAM /*wParam*/, LPARAM /*lParam*/)
1107 if (m_bThreadRunning)
1109 SetTimer(IDT_FILTER, 1000, nullptr);
1110 return 0L;
1113 KillTimer(IDT_FILTER);
1115 m_cFileList.SetRedraw(FALSE);
1116 m_arFilteredList.clear();
1117 m_cFileList.DeleteAllItems();
1119 Filter(L"");
1121 m_cFileList.SetRedraw(TRUE);
1122 return 0L;
1125 void CFileDiffDlg::OnEnChangeFilter()
1127 SetTimer(IDT_FILTER, 1000, nullptr);
1130 void CFileDiffDlg::OnTimer(UINT_PTR nIDEvent)
1132 if (m_bThreadRunning)
1133 return;
1135 if( nIDEvent == IDT_FILTER)
1137 CString sFilterText;
1138 KillTimer(IDT_FILTER);
1139 m_cFilter.GetWindowText(sFilterText);
1141 m_cFileList.SetRedraw(FALSE);
1142 m_cFileList.DeleteAllItems();
1144 Filter(sFilterText);
1146 m_cFileList.SetRedraw(TRUE);
1148 __super::OnTimer(nIDEvent);
1151 if( nIDEvent == IDT_INPUT)
1153 KillTimer(IDT_INPUT);
1154 TRACE(L"Input Timer\r\n");
1156 GitRev gitrev;
1157 CString str;
1158 int mask = 0;
1159 this->m_ctrRev1Edit.GetWindowText(str);
1160 if (!gitrev.GetCommit(str))
1162 m_rev1 = gitrev;
1163 mask |= 0x1;
1165 else
1167 CString msg;
1168 msg.Format(IDS_PROC_REFINVALID, (LPCTSTR)str);
1169 m_cFileList.ShowText(msg + L'\n' + gitrev.GetLastErr());
1172 this->m_ctrRev2Edit.GetWindowText(str);
1174 if (!gitrev.GetCommit(str))
1176 m_rev2 = gitrev;
1177 mask |= 0x2;
1179 else
1181 CString msg;
1182 msg.Format(IDS_PROC_REFINVALID, (LPCTSTR)str);
1183 m_cFileList.ShowText(msg + L'\n' + gitrev.GetLastErr());
1186 this->SetURLLabels(mask);
1188 if(mask == 0x3)
1190 InterlockedExchange(&m_bThreadRunning, TRUE);
1191 if (!AfxBeginThread(DiffThreadEntry, this))
1193 InterlockedExchange(&m_bThreadRunning, FALSE);
1194 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
1200 void CFileDiffDlg::Filter(CString sFilterText)
1202 sFilterText.MakeLower();
1204 m_arFilteredList.clear();
1206 for (int i=0;i<m_arFileList.GetCount();i++)
1208 CString sPath = m_arFileList[i].GetGitPathString();
1209 sPath.MakeLower();
1210 if (sPath.Find(sFilterText) >= 0)
1211 m_arFilteredList.push_back((CTGitPath*)&(m_arFileList[i]));
1213 for (const auto path : m_arFilteredList)
1214 AddEntry(path);
1217 void CFileDiffDlg::CopySelectionToClipboard(BOOL isFull)
1219 // copy all selected paths to the clipboard
1220 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
1221 int index;
1222 CString sTextForClipboard;
1223 while ((index = m_cFileList.GetNextSelectedItem(pos)) >= 0)
1225 sTextForClipboard += m_cFileList.GetItemText(index, 0);
1226 sTextForClipboard += L'\t';
1228 if(!isFull)
1229 sTextForClipboard += L"\r\n";
1230 else
1232 sTextForClipboard += m_cFileList.GetItemText(index, 1);
1233 sTextForClipboard += L'\t';
1234 sTextForClipboard += m_cFileList.GetItemText(index, 2);
1235 sTextForClipboard += L'\t';
1236 sTextForClipboard += m_cFileList.GetItemText(index, 3);
1237 sTextForClipboard += L'\t';
1238 sTextForClipboard += m_cFileList.GetItemText(index, 4);
1239 sTextForClipboard += L"\r\n";
1242 CStringUtils::WriteAsciiStringToClipboard(sTextForClipboard);
1246 LRESULT CFileDiffDlg::OnRefLoad(WPARAM /*wParam*/, LPARAM /*lParam*/)
1248 for (size_t i = 0; i < m_Reflist.size(); ++i)
1250 CString str=m_Reflist[i];
1252 if (CStringUtils::StartsWith(str, L"remotes/"))
1253 str=str.Mid(8);
1255 m_ctrRev1Edit.AddSearchString(str);
1256 m_ctrRev2Edit.AddSearchString(str);
1258 return 0;
1261 BOOL CFileDiffDlg::DestroyWindow()
1263 return CResizableStandAloneDialog::DestroyWindow();
1266 LRESULT CFileDiffDlg::OnEnUpdate(WPARAM /*wParam*/, LPARAM lParam)
1268 if(lParam == IDC_REV1EDIT)
1270 OnTextUpdate(&this->m_ctrRev1Edit);
1271 ClearURLabels(1);
1273 if(lParam == IDC_REV2EDIT)
1275 OnTextUpdate(&this->m_ctrRev2Edit);
1276 ClearURLabels(1<<1);
1278 return 0;
1281 void CFileDiffDlg::OnTextUpdate(CACEdit * /*pEdit*/)
1283 SetTimer(IDT_INPUT, 1000, nullptr);
1284 this->m_cFileList.ShowText(L"Wait For input validate version");
1287 int CFileDiffDlg::RevertSelectedItemToVersion(CString rev)
1289 if (rev.IsEmpty() || rev == GIT_REV_ZERO)
1290 return 0;
1292 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
1293 int index;
1294 int count = 0;
1295 while ((index = m_cFileList.GetNextSelectedItem(pos)) >= 0)
1297 CString cmd, out;
1298 CTGitPath* fentry = m_arFilteredList[index];
1299 cmd.Format(L"git.exe checkout %s -- \"%s\"", (LPCTSTR)rev, (LPCTSTR)fentry->GetGitPathString());
1300 if (g_Git.Run(cmd, &out, CP_UTF8))
1302 if (CMessageBox::Show(GetSafeHwnd(), out, L"TortoiseGit", 2, IDI_WARNING, CString(MAKEINTRESOURCE(IDS_IGNOREBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
1303 break;
1305 else
1306 count++;
1309 CString out;
1310 out.Format(IDS_STATUSLIST_FILESREVERTED, count, (LPCTSTR)rev);
1311 CMessageBox::Show(GetSafeHwnd(), out, L"TortoiseGit", MB_OK);
1312 return 0;
1315 static void AppendMenuChecked(CMenu &menu, UINT nTextID, UINT_PTR nItemID, BOOL checked = FALSE, BOOL enabled = TRUE)
1317 CString text;
1318 text.LoadString(nTextID);
1319 menu.AppendMenu(MF_STRING | (enabled ? MF_ENABLED : MF_DISABLED) | (checked ? MF_CHECKED : MF_UNCHECKED), nItemID, text);
1322 #define DIFFOPTION_IGNORESPACEATEOL 1
1323 #define DIFFOPTION_IGNORESPACECHANGE 2
1324 #define DIFFOPTION_IGNOREALLSPACE 3
1325 #define DIFFOPTION_IGNORBLANKLINES 4
1327 void CFileDiffDlg::OnBnClickedDiffoption()
1329 CMenu popup;
1330 if (popup.CreatePopupMenu())
1332 m_cDiffOptionsBtn.SetCheck(BST_CHECKED);
1333 AppendMenuChecked(popup, IDS_DIFFOPTION_IGNORESPACEATEOL, DIFFOPTION_IGNORESPACEATEOL, m_bIgnoreSpaceAtEol);
1334 AppendMenuChecked(popup, IDS_DIFFOPTION_IGNORESPACECHANGE, DIFFOPTION_IGNORESPACECHANGE, m_bIgnoreSpaceChange);
1335 AppendMenuChecked(popup, IDS_DIFFOPTION_IGNOREALLSPACE, DIFFOPTION_IGNOREALLSPACE, m_bIgnoreAllSpace);
1336 AppendMenuChecked(popup, IDS_DIFFOPTION_IGNORBLANKLINES, DIFFOPTION_IGNORBLANKLINES, m_bIgnoreBlankLines);
1338 m_tooltips.Pop();
1339 RECT rect;
1340 GetDlgItem(IDC_DIFFOPTION)->GetWindowRect(&rect);
1341 int selection = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, rect.left, rect.bottom, this);
1342 switch (selection)
1344 case DIFFOPTION_IGNORESPACEATEOL:
1345 m_bIgnoreSpaceAtEol = !m_bIgnoreSpaceAtEol;
1346 OnTimer(IDT_INPUT);
1347 break;
1348 case DIFFOPTION_IGNORESPACECHANGE:
1349 m_bIgnoreSpaceChange = !m_bIgnoreSpaceChange;
1350 OnTimer(IDT_INPUT);
1351 break;
1352 case DIFFOPTION_IGNOREALLSPACE:
1353 m_bIgnoreAllSpace = !m_bIgnoreAllSpace;
1354 OnTimer(IDT_INPUT);
1355 break;
1356 case DIFFOPTION_IGNORBLANKLINES:
1357 m_bIgnoreBlankLines = !m_bIgnoreBlankLines;
1358 OnTimer(IDT_INPUT);
1359 break;
1360 default:
1361 break;
1363 UpdateData(FALSE);
1364 m_cDiffOptionsBtn.SetCheck((m_bIgnoreSpaceAtEol || m_bIgnoreSpaceChange || m_bIgnoreAllSpace || m_bIgnoreBlankLines) ? BST_CHECKED : BST_UNCHECKED);
1368 void CFileDiffDlg::OnBnClickedLog()
1370 CLogDlg dlg;
1371 dlg.SetRange(m_rev1.m_CommitHash.ToString() + L".." + m_rev2.m_CommitHash.ToString());
1372 dlg.DoModal();
1375 bool CFileDiffDlg::CheckMultipleDiffs()
1377 UINT selCount = m_cFileList.GetSelectedCount();
1378 if (selCount > max(3, (DWORD)CRegDWORD(L"Software\\TortoiseGit\\NumDiffWarning", 10)))
1380 CString message;
1381 message.Format(IDS_STATUSLIST_WARN_MAXDIFF, selCount);
1382 return ::MessageBox(GetSafeHwnd(), message, L"TortoiseGit", MB_YESNO | MB_ICONQUESTION) == IDYES;
1384 return true;
1387 void CFileDiffDlg::OnLvnBegindrag(NMHDR* pNMHDR, LRESULT* pResult)
1389 *pResult = 0;
1391 // get selected paths
1392 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
1393 if (!pos)
1394 return;
1396 CTGitPathList toExport;
1397 int index = -1;
1398 while ((index = m_cFileList.GetNextSelectedItem(pos)) >= 0)
1400 auto fentry = m_arFilteredList[index];
1401 toExport.AddPath(*fentry);
1405 // build copy source / content
1406 auto pdsrc = std::make_unique<CIDropSource>();
1407 if (!pdsrc)
1408 return;
1410 pdsrc->AddRef();
1412 GitDataObject* pdobj = new GitDataObject(toExport, m_rev2.m_CommitHash);
1413 if (!pdobj)
1414 return;
1415 pdobj->AddRef();
1416 pdobj->SetAsyncMode(TRUE);
1417 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1418 CDragSourceHelper dragsrchelper;
1419 dragsrchelper.InitializeFromWindow(GetSafeHwnd(), pNMLV->ptAction, pdobj);
1420 pdsrc->m_pIDataObj = pdobj;
1421 pdsrc->m_pIDataObj->AddRef();
1423 // Initiate the Drag & Drop
1424 DWORD dwEffect;
1425 ::DoDragDrop(pdobj, pdsrc.get(), DROPEFFECT_MOVE | DROPEFFECT_COPY, &dwEffect);
1426 pdsrc->Release();
1427 pdsrc.release();
1428 pdobj->Release();