1 /* 2 * Progress dialog 3 * 4 * Copyright 2007 Mikolaj Zalewski 5 * Copyright 2014 Huw Campbell 6 * 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. 11 * 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. 16 * 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 20 */ 21 22 #include <precomp.h> 23 24 #define COBJMACROS 25 26 #define CANCEL_MSG_LINE 2 27 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) 36 37 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 */ 40 41 #define ID_3SECONDS 101 42 43 #define BUFFER_SIZE 256 44 45 CProgressDialog::CProgressDialog() 46 { 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); 52 53 this->lines[0][0] = this->lines[1][0] = this->lines[2][0] = UNICODE_NULL; 54 this->cancelMsg[0] = this->title[0] = UNICODE_NULL; 55 56 this->clockHand = -1; 57 this->progressClock[29].ullMark = 0ull; 58 this->dwStartTime = GetTickCount(); 59 60 InitializeCriticalSection(&this->cs); 61 } 62 63 CProgressDialog::~CProgressDialog() 64 { 65 if (this->hwnd) 66 this->end_dialog(); 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); 73 } 74 75 static void set_buffer(LPWSTR *buffer, LPCWSTR string) 76 { 77 if (!string) 78 { 79 (*buffer)[0] = UNICODE_NULL; 80 return; 81 } 82 83 StringCbCopyW(*buffer, BUFFER_SIZE, string); 84 } 85 86 struct create_params 87 { 88 CProgressDialog *This; 89 HANDLE hEvent; 90 HWND hwndParent; 91 }; 92 93 static void load_string(LPWSTR *buffer, HINSTANCE hInstance, UINT uiResourceId) 94 { 95 WCHAR string[256]; 96 97 LoadStringW(hInstance, uiResourceId, string, sizeof(string)/sizeof(string[0])); 98 99 set_buffer(buffer, string); 100 } 101 102 void CProgressDialog::set_progress_marquee() 103 { 104 HWND hProgress = GetDlgItem(this->hwnd, IDC_PROGRESS_BAR); 105 SetWindowLongW(hProgress, GWL_STYLE, 106 GetWindowLongW(hProgress, GWL_STYLE)|PBS_MARQUEE); 107 } 108 109 void CProgressDialog::update_dialog(DWORD dwUpdate) 110 { 111 WCHAR empty[] = {0}; 112 113 if (dwUpdate & UPDATE_TITLE) 114 SetWindowTextW(this->hwnd, this->title); 115 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])); 122 123 if (dwUpdate & UPDATE_PROGRESS) 124 { 125 ULONGLONG ullTotal = this->ullTotal; 126 ULONGLONG ullCompleted = this->ullCompleted; 127 128 /* progress bar requires 32-bit coordinates */ 129 while (ullTotal >> 32) 130 { 131 ullTotal >>= 1; 132 ullCompleted >>= 1; 133 } 134 135 SendDlgItemMessageW(this->hwnd, IDC_PROGRESS_BAR, PBM_SETRANGE32, 0, (DWORD)ullTotal); 136 SendDlgItemMessageW(this->hwnd, IDC_PROGRESS_BAR, PBM_SETPOS, (DWORD)ullCompleted, 0); 137 } 138 } 139 140 void CProgressDialog::end_dialog() 141 { 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); 146 this->hwnd = NULL; 147 } 148 149 static INT_PTR CALLBACK dialog_proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) 150 { 151 CProgressDialog *This = (CProgressDialog *)GetWindowLongPtrW(hwnd, DWLP_USER); 152 153 switch (msg) 154 { 155 case WM_INITDIALOG: 156 { 157 struct create_params *params = (struct create_params *)lParam; 158 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); 162 This = params->This; 163 This->hwnd = hwnd; 164 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)); 173 174 This->update_dialog(0xffffffff); 175 This->dwUpdate = 0; 176 This->isCancelled = FALSE; 177 178 SetTimer(hwnd, ID_3SECONDS, 3 * 1000, NULL); 179 180 SetEvent(params->hEvent); 181 return TRUE; 182 } 183 184 case WM_DLG_UPDATE: 185 EnterCriticalSection(&This->cs); 186 This->update_dialog(This->dwUpdate); 187 This->dwUpdate = 0; 188 LeaveCriticalSection(&This->cs); 189 return TRUE; 190 191 case WM_DLG_DESTROY: 192 DestroyWindow(hwnd); 193 PostThreadMessageW(GetCurrentThreadId(), WM_NULL, 0, 0); /* wake up the GetMessage */ 194 KillTimer(hwnd, ID_3SECONDS); 195 196 return TRUE; 197 198 case WM_CLOSE: 199 case WM_COMMAND: 200 if (msg == WM_CLOSE || wParam == IDCANCEL) 201 { 202 EnterCriticalSection(&This->cs); 203 This->isCancelled = TRUE; 204 205 if (!This->cancelMsg[0]) { 206 load_string(&This->cancelMsg, _AtlBaseModule.GetResourceInstance(), IDS_CANCELLING); 207 } 208 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); 213 } 214 return TRUE; 215 216 case WM_TIMER: 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 - 225 This->dwStartTime; 226 ULONGLONG sizeLeft = This->ullTotal - This->progressClock[This->clockHand].ullMark; 227 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); 232 233 StrFromTimeIntervalW(This->lines[2], 128, timeLeftD * 0.3 + timeLeftI * 0.7 , 2); 234 This->update_dialog( UPDATE_LINE1 << 2 ); 235 } 236 LeaveCriticalSection(&This->cs); 237 238 return TRUE; 239 } 240 return FALSE; 241 } 242 243 static DWORD WINAPI dialog_thread(LPVOID lpParameter) 244 { 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; 248 HWND hwnd; 249 MSG msg; 250 251 hwnd = CreateDialogParamW(_AtlBaseModule.GetResourceInstance(), 252 MAKEINTRESOURCEW(IDD_PROGRESS_DLG), 253 params->hwndParent, 254 dialog_proc, 255 (LPARAM)params); 256 257 while (GetMessageW(&msg, NULL, 0, 0) > 0) 258 { 259 if (!IsWindow(hwnd)) 260 break; 261 if(!IsDialogMessageW(hwnd, &msg)) 262 { 263 TranslateMessage(&msg); 264 DispatchMessageW(&msg); 265 } 266 } 267 268 return 0; 269 } 270 271 HRESULT WINAPI CProgressDialog::StartProgressDialog(HWND hwndParent, IUnknown *punkEnableModeless, DWORD dwFlags, LPCVOID reserved) 272 { 273 static const INITCOMMONCONTROLSEX init = { sizeof(init), ICC_ANIMATE_CLASS }; 274 275 struct create_params params; 276 HANDLE hThread; 277 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"); 285 286 InitCommonControlsEx( &init ); 287 288 EnterCriticalSection(&this->cs); 289 290 if (this->hwnd) 291 { 292 LeaveCriticalSection(&this->cs); 293 return S_OK; /* as on XP */ 294 } 295 this->dwFlags = dwFlags; 296 params.This = this; 297 params.hwndParent = hwndParent; 298 params.hEvent = CreateEventW(NULL, TRUE, FALSE, NULL); 299 300 hThread = CreateThread(NULL, 0, dialog_thread, ¶ms, 0, NULL); 301 WaitForSingleObject(params.hEvent, INFINITE); 302 CloseHandle(params.hEvent); 303 CloseHandle(hThread); 304 305 this->hwndDisabledParent = NULL; 306 if (hwndParent && (dwFlags & PROGDLG_MODAL)) 307 { 308 HWND hwndDisable = GetAncestor(hwndParent, GA_ROOT); 309 if (EnableWindow(hwndDisable, FALSE)) 310 this->hwndDisabledParent = hwndDisable; 311 } 312 313 LeaveCriticalSection(&this->cs); 314 315 return S_OK; 316 } 317 318 HRESULT WINAPI CProgressDialog::StopProgressDialog() 319 { 320 EnterCriticalSection(&this->cs); 321 if (this->hwnd) 322 this->end_dialog(); 323 LeaveCriticalSection(&this->cs); 324 325 return S_OK; 326 } 327 328 HRESULT WINAPI CProgressDialog::SetTitle(LPCWSTR pwzTitle) 329 { 330 HWND hwnd; 331 332 EnterCriticalSection(&this->cs); 333 set_buffer(&this->title, pwzTitle); 334 this->dwUpdate |= UPDATE_TITLE; 335 hwnd = this->hwnd; 336 LeaveCriticalSection(&this->cs); 337 338 if (hwnd) 339 SendMessageW(hwnd, WM_DLG_UPDATE, 0, 0); 340 341 return S_OK; 342 } 343 344 HRESULT WINAPI CProgressDialog::SetAnimation(HINSTANCE hInstance, UINT uiResourceId) 345 { 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); 349 350 if(!Animate_OpenEx(hAnimation,hInstance,MAKEINTRESOURCEW(uiResourceId))) 351 return S_FALSE; 352 353 return S_OK; 354 } 355 356 BOOL WINAPI CProgressDialog::HasUserCancelled() 357 { 358 return this->isCancelled; 359 } 360 361 HRESULT WINAPI CProgressDialog::SetProgress64(ULONGLONG ullCompleted, ULONGLONG ullTotal) 362 { 363 HWND hwnd; 364 365 EnterCriticalSection(&this->cs); 366 this->ullTotal = ullTotal; 367 this->ullCompleted = ullCompleted; 368 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(); 373 } 374 375 this->dwUpdate |= UPDATE_PROGRESS; 376 hwnd = this->hwnd; 377 LeaveCriticalSection(&this->cs); 378 379 if (hwnd) 380 SendMessageW(hwnd, WM_DLG_UPDATE, 0, 0); 381 382 return S_OK; /* Windows sometimes returns S_FALSE */ 383 } 384 385 HRESULT WINAPI CProgressDialog::SetProgress(DWORD dwCompleted, DWORD dwTotal) 386 { 387 return this->SetProgress64(dwCompleted, dwTotal); 388 } 389 390 HRESULT WINAPI CProgressDialog::SetLine(DWORD dwLineNum, LPCWSTR pwzLine, BOOL bPath, LPCVOID reserved) 391 { 392 HWND hwnd; 393 394 if (reserved) 395 FIXME("reserved pointer not null (%p)\n", reserved); 396 397 dwLineNum--; 398 if (dwLineNum >= 3) /* Windows seems to do something like that */ 399 dwLineNum = 0; 400 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); 406 407 if (hwnd) 408 SendMessageW(hwnd, WM_DLG_UPDATE, 0, 0); 409 410 return S_OK; 411 } 412 413 HRESULT WINAPI CProgressDialog::SetCancelMsg(LPCWSTR pwzMsg, LPCVOID reserved) 414 { 415 HWND hwnd; 416 417 if (reserved) 418 FIXME("reserved pointer not null (%p)\n", reserved); 419 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); 425 426 if (hwnd) 427 SendMessageW(hwnd, WM_DLG_UPDATE, 0, 0); 428 429 return S_OK; 430 } 431 432 HRESULT WINAPI CProgressDialog::Timer(DWORD dwTimerAction, LPCVOID reserved) 433 { 434 if (reserved) 435 FIXME("Reserved field not NULL but %p\n", reserved); 436 437 return S_OK; 438 } 439 440 HRESULT WINAPI CProgressDialog::GetWindow(HWND* phwnd) 441 { 442 EnterCriticalSection(&this->cs); 443 *phwnd = this->hwnd; 444 LeaveCriticalSection(&this->cs); 445 return S_OK; 446 } 447 448 HRESULT WINAPI CProgressDialog::ContextSensitiveHelp(BOOL fEnterMode) 449 { 450 return E_NOTIMPL; 451 } 452