browseui: Fix typo in declaration of empty_string in set_buffer.
[wine] / dlls / browseui / progressdlg.c
1 /*
2  *      Progress dialog
3  *
4  *      Copyright 2007  Mikolaj Zalewski
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library 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 GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21 #include "config.h"
22
23 #include <stdarg.h>
24
25 #define COBJMACROS
26
27 #include "wine/debug.h"
28 #include "windef.h"
29 #include "winbase.h"
30 #include "winreg.h"
31 #include "winuser.h"
32 #include "shlwapi.h"
33 #include "winerror.h"
34 #include "objbase.h"
35
36 #include "shlguid.h"
37 #include "shlobj.h"
38
39 #include "wine/unicode.h"
40
41 #include "browseui.h"
42 #include "resids.h"
43
44 WINE_DEFAULT_DEBUG_CHANNEL(browseui);
45
46 #define CANCEL_MSG_LINE 2
47
48 /* Note: to avoid a deadlock we don't want to send messages to the dialog
49  * with the critical section held. Instead we only mark what fields should be
50  * updated and the dialog proc does the update */
51 #define UPDATE_PROGRESS         0x1
52 #define UPDATE_TITLE            0x2
53 #define UPDATE_LINE1            0x4
54 #define UPDATE_LINE2            (UPDATE_LINE1<<1)
55 #define UPDATE_LINE3            (UPDATE_LINE2<<2)
56
57
58 #define WM_DLG_UPDATE   (WM_APP+1)  /* set to the dialog when it should update */
59 #define WM_DLG_DESTROY  (WM_APP+2)  /* DestroyWindow must be called from the owning thread */
60
61 typedef struct tagProgressDialog {
62     const IProgressDialogVtbl *vtbl;
63     LONG refCount;
64     CRITICAL_SECTION cs;
65     HWND hwnd;
66     DWORD dwFlags;
67     DWORD dwUpdate;
68     LPWSTR lines[3];
69     LPWSTR cancelMsg;
70     LPWSTR title;
71     BOOL isCancelled;
72     ULONGLONG ullCompleted;
73     ULONGLONG ullTotal;
74     HWND hwndDisabledParent;    /* For modal dialog: the parent that need to be re-enabled when the dialog ends */
75 } ProgressDialog;
76
77 static const IProgressDialogVtbl ProgressDialogVtbl;
78
79 static void set_buffer(LPWSTR *buffer, LPCWSTR string)
80 {
81     static const WCHAR empty_string[] = {0};
82     IMalloc *malloc;
83     int cb;
84
85     if (string == NULL)
86         string = empty_string;
87     CoGetMalloc(1, &malloc);
88
89     cb = (strlenW(string) + 1)*sizeof(WCHAR);
90     if (*buffer == NULL || cb > IMalloc_GetSize(malloc, *buffer))
91         *buffer = IMalloc_Realloc(malloc, *buffer, cb);
92     memcpy(*buffer, string, cb);
93 }
94
95 struct create_params
96 {
97     ProgressDialog *This;
98     HANDLE hEvent;
99     HWND hwndParent;
100 };
101
102 static LPWSTR load_string(HINSTANCE hInstance, UINT uiResourceId)
103 {
104     WCHAR string[256];
105     LPWSTR ret;
106
107     LoadStringW(hInstance, uiResourceId, string, sizeof(string)/sizeof(string[0]));
108     ret = HeapAlloc(GetProcessHeap(), 0, (strlenW(string) + 1) * sizeof(WCHAR));
109     strcpyW(ret, string);
110     return ret;
111 }
112
113 static void set_progress_marquee(ProgressDialog *This)
114 {
115     HWND hProgress = GetDlgItem(This->hwnd, IDC_PROGRESS_BAR);
116     SetWindowLongW(hProgress, GWL_STYLE,
117         GetWindowLongW(hProgress, GWL_STYLE)|PBS_MARQUEE);
118 }
119
120 void update_dialog(ProgressDialog *This, DWORD dwUpdate)
121 {
122     WCHAR empty[] = {0};
123
124     if (dwUpdate & UPDATE_TITLE)
125         SetWindowTextW(This->hwnd, This->title);
126
127     if (dwUpdate & UPDATE_LINE1)
128         SetDlgItemTextW(This->hwnd, IDC_TEXT_LINE, (This->isCancelled ? empty : This->lines[0]));
129     if (dwUpdate & UPDATE_LINE2)
130         SetDlgItemTextW(This->hwnd, IDC_TEXT_LINE+1, (This->isCancelled ? empty : This->lines[1]));
131     if (dwUpdate & UPDATE_LINE3)
132         SetDlgItemTextW(This->hwnd, IDC_TEXT_LINE+2, (This->isCancelled ? This->cancelMsg : This->lines[2]));
133
134     if (dwUpdate & UPDATE_PROGRESS)
135     {
136         ULONGLONG ullTotal = This->ullTotal;
137         ULONGLONG ullCompleted = This->ullCompleted;
138
139         /* progress bar requires 32-bit coordinates */
140         while (ullTotal >> 32)
141         {
142             ullTotal >>= 1;
143             ullCompleted >>= 1;
144         }
145
146         SendDlgItemMessageW(This->hwnd, IDC_PROGRESS_BAR, PBM_SETRANGE32, 0, (DWORD)ullTotal);
147         SendDlgItemMessageW(This->hwnd, IDC_PROGRESS_BAR, PBM_SETPOS, (DWORD)ullCompleted, 0);
148     }
149 }
150
151 static void end_dialog(ProgressDialog *This)
152 {
153     SendMessageW(This->hwnd, WM_DLG_DESTROY, 0, 0);
154     /* native doesn't reenable the window? */
155     if (This->hwndDisabledParent)
156         EnableWindow(This->hwndDisabledParent, TRUE);
157     This->hwnd = NULL;
158 }
159
160 static INT_PTR CALLBACK dialog_proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
161 {
162     ProgressDialog *This = (ProgressDialog *)GetWindowLongPtrW(hwnd, DWLP_USER);
163
164     switch (msg)
165     {
166         case WM_INITDIALOG:
167         {
168             struct create_params *params = (struct create_params *)lParam;
169
170             /* Note: until we set the hEvent, the object is protected by
171              * the critical section held by StartProgress */
172             SetWindowLongPtrW(hwnd, DWLP_USER, (LONG_PTR)params->This);
173             This = params->This;
174             This->hwnd = hwnd;
175
176             if (This->dwFlags & PROGDLG_NOPROGRESSBAR)
177                 ShowWindow(GetDlgItem(hwnd, IDC_PROGRESS_BAR), SW_HIDE);
178             if (This->dwFlags & PROGDLG_NOCANCEL)
179                 ShowWindow(GetDlgItem(hwnd, IDCANCEL), SW_HIDE);
180             if (This->dwFlags & PROGDLG_MARQUEEPROGRESS)
181                 set_progress_marquee(This);
182             if (This->dwFlags & PROGDLG_NOMINIMIZE)
183                 SetWindowLongW(hwnd, GWL_STYLE, GetWindowLongW(hwnd, GWL_STYLE) & (~WS_MINIMIZEBOX));
184
185             update_dialog(This, 0xffffffff);
186             This->dwUpdate = 0;
187             This->isCancelled = FALSE;
188             SetEvent(params->hEvent);
189             return TRUE;
190         }
191
192         case WM_DLG_UPDATE:
193             EnterCriticalSection(&This->cs);
194             update_dialog(This, This->dwUpdate);
195             This->dwUpdate = 0;
196             LeaveCriticalSection(&This->cs);
197             return TRUE;
198
199         case WM_DLG_DESTROY:
200             DestroyWindow(hwnd);
201             PostThreadMessageW(GetCurrentThreadId(), WM_NULL, 0, 0); /* wake up the GetMessage */
202             return TRUE;
203
204         case WM_CLOSE:
205         case WM_COMMAND:
206             if (msg == WM_CLOSE || wParam == IDCANCEL)
207             {
208                 EnterCriticalSection(&This->cs);
209                 This->isCancelled = TRUE;
210
211                 if (!This->cancelMsg)
212                     This->cancelMsg = load_string(BROWSEUI_hinstance, IDS_CANCELLING);
213
214                 set_progress_marquee(This);
215                 EnableWindow(GetDlgItem(This->hwnd, IDCANCEL), FALSE);
216                 update_dialog(This, UPDATE_LINE1|UPDATE_LINE2|UPDATE_LINE3);
217                 LeaveCriticalSection(&This->cs);
218             }
219             return TRUE;
220     }
221     return FALSE;
222 }
223
224 static DWORD WINAPI dialog_thread(LPVOID lpParameter)
225 {
226     /* Note: until we set the hEvent in WM_INITDIALOG, the ProgressDialog object
227      * is protected by the critical section held by StartProgress */
228     struct create_params *params = (struct create_params *)lpParameter;
229     HWND hwnd;
230     MSG msg;
231
232     hwnd = CreateDialogParamW(BROWSEUI_hinstance, MAKEINTRESOURCEW(IDD_PROGRESS_DLG),
233         params->hwndParent, dialog_proc, (LPARAM)params);
234
235     while (GetMessageW(&msg, NULL, 0, 0) > 0)
236     {
237         if (!IsWindow(hwnd))
238             break;
239         if(!IsDialogMessageW(hwnd, &msg))
240         {
241             TranslateMessage(&msg);
242             DispatchMessageW(&msg);
243         }
244     }
245
246     return 0;
247 }
248
249 HRESULT ProgressDialog_Constructor(IUnknown *pUnkOuter, IUnknown **ppOut)
250 {
251     ProgressDialog *This;
252     if (pUnkOuter)
253         return CLASS_E_NOAGGREGATION;
254
255     This = CoTaskMemAlloc(sizeof(ProgressDialog));
256     if (This == NULL)
257         return E_OUTOFMEMORY;
258     ZeroMemory(This, sizeof(*This));
259     This->vtbl = &ProgressDialogVtbl;
260     This->refCount = 1;
261     InitializeCriticalSection(&This->cs);
262
263     TRACE("returning %p\n", This);
264     *ppOut = (IUnknown *)This;
265     BROWSEUI_refCount++;
266     return S_OK;
267 }
268
269 static void WINAPI ProgressDialog_Destructor(ProgressDialog *This)
270 {
271     TRACE("destroying %p\n", This);
272     if (This->hwnd)
273         end_dialog(This);
274     CoTaskMemFree(This->lines[0]);
275     CoTaskMemFree(This->lines[1]);
276     CoTaskMemFree(This->lines[2]);
277     CoTaskMemFree(This->cancelMsg);
278     CoTaskMemFree(This->title);
279     CoTaskMemFree(This);
280     BROWSEUI_refCount--;
281 }
282
283 static HRESULT WINAPI ProgressDialog_QueryInterface(IProgressDialog *iface, REFIID iid, LPVOID *ppvOut)
284 {
285     ProgressDialog *This = (ProgressDialog *)iface;
286     *ppvOut = NULL;
287
288     if (IsEqualIID(iid, &IID_IUnknown) || IsEqualIID(iid, &IID_IProgressDialog))
289     {
290         *ppvOut = This;
291     }
292
293     if (*ppvOut)
294     {
295         IUnknown_AddRef(iface);
296         return S_OK;
297     }
298
299     WARN("unsupported interface: %s\n", debugstr_guid(iid));
300     return E_NOINTERFACE;
301 }
302
303 static ULONG WINAPI ProgressDialog_AddRef(IProgressDialog *iface)
304 {
305     ProgressDialog *This = (ProgressDialog *)iface;
306     return InterlockedIncrement(&This->refCount);
307 }
308
309 static ULONG WINAPI ProgressDialog_Release(IProgressDialog *iface)
310 {
311     ProgressDialog *This = (ProgressDialog *)iface;
312     ULONG ret;
313
314     ret = InterlockedDecrement(&This->refCount);
315     if (ret == 0)
316         ProgressDialog_Destructor(This);
317     return ret;
318 }
319
320 static HRESULT WINAPI ProgressDialog_StartProgressDialog(IProgressDialog *iface, HWND hwndParent, IUnknown *punkEnableModeless, DWORD dwFlags, LPCVOID reserved)
321 {
322     ProgressDialog *This = (ProgressDialog *)iface;
323     struct create_params params;
324     HANDLE hThread;
325
326     TRACE("(%p, %p, %x, %p)\n", iface, punkEnableModeless, dwFlags, reserved);
327     if (punkEnableModeless || reserved)
328         FIXME("Reserved parameters not null (%p, %p)\n", punkEnableModeless, reserved);
329     if (dwFlags & PROGDLG_AUTOTIME)
330         FIXME("Flags PROGDLG_AUTOTIME not supported\n");
331     if (dwFlags & PROGDLG_NOTIME)
332         FIXME("Flags PROGDLG_NOTIME not supported\n");
333
334     EnterCriticalSection(&This->cs);
335
336     if (This->hwnd)
337     {
338         LeaveCriticalSection(&This->cs);
339         return S_OK;  /* as on XP */
340     }
341     This->dwFlags = dwFlags;
342     params.This = This;
343     params.hwndParent = hwndParent;
344     params.hEvent = CreateEventW(NULL, TRUE, FALSE, NULL);
345
346     hThread = CreateThread(NULL, 0, dialog_thread, &params, 0, NULL);
347     WaitForSingleObject(params.hEvent, INFINITE);
348
349     This->hwndDisabledParent = NULL;
350     if (hwndParent && (dwFlags & PROGDLG_MODAL))
351     {
352         HWND hwndDisable = GetAncestor(hwndParent, GA_ROOT);
353         if (EnableWindow(hwndDisable, FALSE))
354             This->hwndDisabledParent = hwndDisable;
355     }
356
357     LeaveCriticalSection(&This->cs);
358
359     return S_OK;
360 }
361
362 static HRESULT WINAPI ProgressDialog_StopProgressDialog(IProgressDialog *iface)
363 {
364     ProgressDialog *This = (ProgressDialog *)iface;
365
366     EnterCriticalSection(&This->cs);
367     if (This->hwnd)
368         end_dialog(This);
369     LeaveCriticalSection(&This->cs);
370
371     return S_OK;
372 }
373
374 static HRESULT WINAPI ProgressDialog_SetTitle(IProgressDialog *iface, LPCWSTR pwzTitle)
375 {
376     ProgressDialog *This = (ProgressDialog *)iface;
377     HWND hwnd;
378
379     TRACE("(%p, %s)\n", This, wine_dbgstr_w(pwzTitle));
380
381     EnterCriticalSection(&This->cs);
382     set_buffer(&This->title, pwzTitle);
383     This->dwUpdate |= UPDATE_TITLE;
384     hwnd = This->hwnd;
385     LeaveCriticalSection(&This->cs);
386
387     if (hwnd)
388         SendMessageW(hwnd, WM_DLG_UPDATE, 0, 0);
389
390     return S_OK;
391 }
392
393 static HRESULT WINAPI ProgressDialog_SetAnimation(IProgressDialog *iface, HINSTANCE hInstance, UINT uiResourceId)
394 {
395     FIXME("(%p, %p, %d) - stub\n", iface, hInstance, uiResourceId);
396     return S_OK;
397 }
398
399 static BOOL WINAPI ProgressDialog_HasUserCancelled(IProgressDialog *iface)
400 {
401     ProgressDialog *This = (ProgressDialog *)iface;
402     return This->isCancelled;
403 }
404
405 static HRESULT WINAPI ProgressDialog_SetProgress64(IProgressDialog *iface, ULONGLONG ullCompleted, ULONGLONG ullTotal)
406 {
407     ProgressDialog *This = (ProgressDialog *)iface;
408     HWND hwnd;
409
410     TRACE("(%p, 0x%s, 0x%s)\n", This, wine_dbgstr_longlong(ullCompleted), wine_dbgstr_longlong(ullTotal));
411
412     EnterCriticalSection(&This->cs);
413     This->ullTotal = ullTotal;
414     This->ullCompleted = ullCompleted;
415     This->dwUpdate |= UPDATE_PROGRESS;
416     hwnd = This->hwnd;
417     LeaveCriticalSection(&This->cs);
418
419     if (hwnd)
420         SendMessageW(hwnd, WM_DLG_UPDATE, 0, 0);
421
422     return S_OK;  /* Windows sometimes returns S_FALSE */
423 }
424
425 static HRESULT WINAPI ProgressDialog_SetProgress(IProgressDialog *iface, DWORD dwCompleted, DWORD dwTotal)
426 {
427     return IProgressDialog_SetProgress64(iface, dwCompleted, dwTotal);
428 }
429
430 static HRESULT WINAPI ProgressDialog_SetLine(IProgressDialog *iface, DWORD dwLineNum, LPCWSTR pwzLine, BOOL bPath, LPCVOID reserved)
431 {
432     ProgressDialog *This = (ProgressDialog *)iface;
433     HWND hwnd;
434
435     TRACE("(%p, %d, %s, %d)\n", This, dwLineNum, wine_dbgstr_w(pwzLine), bPath);
436
437     if (reserved)
438         FIXME("reserved pointer not null (%p)\n", reserved);
439
440     dwLineNum--;
441     if (dwLineNum >= 3)  /* Windows seems to do something like that */
442         dwLineNum = 0;
443
444     EnterCriticalSection(&This->cs);
445     set_buffer(&This->lines[dwLineNum], pwzLine);
446     This->dwUpdate |= UPDATE_LINE1 << dwLineNum;
447     hwnd = (This->isCancelled ? NULL : This->hwnd); /* no sense to send the message if window cancelled */
448     LeaveCriticalSection(&This->cs);
449
450     if (hwnd)
451         SendMessageW(hwnd, WM_DLG_UPDATE, 0, 0);
452
453     return S_OK;
454 }
455
456 static HRESULT WINAPI ProgressDialog_SetCancelMsg(IProgressDialog *iface, LPCWSTR pwzMsg, LPCVOID reserved)
457 {
458     ProgressDialog *This = (ProgressDialog *)iface;
459     HWND hwnd;
460
461     TRACE("(%p, %s)\n", This, wine_dbgstr_w(pwzMsg));
462
463     if (reserved)
464         FIXME("reserved pointer not null (%p)\n", reserved);
465
466     EnterCriticalSection(&This->cs);
467     set_buffer(&This->cancelMsg, pwzMsg);
468     This->dwUpdate |= UPDATE_LINE1 << CANCEL_MSG_LINE;
469     hwnd = (This->isCancelled ? This->hwnd : NULL); /* no sense to send the message if window not cancelled */
470     LeaveCriticalSection(&This->cs);
471
472     if (hwnd)
473         SendMessageW(hwnd, WM_DLG_UPDATE, 0, 0);
474
475     return S_OK;
476 }
477
478 static HRESULT WINAPI ProgressDialog_Timer(IProgressDialog *iface, DWORD dwTimerAction, LPCVOID reserved)
479 {
480     ProgressDialog *This = (ProgressDialog *)iface;
481
482     FIXME("(%p, %d, %p) - stub\n", This, dwTimerAction, reserved);
483
484     if (reserved)
485         FIXME("Reserved field not NULL but %p\n", reserved);
486
487     return S_OK;
488 }
489
490 static const IProgressDialogVtbl ProgressDialogVtbl =
491 {
492     ProgressDialog_QueryInterface,
493     ProgressDialog_AddRef,
494     ProgressDialog_Release,
495
496     ProgressDialog_StartProgressDialog,
497     ProgressDialog_StopProgressDialog,
498     ProgressDialog_SetTitle,
499     ProgressDialog_SetAnimation,
500     ProgressDialog_HasUserCancelled,
501     ProgressDialog_SetProgress,
502     ProgressDialog_SetProgress64,
503     ProgressDialog_SetLine,
504     ProgressDialog_SetCancelMsg,
505     ProgressDialog_Timer
506 };