Don't import ogdf namespace
[TortoiseGit.git] / src / TortoiseProc / FileDiffDlg.cpp
bloba00477362a39df99a965fa8cdde19b00591f8985
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2018 - TortoiseGit
4 // Copyright (C) 2003-2008, 2018 - 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_nIconFolder(0)
64 , m_bThreadRunning(FALSE)
65 , m_bIgnoreSpaceAtEol(false)
66 , m_bIgnoreSpaceChange(false)
67 , m_bIgnoreAllSpace(false)
68 , m_bIgnoreBlankLines(false)
69 , m_bIsBare(false)
70 , m_bLoadingRef(FALSE)
74 CFileDiffDlg::~CFileDiffDlg()
78 void CFileDiffDlg::DoDataExchange(CDataExchange* pDX)
80 CResizableStandAloneDialog::DoDataExchange(pDX);
81 DDX_Control(pDX, IDC_FILELIST, m_cFileList);
82 DDX_Control(pDX, IDC_SWITCHLEFTRIGHT, m_SwitchButton);
83 DDX_Control(pDX, IDC_REV1BTN, m_cRev1Btn);
84 DDX_Control(pDX, IDC_REV2BTN, m_cRev2Btn);
85 DDX_Control(pDX, IDC_FILTER, m_cFilter);
86 DDX_Control(pDX, IDC_REV1EDIT, m_ctrRev1Edit);
87 DDX_Control(pDX, IDC_REV2EDIT, m_ctrRev2Edit);
88 DDX_Control(pDX, IDC_DIFFOPTION, m_cDiffOptionsBtn);
92 BEGIN_MESSAGE_MAP(CFileDiffDlg, CResizableStandAloneDialog)
93 ON_NOTIFY(NM_DBLCLK, IDC_FILELIST, OnNMDblclkFilelist)
94 ON_NOTIFY(LVN_GETINFOTIP, IDC_FILELIST, OnLvnGetInfoTipFilelist)
95 ON_NOTIFY(NM_CUSTOMDRAW, IDC_FILELIST, OnNMCustomdrawFilelist)
96 ON_WM_CONTEXTMENU()
97 ON_WM_SETCURSOR()
98 ON_EN_SETFOCUS(IDC_SECONDURL, &CFileDiffDlg::OnEnSetfocusSecondurl)
99 ON_EN_SETFOCUS(IDC_FIRSTURL, &CFileDiffDlg::OnEnSetfocusFirsturl)
100 ON_BN_CLICKED(IDC_SWITCHLEFTRIGHT, &CFileDiffDlg::OnBnClickedSwitchleftright)
101 ON_NOTIFY(HDN_ITEMCLICK, 0, &CFileDiffDlg::OnHdnItemclickFilelist)
102 ON_BN_CLICKED(IDC_REV1BTN, &CFileDiffDlg::OnBnClickedRev1btn)
103 ON_BN_CLICKED(IDC_REV2BTN, &CFileDiffDlg::OnBnClickedRev2btn)
104 ON_REGISTERED_MESSAGE(CFilterEdit::WM_FILTEREDIT_CANCELCLICKED, OnClickedCancelFilter)
105 ON_EN_CHANGE(IDC_FILTER, &CFileDiffDlg::OnEnChangeFilter)
106 ON_WM_TIMER()
107 ON_MESSAGE(ENAC_UPDATE, &CFileDiffDlg::OnEnUpdate)
108 ON_MESSAGE(MSG_REF_LOADED, OnRefLoad)
109 ON_REGISTERED_MESSAGE(WM_DISABLEBUTTONS, OnDisableButtons)
110 ON_REGISTERED_MESSAGE(WM_DIFFFINISHED, OnDiffFinished)
111 ON_BN_CLICKED(IDC_DIFFOPTION, OnBnClickedDiffoption)
112 ON_BN_CLICKED(IDC_LOG, &CFileDiffDlg::OnBnClickedLog)
113 ON_NOTIFY(LVN_BEGINDRAG, IDC_FILELIST, OnLvnBegindrag)
114 END_MESSAGE_MAP()
117 void CFileDiffDlg::SetDiff(const CTGitPath* path, const GitRev& baseRev1, const GitRev& rev2)
119 if (path)
121 m_path1 = *path;
122 m_path2 = *path;
123 m_sFilter = path->GetGitPathString();
125 m_rev1 = baseRev1;
126 m_rev2 = rev2;
129 void CFileDiffDlg::SetDiff(const CTGitPath* path, const CString &baseRev1, const CString& hash2)
131 if (path)
133 m_path1 = *path;
134 m_path2 = *path;
135 m_sFilter = path->GetGitPathString();
138 BYTE_VECTOR logout;
140 if (baseRev1 == GIT_REV_ZERO)
142 m_rev1.m_CommitHash.Empty();
143 m_rev1.GetSubject().LoadString(IDS_WORKING_TREE);
145 else
147 if (m_rev1.GetCommit(baseRev1))
148 MessageBox(m_rev1.GetLastErr(), L"TortoiseGit", MB_ICONERROR);
151 logout.clear();
153 if(hash2 == GIT_REV_ZERO)
155 m_rev2.m_CommitHash.Empty();
156 m_rev2.GetSubject().LoadString(IDS_WORKING_TREE);
158 else
160 if (m_rev2.GetCommit(hash2))
161 MessageBox(m_rev2.GetLastErr(), L"TortoiseGit", MB_ICONERROR);
165 void CFileDiffDlg::SetDiff(const CTGitPath* path, const GitRev &baseRev1)
167 if (path)
169 m_path1 = *path;
170 m_path2 = *path;
171 m_sFilter = path->GetGitPathString();
173 m_rev1 = baseRev1;
174 m_rev2.m_CommitHash.Empty();
175 m_rev2.GetSubject().LoadString(IDS_PROC_PREVIOUSVERSION);
177 //this->GetDlgItem()->EnableWindow(FALSE);
180 BOOL CFileDiffDlg::OnInitDialog()
182 CResizableStandAloneDialog::OnInitDialog();
183 CString temp;
185 CString sWindowTitle;
186 GetWindowText(sWindowTitle);
187 CString pathText = g_Git.m_CurrentDir;
188 if (!m_path1.IsEmpty())
189 pathText = g_Git.CombinePath(m_path1);
190 CAppUtils::SetWindowTitle(m_hWnd, pathText, sWindowTitle);
192 this->m_ctrRev1Edit.Init();
193 this->m_ctrRev2Edit.Init();
195 m_tooltips.AddTool(IDC_SWITCHLEFTRIGHT, IDS_FILEDIFF_SWITCHLEFTRIGHT_TT);
197 m_cFileList.SetRedraw(false);
198 m_cFileList.DeleteAllItems();
199 DWORD exStyle = LVS_EX_DOUBLEBUFFER | LVS_EX_INFOTIP;
200 if (CRegDWORD(L"Software\\TortoiseGit\\FullRowSelect", TRUE))
201 exStyle |= LVS_EX_FULLROWSELECT;
202 m_cFileList.SetExtendedStyle(exStyle);
204 m_nIconFolder = SYS_IMAGE_LIST().GetDirIconIndex();
205 m_cFileList.SetImageList(&SYS_IMAGE_LIST(), LVSIL_SMALL);
207 int iconWidth = GetSystemMetrics(SM_CXSMICON);
208 int iconHeight = GetSystemMetrics(SM_CYSMICON);
209 m_SwitchButton.SetImage(CCommonAppUtils::LoadIconEx(IDI_SWITCHLEFTRIGHT, iconWidth, iconHeight));
210 m_SwitchButton.Invalidate();
212 m_cFilter.SetCancelBitmaps(IDI_CANCELNORMAL, IDI_CANCELPRESSED, 14, 14);
213 m_cFilter.SetInfoIcon(IDI_LOGFILTER, 19, 19);
214 temp.LoadString(IDS_FILEDIFF_FILTERCUE);
215 temp = L" " + temp;
216 m_cFilter.SetCueBanner(temp);
217 if (!m_sFilter.IsEmpty())
218 m_cFilter.SetWindowText(m_sFilter);
220 int c = m_cFileList.GetHeaderCtrl()->GetItemCount() - 1;
221 while (c>=0)
222 m_cFileList.DeleteColumn(c--);
224 temp.LoadString(IDS_FILEDIFF_FILE);
225 m_cFileList.InsertColumn(0, temp);
226 temp.LoadString(IDS_FILEDIFF_EXT);
227 m_cFileList.InsertColumn(1, temp);
228 temp.LoadString(IDS_FILEDIFF_ACTION);
229 m_cFileList.InsertColumn(2, temp);
231 temp.LoadString(IDS_FILEDIFF_STATADD);
232 m_cFileList.InsertColumn(3, temp);
233 temp.LoadString(IDS_FILEDIFF_STATDEL);
234 m_cFileList.InsertColumn(4, temp);
236 int mincol = 0;
237 int maxcol = m_cFileList.GetHeaderCtrl()->GetItemCount() - 1;
238 int col;
239 for (col = mincol; col <= maxcol; col++)
240 m_cFileList.SetColumnWidth(col,LVSCW_AUTOSIZE_USEHEADER);
242 m_cFileList.SetRedraw(true);
244 AddAnchor(IDC_DIFFSTATIC1, TOP_LEFT, TOP_RIGHT);
245 AddAnchor(IDC_SWITCHLEFTRIGHT, TOP_RIGHT);
246 AddAnchor(IDC_FIRSTURL, TOP_LEFT, TOP_RIGHT);
247 AddAnchor(IDC_REV1BTN, TOP_RIGHT);
248 //AddAnchor(IDC_DIFFSTATIC2, TOP_LEFT, TOP_RIGHT);
249 AddAnchor(IDC_SECONDURL, TOP_LEFT, TOP_RIGHT);
250 AddAnchor(IDC_REV2BTN, TOP_RIGHT);
251 AddAnchor(IDC_FILTER, TOP_LEFT, TOP_RIGHT);
252 AddAnchor(IDC_FILELIST, TOP_LEFT, BOTTOM_RIGHT);
253 AddAnchor(IDC_REV1GROUP,TOP_LEFT,TOP_RIGHT);
254 AddAnchor(IDC_REV2GROUP,TOP_LEFT,TOP_RIGHT);
255 AddAnchor(IDC_REV1EDIT,TOP_LEFT);
256 AddAnchor(IDC_REV2EDIT,TOP_LEFT);
257 AddAnchor(IDC_DIFFOPTION, TOP_RIGHT);
258 AddAnchor(IDC_LOG, TOP_RIGHT);
260 EnableSaveRestore(L"FileDiffDlg");
262 m_bIsBare = GitAdminDir::IsBareRepo(g_Git.m_CurrentDir);
264 if(this->m_strRev1.IsEmpty())
265 this->m_ctrRev1Edit.SetWindowText(this->m_rev1.m_CommitHash.ToString());
266 else
268 if (m_rev1.GetCommit(m_strRev1))
270 CString msg;
271 msg.Format(IDS_PROC_REFINVALID, (LPCTSTR)m_strRev1);
272 m_cFileList.ShowText(msg + L'\n' + m_rev1.GetLastErr());
275 this->m_ctrRev1Edit.SetWindowText(m_strRev1);
278 if(this->m_strRev2.IsEmpty())
279 this->m_ctrRev2Edit.SetWindowText(this->m_rev2.m_CommitHash.ToString());
280 else
282 if (m_rev2.GetCommit(m_strRev2))
284 CString msg;
285 msg.Format(IDS_PROC_REFINVALID, (LPCTSTR)m_strRev2);
286 m_cFileList.ShowText(msg + L'\n' + m_rev1.GetLastErr());
289 this->m_ctrRev2Edit.SetWindowText(m_strRev2);
292 SetURLLabels();
294 InterlockedExchange(&m_bThreadRunning, TRUE);
295 if (!AfxBeginThread(DiffThreadEntry, this))
297 InterlockedExchange(&m_bThreadRunning, FALSE);
298 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
301 InterlockedExchange(&m_bLoadingRef, TRUE);
302 if (!AfxBeginThread(LoadRefThreadEntry, this))
304 InterlockedExchange(&m_bLoadingRef, FALSE);
305 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
308 this->m_cRev1Btn.AddEntry(CString(MAKEINTRESOURCE(IDS_REFBROWSE)));
309 this->m_cRev1Btn.AddEntry(CString(MAKEINTRESOURCE(IDS_LOG)));
310 this->m_cRev1Btn.AddEntry(CString(MAKEINTRESOURCE(IDS_REFLOG)));
312 this->m_cRev2Btn.AddEntry(CString(MAKEINTRESOURCE(IDS_REFBROWSE)));
313 this->m_cRev2Btn.AddEntry(CString(MAKEINTRESOURCE(IDS_LOG)));
314 this->m_cRev2Btn.AddEntry(CString(MAKEINTRESOURCE(IDS_REFLOG)));
316 // Start with focus on file list
317 GetDlgItem(IDC_FILELIST)->SetFocus();
319 if(m_rev2.m_CommitHash.IsEmpty())
320 m_SwitchButton.EnableWindow(FALSE);
322 m_cDiffOptionsBtn.m_bAlwaysShowArrow = true;
324 KillTimer(IDT_INPUT);
325 return FALSE;
328 UINT CFileDiffDlg::DiffThreadEntry(LPVOID pVoid)
330 return reinterpret_cast<CFileDiffDlg*>(pVoid)->DiffThread();
333 UINT CFileDiffDlg::DiffThread()
335 SendMessage(WM_DISABLEBUTTONS);
337 if( m_rev1.m_CommitHash.IsEmpty() || m_rev2.m_CommitHash.IsEmpty())
338 g_Git.RefreshGitIndex();
340 g_Git.GetCommitDiffList(m_rev2.m_CommitHash.ToString(), m_rev1.m_CommitHash.ToString(), m_arFileList, m_bIgnoreSpaceAtEol, m_bIgnoreSpaceChange, m_bIgnoreAllSpace, m_bIgnoreBlankLines);
341 Sort();
343 SendMessage(WM_DIFFFINISHED);
345 InterlockedExchange(&m_bThreadRunning, FALSE);
346 return 0;
349 LRESULT CFileDiffDlg::OnDisableButtons(WPARAM, LPARAM)
351 RefreshCursor();
352 m_cFileList.ShowText(CString(MAKEINTRESOURCE(IDS_FILEDIFF_WAIT)));
353 m_cFileList.DeleteAllItems();
354 m_arFileList.Clear();
355 EnableInputControl(false);
356 return 0;
359 LRESULT CFileDiffDlg::OnDiffFinished(WPARAM, LPARAM)
361 CString sFilterText;
362 m_cFilter.GetWindowText(sFilterText);
363 m_cFileList.SetRedraw(false);
364 Filter(sFilterText);
365 if (!m_arFileList.IsEmpty())
367 // Highlight first entry in file list
368 m_cFileList.SetSelectionMark(0);
369 m_cFileList.SetItemState(0, LVIS_SELECTED, LVIS_SELECTED);
372 int mincol = 0;
373 int maxcol = m_cFileList.GetHeaderCtrl()->GetItemCount() - 1;
374 int col;
375 for (col = mincol; col <= maxcol; ++col)
376 m_cFileList.SetColumnWidth(col, LVSCW_AUTOSIZE_USEHEADER);
378 m_cFileList.ClearText();
379 if (m_arFileList.IsEmpty())
380 m_cFileList.ShowText(CString(MAKEINTRESOURCE(IDS_COMPAREREV_NODIFF)));
381 m_cFileList.SetRedraw(true);
383 InvalidateRect(nullptr);
384 RefreshCursor();
385 EnableInputControl(true);
386 return 0;
389 int CFileDiffDlg::AddEntry(const CTGitPath * fd)
391 int ret = -1;
392 if (fd)
394 int index = m_cFileList.GetItemCount();
396 int icon_idx = 0;
397 if (fd->IsDirectory())
398 icon_idx = m_nIconFolder;
399 else
400 icon_idx = SYS_IMAGE_LIST().GetPathIconIndex(fd->GetGitPathString());
402 ret = m_cFileList.InsertItem(index, fd->GetGitPathString(), icon_idx);
403 m_cFileList.SetItemText(index, 1, fd->GetFileExtension());
404 m_cFileList.SetItemText(index, 2, fd->GetActionName());
405 m_cFileList.SetItemText(index, 3, fd->m_StatAdd);
406 m_cFileList.SetItemText(index, 4, fd->m_StatDel);
408 return ret;
411 void CFileDiffDlg::EnableInputControl(bool b)
413 this->m_ctrRev1Edit.EnableWindow(b);
414 this->m_ctrRev2Edit.EnableWindow(b);
415 this->m_cRev1Btn.EnableWindow(b);
416 this->m_cRev2Btn.EnableWindow(b);
417 m_cFilter.EnableWindow(b);
418 m_SwitchButton.EnableWindow(b);
419 GetDlgItem(IDC_LOG)->EnableWindow(b && !(m_rev1.m_CommitHash.IsEmpty() || m_rev2.m_CommitHash.IsEmpty()));
422 void CFileDiffDlg::DoDiff(int selIndex, bool blame)
424 CTGitPath* fd2 = m_arFilteredList[selIndex];
425 CTGitPath* fd1 = fd2;
426 if (m_rev2.m_CommitHash.IsEmpty() && g_Git.IsInitRepos())
428 CGitDiff::DiffNull(GetSafeHwnd(), fd2, GIT_REV_ZERO, true, 0, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
429 return;
431 if (fd1->m_Action & CTGitPath::LOGACTIONS_ADDED)
433 CGitDiff::DiffNull(GetSafeHwnd(), fd1, m_rev2.m_CommitHash.ToString(), true, 0, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
434 return;
436 if (fd1->m_Action & CTGitPath::LOGACTIONS_DELETED)
438 CGitDiff::DiffNull(GetSafeHwnd(), fd1, m_rev1.m_CommitHash.ToString(), false, 0, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
439 return;
441 if (fd1->m_Action & CTGitPath::LOGACTIONS_REPLACED)
442 fd2 = new CTGitPath(fd1->GetGitOldPathString());
443 CGitDiff::Diff(GetSafeHwnd(), fd1, fd2, m_rev2.m_CommitHash.ToString(), m_rev1.m_CommitHash.ToString(), blame, FALSE, 0, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
444 if (fd1 != fd2)
445 delete fd2;
449 void CFileDiffDlg::OnNMDblclkFilelist(NMHDR *pNMHDR, LRESULT *pResult)
451 *pResult = 0;
452 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
453 int selIndex = pNMLV->iItem;
454 if (selIndex < 0)
455 return;
456 if (selIndex >= (int)m_arFilteredList.size())
457 return;
459 DoDiff(selIndex, m_bBlame);
462 void CFileDiffDlg::OnLvnGetInfoTipFilelist(NMHDR *pNMHDR, LRESULT *pResult)
464 LPNMLVGETINFOTIP pGetInfoTip = reinterpret_cast<LPNMLVGETINFOTIP>(pNMHDR);
465 if (pGetInfoTip->iItem >= (int)m_arFilteredList.size())
466 return;
468 CString path = m_path1.GetGitPathString() + L'/' + m_arFilteredList[pGetInfoTip->iItem]->GetGitPathString();
469 if (pGetInfoTip->cchTextMax > path.GetLength())
470 wcsncpy_s(pGetInfoTip->pszText, pGetInfoTip->cchTextMax, path, pGetInfoTip->cchTextMax - 1);
472 *pResult = 0;
475 void CFileDiffDlg::OnNMCustomdrawFilelist(NMHDR *pNMHDR, LRESULT *pResult)
477 NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>( pNMHDR );
478 // Take the default processing unless we set this to something else below.
479 *pResult = CDRF_DODEFAULT;
481 // First thing - check the draw stage. If it's the control's prepaint
482 // stage, then tell Windows we want messages for every item.
484 if ( CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage )
486 *pResult = CDRF_NOTIFYITEMDRAW;
488 else if ( CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage )
490 // This is the prepaint stage for an item. Here's where we set the
491 // item's text color. Our return value will tell Windows to draw the
492 // item itself, but it will use the new color we set here.
494 // Tell Windows to paint the control itself.
495 *pResult = CDRF_DODEFAULT;
497 COLORREF crText = GetSysColor(COLOR_WINDOWTEXT);
499 if (m_arFilteredList.size() > pLVCD->nmcd.dwItemSpec)
501 CTGitPath * fd = m_arFilteredList[pLVCD->nmcd.dwItemSpec];
502 switch (fd->m_Action)
504 case CTGitPath::LOGACTIONS_ADDED:
505 crText = m_colors.GetColor(CColors::Added);
506 break;
507 case CTGitPath::LOGACTIONS_DELETED:
508 crText = m_colors.GetColor(CColors::Deleted);
509 break;
510 case CTGitPath::LOGACTIONS_MODIFIED:
511 crText = m_colors.GetColor(CColors::Modified);
512 break;
513 default:
514 crText = m_colors.GetColor(CColors::PropertyChanged);
515 break;
518 // Store the color back in the NMLVCUSTOMDRAW struct.
519 pLVCD->clrText = crText;
523 UINT CFileDiffDlg::LoadRefThread()
525 g_Git.GetBranchList(m_Reflist, nullptr, CGit::BRANCH_ALL_F);
526 g_Git.GetTagList(m_Reflist);
528 this->PostMessage(MSG_REF_LOADED);
529 InterlockedExchange(&m_bLoadingRef, FALSE);
530 return 0;
533 void CFileDiffDlg::OnContextMenu(CWnd* pWnd, CPoint point)
535 if (!pWnd || pWnd != &m_cFileList)
536 return;
537 if (m_cFileList.GetSelectedCount() == 0)
538 return;
539 // if the context menu is invoked through the keyboard, we have to use
540 // a calculated position on where to anchor the menu on
541 if ((point.x == -1) && (point.y == -1))
543 CRect rect;
544 m_cFileList.GetItemRect(m_cFileList.GetSelectionMark(), &rect, LVIR_LABEL);
545 m_cFileList.ClientToScreen(&rect);
546 point = rect.CenterPoint();
548 CIconMenu popup;
549 if (popup.CreatePopupMenu())
551 int firstEntry = -1;
552 POSITION firstPos = m_cFileList.GetFirstSelectedItemPosition();
553 if (firstPos)
554 firstEntry = m_cFileList.GetNextSelectedItem(firstPos);
556 CString menuText;
557 popup.AppendMenuIcon(ID_COMPARE, IDS_LOG_POPUP_COMPARETWO, IDI_DIFF);
558 popup.SetDefaultItem(ID_COMPARE, FALSE);
559 popup.AppendMenuIcon(ID_GNUDIFFCOMPARE, IDS_LOG_POPUP_GNUDIFF, IDI_DIFF);
560 popup.AppendMenu(MF_SEPARATOR, NULL);
561 if (!m_bIsBare)
563 if (!m_rev1.m_CommitHash.IsEmpty())
565 menuText.Format(IDS_FILEDIFF_POPREVERTTOREV, (LPCTSTR)m_rev1.m_CommitHash.ToString().Left(g_Git.GetShortHASHLength()));
566 popup.AppendMenuIcon(ID_REVERT1, menuText, IDI_REVERT);
568 if (!m_rev2.m_CommitHash.IsEmpty())
570 menuText.Format(IDS_FILEDIFF_POPREVERTTOREV, (LPCTSTR)m_rev2.m_CommitHash.ToString().Left(g_Git.GetShortHASHLength()));
571 popup.AppendMenuIcon(ID_REVERT2, menuText, IDI_REVERT);
573 popup.AppendMenu(MF_SEPARATOR, NULL);
575 popup.AppendMenuIcon(ID_LOG, IDS_FILEDIFF_LOG, IDI_LOG);
576 if (firstEntry >= 0 && !m_arFilteredList[firstEntry]->IsDirectory())
578 if (!m_bIsBare)
580 popup.AppendMenuIcon(ID_BLAME, IDS_FILEDIFF_POPBLAME, IDI_BLAME);
581 popup.AppendMenu(MF_SEPARATOR, NULL);
583 popup.AppendMenuIcon(ID_EXPORT, IDS_FILEDIFF_POPEXPORT, IDI_EXPORT);
585 else if (firstEntry >= 0)
586 popup.AppendMenuIcon(ID_LOGSUBMODULE, IDS_MENULOGSUBMODULE, IDI_LOG);
587 popup.AppendMenu(MF_SEPARATOR, NULL);
588 popup.AppendMenuIcon(ID_SAVEAS, IDS_FILEDIFF_POPSAVELIST, IDI_SAVEAS);
589 popup.AppendMenuIcon(ID_CLIPBOARD_PATH, IDS_STATUSLIST_CONTEXT_COPY, IDI_COPYCLIP);
590 popup.AppendMenuIcon(ID_CLIPBOARD_ALL, IDS_STATUSLIST_CONTEXT_COPYEXT, IDI_COPYCLIP);
592 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this);
593 switch (cmd)
595 case ID_COMPARE:
597 if (!CheckMultipleDiffs())
598 break;
599 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
600 while (pos)
602 int index = m_cFileList.GetNextSelectedItem(pos);
603 DoDiff(index, false);
606 break;
607 case ID_GNUDIFFCOMPARE:
609 if (!CheckMultipleDiffs())
610 break;
611 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
612 while (pos)
614 CTGitPath *fd2 = m_arFilteredList[m_cFileList.GetNextSelectedItem(pos)];
615 CTGitPath *fd1 = fd2;
616 if (fd1->m_Action & CTGitPath::LOGACTIONS_REPLACED)
617 fd2 = new CTGitPath(fd2->GetGitOldPathString());
618 CAppUtils::StartShowUnifiedDiff(m_hWnd, *fd1, m_rev1.m_CommitHash.ToString(), *fd2, m_rev2.m_CommitHash.ToString(), !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
619 if (fd1 != fd2)
620 delete fd2;
623 break;
624 case ID_REVERT1:
625 RevertSelectedItemToVersion(m_rev1.m_CommitHash.ToString());
626 break;
627 case ID_REVERT2:
628 RevertSelectedItemToVersion(m_rev2.m_CommitHash.ToString());
629 break;
630 case ID_BLAME:
632 if (!CheckMultipleDiffs())
633 break;
634 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
635 while (pos)
637 int index = m_cFileList.GetNextSelectedItem(pos);
638 if (m_arFilteredList[index]->m_Action & CTGitPath::LOGACTIONS_DELETED)
640 if (!m_rev1.m_CommitHash.IsEmpty())
641 CAppUtils::LaunchTortoiseBlame(m_arFilteredList[index]->GetWinPathString(), m_rev1.m_CommitHash.ToString());
642 continue;
644 if (m_rev2.m_CommitHash.IsEmpty() && (m_arFilteredList[index]->m_Action & CTGitPath::LOGACTIONS_ADDED))
645 continue;
646 if (m_rev2.m_CommitHash.IsEmpty() && (m_arFilteredList[index]->m_Action & CTGitPath::LOGACTIONS_REPLACED))
648 CAppUtils::LaunchTortoiseBlame(m_arFilteredList[index]->GetGitOldPathString(), m_rev1.m_CommitHash.ToString());
649 continue;
651 CAppUtils::LaunchTortoiseBlame(m_arFilteredList[index]->GetWinPathString(), m_rev2.m_CommitHash.ToString());
654 break;
655 case ID_LOG:
656 case ID_LOGSUBMODULE:
658 if (!CheckMultipleDiffs())
659 break;
660 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
661 while (pos)
663 int index = m_cFileList.GetNextSelectedItem(pos);
664 CString sCmd = L"/command:log";
665 if (sCmd == ID_LOGSUBMODULE)
666 sCmd += L" /submodule";
667 sCmd += L" /path:\"" + m_arFilteredList[index]->GetWinPathString() + L"\" ";
668 sCmd += L" /endrev:" + m_rev2.m_CommitHash.ToString();
669 CAppUtils::RunTortoiseGitProc(sCmd);
672 break;
673 case ID_SAVEAS:
675 if (m_cFileList.GetSelectedCount() > 0)
677 CTGitPath savePath;
678 CString pathSave;
679 if (!CAppUtils::FileOpenSave(pathSave, nullptr, IDS_FILEDIFF_POPSAVELIST, IDS_TEXTFILEFILTER, false, m_hWnd, L"txt"))
680 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 CString temp;
688 if (m_path1.IsEmpty() && m_path2.IsEmpty())
689 temp.FormatMessage(IDS_FILEDIFF_CHANGEDLISTINTROROOT, (LPCTSTR)m_rev1.m_CommitHash.ToString(), (LPCTSTR)m_rev2.m_CommitHash.ToString());
690 else
691 temp.FormatMessage(IDS_FILEDIFF_CHANGEDLISTINTRO, (LPCTSTR)m_path1.GetGitPathString(), (LPCTSTR)m_rev1.m_CommitHash.ToString(), (LPCTSTR)m_path2.GetGitPathString(), (LPCTSTR)m_rev2.m_CommitHash.ToString());
692 file.WriteString(temp + L"\r\n");
693 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
694 while (pos)
696 int index = m_cFileList.GetNextSelectedItem(pos);
697 CTGitPath* fd = m_arFilteredList[index];
698 file.WriteString(fd->GetGitPathString());
699 file.WriteString(L"\r\n");
701 file.Close();
703 catch (CFileException* pE)
705 pE->ReportError();
709 break;
710 case ID_CLIPBOARD_PATH:
712 CopySelectionToClipboard();
714 break;
716 case ID_CLIPBOARD_ALL:
718 CopySelectionToClipboard(TRUE);
720 break;
721 case ID_EXPORT:
723 // export all changed files to a folder
724 CBrowseFolder browseFolder;
725 browseFolder.m_style = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS;
726 if (browseFolder.Show(GetSafeHwnd(), m_strExportDir) == CBrowseFolder::OK)
728 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
729 while (pos)
731 int index = m_cFileList.GetNextSelectedItem(pos);
732 CTGitPath* fd = m_arFilteredList[index];
733 // we cannot export directories or folders
734 if (fd->m_Action == CTGitPath::LOGACTIONS_DELETED || fd->IsDirectory())
735 continue;
736 CPathUtils::MakeSureDirectoryPathExists(m_strExportDir + L'\\' + fd->GetContainingDirectory().GetWinPathString());
737 CString filename = m_strExportDir + L'\\' + fd->GetWinPathString();
738 if (m_rev2.m_CommitHash.ToString() == GIT_REV_ZERO)
740 if(!CopyFile(g_Git.CombinePath(fd), filename, false))
742 MessageBox(CFormatMessageWrapper(), L"TortoiseGit", MB_OK | MB_ICONERROR);
743 return;
746 else
748 if (g_Git.GetOneFile(m_rev2.m_CommitHash.ToString(), *fd, filename))
750 CString out;
751 out.FormatMessage(IDS_STATUSLIST_CHECKOUTFILEFAILED, (LPCTSTR)fd->GetGitPathString(), (LPCTSTR)m_rev2.m_CommitHash.ToString(), (LPCTSTR)filename);
752 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)
753 return;
760 break;
766 BOOL CFileDiffDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
768 if (pWnd != &m_cFileList)
769 return CResizableStandAloneDialog::OnSetCursor(pWnd, nHitTest, message);
770 if (m_bThreadRunning == 0)
772 HCURSOR hCur = LoadCursor(nullptr, IDC_ARROW);
773 SetCursor(hCur);
774 return CResizableStandAloneDialog::OnSetCursor(pWnd, nHitTest, message);
776 HCURSOR hCur = LoadCursor(nullptr, IDC_WAIT);
777 SetCursor(hCur);
778 return TRUE;
781 void CFileDiffDlg::OnEnSetfocusFirsturl()
783 GetDlgItem(IDC_FIRSTURL)->HideCaret();
786 void CFileDiffDlg::OnEnSetfocusSecondurl()
788 GetDlgItem(IDC_SECONDURL)->HideCaret();
791 void CFileDiffDlg::OnBnClickedSwitchleftright()
793 if (m_bThreadRunning)
794 return;
796 #if 0
797 CString sFilterString;
798 m_cFilter.GetWindowText(sFilterString);
800 m_cFileList.SetRedraw(false);
801 m_cFileList.DeleteAllItems();
802 for (int i=0; i<(int)m_arFileList.GetCount(); ++i)
804 CTGitPath fd = m_arFileList[i];
805 if (fd.m_Action == CTGitPath::LOGACTIONS_ADDED)
806 fd.m_Action = CTGitPath::LOGACTIONS_DELETED;
807 else if (fd.m_Action == CTGitPath::LOGACTIONS_DELETED)
808 fd.m_Action = CTGitPath::LOGACTIONS_ADDED;
809 std::swap(fd.m_StatAdd, fd.m_StatDel);
810 (CTGitPath&)m_arFileList[i] = fd;
812 Filter(sFilterString);
813 #endif
815 m_cFileList.SetRedraw(true);
816 CTGitPath path = m_path1;
817 m_path1 = m_path2;
818 m_path2 = path;
819 GitRev rev = m_rev1;
820 m_rev1 = m_rev2;
821 m_rev2 = rev;
823 CString str1,str2;
824 this->m_ctrRev1Edit.GetWindowText(str1);
825 this->m_ctrRev2Edit.GetWindowText(str2);
827 this->m_ctrRev1Edit.SetWindowText(str2);
828 this->m_ctrRev2Edit.SetWindowText(str1);
830 SetURLLabels();
831 //KillTimer(IDT_INPUT);
834 void CFileDiffDlg::SetURLLabels(int mask)
836 if(mask &0x1)
838 SetDlgItemText(IDC_FIRSTURL, m_rev1.m_CommitHash.ToString().Left(g_Git.GetShortHASHLength()) + L": " + m_rev1.GetSubject());
839 if (!m_rev1.m_CommitHash.IsEmpty())
840 m_tooltips.AddTool(IDC_FIRSTURL,
841 CLoglistUtils::FormatDateAndTime(m_rev1.GetAuthorDate(), DATE_SHORTDATE) + L" " + m_rev1.GetAuthorName());
844 if(mask &0x2)
846 SetDlgItemText(IDC_SECONDURL, m_rev2.m_CommitHash.ToString().Left(g_Git.GetShortHASHLength()) + L": " + m_rev2.GetSubject());
847 if (!m_rev2.m_CommitHash.IsEmpty())
848 m_tooltips.AddTool(IDC_SECONDURL,
849 CLoglistUtils::FormatDateAndTime(m_rev2.GetAuthorDate(), DATE_SHORTDATE) + L" " + m_rev2.GetAuthorName());
852 this->GetDlgItem(IDC_REV1GROUP)->SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_FILEDIFF_VERSION1BASE)));
853 this->GetDlgItem(IDC_REV2GROUP)->SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_FILEDIFF_VERSION2)));
855 if ((mask & 0x3) == 0x3 && !m_rev1.m_CommitHash.IsEmpty() && !m_rev2.m_CommitHash.IsEmpty())
856 if(m_rev1.GetCommitterDate() > m_rev2.GetCommitterDate())
857 GetDlgItem(IDC_REV1GROUP)->SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_FILEDIFF_VERSION1BASENEWER)));
858 else if (m_rev1.GetCommitterDate() < m_rev2.GetCommitterDate())
859 GetDlgItem(IDC_REV2GROUP)->SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_FILEDIFF_VERSION2NEWER)));
862 void CFileDiffDlg::ClearURLabels(int mask)
864 if(mask&0x1)
866 SetDlgItemText(IDC_FIRSTURL, L"");
867 m_tooltips.AddTool(IDC_FIRSTURL, L"");
870 if(mask&0x2)
872 SetDlgItemText(IDC_SECONDURL, L"");
873 m_tooltips.AddTool(IDC_SECONDURL, L"");
876 BOOL CFileDiffDlg::PreTranslateMessage(MSG* pMsg)
878 if (pMsg->message == WM_KEYDOWN)
880 switch (pMsg->wParam)
882 case 'A':
884 if (GetFocus() != GetDlgItem(IDC_FILELIST))
885 break;
886 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
888 // select all entries
889 for (int i=0; i<m_cFileList.GetItemCount(); ++i)
890 m_cFileList.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
891 return TRUE;
894 break;
895 case 'C':
896 case VK_INSERT:
898 if (GetFocus() != GetDlgItem(IDC_FILELIST))
899 break;
900 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
902 CopySelectionToClipboard();
903 return TRUE;
906 break;
907 case '\r':
909 if (GetFocus() == GetDlgItem(IDC_FILELIST))
911 // Return pressed in file list. Show diff, as for double click
912 int selIndex = m_cFileList.GetSelectionMark();
913 if ((selIndex >= 0) && (selIndex < (int)m_arFileList.GetCount()))
914 DoDiff(selIndex, m_bBlame);
915 return TRUE;
918 break;
919 case VK_F5:
921 OnTimer(IDT_INPUT);
923 break;
924 case VK_ESCAPE:
925 if (GetFocus() == GetDlgItem(IDC_FILTER) && m_cFilter.GetWindowTextLength())
927 m_cFilter.SetWindowText(L"");
928 OnClickedCancelFilter(NULL, NULL);
929 return TRUE;
931 break;
934 return __super::PreTranslateMessage(pMsg);
937 void CFileDiffDlg::OnCancel()
939 if (m_bThreadRunning)
940 return;
941 __super::OnCancel();
944 void CFileDiffDlg::OnHdnItemclickFilelist(NMHDR *pNMHDR, LRESULT *pResult)
946 LPNMHEADER phdr = reinterpret_cast<LPNMHEADER>(pNMHDR);
947 if (m_bThreadRunning)
948 return;
950 if (m_nSortedColumn == phdr->iItem)
951 m_bAscending = !m_bAscending;
952 else
953 m_bAscending = TRUE;
954 m_nSortedColumn = phdr->iItem;
955 Sort();
957 CString temp;
958 m_cFileList.SetRedraw(FALSE);
959 m_cFileList.DeleteAllItems();
960 m_cFilter.GetWindowText(temp);
961 Filter(temp);
963 CHeaderCtrl * pHeader = m_cFileList.GetHeaderCtrl();
964 HDITEM HeaderItem = {0};
965 HeaderItem.mask = HDI_FORMAT;
966 for (int i=0; i<pHeader->GetItemCount(); ++i)
968 pHeader->GetItem(i, &HeaderItem);
969 HeaderItem.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
970 pHeader->SetItem(i, &HeaderItem);
972 pHeader->GetItem(m_nSortedColumn, &HeaderItem);
973 HeaderItem.fmt |= (m_bAscending ? HDF_SORTUP : HDF_SORTDOWN);
974 pHeader->SetItem(m_nSortedColumn, &HeaderItem);
976 m_cFileList.SetRedraw(TRUE);
978 *pResult = 0;
981 void CFileDiffDlg::Sort()
983 if(m_arFileList.GetCount() < 2)
984 return;
986 std::sort(m_arFileList.m_paths.begin(), m_arFileList.m_paths.end(), &CFileDiffDlg::SortCompare);
989 bool CFileDiffDlg::SortCompare(const CTGitPath& Data1, const CTGitPath& Data2)
991 int result = 0;
992 int d1, d2;
993 switch (m_nSortedColumn)
995 case 0: //path column
996 result = Data1.GetWinPathString().Compare(Data2.GetWinPathString());
997 break;
998 case 1: //extension column
999 result = Data1.GetFileExtension().Compare(Data2.GetFileExtension());
1000 break;
1001 case 2: //action column
1002 result = Data1.m_Action - Data2.m_Action;
1003 break;
1004 case 3:
1005 d1 = CSorter::A2L(Data1.m_StatAdd);
1006 d2 = CSorter::A2L(Data2.m_StatAdd);
1007 result = d1 - d2;
1008 break;
1009 case 4:
1010 d1 = CSorter::A2L(Data1.m_StatDel);;
1011 d2 = CSorter::A2L(Data2.m_StatDel);
1012 result = d1 - d2;
1013 break;
1014 default:
1015 break;
1017 // sort by path name as second priority
1018 if (m_nSortedColumn != 0 && result == 0)
1019 result = Data1.GetWinPathString().Compare(Data2.GetWinPathString());
1021 if (!m_bAscending)
1022 result = -result;
1023 return result < 0;
1027 void CFileDiffDlg::OnBnClickedRev1btn()
1029 ClickRevButton(&this->m_cRev1Btn,&this->m_rev1, &this->m_ctrRev1Edit);
1032 void CFileDiffDlg::ClickRevButton(CMenuButton *button, GitRev *rev, CACEdit *edit)
1034 INT_PTR entry=button->GetCurrentEntry();
1035 if(entry == 0) /* Browse Refence*/
1038 CString str = CBrowseRefsDlg::PickRef();
1039 if(str.IsEmpty())
1040 return;
1042 if(FillRevFromString(rev,str))
1043 return;
1045 edit->SetWindowText(str);
1049 if(entry == 1) /*Log*/
1051 CLogDlg dlg;
1052 CString revision;
1053 edit->GetWindowText(revision);
1054 dlg.SetParams(CTGitPath(), CTGitPath(), revision, revision, 0);
1055 dlg.SetSelect(true);
1056 if(dlg.DoModal() == IDOK)
1058 if (dlg.GetSelectedHash().empty())
1059 return;
1061 if (FillRevFromString(rev, dlg.GetSelectedHash().at(0).ToString()))
1062 return;
1064 edit->SetWindowText(dlg.GetSelectedHash().at(0).ToString());
1066 else
1067 return;
1070 if(entry == 2) /*RefLog*/
1072 CRefLogDlg dlg;
1073 if(dlg.DoModal() == IDOK)
1075 if (FillRevFromString(rev, dlg.m_SelectedHash.ToString()))
1076 return;
1078 edit->SetWindowText(dlg.m_SelectedHash.ToString());
1080 else
1081 return;
1084 SetURLLabels();
1086 InterlockedExchange(&m_bThreadRunning, TRUE);
1087 if (!AfxBeginThread(DiffThreadEntry, this))
1089 InterlockedExchange(&m_bThreadRunning, FALSE);
1090 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
1092 KillTimer(IDT_INPUT);
1095 void CFileDiffDlg::OnBnClickedRev2btn()
1097 ClickRevButton(&this->m_cRev2Btn,&this->m_rev2, &this->m_ctrRev2Edit);
1100 LRESULT CFileDiffDlg::OnClickedCancelFilter(WPARAM /*wParam*/, LPARAM /*lParam*/)
1102 if (m_bThreadRunning)
1104 SetTimer(IDT_FILTER, 1000, nullptr);
1105 return 0L;
1108 KillTimer(IDT_FILTER);
1110 m_cFileList.SetRedraw(FALSE);
1111 m_arFilteredList.clear();
1112 m_cFileList.DeleteAllItems();
1114 Filter(L"");
1116 m_cFileList.SetRedraw(TRUE);
1117 return 0L;
1120 void CFileDiffDlg::OnEnChangeFilter()
1122 SetTimer(IDT_FILTER, 1000, nullptr);
1125 void CFileDiffDlg::OnTimer(UINT_PTR nIDEvent)
1127 if (m_bThreadRunning)
1128 return;
1130 if( nIDEvent == IDT_FILTER)
1132 CString sFilterText;
1133 KillTimer(IDT_FILTER);
1134 m_cFilter.GetWindowText(sFilterText);
1136 m_cFileList.SetRedraw(FALSE);
1137 m_cFileList.DeleteAllItems();
1139 Filter(sFilterText);
1141 m_cFileList.SetRedraw(TRUE);
1143 __super::OnTimer(nIDEvent);
1146 if( nIDEvent == IDT_INPUT)
1148 KillTimer(IDT_INPUT);
1149 TRACE(L"Input Timer\r\n");
1151 GitRev gitrev;
1152 CString str;
1153 int mask = 0;
1154 this->m_ctrRev1Edit.GetWindowText(str);
1155 if (!gitrev.GetCommit(str))
1157 m_rev1 = gitrev;
1158 mask |= 0x1;
1160 else
1162 CString msg;
1163 msg.Format(IDS_PROC_REFINVALID, (LPCTSTR)str);
1164 m_cFileList.ShowText(msg + L'\n' + gitrev.GetLastErr());
1167 this->m_ctrRev2Edit.GetWindowText(str);
1169 if (!gitrev.GetCommit(str))
1171 m_rev2 = gitrev;
1172 mask |= 0x2;
1174 else
1176 CString msg;
1177 msg.Format(IDS_PROC_REFINVALID, (LPCTSTR)str);
1178 m_cFileList.ShowText(msg + L'\n' + gitrev.GetLastErr());
1181 this->SetURLLabels(mask);
1183 if(mask == 0x3)
1185 InterlockedExchange(&m_bThreadRunning, TRUE);
1186 if (!AfxBeginThread(DiffThreadEntry, this))
1188 InterlockedExchange(&m_bThreadRunning, FALSE);
1189 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
1195 void CFileDiffDlg::Filter(CString sFilterText)
1197 sFilterText.MakeLower();
1199 m_arFilteredList.clear();
1201 for (int i=0;i<m_arFileList.GetCount();i++)
1203 CString sPath = m_arFileList[i].GetGitPathString();
1204 sPath.MakeLower();
1205 if (sPath.Find(sFilterText) >= 0)
1206 m_arFilteredList.push_back((CTGitPath*)&(m_arFileList[i]));
1208 for (const auto path : m_arFilteredList)
1209 AddEntry(path);
1212 void CFileDiffDlg::CopySelectionToClipboard(BOOL isFull)
1214 // copy all selected paths to the clipboard
1215 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
1216 int index;
1217 CString sTextForClipboard;
1218 while ((index = m_cFileList.GetNextSelectedItem(pos)) >= 0)
1220 sTextForClipboard += m_cFileList.GetItemText(index, 0);
1221 sTextForClipboard += L'\t';
1223 if(!isFull)
1224 sTextForClipboard += L"\r\n";
1225 else
1227 sTextForClipboard += m_cFileList.GetItemText(index, 1);
1228 sTextForClipboard += L'\t';
1229 sTextForClipboard += m_cFileList.GetItemText(index, 2);
1230 sTextForClipboard += L'\t';
1231 sTextForClipboard += m_cFileList.GetItemText(index, 3);
1232 sTextForClipboard += L'\t';
1233 sTextForClipboard += m_cFileList.GetItemText(index, 4);
1234 sTextForClipboard += L"\r\n";
1237 CStringUtils::WriteAsciiStringToClipboard(sTextForClipboard);
1241 LRESULT CFileDiffDlg::OnRefLoad(WPARAM /*wParam*/, LPARAM /*lParam*/)
1243 for (size_t i = 0; i < m_Reflist.size(); ++i)
1245 CString str=m_Reflist[i];
1247 if (CStringUtils::StartsWith(str, L"remotes/"))
1248 str = str.Mid((int)wcslen(L"remotes/"));
1250 m_ctrRev1Edit.AddSearchString(str);
1251 m_ctrRev2Edit.AddSearchString(str);
1253 return 0;
1256 BOOL CFileDiffDlg::DestroyWindow()
1258 return CResizableStandAloneDialog::DestroyWindow();
1261 LRESULT CFileDiffDlg::OnEnUpdate(WPARAM /*wParam*/, LPARAM lParam)
1263 if(lParam == IDC_REV1EDIT)
1265 OnTextUpdate(&this->m_ctrRev1Edit);
1266 ClearURLabels(1);
1268 if(lParam == IDC_REV2EDIT)
1270 OnTextUpdate(&this->m_ctrRev2Edit);
1271 ClearURLabels(1<<1);
1273 return 0;
1276 void CFileDiffDlg::OnTextUpdate(CACEdit * /*pEdit*/)
1278 SetTimer(IDT_INPUT, 1000, nullptr);
1279 this->m_cFileList.ShowText(L"Wait For input validate version");
1282 int CFileDiffDlg::RevertSelectedItemToVersion(CString rev)
1284 if (rev.IsEmpty() || rev == GIT_REV_ZERO)
1285 return 0;
1287 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
1288 int index;
1289 int count = 0;
1290 while ((index = m_cFileList.GetNextSelectedItem(pos)) >= 0)
1292 CString cmd, out;
1293 CTGitPath* fentry = m_arFilteredList[index];
1294 cmd.Format(L"git.exe checkout %s -- \"%s\"", (LPCTSTR)rev, (LPCTSTR)fentry->GetGitPathString());
1295 if (g_Git.Run(cmd, &out, CP_UTF8))
1297 if (CMessageBox::Show(GetSafeHwnd(), out, L"TortoiseGit", 2, IDI_WARNING, CString(MAKEINTRESOURCE(IDS_IGNOREBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
1298 break;
1300 else
1301 count++;
1304 CString out;
1305 out.FormatMessage(IDS_STATUSLIST_FILESREVERTED, count, (LPCTSTR)rev);
1306 CMessageBox::Show(GetSafeHwnd(), out, L"TortoiseGit", MB_OK);
1307 return 0;
1310 static void AppendMenuChecked(CMenu &menu, UINT nTextID, UINT_PTR nItemID, BOOL checked = FALSE, BOOL enabled = TRUE)
1312 CString text;
1313 text.LoadString(nTextID);
1314 menu.AppendMenu(MF_STRING | (enabled ? MF_ENABLED : MF_DISABLED) | (checked ? MF_CHECKED : MF_UNCHECKED), nItemID, text);
1317 #define DIFFOPTION_IGNORESPACEATEOL 1
1318 #define DIFFOPTION_IGNORESPACECHANGE 2
1319 #define DIFFOPTION_IGNOREALLSPACE 3
1320 #define DIFFOPTION_IGNORBLANKLINES 4
1322 void CFileDiffDlg::OnBnClickedDiffoption()
1324 CMenu popup;
1325 if (popup.CreatePopupMenu())
1327 m_cDiffOptionsBtn.SetCheck(BST_CHECKED);
1328 AppendMenuChecked(popup, IDS_DIFFOPTION_IGNORESPACEATEOL, DIFFOPTION_IGNORESPACEATEOL, m_bIgnoreSpaceAtEol);
1329 AppendMenuChecked(popup, IDS_DIFFOPTION_IGNORESPACECHANGE, DIFFOPTION_IGNORESPACECHANGE, m_bIgnoreSpaceChange);
1330 AppendMenuChecked(popup, IDS_DIFFOPTION_IGNOREALLSPACE, DIFFOPTION_IGNOREALLSPACE, m_bIgnoreAllSpace);
1331 AppendMenuChecked(popup, IDS_DIFFOPTION_IGNORBLANKLINES, DIFFOPTION_IGNORBLANKLINES, m_bIgnoreBlankLines);
1333 m_tooltips.Pop();
1334 RECT rect;
1335 GetDlgItem(IDC_DIFFOPTION)->GetWindowRect(&rect);
1336 int selection = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, rect.left, rect.bottom, this);
1337 switch (selection)
1339 case DIFFOPTION_IGNORESPACEATEOL:
1340 m_bIgnoreSpaceAtEol = !m_bIgnoreSpaceAtEol;
1341 OnTimer(IDT_INPUT);
1342 break;
1343 case DIFFOPTION_IGNORESPACECHANGE:
1344 m_bIgnoreSpaceChange = !m_bIgnoreSpaceChange;
1345 OnTimer(IDT_INPUT);
1346 break;
1347 case DIFFOPTION_IGNOREALLSPACE:
1348 m_bIgnoreAllSpace = !m_bIgnoreAllSpace;
1349 OnTimer(IDT_INPUT);
1350 break;
1351 case DIFFOPTION_IGNORBLANKLINES:
1352 m_bIgnoreBlankLines = !m_bIgnoreBlankLines;
1353 OnTimer(IDT_INPUT);
1354 break;
1355 default:
1356 break;
1358 UpdateData(FALSE);
1359 m_cDiffOptionsBtn.SetCheck((m_bIgnoreSpaceAtEol || m_bIgnoreSpaceChange || m_bIgnoreAllSpace || m_bIgnoreBlankLines) ? BST_CHECKED : BST_UNCHECKED);
1363 void CFileDiffDlg::OnBnClickedLog()
1365 CLogDlg dlg;
1366 dlg.SetRange(m_rev1.m_CommitHash.ToString() + L".." + m_rev2.m_CommitHash.ToString());
1367 dlg.DoModal();
1370 bool CFileDiffDlg::CheckMultipleDiffs()
1372 UINT selCount = m_cFileList.GetSelectedCount();
1373 if (selCount > max(3, (DWORD)CRegDWORD(L"Software\\TortoiseGit\\NumDiffWarning", 10)))
1375 CString message;
1376 message.Format(IDS_STATUSLIST_WARN_MAXDIFF, selCount);
1377 return ::MessageBox(GetSafeHwnd(), message, L"TortoiseGit", MB_YESNO | MB_ICONQUESTION) == IDYES;
1379 return true;
1382 void CFileDiffDlg::OnLvnBegindrag(NMHDR* pNMHDR, LRESULT* pResult)
1384 *pResult = 0;
1386 // get selected paths
1387 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
1388 if (!pos)
1389 return;
1391 CTGitPathList toExport;
1392 int index = -1;
1393 while ((index = m_cFileList.GetNextSelectedItem(pos)) >= 0)
1395 auto fentry = m_arFilteredList[index];
1396 toExport.AddPath(*fentry);
1400 // build copy source / content
1401 auto pdsrc = std::make_unique<CIDropSource>();
1402 if (!pdsrc)
1403 return;
1405 pdsrc->AddRef();
1407 GitDataObject* pdobj = new GitDataObject(toExport, m_rev2.m_CommitHash);
1408 if (!pdobj)
1409 return;
1410 pdobj->AddRef();
1411 pdobj->SetAsyncMode(TRUE);
1412 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1413 CDragSourceHelper dragsrchelper;
1414 dragsrchelper.InitializeFromWindow(GetSafeHwnd(), pNMLV->ptAction, pdobj);
1415 pdsrc->m_pIDataObj = pdobj;
1416 pdsrc->m_pIDataObj->AddRef();
1418 // Initiate the Drag & Drop
1419 DWORD dwEffect;
1420 ::DoDragDrop(pdobj, pdsrc.get(), DROPEFFECT_MOVE | DROPEFFECT_COPY, &dwEffect);
1421 pdsrc->Release();
1422 pdsrc.release();
1423 pdobj->Release();