4 * Copyright 2007 Mikolaj Zalewski
5 * Copyright 2014 Huw Campbell
7 * this library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * this library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
26 #define CANCEL_MSG_LINE 2
28 /* Note: to avoid a deadlock we don't want to send messages to the dialog
29 * with the critical section held. Instead we only mark what fields should be
30 * updated and the dialog proc does the update */
31 #define UPDATE_PROGRESS 0x1
32 #define UPDATE_TITLE 0x2
33 #define UPDATE_LINE1 0x4
34 #define UPDATE_LINE2 (UPDATE_LINE1<<1)
35 #define UPDATE_LINE3 (UPDATE_LINE1<<2)
38 #define WM_DLG_UPDATE (WM_APP+1) /* set to the dialog when it should update */
39 #define WM_DLG_DESTROY (WM_APP+2) /* DestroyWindow must be called from the owning thread */
41 #define ID_3SECONDS 101
43 #define BUFFER_SIZE 256
45 CProgressDialog::CProgressDialog()
47 this->lines
[0] = (LPWSTR
) HeapAlloc(GetProcessHeap(), 0, BUFFER_SIZE
);
48 this->lines
[1] = (LPWSTR
) HeapAlloc(GetProcessHeap(), 0, BUFFER_SIZE
);
49 this->lines
[2] = (LPWSTR
) HeapAlloc(GetProcessHeap(), 0, BUFFER_SIZE
);
50 this->cancelMsg
= (LPWSTR
) HeapAlloc(GetProcessHeap(), 0, BUFFER_SIZE
);
51 this->title
= (LPWSTR
) HeapAlloc(GetProcessHeap(), 0, BUFFER_SIZE
);
53 this->lines
[0][0] = this->lines
[1][0] = this->lines
[2][0] = UNICODE_NULL
;
54 this->cancelMsg
[0] = this->title
[0] = UNICODE_NULL
;
57 this->progressClock
[29].ullMark
= 0ull;
58 this->dwStartTime
= GetTickCount();
60 InitializeCriticalSection(&this->cs
);
63 CProgressDialog::~CProgressDialog()
67 HeapFree(GetProcessHeap(), 0, this->lines
[0]);
68 HeapFree(GetProcessHeap(), 0, this->lines
[1]);
69 HeapFree(GetProcessHeap(), 0, this->lines
[2]);
70 HeapFree(GetProcessHeap(), 0, this->cancelMsg
);
71 HeapFree(GetProcessHeap(), 0, this->title
);
72 DeleteCriticalSection(&this->cs
);
75 static void set_buffer(LPWSTR
*buffer
, LPCWSTR string
)
79 (*buffer
)[0] = UNICODE_NULL
;
83 StringCbCopyW(*buffer
, BUFFER_SIZE
, string
);
88 CProgressDialog
*This
;
93 static void load_string(LPWSTR
*buffer
, HINSTANCE hInstance
, UINT uiResourceId
)
97 LoadStringW(hInstance
, uiResourceId
, string
, sizeof(string
)/sizeof(string
[0]));
99 set_buffer(buffer
, string
);
102 void CProgressDialog::set_progress_marquee()
104 HWND hProgress
= GetDlgItem(this->hwnd
, IDC_PROGRESS_BAR
);
105 SetWindowLongW(hProgress
, GWL_STYLE
,
106 GetWindowLongW(hProgress
, GWL_STYLE
)|PBS_MARQUEE
);
109 void CProgressDialog::update_dialog(DWORD dwUpdate
)
113 if (dwUpdate
& UPDATE_TITLE
)
114 SetWindowTextW(this->hwnd
, this->title
);
116 if (dwUpdate
& UPDATE_LINE1
)
117 SetDlgItemTextW(this->hwnd
, IDC_TEXT_LINE
, (this->isCancelled
? empty
: this->lines
[0]));
118 if (dwUpdate
& UPDATE_LINE2
)
119 SetDlgItemTextW(this->hwnd
, IDC_TEXT_LINE
+1, (this->isCancelled
? empty
: this->lines
[1]));
120 if (dwUpdate
& UPDATE_LINE3
)
121 SetDlgItemTextW(this->hwnd
, IDC_TEXT_LINE
+2, (this->isCancelled
? this->cancelMsg
: this->lines
[2]));
123 if (dwUpdate
& UPDATE_PROGRESS
)
125 ULONGLONG ullTotal
= this->ullTotal
;
126 ULONGLONG ullCompleted
= this->ullCompleted
;
128 /* progress bar requires 32-bit coordinates */
129 while (ullTotal
>> 32)
135 SendDlgItemMessageW(this->hwnd
, IDC_PROGRESS_BAR
, PBM_SETRANGE32
, 0, (DWORD
)ullTotal
);
136 SendDlgItemMessageW(this->hwnd
, IDC_PROGRESS_BAR
, PBM_SETPOS
, (DWORD
)ullCompleted
, 0);
140 void CProgressDialog::end_dialog()
142 SendMessageW(this->hwnd
, WM_DLG_DESTROY
, 0, 0);
143 /* native doesn't re-enable the window? */
144 if (this->hwndDisabledParent
)
145 EnableWindow(this->hwndDisabledParent
, TRUE
);
149 static INT_PTR CALLBACK
dialog_proc(HWND hwnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
151 CProgressDialog
*This
= (CProgressDialog
*)GetWindowLongPtrW(hwnd
, DWLP_USER
);
157 struct create_params
*params
= (struct create_params
*)lParam
;
159 /* Note: until we set the hEvent, the object is protected by
160 * the critical section held by StartProgress */
161 SetWindowLongPtrW(hwnd
, DWLP_USER
, (LONG_PTR
)params
->This
);
165 if (This
->dwFlags
& PROGDLG_NOPROGRESSBAR
)
166 ShowWindow(GetDlgItem(hwnd
, IDC_PROGRESS_BAR
), SW_HIDE
);
167 if (This
->dwFlags
& PROGDLG_NOCANCEL
)
168 ShowWindow(GetDlgItem(hwnd
, IDCANCEL
), SW_HIDE
);
169 if (This
->dwFlags
& PROGDLG_MARQUEEPROGRESS
)
170 This
->set_progress_marquee();
171 if (This
->dwFlags
& PROGDLG_NOMINIMIZE
)
172 SetWindowLongW(hwnd
, GWL_STYLE
, GetWindowLongW(hwnd
, GWL_STYLE
) & (~WS_MINIMIZEBOX
));
174 This
->update_dialog(0xffffffff);
176 This
->isCancelled
= FALSE
;
178 SetTimer(hwnd
, ID_3SECONDS
, 3 * 1000, NULL
);
180 SetEvent(params
->hEvent
);
185 EnterCriticalSection(&This
->cs
);
186 This
->update_dialog(This
->dwUpdate
);
188 LeaveCriticalSection(&This
->cs
);
193 PostThreadMessageW(GetCurrentThreadId(), WM_NULL
, 0, 0); /* wake up the GetMessage */
194 KillTimer(hwnd
, ID_3SECONDS
);
200 if (msg
== WM_CLOSE
|| wParam
== IDCANCEL
)
202 EnterCriticalSection(&This
->cs
);
203 This
->isCancelled
= TRUE
;
205 if (!This
->cancelMsg
[0]) {
206 load_string(&This
->cancelMsg
, _AtlBaseModule
.GetResourceInstance(), IDS_CANCELLING
);
209 This
->set_progress_marquee();
210 EnableWindow(GetDlgItem(This
->hwnd
, IDCANCEL
), FALSE
);
211 This
->update_dialog(UPDATE_LINE1
|UPDATE_LINE2
|UPDATE_LINE3
);
212 LeaveCriticalSection(&This
->cs
);
217 EnterCriticalSection(&This
->cs
);
218 if (This
->progressClock
[29].ullMark
!= 0ull) {
219 // We have enough info to take a guess
220 ULONGLONG sizeDiff
= This
->progressClock
[This
->clockHand
].ullMark
-
221 This
->progressClock
[(This
->clockHand
+ 29) % 30].ullMark
;
222 DWORD timeDiff
= This
->progressClock
[This
->clockHand
].dwTime
-
223 This
->progressClock
[(This
->clockHand
+ 29) % 30].dwTime
;
224 DWORD runDiff
= This
->progressClock
[This
->clockHand
].dwTime
-
226 ULONGLONG sizeLeft
= This
->ullTotal
- This
->progressClock
[This
->clockHand
].ullMark
;
228 // A guess for time remaining based on the recent slope.
229 DWORD timeLeftD
= (DWORD
) timeDiff
* ((double) sizeLeft
) / ((double) sizeDiff
);
230 // A guess for time remaining based on the start time and current position
231 DWORD timeLeftI
= (DWORD
) runDiff
* ((double) sizeLeft
) / ((double) This
->progressClock
[This
->clockHand
].ullMark
);
233 StrFromTimeIntervalW(This
->lines
[2], 128, timeLeftD
* 0.3 + timeLeftI
* 0.7 , 2);
234 This
->update_dialog( UPDATE_LINE1
<< 2 );
236 LeaveCriticalSection(&This
->cs
);
243 static DWORD WINAPI
dialog_thread(LPVOID lpParameter
)
245 /* Note: until we set the hEvent in WM_INITDIALOG, the ProgressDialog object
246 * is protected by the critical section held by StartProgress */
247 struct create_params
*params
= (struct create_params
*) lpParameter
;
251 hwnd
= CreateDialogParamW(_AtlBaseModule
.GetResourceInstance(),
252 MAKEINTRESOURCEW(IDD_PROGRESS_DLG
),
257 while (GetMessageW(&msg
, NULL
, 0, 0) > 0)
261 if(!IsDialogMessageW(hwnd
, &msg
))
263 TranslateMessage(&msg
);
264 DispatchMessageW(&msg
);
271 HRESULT WINAPI
CProgressDialog::StartProgressDialog(HWND hwndParent
, IUnknown
*punkEnableModeless
, DWORD dwFlags
, LPCVOID reserved
)
273 static const INITCOMMONCONTROLSEX init
= { sizeof(init
), ICC_ANIMATE_CLASS
};
275 struct create_params params
;
278 // TRACE("(%p, %p, %x, %p)\n", this, punkEnableModeless, dwFlags, reserved);
279 if (punkEnableModeless
|| reserved
)
280 FIXME("Reserved parameters not null (%p, %p)\n", punkEnableModeless
, reserved
);
281 if (dwFlags
& PROGDLG_AUTOTIME
)
282 FIXME("Flags PROGDLG_AUTOTIME not supported\n");
283 if (dwFlags
& PROGDLG_NOTIME
)
284 FIXME("Flags PROGDLG_NOTIME not supported\n");
286 InitCommonControlsEx( &init
);
288 EnterCriticalSection(&this->cs
);
292 LeaveCriticalSection(&this->cs
);
293 return S_OK
; /* as on XP */
295 this->dwFlags
= dwFlags
;
297 params
.hwndParent
= hwndParent
;
298 params
.hEvent
= CreateEventW(NULL
, TRUE
, FALSE
, NULL
);
300 hThread
= CreateThread(NULL
, 0, dialog_thread
, ¶ms
, 0, NULL
);
301 WaitForSingleObject(params
.hEvent
, INFINITE
);
302 CloseHandle(params
.hEvent
);
303 CloseHandle(hThread
);
305 this->hwndDisabledParent
= NULL
;
306 if (hwndParent
&& (dwFlags
& PROGDLG_MODAL
))
308 HWND hwndDisable
= GetAncestor(hwndParent
, GA_ROOT
);
309 if (EnableWindow(hwndDisable
, FALSE
))
310 this->hwndDisabledParent
= hwndDisable
;
313 LeaveCriticalSection(&this->cs
);
318 HRESULT WINAPI
CProgressDialog::StopProgressDialog()
320 EnterCriticalSection(&this->cs
);
323 LeaveCriticalSection(&this->cs
);
328 HRESULT WINAPI
CProgressDialog::SetTitle(LPCWSTR pwzTitle
)
332 EnterCriticalSection(&this->cs
);
333 set_buffer(&this->title
, pwzTitle
);
334 this->dwUpdate
|= UPDATE_TITLE
;
336 LeaveCriticalSection(&this->cs
);
339 SendMessageW(hwnd
, WM_DLG_UPDATE
, 0, 0);
344 HRESULT WINAPI
CProgressDialog::SetAnimation(HINSTANCE hInstance
, UINT uiResourceId
)
346 HWND hAnimation
= GetDlgItem(this->hwnd
, IDD_PROGRESS_DLG
);
347 SetWindowLongW(hAnimation
, GWL_STYLE
,
348 GetWindowLongW(hAnimation
, GWL_STYLE
)|ACS_TRANSPARENT
|ACS_CENTER
|ACS_AUTOPLAY
);
350 if(!Animate_OpenEx(hAnimation
,hInstance
,MAKEINTRESOURCEW(uiResourceId
)))
356 BOOL WINAPI
CProgressDialog::HasUserCancelled()
358 return this->isCancelled
;
361 HRESULT WINAPI
CProgressDialog::SetProgress64(ULONGLONG ullCompleted
, ULONGLONG ullTotal
)
365 EnterCriticalSection(&this->cs
);
366 this->ullTotal
= ullTotal
;
367 this->ullCompleted
= ullCompleted
;
369 if (GetTickCount() - this->progressClock
[(this->clockHand
+ 29) % 30].dwTime
> 20) {
370 this->clockHand
= (this->clockHand
+ 1) % 30;
371 this->progressClock
[this->clockHand
].ullMark
= ullCompleted
;
372 this->progressClock
[this->clockHand
].dwTime
= GetTickCount();
375 this->dwUpdate
|= UPDATE_PROGRESS
;
377 LeaveCriticalSection(&this->cs
);
380 SendMessageW(hwnd
, WM_DLG_UPDATE
, 0, 0);
382 return S_OK
; /* Windows sometimes returns S_FALSE */
385 HRESULT WINAPI
CProgressDialog::SetProgress(DWORD dwCompleted
, DWORD dwTotal
)
387 return this->SetProgress64(dwCompleted
, dwTotal
);
390 HRESULT WINAPI
CProgressDialog::SetLine(DWORD dwLineNum
, LPCWSTR pwzLine
, BOOL bPath
, LPCVOID reserved
)
395 FIXME("reserved pointer not null (%p)\n", reserved
);
398 if (dwLineNum
>= 3) /* Windows seems to do something like that */
401 EnterCriticalSection(&this->cs
);
402 set_buffer(&this->lines
[dwLineNum
], pwzLine
);
403 this->dwUpdate
|= UPDATE_LINE1
<< dwLineNum
;
404 hwnd
= (this->isCancelled
? NULL
: this->hwnd
); /* no sense to send the message if window cancelled */
405 LeaveCriticalSection(&this->cs
);
408 SendMessageW(hwnd
, WM_DLG_UPDATE
, 0, 0);
413 HRESULT WINAPI
CProgressDialog::SetCancelMsg(LPCWSTR pwzMsg
, LPCVOID reserved
)
418 FIXME("reserved pointer not null (%p)\n", reserved
);
420 EnterCriticalSection(&this->cs
);
421 set_buffer(&this->cancelMsg
, pwzMsg
);
422 this->dwUpdate
|= UPDATE_LINE1
<< CANCEL_MSG_LINE
;
423 hwnd
= (this->isCancelled
? this->hwnd
: NULL
); /* no sense to send the message if window not cancelled */
424 LeaveCriticalSection(&this->cs
);
427 SendMessageW(hwnd
, WM_DLG_UPDATE
, 0, 0);
432 HRESULT WINAPI
CProgressDialog::Timer(DWORD dwTimerAction
, LPCVOID reserved
)
435 FIXME("Reserved field not NULL but %p\n", reserved
);
440 HRESULT WINAPI
CProgressDialog::GetWindow(HWND
* phwnd
)
442 EnterCriticalSection(&this->cs
);
444 LeaveCriticalSection(&this->cs
);
448 HRESULT WINAPI
CProgressDialog::ContextSensitiveHelp(BOOL fEnterMode
)