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.
21 #include "TortoiseProc.h"
22 #include "UnicodeUtils.h"
23 #include "MessageBox.h"
25 #include "SysImageList.h"
27 #include "StringUtils.h"
28 #include "PathUtils.h"
29 #include "BrowseFolder.h"
30 #include "FileDiffDlg.h"
32 #include "LoglistCommonResource.h"
33 #include "LoglistUtils.h"
34 #include "BrowseRefsDlg.h"
36 #include "RefLogDlg.h"
37 #include "GitStatusListCtrl.h"
38 #include "FormatMessageWrapper.h"
39 #include "GitDataObject.h"
45 #define ID_CLIPBOARD_PATH 5
46 #define ID_CLIPBOARD_ALL 6
48 #define ID_GNUDIFFCOMPARE 8
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
)
64 , m_bThreadRunning(FALSE
)
65 , m_bIgnoreSpaceAtEol(false)
66 , m_bIgnoreSpaceChange(false)
67 , m_bIgnoreAllSpace(false)
68 , m_bIgnoreBlankLines(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
)
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
)
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
)
117 void CFileDiffDlg::SetDiff(const CTGitPath
* path
, const GitRev
& baseRev1
, const GitRev
& rev2
)
123 m_sFilter
= path
->GetGitPathString();
129 void CFileDiffDlg::SetDiff(const CTGitPath
* path
, const CString
&baseRev1
, const CString
& hash2
)
135 m_sFilter
= path
->GetGitPathString();
140 if (baseRev1
== GIT_REV_ZERO
)
142 m_rev1
.m_CommitHash
.Empty();
143 m_rev1
.GetSubject().LoadString(IDS_WORKING_TREE
);
147 if (m_rev1
.GetCommit(baseRev1
))
148 MessageBox(m_rev1
.GetLastErr(), L
"TortoiseGit", MB_ICONERROR
);
153 if(hash2
== GIT_REV_ZERO
)
155 m_rev2
.m_CommitHash
.Empty();
156 m_rev2
.GetSubject().LoadString(IDS_WORKING_TREE
);
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
)
171 m_sFilter
= path
->GetGitPathString();
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();
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
);
216 m_cFilter
.SetCueBanner(temp
);
217 if (!m_sFilter
.IsEmpty())
218 m_cFilter
.SetWindowText(m_sFilter
);
220 int c
= m_cFileList
.GetHeaderCtrl()->GetItemCount() - 1;
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
);
237 int maxcol
= m_cFileList
.GetHeaderCtrl()->GetItemCount() - 1;
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());
268 if (m_rev1
.GetCommit(m_strRev1
))
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());
282 if (m_rev2
.GetCommit(m_strRev2
))
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
);
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
);
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
);
343 SendMessage(WM_DIFFFINISHED
);
345 InterlockedExchange(&m_bThreadRunning
, FALSE
);
349 LRESULT
CFileDiffDlg::OnDisableButtons(WPARAM
, LPARAM
)
352 m_cFileList
.ShowText(CString(MAKEINTRESOURCE(IDS_FILEDIFF_WAIT
)));
353 m_cFileList
.DeleteAllItems();
354 m_arFileList
.Clear();
355 EnableInputControl(false);
359 LRESULT
CFileDiffDlg::OnDiffFinished(WPARAM
, LPARAM
)
362 m_cFilter
.GetWindowText(sFilterText
);
363 m_cFileList
.SetRedraw(false);
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
);
373 int maxcol
= m_cFileList
.GetHeaderCtrl()->GetItemCount() - 1;
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);
385 EnableInputControl(true);
389 int CFileDiffDlg::AddEntry(const CTGitPath
* fd
)
394 int index
= m_cFileList
.GetItemCount();
397 if (fd
->IsDirectory())
398 icon_idx
= m_nIconFolder
;
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
);
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));
431 if (fd1
->m_Action
& CTGitPath::LOGACTIONS_ADDED
)
433 CGitDiff::DiffNull(GetSafeHwnd(), fd1
, m_rev2
.m_CommitHash
.ToString(), true, 0, !!(GetAsyncKeyState(VK_SHIFT
) & 0x8000));
436 if (fd1
->m_Action
& CTGitPath::LOGACTIONS_DELETED
)
438 CGitDiff::DiffNull(GetSafeHwnd(), fd1
, m_rev1
.m_CommitHash
.ToString(), false, 0, !!(GetAsyncKeyState(VK_SHIFT
) & 0x8000));
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));
449 void CFileDiffDlg::OnNMDblclkFilelist(NMHDR
*pNMHDR
, LRESULT
*pResult
)
452 LPNMLISTVIEW pNMLV
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
453 int selIndex
= pNMLV
->iItem
;
456 if (selIndex
>= (int)m_arFilteredList
.size())
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())
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);
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
);
507 case CTGitPath::LOGACTIONS_DELETED
:
508 crText
= m_colors
.GetColor(CColors::Deleted
);
510 case CTGitPath::LOGACTIONS_MODIFIED
:
511 crText
= m_colors
.GetColor(CColors::Modified
);
514 crText
= m_colors
.GetColor(CColors::PropertyChanged
);
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
);
533 void CFileDiffDlg::OnContextMenu(CWnd
* pWnd
, CPoint point
)
535 if (!pWnd
|| pWnd
!= &m_cFileList
)
537 if (m_cFileList
.GetSelectedCount() == 0)
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))
544 m_cFileList
.GetItemRect(m_cFileList
.GetSelectionMark(), &rect
, LVIR_LABEL
);
545 m_cFileList
.ClientToScreen(&rect
);
546 point
= rect
.CenterPoint();
549 if (popup
.CreatePopupMenu())
552 POSITION firstPos
= m_cFileList
.GetFirstSelectedItemPosition();
554 firstEntry
= m_cFileList
.GetNextSelectedItem(firstPos
);
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
);
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())
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);
597 if (!CheckMultipleDiffs())
599 POSITION pos
= m_cFileList
.GetFirstSelectedItemPosition();
602 int index
= m_cFileList
.GetNextSelectedItem(pos
);
603 DoDiff(index
, false);
607 case ID_GNUDIFFCOMPARE
:
609 if (!CheckMultipleDiffs())
611 POSITION pos
= m_cFileList
.GetFirstSelectedItemPosition();
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));
625 RevertSelectedItemToVersion(m_rev1
.m_CommitHash
.ToString());
628 RevertSelectedItemToVersion(m_rev2
.m_CommitHash
.ToString());
632 if (!CheckMultipleDiffs())
634 POSITION pos
= m_cFileList
.GetFirstSelectedItemPosition();
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());
644 if (m_rev2
.m_CommitHash
.IsEmpty() && (m_arFilteredList
[index
]->m_Action
& CTGitPath::LOGACTIONS_ADDED
))
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());
651 CAppUtils::LaunchTortoiseBlame(m_arFilteredList
[index
]->GetWinPathString(), m_rev2
.m_CommitHash
.ToString());
656 case ID_LOGSUBMODULE
:
658 if (!CheckMultipleDiffs())
660 POSITION pos
= m_cFileList
.GetFirstSelectedItemPosition();
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
);
675 if (m_cFileList
.GetSelectedCount() > 0)
679 if (!CAppUtils::FileOpenSave(pathSave
, nullptr, IDS_FILEDIFF_POPSAVELIST
, IDS_TEXTFILEFILTER
, false, m_hWnd
, L
"txt"))
681 savePath
= CTGitPath(pathSave
);
683 // now open the selected file for writing
686 CStdioFile
file(savePath
.GetWinPathString(), CFile::typeBinary
| CFile::modeReadWrite
| CFile::modeCreate
);
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());
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();
696 int index
= m_cFileList
.GetNextSelectedItem(pos
);
697 CTGitPath
* fd
= m_arFilteredList
[index
];
698 file
.WriteString(fd
->GetGitPathString());
699 file
.WriteString(L
"\r\n");
703 catch (CFileException
* pE
)
710 case ID_CLIPBOARD_PATH
:
712 CopySelectionToClipboard();
716 case ID_CLIPBOARD_ALL
:
718 CopySelectionToClipboard(TRUE
);
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();
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())
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
);
748 if (g_Git
.GetOneFile(m_rev2
.m_CommitHash
.ToString(), *fd
, filename
))
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)
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
);
774 return CResizableStandAloneDialog::OnSetCursor(pWnd
, nHitTest
, message
);
776 HCURSOR hCur
= LoadCursor(nullptr, IDC_WAIT
);
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
)
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
);
815 m_cFileList
.SetRedraw(true);
816 CTGitPath path
= m_path1
;
824 this->m_ctrRev1Edit
.GetWindowText(str1
);
825 this->m_ctrRev2Edit
.GetWindowText(str2
);
827 this->m_ctrRev1Edit
.SetWindowText(str2
);
828 this->m_ctrRev2Edit
.SetWindowText(str1
);
831 //KillTimer(IDT_INPUT);
834 void CFileDiffDlg::SetURLLabels(int mask
)
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());
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
)
866 SetDlgItemText(IDC_FIRSTURL
, L
"");
867 m_tooltips
.AddTool(IDC_FIRSTURL
, L
"");
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
)
884 if (GetFocus() != GetDlgItem(IDC_FILELIST
))
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
);
898 if (GetFocus() != GetDlgItem(IDC_FILELIST
))
900 if (GetAsyncKeyState(VK_CONTROL
)&0x8000)
902 CopySelectionToClipboard();
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
);
925 if (GetFocus() == GetDlgItem(IDC_FILTER
) && m_cFilter
.GetWindowTextLength())
927 m_cFilter
.SetWindowText(L
"");
928 OnClickedCancelFilter(NULL
, NULL
);
934 return __super::PreTranslateMessage(pMsg
);
937 void CFileDiffDlg::OnCancel()
939 if (m_bThreadRunning
)
944 void CFileDiffDlg::OnHdnItemclickFilelist(NMHDR
*pNMHDR
, LRESULT
*pResult
)
946 LPNMHEADER phdr
= reinterpret_cast<LPNMHEADER
>(pNMHDR
);
947 if (m_bThreadRunning
)
950 if (m_nSortedColumn
== phdr
->iItem
)
951 m_bAscending
= !m_bAscending
;
954 m_nSortedColumn
= phdr
->iItem
;
958 m_cFileList
.SetRedraw(FALSE
);
959 m_cFileList
.DeleteAllItems();
960 m_cFilter
.GetWindowText(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
);
981 void CFileDiffDlg::Sort()
983 if(m_arFileList
.GetCount() < 2)
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
)
993 switch (m_nSortedColumn
)
995 case 0: //path column
996 result
= Data1
.GetWinPathString().Compare(Data2
.GetWinPathString());
998 case 1: //extension column
999 result
= Data1
.GetFileExtension().Compare(Data2
.GetFileExtension());
1001 case 2: //action column
1002 result
= Data1
.m_Action
- Data2
.m_Action
;
1005 d1
= CSorter::A2L(Data1
.m_StatAdd
);
1006 d2
= CSorter::A2L(Data2
.m_StatAdd
);
1010 d1
= CSorter::A2L(Data1
.m_StatDel
);;
1011 d2
= CSorter::A2L(Data2
.m_StatDel
);
1017 // sort by path name as second priority
1018 if (m_nSortedColumn
!= 0 && result
== 0)
1019 result
= Data1
.GetWinPathString().Compare(Data2
.GetWinPathString());
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();
1042 if(FillRevFromString(rev
,str
))
1045 edit
->SetWindowText(str
);
1049 if(entry
== 1) /*Log*/
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())
1061 if (FillRevFromString(rev
, dlg
.GetSelectedHash().at(0).ToString()))
1064 edit
->SetWindowText(dlg
.GetSelectedHash().at(0).ToString());
1070 if(entry
== 2) /*RefLog*/
1073 if(dlg
.DoModal() == IDOK
)
1075 if (FillRevFromString(rev
, dlg
.m_SelectedHash
.ToString()))
1078 edit
->SetWindowText(dlg
.m_SelectedHash
.ToString());
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);
1108 KillTimer(IDT_FILTER
);
1110 m_cFileList
.SetRedraw(FALSE
);
1111 m_arFilteredList
.clear();
1112 m_cFileList
.DeleteAllItems();
1116 m_cFileList
.SetRedraw(TRUE
);
1120 void CFileDiffDlg::OnEnChangeFilter()
1122 SetTimer(IDT_FILTER
, 1000, nullptr);
1125 void CFileDiffDlg::OnTimer(UINT_PTR nIDEvent
)
1127 if (m_bThreadRunning
)
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");
1154 this->m_ctrRev1Edit
.GetWindowText(str
);
1155 if (!gitrev
.GetCommit(str
))
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
))
1177 msg
.Format(IDS_PROC_REFINVALID
, (LPCTSTR
)str
);
1178 m_cFileList
.ShowText(msg
+ L
'\n' + gitrev
.GetLastErr());
1181 this->SetURLLabels(mask
);
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();
1205 if (sPath
.Find(sFilterText
) >= 0)
1206 m_arFilteredList
.push_back((CTGitPath
*)&(m_arFileList
[i
]));
1208 for (const auto path
: m_arFilteredList
)
1212 void CFileDiffDlg::CopySelectionToClipboard(BOOL isFull
)
1214 // copy all selected paths to the clipboard
1215 POSITION pos
= m_cFileList
.GetFirstSelectedItemPosition();
1217 CString sTextForClipboard
;
1218 while ((index
= m_cFileList
.GetNextSelectedItem(pos
)) >= 0)
1220 sTextForClipboard
+= m_cFileList
.GetItemText(index
, 0);
1221 sTextForClipboard
+= L
'\t';
1224 sTextForClipboard
+= L
"\r\n";
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
);
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
);
1268 if(lParam
== IDC_REV2EDIT
)
1270 OnTextUpdate(&this->m_ctrRev2Edit
);
1271 ClearURLabels(1<<1);
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
)
1287 POSITION pos
= m_cFileList
.GetFirstSelectedItemPosition();
1290 while ((index
= m_cFileList
.GetNextSelectedItem(pos
)) >= 0)
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)
1305 out
.FormatMessage(IDS_STATUSLIST_FILESREVERTED
, count
, (LPCTSTR
)rev
);
1306 CMessageBox::Show(GetSafeHwnd(), out
, L
"TortoiseGit", MB_OK
);
1310 static void AppendMenuChecked(CMenu
&menu
, UINT nTextID
, UINT_PTR nItemID
, BOOL checked
= FALSE
, BOOL enabled
= TRUE
)
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()
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
);
1335 GetDlgItem(IDC_DIFFOPTION
)->GetWindowRect(&rect
);
1336 int selection
= popup
.TrackPopupMenu(TPM_RETURNCMD
| TPM_LEFTALIGN
| TPM_NONOTIFY
, rect
.left
, rect
.bottom
, this);
1339 case DIFFOPTION_IGNORESPACEATEOL
:
1340 m_bIgnoreSpaceAtEol
= !m_bIgnoreSpaceAtEol
;
1343 case DIFFOPTION_IGNORESPACECHANGE
:
1344 m_bIgnoreSpaceChange
= !m_bIgnoreSpaceChange
;
1347 case DIFFOPTION_IGNOREALLSPACE
:
1348 m_bIgnoreAllSpace
= !m_bIgnoreAllSpace
;
1351 case DIFFOPTION_IGNORBLANKLINES
:
1352 m_bIgnoreBlankLines
= !m_bIgnoreBlankLines
;
1359 m_cDiffOptionsBtn
.SetCheck((m_bIgnoreSpaceAtEol
|| m_bIgnoreSpaceChange
|| m_bIgnoreAllSpace
|| m_bIgnoreBlankLines
) ? BST_CHECKED
: BST_UNCHECKED
);
1363 void CFileDiffDlg::OnBnClickedLog()
1366 dlg
.SetRange(m_rev1
.m_CommitHash
.ToString() + L
".." + m_rev2
.m_CommitHash
.ToString());
1370 bool CFileDiffDlg::CheckMultipleDiffs()
1372 UINT selCount
= m_cFileList
.GetSelectedCount();
1373 if (selCount
> max(3, (DWORD
)CRegDWORD(L
"Software\\TortoiseGit\\NumDiffWarning", 10)))
1376 message
.Format(IDS_STATUSLIST_WARN_MAXDIFF
, selCount
);
1377 return ::MessageBox(GetSafeHwnd(), message
, L
"TortoiseGit", MB_YESNO
| MB_ICONQUESTION
) == IDYES
;
1382 void CFileDiffDlg::OnLvnBegindrag(NMHDR
* pNMHDR
, LRESULT
* pResult
)
1386 // get selected paths
1387 POSITION pos
= m_cFileList
.GetFirstSelectedItemPosition();
1391 CTGitPathList toExport
;
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
>();
1407 GitDataObject
* pdobj
= new GitDataObject(toExport
, m_rev2
.m_CommitHash
);
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
1420 ::DoDragDrop(pdobj
, pdsrc
.get(), DROPEFFECT_MOVE
| DROPEFFECT_COPY
, &dwEffect
);