IProgressDialog::SetAnimation is not supported any more on Windows >= Vista
[TortoiseGit.git] / src / Utils / MiscUI / SysProgressDlg.cpp
blob592d4a3ef9c35d6c40368edd8c7157a7b4eaf745
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2010-2013, 2016-2017 - TortoiseGit
4 // Copyright (C) 2003-2006,2008-2011,2015 - 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 "SysProgressDlg.h"
23 CSysProgressDlg::CSysProgressDlg()
24 : m_pIDlg(nullptr)
25 , m_isVisible(false)
26 , m_dwDlgFlags(PROGDLG_NORMAL)
27 , m_hWndProgDlg(nullptr)
28 , m_hWndParent(nullptr)
29 , m_hWndFocus(nullptr)
31 EnsureValid();
34 CSysProgressDlg::~CSysProgressDlg()
36 if (IsValid())
38 if (m_isVisible) //still visible, so stop first before destroying
39 m_pIDlg->StopProgressDialog();
41 m_pIDlg.Release();
42 m_hWndProgDlg = nullptr;
46 bool CSysProgressDlg::EnsureValid()
48 if(IsValid())
49 return true;
51 HRESULT hr = m_pIDlg.CoCreateInstance(CLSID_ProgressDialog, nullptr, CLSCTX_INPROC_SERVER);
52 return (SUCCEEDED(hr));
55 void CSysProgressDlg::SetTitle(LPCTSTR szTitle)
57 USES_CONVERSION;
58 if (IsValid())
59 m_pIDlg->SetTitle(T2COLE(szTitle));
61 void CSysProgressDlg::SetTitle ( UINT idTitle)
63 SetTitle(CString(MAKEINTRESOURCE(idTitle)));
66 void CSysProgressDlg::SetLine(DWORD dwLine, LPCTSTR szText, bool bCompactPath /* = false */)
68 USES_CONVERSION;
69 if (IsValid())
70 m_pIDlg->SetLine(dwLine, T2COLE(szText), bCompactPath, nullptr);
73 #ifdef _MFC_VER
74 void CSysProgressDlg::SetCancelMsg ( UINT idMessage )
76 SetCancelMsg(CString(MAKEINTRESOURCE(idMessage)));
78 #endif // _MFC_VER
80 void CSysProgressDlg::SetCancelMsg(LPCTSTR szMessage)
82 USES_CONVERSION;
83 if (IsValid())
84 m_pIDlg->SetCancelMsg(T2COLE(szMessage), nullptr);
87 void CSysProgressDlg::SetTime(bool bTime /* = true */)
89 m_dwDlgFlags &= ~(PROGDLG_NOTIME | PROGDLG_AUTOTIME);
91 if (bTime)
92 m_dwDlgFlags |= PROGDLG_AUTOTIME;
93 else
94 m_dwDlgFlags |= PROGDLG_NOTIME;
97 void CSysProgressDlg::SetShowProgressBar(bool bShow /* = true */)
99 if (bShow)
100 m_dwDlgFlags &= ~PROGDLG_NOPROGRESSBAR;
101 else
102 m_dwDlgFlags |= PROGDLG_NOPROGRESSBAR;
104 #ifdef _MFC_VER
105 HRESULT CSysProgressDlg::ShowModal (CWnd* pwndParent, BOOL immediately /* = true */)
107 EnsureValid();
108 return ShowModal(pwndParent->GetSafeHwnd(), immediately);
111 HRESULT CSysProgressDlg::ShowModeless(CWnd* pwndParent, BOOL immediately)
113 EnsureValid();
114 return ShowModeless(pwndParent->GetSafeHwnd(), immediately);
117 void CSysProgressDlg::FormatPathLine ( DWORD dwLine, UINT idFormatText, ...)
119 va_list args;
120 va_start(args, idFormatText);
122 CString sText;
123 sText.FormatV(CString(MAKEINTRESOURCE(idFormatText)), args);
124 SetLine(dwLine, sText, true);
126 va_end(args);
129 void CSysProgressDlg::FormatPathLine ( DWORD dwLine, CString FormatText, ...)
131 va_list args;
132 va_start(args, FormatText);
134 CString sText;
135 sText.FormatV(FormatText, args);
136 SetLine(dwLine, sText, true);
138 va_end(args);
141 void CSysProgressDlg::FormatNonPathLine(DWORD dwLine, UINT idFormatText, ...)
143 va_list args;
144 va_start(args, idFormatText);
146 CString sText;
147 sText.FormatV(CString(MAKEINTRESOURCE(idFormatText)), args);
148 SetLine(dwLine, sText, false);
150 va_end(args);
153 void CSysProgressDlg::FormatNonPathLine(DWORD dwLine, CString FormatText, ...)
155 va_list args;
156 va_start(args, FormatText);
158 CString sText;
159 sText.FormatV(FormatText, args);
160 SetLine(dwLine, sText, false);
162 va_end(args);
165 #endif
166 HRESULT CSysProgressDlg::ShowModal(HWND hWndParent, BOOL immediately /* = true */)
168 EnsureValid();
169 m_hWndProgDlg = nullptr;
170 if (!IsValid())
171 return E_FAIL;
172 m_hWndParent = hWndParent;
173 auto winId = GetWindowThreadProcessId(m_hWndParent, nullptr);
174 auto threadId = GetCurrentThreadId();
175 if (winId != threadId)
176 AttachThreadInput(winId, threadId, TRUE);
177 m_hWndFocus = GetFocus();
178 if (winId != threadId)
179 AttachThreadInput(winId, threadId, FALSE);
180 HRESULT hr = m_pIDlg->StartProgressDialog(hWndParent, nullptr, m_dwDlgFlags | PROGDLG_MODAL, nullptr);
181 if(FAILED(hr))
182 return hr;
184 ATL::CComPtr<IOleWindow> pOleWindow;
185 HRESULT hr2 = m_pIDlg.QueryInterface(&pOleWindow);
186 if(SUCCEEDED(hr2))
188 hr2 = pOleWindow->GetWindow(&m_hWndProgDlg);
189 if(SUCCEEDED(hr2))
191 if(immediately)
192 ShowWindow(m_hWndProgDlg, SW_SHOW);
196 m_isVisible = true;
197 return hr;
200 HRESULT CSysProgressDlg::ShowModeless(HWND hWndParent, BOOL immediately)
202 EnsureValid();
203 m_hWndProgDlg = nullptr;
204 if (!IsValid())
205 return E_FAIL;
206 m_hWndParent = hWndParent;
207 auto winId = GetWindowThreadProcessId(m_hWndParent, nullptr);
208 auto threadId = GetCurrentThreadId();
209 if (winId != threadId)
210 AttachThreadInput(winId, threadId, TRUE);
211 m_hWndFocus = GetFocus();
212 if (winId != threadId)
213 AttachThreadInput(winId, threadId, FALSE);
214 HRESULT hr = m_pIDlg->StartProgressDialog(hWndParent, nullptr, m_dwDlgFlags, nullptr);
215 if(FAILED(hr))
216 return hr;
218 ATL::CComPtr<IOleWindow> pOleWindow;
219 HRESULT hr2 = m_pIDlg.QueryInterface(&pOleWindow);
220 if(SUCCEEDED(hr2))
222 hr2 = pOleWindow->GetWindow(&m_hWndProgDlg);
223 if(SUCCEEDED(hr2))
225 if (immediately)
226 ShowWindow(m_hWndProgDlg, SW_SHOW);
229 m_isVisible = true;
230 return hr;
233 void CSysProgressDlg::SetProgress(DWORD dwProgress, DWORD dwMax)
235 if (IsValid())
236 m_pIDlg->SetProgress(dwProgress, dwMax);
239 void CSysProgressDlg::SetProgress64(ULONGLONG u64Progress, ULONGLONG u64ProgressMax)
241 if (IsValid())
242 m_pIDlg->SetProgress64(u64Progress, u64ProgressMax);
245 bool CSysProgressDlg::HasUserCancelled()
247 if (!IsValid())
248 return false;
250 return (0 != m_pIDlg->HasUserCancelled());
253 void CSysProgressDlg::Stop()
255 if ((m_isVisible)&&(IsValid()))
257 m_pIDlg->StopProgressDialog();
258 // Sometimes the progress dialog sticks around after stopping it,
259 // until the mouse pointer is moved over it or some other triggers.
260 // We hide the window here immediately.
261 if (m_hWndProgDlg)
263 // The progress dialog is handled on a separate thread, which means
264 // even calling StopProgressDialog() will not stop it immediately.
265 // Even destroying the progress window is not enough.
266 // Which can cause problems with modality: if we stop the progress
267 // dialog and immediately show e.g. a messagebox over the same
268 // window the progress dialog has as its parent, then the messagebox
269 // is modal first (deactivates the parent), but then a little bit
270 // later the progress dialog finally closes properly, and by doing that
271 // re-enables its parent window.
272 // Which leads to the parent window being enabled even though
273 // the modal messagebox is shown over the parent window.
274 // This situation can even lead to the messagebox appearing *behind*
275 // the parent window (race condition)
277 // So, to really ensure that the progress dialog is fully stopped
278 // and destroyed, we have to attach to its UI thread and handle
279 // all messages until there are no more messages: that's when
280 // the progress dialog is really gone.
281 AttachThreadInput(GetWindowThreadProcessId(m_hWndProgDlg, 0), GetCurrentThreadId(), TRUE);
282 // StartProgressDialog creates a new thread to host the progress window.
283 // When the window receives WM_DESTROY message StopProgressDialog() wrongly
284 // attempts to re-enable the parent in the calling thread (our thread),
285 // after the progress window is destroyed and the progress thread has died.
286 // When the progress window dies, the system tries to assign a new foreground window.
287 // It cannot assign to hwndParent because StartProgressDialog (w/PROGDLG_MODAL) disabled the parent window.
288 // So the system hands the foreground activation to the next process that wants it in the
289 // system foreground queue. Thus we lose our right to recapture the foreground window.
290 // To fix this problem, we enable the parent window and set to focus to it here, after
291 // we've attached to the window thread.
292 ShowWindow(m_hWndProgDlg, SW_HIDE);
293 EnableWindow(m_hWndParent, TRUE);
294 if (m_hWndFocus)
295 SetFocus(m_hWndFocus);
296 else
297 SetFocus(m_hWndParent);
298 auto start = GetTickCount64();
299 while (::IsWindow(m_hWndProgDlg) && ((GetTickCount64() - start) < 3000))
301 MSG msg = { 0 };
302 while (PeekMessage(&msg, m_hWndProgDlg, 0, 0, PM_REMOVE))
307 m_isVisible = false;
308 m_pIDlg.Release();
310 m_hWndProgDlg = nullptr;
314 void CSysProgressDlg::ResetTimer()
316 if (IsValid())
317 m_pIDlg->Timer(PDTIMER_RESET, nullptr);