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.
21 #include "TortoiseProc.h"
22 #include "GitProgressDlg.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");
39 m_AutoClose
= GitProgressAutoClose::AUTOCLOSE_IF_NO_OPTIONS
;
42 m_AutoClose
= GitProgressAutoClose::AUTOCLOSE_IF_NO_ERRORS
;
45 m_AutoClose
= GitProgressAutoClose::AUTOCLOSE_NO
;
50 CGitProgressDlg::~CGitProgressDlg()
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
)
71 ON_EN_SETFOCUS(IDC_INFOTEXT
, &CGitProgressDlg::OnEnSetfocusInfotext
)
73 ON_MESSAGE(WM_PROG_CMD_FINISH
, OnCmdEnd
)
74 ON_MESSAGE(WM_PROG_CMD_START
, OnCmdStart
)
75 ON_REGISTERED_MESSAGE(TaskBarButtonCreated
, OnTaskbarBtnCreated
)
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");
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;
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
));
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()
141 m_PostCmdList
.at(m_cMenuButton
.GetCurrentEntry()).action();
146 void CGitProgressDlg::OnClose()
148 DialogEnableWindow(IDCANCEL
, TRUE
);
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);
167 void CGitProgressDlg::OnCancel()
169 if ((m_ProgList
.IsCancelled())&&(!m_ProgList
.IsRunning()))
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
);
186 if (pWnd
&& pWnd
== GetDlgItem(IDC_INFOTEXT
))
187 return CResizableStandAloneDialog::OnSetCursor(pWnd
, nHitTest
, message
);
188 HCURSOR hCur
= LoadCursor(nullptr, IDC_ARROW
);
190 return CResizableStandAloneDialog::OnSetCursor(pWnd
, nHitTest
, message
);
193 BOOL
CGitProgressDlg::PreTranslateMessage(MSG
* pMsg
)
195 if (m_hAccel
&& TranslateAccelerator(m_hWnd
, m_hAccel
, pMsg
))
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)
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()
221 GetDlgItemText(IDC_INFOTEXT
, sTemp
);
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()));
241 HBRUSH
CGitProgressDlg::OnCtlColor(CDC
* pDC
, CWnd
* pWnd
, UINT nCtlColor
)
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());
251 hbr
= CResizableStandAloneDialog::OnCtlColor(pDC
, pWnd
, nCtlColor
);
253 // TODO: Return a different brush if the default is not desired
257 LRESULT
CGitProgressDlg::OnCmdEnd(WPARAM
/*wParam*/, LPARAM
/*lParam*/)
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())
271 for (const auto& entry
: m_PostCmdList
)
274 m_cMenuButton
.AddEntry(entry
.label
, entry
.icon
);
275 wchar_t accellerator
= CStringUtils::GetAccellerator(entry
.label
);
276 if (accellerator
== L
'\0')
278 ++m_accellerators
[accellerator
].cnt
;
279 if (m_accellerators
[accellerator
].cnt
> 1)
280 m_accellerators
[accellerator
].id
= -1;
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
); };
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
;
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()));
316 LRESULT
CGitProgressDlg::OnCmdStart(WPARAM
/*wParam*/, LPARAM
/*lParam*/)
318 DialogEnableWindow(IDOK
, FALSE
);
319 DialogEnableWindow(IDCANCEL
, TRUE
);
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
))
332 if (entry
.second
.id
== -1)
333 m_cMenuButton
.PostMessage(WM_KEYDOWN
, VK_F4
, NULL
);
336 m_cMenuButton
.SetCurrentEntry(entry
.second
.id
);
337 OnBnClickedLogbutton();
343 return __super::DefWindowProc(message
, wParam
, lParam
);