Fix typos
[TortoiseGit.git] / src / TortoiseMerge / FilePatchesDlg.cpp
bloba6c96145d5f907de6aa9b2e54a6f981ae11b351c
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.
20 #include "stdafx.h"
21 #include "TortoiseMerge.h"
22 #include "FilePatchesDlg.h"
23 #include "GitPatch.h"
24 #include "AppUtils.h"
25 #include "PathUtils.h"
26 #include "SysProgressDlg.h"
27 #include "SysImageList.h"
28 #include "Theme.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);
54 Invalidate();
55 return TRUE;
58 return FALSE;
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;
68 return temp;
71 BOOL CFilePatchesDlg::OnInitDialog()
73 CResizableStandAloneDialog::OnInitDialog();
75 // hide the grip since it would overlap with the "path all" button
76 #if 0
77 HideGrip();
78 #endif
80 auto hFont = reinterpret_cast<HFONT>(m_cFileList.SendMessage(WM_GETFONT));
81 LOGFONT lf = {0};
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);
90 return TRUE;
93 BOOL CFilePatchesDlg::Init(GitPatch * pPatch, CPatchFilesDlgCallBack * pCallBack, CString sPath, CWnd * pParent)
95 if (!pCallBack || !pPatch)
97 m_cFileList.DeleteAllItems();
98 return FALSE;
100 m_arFileStates.RemoveAll();
101 m_pPatch = pPatch;
102 m_pCallBack = pCallBack;
103 m_sPath = sPath;
104 if (m_sPath.IsEmpty())
106 CString title(MAKEINTRESOURCE(IDS_DIFF_TITLE));
107 SetWindowText(title);
109 else
111 CRect rect;
112 GetClientRect(&rect);
113 SetTitleWithPath(rect.Width());
114 m_sPath.TrimRight(L'\\');
115 m_sPath += 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;
122 while (c>=0)
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));
133 int state;
134 if (m_sPath.IsEmpty())
135 state = 0;
136 else
138 state = m_pPatch->GetFailedHunks(i);
140 if (m_pPatch->GetHasConflict(i))
141 state = FPDLG_FILESTATE_CONFLICT;
142 if (state > 0)
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);
158 RECT parentrect;
159 pParent->GetWindowRect(&parentrect);
160 RECT windowrect;
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);
170 if (hMonitor)
171 SetWindowPos(nullptr, windowrect.left, windowrect.top, width, height, SWP_NOACTIVATE | SWP_NOZORDER);
173 m_nWindowHeight = windowrect.bottom - windowrect.top;
174 m_pMainFrame = pParent;
175 return TRUE;
178 BEGIN_MESSAGE_MAP(CFilePatchesDlg, CResizableStandAloneDialog)
179 ON_WM_SIZE()
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()
185 ON_WM_MOVING()
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)
189 END_MESSAGE_MAP()
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)
207 CString temp;
208 if (m_arFileStates.GetAt(pGetInfoTip->iItem) == 0)
209 temp = GetFullPath(pGetInfoTip->iItem);
210 else
211 temp.Format(IDS_PATCH_ITEMTT, static_cast<LPCWSTR>(GetFullPath(pGetInfoTip->iItem)));
212 wcsncpy_s(pGetInfoTip->pszText, pGetInfoTip->cchTextMax, temp, pGetInfoTip->cchTextMax - 1);
214 else
215 pGetInfoTip->pszText[0] = 0;
217 *pResult = 0;
220 void CFilePatchesDlg::OnNMDblclkFilelist(NMHDR *pNMHDR, LRESULT *pResult)
222 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
223 *pResult = 0;
224 if ((pNMLV->iItem < 0) || (pNMLV->iItem >= m_arFileStates.GetCount()))
225 return;
226 if (!m_pCallBack)
227 return;
229 if (m_arFileStates.GetAt(pNMLV->iItem) == FPDLG_FILESTATE_ERROR)
231 MessageBox(m_pPatch->GetPatchRejects(pNMLV->iItem), nullptr, MB_ICONERROR);
232 return;
235 if (m_sPath.IsEmpty())
237 m_pCallBack->DiffFiles(GetFullPath(pNMLV->iItem), L"", L"", L"");
238 m_ShownIndex = pNMLV->iItem;
239 m_cFileList.Invalidate();
241 else
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)
307 *pResult = 0;
308 if (m_sPath.IsEmpty())
309 return;
310 CString temp;
311 CMenu popup;
312 POINT point;
313 DWORD ptW = GetMessagePos();
314 point.x = GET_X_LPARAM(ptW);
315 point.y = GET_Y_LPARAM(ptW);
316 if (!popup.CreatePopupMenu())
317 return;
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))
335 CRect rect;
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);
341 switch (cmd)
343 case ID_PATCHPREVIEW:
344 if (m_pCallBack)
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();
358 break;
359 case ID_PATCHALL:
360 PatchAll();
361 break;
362 case ID_PATCHSELECTED:
363 PatchSelected();
364 break;
365 default:
366 break;
370 void CFilePatchesDlg::OnNcLButtonDblClk(UINT nHitTest, CPoint point)
372 if (!m_bMinimized)
374 RECT windowrect;
375 RECT clientrect;
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));
383 else
385 RECT windowrect;
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;
399 return 0;
402 void CFilePatchesDlg::OnMoving(UINT fwSide, LPRECT pRect)
404 RECT parentRect;
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))
420 return;
422 if (!::IsWindow(parentHWND))
423 return;
425 RECT patchrect;
426 GetWindowRect(&patchrect);
428 RECT parentRect;
429 ::GetWindowRect(parentHWND, &parentRect);
431 if (patchrect.left - (parentRect.left - pRect->left) <= 0)
432 return;
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()
441 return;
444 void CFilePatchesDlg::SetTitleWithPath(int width)
446 CString title;
447 title.LoadString(IDS_PATCH_TITLE);
448 title += L" " + m_sPath;
449 title = title.Left(MAX_PATH-1);
450 CDC * pDC = GetDC();
451 if (pDC)
453 PathCompactPath(pDC->GetSafeHdc(), CStrBuf(title), width);
454 ReleaseDC(pDC);
456 SetWindowText(title);
459 void CFilePatchesDlg::OnBnClickedPatchselectedbutton()
461 PatchSelected();
464 void CFilePatchesDlg::OnBnClickedPatchallbutton()
466 PatchAll();
469 void CFilePatchesDlg::PatchAll()
471 if (m_pCallBack)
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);
486 m_ShownIndex = i;
487 m_cFileList.Invalidate();
489 progDlg.SetProgress64(i, m_arFileStates.GetCount());
491 progDlg.Stop();
495 void CFilePatchesDlg::PatchSelected()
497 if (m_pCallBack)
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
506 // m_arFileStates.
507 int selCount = m_cFileList.GetSelectedCount();
508 int count = 1;
509 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
510 int index;
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);
524 progDlg.Stop();
528 void CFilePatchesDlg::OnLvnItemchangedFilelist(NMHDR * /*pNMHDR*/, LRESULT *pResult)
530 DialogEnableWindow(IDC_PATCHSELECTEDBUTTON, m_cFileList.GetSelectedCount() > 0);
532 *pResult = 0;
535 void CFilePatchesDlg::SetStateText(int i, int state)
537 CString sState;
538 switch (state)
540 case FPDLG_FILESTATE_PATCHED:
541 sState.LoadString(IDS_STATE_PATCHED);
542 break;
543 case FPDLG_FILESTATE_ERROR:
544 sState.LoadString(IDS_STATE_ERROR);
545 break;
546 case 0:
547 // all is ok, not yet patched but no failed hunks
548 break;
549 default:
550 // there are failed hunks in the patch
551 sState.LoadString(IDS_STATE_CONFLICTS);
552 break;
554 m_cFileList.SetItemText(i, 1, sState);