1
// TortoiseGitMerge - a Diff/Patch program
3 // Copyright (C) 2006, 2008, 2010-2012, 2015, 2020 - TortoiseSVN
4 // Copyright (C) 2012, 2016-2017, 2019-2020-2023 - TortoiseGit
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 "TortoiseMerge.h"
22 #include "FilePatchesDlg.h"
25 #include "PathUtils.h"
26 #include "SysProgressDlg.h"
27 #include "SysImageList.h"
30 IMPLEMENT_DYNAMIC(CFilePatchesDlg
, CResizableStandAloneDialog
)
31 CFilePatchesDlg::CFilePatchesDlg(CWnd
* pParent
/*=nullptr*/)
32 : CResizableStandAloneDialog(CFilePatchesDlg::IDD
, pParent
)
36 CFilePatchesDlg::~CFilePatchesDlg()
40 void CFilePatchesDlg::DoDataExchange(CDataExchange
* pDX
)
42 CResizableStandAloneDialog::DoDataExchange(pDX
);
43 DDX_Control(pDX
, IDC_FILELIST
, m_cFileList
);
46 BOOL
CFilePatchesDlg::SetFileStatusAsPatched(CString sPath
)
48 for (int i
=0; i
<m_arFileStates
.GetCount(); i
++)
50 if (sPath
.CompareNoCase(GetFullPath(i
))==0)
52 m_arFileStates
.SetAt(i
, static_cast<DWORD
>(FPDLG_FILESTATE_PATCHED
));
53 SetStateText(i
, FPDLG_FILESTATE_PATCHED
);
61 CString
CFilePatchesDlg::GetFullPath(int nIndex
)
63 CString temp
= m_pPatch
->GetStrippedPath(nIndex
);
64 temp
.Replace('/', '\\');
65 //temp = temp.Mid(temp.Find('\\')+1);
66 if (PathIsRelative(temp
))
67 temp
= m_sPath
+ temp
;
71 BOOL
CFilePatchesDlg::OnInitDialog()
73 CResizableStandAloneDialog::OnInitDialog();
75 // hide the grip since it would overlap with the "path all" button
80 auto hFont
= reinterpret_cast<HFONT
>(m_cFileList
.SendMessage(WM_GETFONT
));
82 GetObject(hFont
, sizeof(LOGFONT
), &lf
);
83 lf
.lfWeight
= FW_BOLD
;
84 m_boldFont
.CreateFontIndirect(&lf
);
86 AddAnchor(IDC_FILELIST
, TOP_LEFT
, BOTTOM_RIGHT
);
87 AddAnchor(IDC_PATCHSELECTEDBUTTON
, BOTTOM_LEFT
, BOTTOM_RIGHT
);
88 AddAnchor(IDC_PATCHALLBUTTON
, BOTTOM_LEFT
, BOTTOM_RIGHT
);
93 BOOL
CFilePatchesDlg::Init(GitPatch
* pPatch
, CPatchFilesDlgCallBack
* pCallBack
, CString sPath
, CWnd
* pParent
)
95 if (!pCallBack
|| !pPatch
)
97 m_cFileList
.DeleteAllItems();
100 m_arFileStates
.RemoveAll();
102 m_pCallBack
= pCallBack
;
104 if (m_sPath
.IsEmpty())
106 CString
title(MAKEINTRESOURCE(IDS_DIFF_TITLE
));
107 SetWindowText(title
);
112 GetClientRect(&rect
);
113 SetTitleWithPath(rect
.Width());
114 m_sPath
.TrimRight(L
'\\');
118 SetWindowTheme(m_cFileList
.GetSafeHwnd(), L
"Explorer", nullptr);
119 m_cFileList
.SetExtendedStyle(LVS_EX_INFOTIP
| LVS_EX_FULLROWSELECT
| LVS_EX_DOUBLEBUFFER
);
120 m_cFileList
.DeleteAllItems();
121 int c
= m_cFileList
.GetHeaderCtrl()->GetItemCount() - 1;
123 m_cFileList
.DeleteColumn(c
--);
124 m_cFileList
.InsertColumn(0, CString(MAKEINTRESOURCE(IDS_PATH
)));
125 m_cFileList
.InsertColumn(1, CString(MAKEINTRESOURCE(IDS_STATE
)));
127 m_cFileList
.SetRedraw(false);
129 for(int i
=0; i
<m_pPatch
->GetNumberOfFiles(); i
++)
131 CString sFile
= CPathUtils::GetFileNameFromPath(m_pPatch
->GetStrippedPath(i
));
134 if (m_sPath
.IsEmpty())
138 state
= m_pPatch
->GetFailedHunks(i
);
140 if (m_pPatch
->GetHasConflict(i
))
141 state
= FPDLG_FILESTATE_CONFLICT
;
143 state
= FPDLG_FILESTATE_ERROR
;
144 m_arFileStates
.Add(state
);
145 CString sFileName
= GetFullPath(i
);
146 sFileName
= CPathUtils::GetFileNameFromPath(sFileName
);
147 m_cFileList
.InsertItem(i
, sFile
, SYS_IMAGE_LIST().GetPathIconIndex(sFileName
));
148 SetStateText(i
, state
);
150 for (int col
= 0, maxcol
= m_cFileList
.GetHeaderCtrl()->GetItemCount(); col
< maxcol
; ++col
)
152 m_cFileList
.SetColumnWidth(col
,LVSCW_AUTOSIZE_USEHEADER
);
155 m_cFileList
.SetImageList(&SYS_IMAGE_LIST(), LVSIL_SMALL
);
156 m_cFileList
.SetRedraw(true);
159 pParent
->GetWindowRect(&parentrect
);
161 GetWindowRect(&windowrect
);
163 int width
= windowrect
.right
- windowrect
.left
;
164 int height
= windowrect
.bottom
- windowrect
.top
;
165 windowrect
.right
= parentrect
.left
;
166 windowrect
.left
= windowrect
.right
- width
;
167 windowrect
.top
= parentrect
.top
;
168 windowrect
.bottom
= windowrect
.top
+ height
;
169 auto hMonitor
= MonitorFromRect(&windowrect
, MONITOR_DEFAULTTONULL
);
171 SetWindowPos(nullptr, windowrect
.left
, windowrect
.top
, width
, height
, SWP_NOACTIVATE
| SWP_NOZORDER
);
173 m_nWindowHeight
= windowrect
.bottom
- windowrect
.top
;
174 m_pMainFrame
= pParent
;
178 BEGIN_MESSAGE_MAP(CFilePatchesDlg
, CResizableStandAloneDialog
)
180 ON_NOTIFY(LVN_GETINFOTIP
, IDC_FILELIST
, OnLvnGetInfoTipFilelist
)
181 ON_NOTIFY(NM_DBLCLK
, IDC_FILELIST
, OnNMDblclkFilelist
)
182 ON_NOTIFY(NM_CUSTOMDRAW
, IDC_FILELIST
, OnNMCustomdrawFilelist
)
183 ON_NOTIFY(NM_RCLICK
, IDC_FILELIST
, OnNMRclickFilelist
)
184 ON_WM_NCLBUTTONDBLCLK()
186 ON_BN_CLICKED(IDC_PATCHSELECTEDBUTTON
, &CFilePatchesDlg::OnBnClickedPatchselectedbutton
)
187 ON_BN_CLICKED(IDC_PATCHALLBUTTON
, &CFilePatchesDlg::OnBnClickedPatchallbutton
)
188 ON_NOTIFY(LVN_ITEMCHANGED
, IDC_FILELIST
, &CFilePatchesDlg::OnLvnItemchangedFilelist
)
191 void CFilePatchesDlg::OnSize(UINT nType
, int cx
, int cy
)
193 CResizableStandAloneDialog::OnSize(nType
, cx
, cy
);
194 if (this->IsWindowVisible())
196 m_cFileList
.SetColumnWidth(0, LVSCW_AUTOSIZE
);
198 SetTitleWithPath(cx
);
201 void CFilePatchesDlg::OnLvnGetInfoTipFilelist(NMHDR
*pNMHDR
, LRESULT
*pResult
)
203 LPNMLVGETINFOTIP pGetInfoTip
= reinterpret_cast<LPNMLVGETINFOTIP
>(pNMHDR
);
205 if (m_arFileStates
.GetCount() > pGetInfoTip
->iItem
)
208 if (m_arFileStates
.GetAt(pGetInfoTip
->iItem
) == 0)
209 temp
= GetFullPath(pGetInfoTip
->iItem
);
211 temp
.Format(IDS_PATCH_ITEMTT
, static_cast<LPCWSTR
>(GetFullPath(pGetInfoTip
->iItem
)));
212 wcsncpy_s(pGetInfoTip
->pszText
, pGetInfoTip
->cchTextMax
, temp
, pGetInfoTip
->cchTextMax
- 1);
215 pGetInfoTip
->pszText
[0] = 0;
220 void CFilePatchesDlg::OnNMDblclkFilelist(NMHDR
*pNMHDR
, LRESULT
*pResult
)
222 LPNMLISTVIEW pNMLV
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
224 if ((pNMLV
->iItem
< 0) || (pNMLV
->iItem
>= m_arFileStates
.GetCount()))
229 if (m_arFileStates
.GetAt(pNMLV
->iItem
) == FPDLG_FILESTATE_ERROR
)
231 MessageBox(m_pPatch
->GetPatchRejects(pNMLV
->iItem
), nullptr, MB_ICONERROR
);
235 if (m_sPath
.IsEmpty())
237 m_pCallBack
->DiffFiles(GetFullPath(pNMLV
->iItem
), L
"", L
"", L
"");
238 m_ShownIndex
= pNMLV
->iItem
;
239 m_cFileList
.Invalidate();
243 if (m_arFileStates
.GetAt(pNMLV
->iItem
)!=FPDLG_FILESTATE_PATCHED
)
245 m_pCallBack
->PatchFile(m_pPatch
->GetStrippedPath(pNMLV
->iItem
), m_pPatch
->GetContentMods(pNMLV
->iItem
), m_pPatch
->GetPropMods(pNMLV
->iItem
), L
"");
246 m_ShownIndex
= pNMLV
->iItem
;
247 m_cFileList
.Invalidate();
252 void CFilePatchesDlg::OnNMCustomdrawFilelist(NMHDR
*pNMHDR
, LRESULT
*pResult
)
254 NMLVCUSTOMDRAW
* pLVCD
= reinterpret_cast<NMLVCUSTOMDRAW
*>( pNMHDR
);
256 // Take the default processing unless we set this to something else below.
257 *pResult
= CDRF_DODEFAULT
;
259 // First thing - check the draw stage. If it's the control's prepaint
260 // stage, then tell Windows we want messages for every item.
262 if ( CDDS_PREPAINT
== pLVCD
->nmcd
.dwDrawStage
)
264 *pResult
= CDRF_NOTIFYITEMDRAW
;
266 else if ( CDDS_ITEMPREPAINT
== pLVCD
->nmcd
.dwDrawStage
)
268 // This is the prepaint stage for an item. Here's where we set the
269 // item's text color. Our return value will tell Windows to draw the
270 // item itself, but it will use the new color we set here.
272 COLORREF crText
= CTheme::Instance().IsDarkTheme() ? CTheme::darkTextColor
: GetSysColor(COLOR_WINDOWTEXT
);
274 if (m_arFileStates
.GetCount() > static_cast<INT_PTR
>(pLVCD
->nmcd
.dwItemSpec
))
276 if (m_arFileStates
.GetAt(pLVCD
->nmcd
.dwItemSpec
) == FPDLG_FILESTATE_CONFLICT
)
278 crText
= CTheme::Instance().GetThemeColor(RGB(255, 200, 100), true); // orange
280 if (m_arFileStates
.GetAt(pLVCD
->nmcd
.dwItemSpec
)==FPDLG_FILESTATE_ERROR
)
282 crText
= CTheme::Instance().GetThemeColor(RGB(200, 0, 0), true);
284 if (m_arFileStates
.GetAt(pLVCD
->nmcd
.dwItemSpec
)>0)
286 crText
= CTheme::Instance().GetThemeColor(RGB(100, 0, 0), true);
288 if (m_arFileStates
.GetAt(pLVCD
->nmcd
.dwItemSpec
)==FPDLG_FILESTATE_PATCHED
)
290 crText
= CTheme::Instance().GetThemeColor(::GetSysColor(COLOR_GRAYTEXT
));
292 // Store the color back in the NMLVCUSTOMDRAW struct.
293 pLVCD
->clrText
= crText
;
294 if (m_ShownIndex
== static_cast<int>(pLVCD
->nmcd
.dwItemSpec
))
296 SelectObject(pLVCD
->nmcd
.hdc
, m_boldFont
);
297 // We changed the font, so we're returning CDRF_NEWFONT. This
298 // tells the control to recalculate the extent of the text.
299 *pResult
= CDRF_NOTIFYSUBITEMDRAW
| CDRF_NEWFONT
;
305 void CFilePatchesDlg::OnNMRclickFilelist(NMHDR
* /*pNMHDR*/, LRESULT
*pResult
)
308 if (m_sPath
.IsEmpty())
313 DWORD ptW
= GetMessagePos();
314 point
.x
= GET_X_LPARAM(ptW
);
315 point
.y
= GET_Y_LPARAM(ptW
);
316 if (!popup
.CreatePopupMenu())
319 UINT nFlags
= MF_STRING
| (m_cFileList
.GetSelectedCount()==1 ? MF_ENABLED
: MF_DISABLED
| MF_GRAYED
);
320 temp
.LoadString(IDS_PATCH_PREVIEW
);
321 popup
.AppendMenu(nFlags
, ID_PATCHPREVIEW
, temp
);
322 popup
.SetDefaultItem(ID_PATCHPREVIEW
, FALSE
);
324 temp
.LoadString(IDS_PATCH_ALL
);
325 popup
.AppendMenu(MF_STRING
| MF_ENABLED
, ID_PATCHALL
, temp
);
327 nFlags
= MF_STRING
| (m_cFileList
.GetSelectedCount() > 0 ? MF_ENABLED
: MF_DISABLED
| MF_GRAYED
);
328 temp
.LoadString(IDS_PATCH_SELECTED
);
329 popup
.AppendMenu(nFlags
, ID_PATCHSELECTED
, temp
);
331 // if the context menu is invoked through the keyboard, we have to use
332 // a calculated position on where to anchor the menu on
333 if ((point
.x
== -1) && (point
.y
== -1))
336 GetWindowRect(&rect
);
337 point
= rect
.CenterPoint();
340 int cmd
= popup
.TrackPopupMenu(TPM_RETURNCMD
| TPM_LEFTALIGN
| TPM_NONOTIFY
| TPM_RIGHTBUTTON
, point
.x
, point
.y
, this);
343 case ID_PATCHPREVIEW
:
346 int nIndex
= m_cFileList
.GetSelectionMark();
347 if (m_arFileStates
.GetAt(nIndex
) == FPDLG_FILESTATE_ERROR
)
349 MessageBox(m_pPatch
->GetPatchRejects(nIndex
), nullptr, MB_ICONERROR
);
351 else if ( m_arFileStates
.GetAt(nIndex
)!=FPDLG_FILESTATE_PATCHED
)
353 m_pCallBack
->PatchFile(m_pPatch
->GetStrippedPath(nIndex
), m_pPatch
->GetContentMods(nIndex
), m_pPatch
->GetPropMods(nIndex
), L
"");
354 m_ShownIndex
= nIndex
;
355 m_cFileList
.Invalidate();
362 case ID_PATCHSELECTED
:
370 void CFilePatchesDlg::OnNcLButtonDblClk(UINT nHitTest
, CPoint point
)
376 GetWindowRect(&windowrect
);
377 GetClientRect(&clientrect
);
378 m_nWindowHeight
= windowrect
.bottom
- windowrect
.top
;
379 MoveWindow(windowrect
.left
, windowrect
.top
,
380 windowrect
.right
- windowrect
.left
,
381 m_nWindowHeight
- (clientrect
.bottom
- clientrect
.top
));
386 GetWindowRect(&windowrect
);
387 MoveWindow(windowrect
.left
, windowrect
.top
, windowrect
.right
- windowrect
.left
, m_nWindowHeight
);
389 m_bMinimized
= !m_bMinimized
;
390 CResizableStandAloneDialog::OnNcLButtonDblClk(nHitTest
, point
);
393 static int GetBorderAjustment(HWND parentHWND
, const RECT
& parentRect
)
395 CRect recta
{ 0, 0, 0, 0 };
396 if (SUCCEEDED(::DwmGetWindowAttribute(parentHWND
, DWMWA_EXTENDED_FRAME_BOUNDS
, &recta
, sizeof(recta
))))
397 return 2 * (recta
.left
- parentRect
.left
) + 1;
402 void CFilePatchesDlg::OnMoving(UINT fwSide
, LPRECT pRect
)
405 m_pMainFrame
->GetWindowRect(&parentRect
);
406 const int stickySize
= 5;
407 int adjust
= GetBorderAjustment(m_pMainFrame
->GetSafeHwnd(), parentRect
);
408 if (abs(parentRect
.left
- pRect
->right
+ adjust
) < stickySize
)
410 int width
= pRect
->right
- pRect
->left
;
411 pRect
->right
= parentRect
.left
+ adjust
;
412 pRect
->left
= pRect
->right
- width
;
414 CResizableStandAloneDialog::OnMoving(fwSide
, pRect
);
417 void CFilePatchesDlg::ParentOnMoving(HWND parentHWND
, LPRECT pRect
)
419 if (!::IsWindow(m_hWnd
))
422 if (!::IsWindow(parentHWND
))
426 GetWindowRect(&patchrect
);
429 ::GetWindowRect(parentHWND
, &parentRect
);
431 if (patchrect
.left
- (parentRect
.left
- pRect
->left
) <= 0)
434 int adjust
= GetBorderAjustment(parentHWND
, parentRect
);
435 if (patchrect
.right
== parentRect
.left
+ adjust
)
436 SetWindowPos(nullptr, patchrect
.left
- (parentRect
.left
- pRect
->left
), patchrect
.top
- (parentRect
.top
- pRect
->top
), 0, 0, SWP_NOACTIVATE
| SWP_NOOWNERZORDER
| SWP_NOSIZE
| SWP_NOZORDER
);
439 void CFilePatchesDlg::OnOK()
444 void CFilePatchesDlg::SetTitleWithPath(int width
)
447 title
.LoadString(IDS_PATCH_TITLE
);
448 title
+= L
" " + m_sPath
;
449 title
= title
.Left(MAX_PATH
-1);
453 PathCompactPath(pDC
->GetSafeHdc(), CStrBuf(title
), width
);
456 SetWindowText(title
);
459 void CFilePatchesDlg::OnBnClickedPatchselectedbutton()
464 void CFilePatchesDlg::OnBnClickedPatchallbutton()
469 void CFilePatchesDlg::PatchAll()
473 CSysProgressDlg progDlg
;
474 progDlg
.SetTitle(IDR_MAINFRAME
);
475 progDlg
.SetShowProgressBar(true);
476 progDlg
.SetLine(1, CString(MAKEINTRESOURCE(IDS_PATCH_ALL
)));
477 progDlg
.ShowModeless(m_hWnd
);
478 for (int i
=0; i
<m_arFileStates
.GetCount() && !progDlg
.HasUserCancelled(); i
++)
480 if (m_arFileStates
.GetAt(i
) == FPDLG_FILESTATE_ERROR
)
481 MessageBox(m_pPatch
->GetPatchRejects(i
), nullptr, MB_ICONERROR
);
482 else if (m_arFileStates
.GetAt(i
) != FPDLG_FILESTATE_PATCHED
)
484 progDlg
.SetLine(2, GetFullPath(i
), true);
485 m_pCallBack
->PatchFile(m_pPatch
->GetStrippedPath(i
), m_pPatch
->GetContentMods(i
), m_pPatch
->GetPropMods(i
), L
"", TRUE
);
487 m_cFileList
.Invalidate();
489 progDlg
.SetProgress64(i
, m_arFileStates
.GetCount());
495 void CFilePatchesDlg::PatchSelected()
499 CSysProgressDlg progDlg
;
500 progDlg
.SetTitle(IDR_MAINFRAME
);
501 progDlg
.SetShowProgressBar(true);
502 progDlg
.SetLine(1, CString(MAKEINTRESOURCE(IDS_PATCH_SELECTED
)));
503 progDlg
.ShowModeless(m_hWnd
);
504 // The list cannot be sorted by user, so the order of the
505 // items in the list is identical to the order in the array
507 int selCount
= m_cFileList
.GetSelectedCount();
509 POSITION pos
= m_cFileList
.GetFirstSelectedItemPosition();
511 while (((index
= m_cFileList
.GetNextSelectedItem(pos
)) >= 0) && (!progDlg
.HasUserCancelled()))
513 if (m_arFileStates
.GetAt(index
) == FPDLG_FILESTATE_ERROR
)
514 MessageBox(m_pPatch
->GetPatchRejects(index
), nullptr, MB_ICONERROR
);
515 else if (m_arFileStates
.GetAt(index
) != FPDLG_FILESTATE_PATCHED
)
517 progDlg
.SetLine(2, GetFullPath(index
), true);
518 m_pCallBack
->PatchFile(m_pPatch
->GetStrippedPath(index
), m_pPatch
->GetContentMods(index
), m_pPatch
->GetPropMods(index
), L
"", TRUE
);
519 m_ShownIndex
= index
;
520 m_cFileList
.Invalidate();
522 progDlg
.SetProgress64(count
++, selCount
);
528 void CFilePatchesDlg::OnLvnItemchangedFilelist(NMHDR
* /*pNMHDR*/, LRESULT
*pResult
)
530 DialogEnableWindow(IDC_PATCHSELECTEDBUTTON
, m_cFileList
.GetSelectedCount() > 0);
535 void CFilePatchesDlg::SetStateText(int i
, int state
)
540 case FPDLG_FILESTATE_PATCHED
:
541 sState
.LoadString(IDS_STATE_PATCHED
);
543 case FPDLG_FILESTATE_ERROR
:
544 sState
.LoadString(IDS_STATE_ERROR
);
547 // all is ok, not yet patched but no failed hunks
550 // there are failed hunks in the patch
551 sState
.LoadString(IDS_STATE_CONFLICTS
);
554 m_cFileList
.SetItemText(i
, 1, sState
);