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.
21 #include "SysProgressDlg.h"
23 CSysProgressDlg::CSysProgressDlg()
26 , m_dwDlgFlags(PROGDLG_NORMAL
)
27 , m_hWndProgDlg(nullptr)
28 , m_hWndParent(nullptr)
29 , m_hWndFocus(nullptr)
34 CSysProgressDlg::~CSysProgressDlg()
38 if (m_isVisible
) //still visible, so stop first before destroying
39 m_pIDlg
->StopProgressDialog();
42 m_hWndProgDlg
= nullptr;
46 bool CSysProgressDlg::EnsureValid()
51 HRESULT hr
= m_pIDlg
.CoCreateInstance(CLSID_ProgressDialog
, nullptr, CLSCTX_INPROC_SERVER
);
52 return (SUCCEEDED(hr
));
55 void CSysProgressDlg::SetTitle(LPCTSTR szTitle
)
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 */)
70 m_pIDlg
->SetLine(dwLine
, T2COLE(szText
), bCompactPath
, nullptr);
74 void CSysProgressDlg::SetCancelMsg ( UINT idMessage
)
76 SetCancelMsg(CString(MAKEINTRESOURCE(idMessage
)));
80 void CSysProgressDlg::SetCancelMsg(LPCTSTR szMessage
)
84 m_pIDlg
->SetCancelMsg(T2COLE(szMessage
), nullptr);
87 void CSysProgressDlg::SetTime(bool bTime
/* = true */)
89 m_dwDlgFlags
&= ~(PROGDLG_NOTIME
| PROGDLG_AUTOTIME
);
92 m_dwDlgFlags
|= PROGDLG_AUTOTIME
;
94 m_dwDlgFlags
|= PROGDLG_NOTIME
;
97 void CSysProgressDlg::SetShowProgressBar(bool bShow
/* = true */)
100 m_dwDlgFlags
&= ~PROGDLG_NOPROGRESSBAR
;
102 m_dwDlgFlags
|= PROGDLG_NOPROGRESSBAR
;
105 HRESULT
CSysProgressDlg::ShowModal (CWnd
* pwndParent
, BOOL immediately
/* = true */)
108 return ShowModal(pwndParent
->GetSafeHwnd(), immediately
);
111 HRESULT
CSysProgressDlg::ShowModeless(CWnd
* pwndParent
, BOOL immediately
)
114 return ShowModeless(pwndParent
->GetSafeHwnd(), immediately
);
117 void CSysProgressDlg::FormatPathLine ( DWORD dwLine
, UINT idFormatText
, ...)
120 va_start(args
, idFormatText
);
123 sText
.FormatV(CString(MAKEINTRESOURCE(idFormatText
)), args
);
124 SetLine(dwLine
, sText
, true);
129 void CSysProgressDlg::FormatPathLine ( DWORD dwLine
, CString FormatText
, ...)
132 va_start(args
, FormatText
);
135 sText
.FormatV(FormatText
, args
);
136 SetLine(dwLine
, sText
, true);
141 void CSysProgressDlg::FormatNonPathLine(DWORD dwLine
, UINT idFormatText
, ...)
144 va_start(args
, idFormatText
);
147 sText
.FormatV(CString(MAKEINTRESOURCE(idFormatText
)), args
);
148 SetLine(dwLine
, sText
, false);
153 void CSysProgressDlg::FormatNonPathLine(DWORD dwLine
, CString FormatText
, ...)
156 va_start(args
, FormatText
);
159 sText
.FormatV(FormatText
, args
);
160 SetLine(dwLine
, sText
, false);
166 HRESULT
CSysProgressDlg::ShowModal(HWND hWndParent
, BOOL immediately
/* = true */)
169 m_hWndProgDlg
= nullptr;
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);
184 ATL::CComPtr
<IOleWindow
> pOleWindow
;
185 HRESULT hr2
= m_pIDlg
.QueryInterface(&pOleWindow
);
188 hr2
= pOleWindow
->GetWindow(&m_hWndProgDlg
);
192 ShowWindow(m_hWndProgDlg
, SW_SHOW
);
200 HRESULT
CSysProgressDlg::ShowModeless(HWND hWndParent
, BOOL immediately
)
203 m_hWndProgDlg
= nullptr;
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);
218 ATL::CComPtr
<IOleWindow
> pOleWindow
;
219 HRESULT hr2
= m_pIDlg
.QueryInterface(&pOleWindow
);
222 hr2
= pOleWindow
->GetWindow(&m_hWndProgDlg
);
226 ShowWindow(m_hWndProgDlg
, SW_SHOW
);
233 void CSysProgressDlg::SetProgress(DWORD dwProgress
, DWORD dwMax
)
236 m_pIDlg
->SetProgress(dwProgress
, dwMax
);
239 void CSysProgressDlg::SetProgress64(ULONGLONG u64Progress
, ULONGLONG u64ProgressMax
)
242 m_pIDlg
->SetProgress64(u64Progress
, u64ProgressMax
);
245 bool CSysProgressDlg::HasUserCancelled()
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.
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
);
295 SetFocus(m_hWndFocus
);
297 SetFocus(m_hWndParent
);
298 auto start
= GetTickCount64();
299 while (::IsWindow(m_hWndProgDlg
) && ((GetTickCount64() - start
) < 3000))
302 while (PeekMessage(&msg
, m_hWndProgDlg
, 0, 0, PM_REMOVE
))
310 m_hWndProgDlg
= nullptr;
314 void CSysProgressDlg::ResetTimer()
317 m_pIDlg
->Timer(PDTIMER_RESET
, nullptr);