Honor renames in patch views
[TortoiseGit.git] / src / TortoiseProc / GitProgressDlg.cpp
bloba3579212eec99bf43a752439a8eb05688328eff4
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2019, 2023 - TortoiseGit
4 // Copyright (C) 2003-2008 - TortoiseSVN
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "stdafx.h"
21 #include "TortoiseProc.h"
22 #include "GitProgressDlg.h"
23 #include "AppUtils.h"
24 #include "SmartHandle.h"
25 #include "StringUtils.h"
26 #include "CmdLineParser.h"
28 IMPLEMENT_DYNAMIC(CGitProgressDlg, CResizableStandAloneDialog)
29 CGitProgressDlg::CGitProgressDlg(CWnd* pParent /*=nullptr*/)
30 : CResizableStandAloneDialog(CGitProgressDlg::IDD, pParent)
32 int autoClose = CRegDWORD(L"Software\\TortoiseGit\\AutoCloseGitProgress", 0);
33 CCmdLineParser parser(AfxGetApp()->m_lpCmdLine);
34 if (parser.HasKey(L"closeonend"))
35 autoClose = parser.GetLongVal(L"closeonend");
36 switch (autoClose)
38 case 1:
39 m_AutoClose = GitProgressAutoClose::AUTOCLOSE_IF_NO_OPTIONS;
40 break;
41 case 2:
42 m_AutoClose = GitProgressAutoClose::AUTOCLOSE_IF_NO_ERRORS;
43 break;
44 default:
45 m_AutoClose = GitProgressAutoClose::AUTOCLOSE_NO;
46 break;
50 CGitProgressDlg::~CGitProgressDlg()
52 if (m_hAccel)
53 DestroyAcceleratorTable(m_hAccel);
56 void CGitProgressDlg::DoDataExchange(CDataExchange* pDX)
58 CResizableStandAloneDialog::DoDataExchange(pDX);
59 DDX_Control(pDX, IDC_SVNPROGRESS, m_ProgList);
60 DDX_Control(pDX, IDC_TITLE_ANIMATE, m_Animate);
61 DDX_Control(pDX, IDC_PROGRESSBAR, m_ProgCtrl);
62 DDX_Control(pDX, IDC_INFOTEXT, m_InfoCtrl);
63 DDX_Control(pDX, IDC_PROGRESSLABEL, m_ProgLableCtrl);
64 DDX_Control(pDX, IDC_LOGBUTTON, m_cMenuButton);
67 BEGIN_MESSAGE_MAP(CGitProgressDlg, CResizableStandAloneDialog)
68 ON_BN_CLICKED(IDC_LOGBUTTON, OnBnClickedLogbutton)
69 ON_WM_CLOSE()
70 ON_WM_SETCURSOR()
71 ON_EN_SETFOCUS(IDC_INFOTEXT, &CGitProgressDlg::OnEnSetfocusInfotext)
72 ON_WM_CTLCOLOR()
73 ON_MESSAGE(WM_PROG_CMD_FINISH, OnCmdEnd)
74 ON_MESSAGE(WM_PROG_CMD_START, OnCmdStart)
75 ON_REGISTERED_MESSAGE(TaskBarButtonCreated, OnTaskbarBtnCreated)
76 END_MESSAGE_MAP()
80 BOOL CGitProgressDlg::OnInitDialog()
82 __super::OnInitDialog();
84 // Let the TaskbarButtonCreated message through the UIPI filter. If we don't
85 // do this, Explorer would be unable to send that message to our window if we
86 // were running elevated. It's OK to make the call all the time, since if we're
87 // not elevated, this is a no-op.
88 CHANGEFILTERSTRUCT cfs = { sizeof(CHANGEFILTERSTRUCT) };
89 using ChangeWindowMessageFilterExDFN = BOOL(STDAPICALLTYPE)(HWND hWnd, UINT message, DWORD action, PCHANGEFILTERSTRUCT pChangeFilterStruct);
90 CAutoLibrary hUser = AtlLoadSystemLibraryUsingFullPath(L"user32.dll");
91 if (hUser)
93 auto pfnChangeWindowMessageFilterEx = reinterpret_cast<ChangeWindowMessageFilterExDFN*>(GetProcAddress(hUser, "ChangeWindowMessageFilterEx"));
94 if (pfnChangeWindowMessageFilterEx)
95 pfnChangeWindowMessageFilterEx(m_hWnd, TaskBarButtonCreated, MSGFLT_ALLOW, &cfs);
97 m_ProgList.m_pTaskbarList.Release();
98 if (FAILED(m_ProgList.m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList)))
99 m_ProgList.m_pTaskbarList = nullptr;
101 UpdateData(FALSE);
103 AddAnchor(IDC_SVNPROGRESS, TOP_LEFT, BOTTOM_RIGHT);
104 AddAnchor(IDC_TITLE_ANIMATE, TOP_LEFT, BOTTOM_RIGHT);
105 AddAnchor(IDC_PROGRESSLABEL, BOTTOM_LEFT, BOTTOM_CENTER);
106 AddAnchor(IDC_PROGRESSBAR, BOTTOM_CENTER, BOTTOM_RIGHT);
107 AddAnchor(IDC_INFOTEXT, BOTTOM_LEFT, BOTTOM_RIGHT);
108 AddAnchor(IDCANCEL, BOTTOM_RIGHT);
109 AddAnchor(IDOK, BOTTOM_RIGHT);
110 AddAnchor(IDC_LOGBUTTON, BOTTOM_RIGHT);
111 //SetPromptParentWindow(this->m_hWnd);
113 m_Animate.Open(IDR_DOWNLOAD);
114 m_ProgList.m_pAnimate = &m_Animate;
115 m_ProgList.m_pProgControl = &m_ProgCtrl;
116 m_ProgList.m_pProgressLabelCtrl = &m_ProgLableCtrl;
117 m_ProgList.m_pInfoCtrl = &m_InfoCtrl;
118 m_ProgList.m_pPostWnd = this;
119 m_ProgList.m_bSetTitle = true;
121 if (GetExplorerHWND())
122 CenterWindow(CWnd::FromHandle(GetExplorerHWND()));
123 EnableSaveRestore(L"GITProgressDlg");
125 m_background_brush.CreateSolidBrush(GetSysColor(COLOR_WINDOW));
126 m_ProgList.Init();
128 return TRUE;
131 LRESULT CGitProgressDlg::OnTaskbarBtnCreated(WPARAM wParam, LPARAM lParam)
133 m_ProgList.m_pTaskbarList.Release();
134 m_ProgList.m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList);
135 return __super::OnTaskbarButtonCreated(wParam, lParam);
138 void CGitProgressDlg::OnBnClickedLogbutton()
140 ShowWindow(SW_HIDE);
141 m_PostCmdList.at(m_cMenuButton.GetCurrentEntry()).action();
142 EndDialog(IDOK);
146 void CGitProgressDlg::OnClose()
148 DialogEnableWindow(IDCANCEL, TRUE);
149 __super::OnClose();
152 void CGitProgressDlg::OnOK()
154 if ((m_ProgList.IsCancelled())&&(!m_ProgList.IsRunning()))
156 // I have made this wait a sensible amount of time (10 seconds) for the thread to finish
157 // You must be careful in the thread that after posting the WM_COMMAND/IDOK message, you
158 // don't do any more operations on the window which might require message passing
159 // If you try to send windows messages once we're waiting here, then the thread can't finished
160 // because the Window's message loop is blocked at this wait
161 WaitForSingleObject(m_ProgList.m_pThread->m_hThread, 10000);
162 __super::OnOK();
164 m_ProgList.Cancel();
167 void CGitProgressDlg::OnCancel()
169 if ((m_ProgList.IsCancelled())&&(!m_ProgList.IsRunning()))
170 __super::OnCancel();
171 m_ProgList.Cancel();
174 BOOL CGitProgressDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
176 if (!GetDlgItem(IDOK)->IsWindowEnabled())
178 // only show the wait cursor over the list control
179 if ((pWnd)&&(pWnd == GetDlgItem(IDC_SVNPROGRESS)))
181 HCURSOR hCur = LoadCursor(nullptr, IDC_WAIT);
182 SetCursor(hCur);
183 return TRUE;
186 if (pWnd && pWnd == GetDlgItem(IDC_INFOTEXT))
187 return CResizableStandAloneDialog::OnSetCursor(pWnd, nHitTest, message);
188 HCURSOR hCur = LoadCursor(nullptr, IDC_ARROW);
189 SetCursor(hCur);
190 return CResizableStandAloneDialog::OnSetCursor(pWnd, nHitTest, message);
193 BOOL CGitProgressDlg::PreTranslateMessage(MSG* pMsg)
195 if (m_hAccel && TranslateAccelerator(m_hWnd, m_hAccel, pMsg))
196 return TRUE;
197 if (pMsg->message == WM_KEYDOWN)
199 if (pMsg->wParam == VK_ESCAPE)
201 // pressing the ESC key should close the dialog. But since we disabled the escape
202 // key (so the user doesn't get the idea that he could simply undo an e.g. update)
203 // this won't work.
204 // So if the user presses the ESC key, change it to VK_RETURN so the dialog gets
205 // the impression that the OK button was pressed.
206 if ((!m_ProgList.IsRunning())&&(!GetDlgItem(IDCANCEL)->IsWindowEnabled())
207 &&(GetDlgItem(IDOK)->IsWindowEnabled())&&(GetDlgItem(IDOK)->IsWindowVisible()))
209 // since we convert ESC to RETURN, make sure the OK button has the focus.
210 GetDlgItem(IDOK)->SetFocus();
211 pMsg->wParam = VK_RETURN;
215 return __super::PreTranslateMessage(pMsg);
218 void CGitProgressDlg::OnEnSetfocusInfotext()
220 CString sTemp;
221 GetDlgItemText(IDC_INFOTEXT, sTemp);
222 if (sTemp.IsEmpty())
223 GetDlgItem(IDC_INFOTEXT)->HideCaret();
226 LRESULT CGitProgressDlg::OnCtlColorStatic(WPARAM wParam, LPARAM lParam)
228 auto hDC = reinterpret_cast<HDC>(wParam);
229 auto hwndCtl = reinterpret_cast<HWND>(lParam);
231 if (::GetDlgCtrlID(hwndCtl) == IDC_TITLE_ANIMATE)
233 CDC *pDC = CDC::FromHandle(hDC);
234 pDC->SetBkColor(GetSysColor(COLOR_WINDOW));
235 pDC->SetBkMode(TRANSPARENT);
236 return reinterpret_cast<LRESULT>(static_cast<HBRUSH>(m_background_brush.GetSafeHandle()));
238 return FALSE;
241 HBRUSH CGitProgressDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
243 HBRUSH hbr;
244 if (pWnd->GetDlgCtrlID() == IDC_TITLE_ANIMATE)
246 pDC->SetBkColor(GetSysColor(COLOR_WINDOW)); // add this
247 pDC->SetBkMode(TRANSPARENT);
248 return static_cast<HBRUSH>(m_background_brush.GetSafeHandle());
250 else
251 hbr = CResizableStandAloneDialog::OnCtlColor(pDC, pWnd, nCtlColor);
253 // TODO: Return a different brush if the default is not desired
254 return hbr;
257 LRESULT CGitProgressDlg::OnCmdEnd(WPARAM /*wParam*/, LPARAM /*lParam*/)
259 RefreshCursor();
261 DialogEnableWindow(IDCANCEL, FALSE);
262 DialogEnableWindow(IDOK, TRUE);
264 m_PostCmdList.clear();
265 if (m_ProgList.m_Command->m_PostCmdCallback)
266 m_ProgList.m_Command->m_PostCmdCallback(m_ProgList.DidErrorsOccur() ? 1 : 0, m_PostCmdList);
268 if (!m_PostCmdList.empty())
270 int i = 0;
271 for (const auto& entry : m_PostCmdList)
273 ++i;
274 m_cMenuButton.AddEntry(entry.label, entry.icon);
275 wchar_t accellerator = CStringUtils::GetAccellerator(entry.label);
276 if (accellerator == L'\0')
277 continue;
278 ++m_accellerators[accellerator].cnt;
279 if (m_accellerators[accellerator].cnt > 1)
280 m_accellerators[accellerator].id = -1;
281 else
282 m_accellerators[accellerator].id = i - 1;
285 if (m_accellerators.size())
287 auto lpaccelNew = static_cast<LPACCEL>(LocalAlloc(LPTR, m_accellerators.size() * sizeof(ACCEL)));
288 SCOPE_EXIT { LocalFree(lpaccelNew); };
289 i = 0;
290 for (auto& entry : m_accellerators)
292 lpaccelNew[i].cmd = static_cast<WORD>(WM_USER + 1 + entry.second.id);
293 lpaccelNew[i].fVirt = FVIRTKEY | FALT;
294 lpaccelNew[i].key = entry.first;
295 entry.second.wmid = lpaccelNew[i].cmd;
296 ++i;
298 m_hAccel = CreateAcceleratorTable(lpaccelNew, static_cast<int>(m_accellerators.size()));
300 m_cMenuButton.EnableWindow(TRUE);
301 m_cMenuButton.ShowWindow(SW_SHOW);
304 CWnd * pWndOk = GetDlgItem(IDOK);
305 if (pWndOk && ::IsWindow(pWndOk->GetSafeHwnd()))
307 SendMessage(DM_SETDEFID, IDOK);
308 GetDlgItem(IDOK)->SetFocus();
309 if (!m_ProgList.DidErrorsOccur() && (m_AutoClose == GitProgressAutoClose::AUTOCLOSE_IF_NO_OPTIONS && m_PostCmdList.empty() || m_AutoClose == GitProgressAutoClose::AUTOCLOSE_IF_NO_ERRORS))
310 PostMessage(WM_COMMAND, 1, reinterpret_cast<LPARAM>(pWndOk->GetSafeHwnd()));
313 return 0;
316 LRESULT CGitProgressDlg::OnCmdStart(WPARAM /*wParam*/, LPARAM /*lParam*/)
318 DialogEnableWindow(IDOK, FALSE);
319 DialogEnableWindow(IDCANCEL, TRUE);
321 return 0;
324 LRESULT CGitProgressDlg::DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam)
326 if (m_hAccel && message == WM_COMMAND && LOWORD(wParam) >= WM_USER && LOWORD(wParam) <= WM_USER + m_accellerators.size())
328 for (const auto& entry : m_accellerators)
330 if (entry.second.wmid != LOWORD(wParam))
331 continue;
332 if (entry.second.id == -1)
333 m_cMenuButton.PostMessage(WM_KEYDOWN, VK_F4, NULL);
334 else
336 m_cMenuButton.SetCurrentEntry(entry.second.id);
337 OnBnClickedLogbutton();
339 return 0;
343 return __super::DefWindowProc(message, wParam, lParam);