browseui: HeapFree after deleting the cs (coverity).
[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     IProgressDialog IProgressDialog_iface;
63     IOleWindow IOleWindow_iface;
64     LONG refCount;
65     CRITICAL_SECTION cs;
66     HWND hwnd;
67     DWORD dwFlags;
68     DWORD dwUpdate;
69     LPWSTR lines[3];
70     LPWSTR cancelMsg;
71     LPWSTR title;
72     BOOL isCancelled;
73     ULONGLONG ullCompleted;
74     ULONGLONG ullTotal;
75     HWND hwndDisabledParent;    /* For modal dialog: the parent that need to be re-enabled when the dialog ends */
76 } ProgressDialog;
77
78 static inline ProgressDialog *impl_from_IProgressDialog(IProgressDialog *iface)
79 {
80     return CONTAINING_RECORD(iface, ProgressDialog, IProgressDialog_iface);
81 }
82
83 static inline ProgressDialog *impl_from_IOleWindow(IOleWindow *iface)
84 {
85     return CONTAINING_RECORD(iface, ProgressDialog, IOleWindow_iface);
86 }
87
88 static void set_buffer(LPWSTR *buffer, LPCWSTR string)
89 {
90     static const WCHAR empty_string[] = {0};
91     IMalloc *malloc;
92     ULONG cb;
93
94     if (string == NULL)
95         string = empty_string;
96     CoGetMalloc(1, &malloc);
97
98     cb = (strlenW(string) + 1)*sizeof(WCHAR);
99     if (*buffer == NULL || cb > IMalloc_GetSize(malloc, *buffer))
100         *buffer = IMalloc_Realloc(malloc, *buffer, cb);
101     memcpy(*buffer, string, cb);
102 }
103
104 struct create_params
105 {
106     ProgressDialog *This;
107     HANDLE hEvent;
108     HWND hwndParent;
109 };
110
111 static LPWSTR load_string(HINSTANCE hInstance, UINT uiResourceId)
112 {
113     WCHAR string[256];
114     LPWSTR ret;
115
116     LoadStringW(hInstance, uiResourceId, string, sizeof(string)/sizeof(string[0]));
117     ret = HeapAlloc(GetProcessHeap(), 0, (strlenW(string) + 1) * sizeof(WCHAR));
118     strcpyW(ret, string);
119     return ret;
120 }
121
122 static void set_progress_marquee(ProgressDialog *This)
123 {
124     HWND hProgress = GetDlgItem(This->hwnd, IDC_PROGRESS_BAR);
125     SetWindowLongW(hProgress, GWL_STYLE,
126         GetWindowLongW(hProgress, GWL_STYLE)|PBS_MARQUEE);
127 }
128
129 static void update_dialog(ProgressDialog *This, DWORD dwUpdate)
130 {
131     WCHAR empty[] = {0};
132
133     if (dwUpdate & UPDATE_TITLE)
134         SetWindowTextW(This->hwnd, This->title);
135
136     if (dwUpdate & UPDATE_LINE1)
137         SetDlgItemTextW(This->hwnd, IDC_TEXT_LINE, (This->isCancelled ? empty : This->lines[0]));
138     if (dwUpdate & UPDATE_LINE2)
139         SetDlgItemTextW(This->hwnd, IDC_TEXT_LINE+1, (This->isCancelled ? empty : This->lines[1]));
140     if (dwUpdate & UPDATE_LINE3)
141         SetDlgItemTextW(This->hwnd, IDC_TEXT_LINE+2, (This->isCancelled ? This->cancelMsg : This->lines[2]));
142
143     if (dwUpdate & UPDATE_PROGRESS)
144     {
145         ULONGLONG ullTotal = This->ullTotal;
146         ULONGLONG ullCompleted = This->ullCompleted;
147
148         /* progress bar requires 32-bit coordinates */
149         while (ullTotal >> 32)
150         {
151             ullTotal >>= 1;
152             ullCompleted >>= 1;
153         }
154
155         SendDlgItemMessageW(This->hwnd, IDC_PROGRESS_BAR, PBM_SETRANGE32, 0, (DWORD)ullTotal);
156         SendDlgItemMessageW(This->hwnd, IDC_PROGRESS_BAR, PBM_SETPOS, (DWORD)ullCompleted, 0);
157     }
158 }
159
160 static void end_dialog(ProgressDialog *This)
161 {
162     SendMessageW(This->hwnd, WM_DLG_DESTROY, 0, 0);
163     /* native doesn't re-enable the window? */
164     if (This->hwndDisabledParent)
165         EnableWindow(This->hwndDisabledParent, TRUE);
166     This->hwnd = NULL;
167 }
168
169 static INT_PTR CALLBACK dialog_proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
170 {
171     ProgressDialog *This = (ProgressDialog *)GetWindowLongPtrW(hwnd, DWLP_USER);
172
173     switch (msg)
174     {
175         case WM_INITDIALOG:
176         {
177             struct create_params *params = (struct create_params *)lParam;
178
179             /* Note: until we set the hEvent, the object is protected by
180              * the critical section held by StartProgress */
181             SetWindowLongPtrW(hwnd, DWLP_USER, (LONG_PTR)params->This);
182             This = params->This;
183             This->hwnd = hwnd;
184
185             if (This->dwFlags & PROGDLG_NOPROGRESSBAR)
186                 ShowWindow(GetDlgItem(hwnd, IDC_PROGRESS_BAR), SW_HIDE);
187             if (This->dwFlags & PROGDLG_NOCANCEL)
188                 ShowWindow(GetDlgItem(hwnd, IDCANCEL), SW_HIDE);
189             if (This->dwFlags & PROGDLG_MARQUEEPROGRESS)
190                 set_progress_marquee(This);
191             if (This->dwFlags & PROGDLG_NOMINIMIZE)
192                 SetWindowLongW(hwnd, GWL_STYLE, GetWindowLongW(hwnd, GWL_STYLE) & (~WS_MINIMIZEBOX));
193
194             update_dialog(This, 0xffffffff);
195             This->dwUpdate = 0;
196             This->isCancelled = FALSE;
197             SetEvent(params->hEvent);
198             return TRUE;
199         }
200
201         case WM_DLG_UPDATE:
202             EnterCriticalSection(&This->cs);
203             update_dialog(This, This->dwUpdate);
204             This->dwUpdate = 0;
205             LeaveCriticalSection(&This->cs);
206             return TRUE;
207
208         case WM_DLG_DESTROY:
209             DestroyWindow(hwnd);
210             PostThreadMessageW(GetCurrentThreadId(), WM_NULL, 0, 0); /* wake up the GetMessage */
211             return TRUE;
212
213         case WM_CLOSE:
214         case WM_COMMAND:
215             if (msg == WM_CLOSE || wParam == IDCANCEL)
216             {
217                 EnterCriticalSection(&This->cs);
218                 This->isCancelled = TRUE;
219
220                 if (!This->cancelMsg)
221                     This->cancelMsg = load_string(BROWSEUI_hinstance, IDS_CANCELLING);
222
223                 set_progress_marquee(This);
224                 EnableWindow(GetDlgItem(This->hwnd, IDCANCEL), FALSE);
225                 update_dialog(This, UPDATE_LINE1|UPDATE_LINE2|UPDATE_LINE3);
226                 LeaveCriticalSection(&This->cs);
227             }
228             return TRUE;
229     }
230     return FALSE;
231 }
232
233 static DWORD WINAPI dialog_thread(LPVOID lpParameter)
234 {
235     /* Note: until we set the hEvent in WM_INITDIALOG, the ProgressDialog object
236      * is protected by the critical section held by StartProgress */
237     struct create_params *params = lpParameter;
238     HWND hwnd;
239     MSG msg;
240
241     hwnd = CreateDialogParamW(BROWSEUI_hinstance, MAKEINTRESOURCEW(IDD_PROGRESS_DLG),
242         params->hwndParent, dialog_proc, (LPARAM)params);
243
244     while (GetMessageW(&msg, NULL, 0, 0) > 0)
245     {
246         if (!IsWindow(hwnd))
247             break;
248         if(!IsDialogMessageW(hwnd, &msg))
249         {
250             TranslateMessage(&msg);
251             DispatchMessageW(&msg);
252         }
253     }
254
255     return 0;
256 }
257
258 static void ProgressDialog_Destructor(ProgressDialog *This)
259 {
260     TRACE("destroying %p\n", This);
261     if (This->hwnd)
262         end_dialog(This);
263     heap_free(This->lines[0]);
264     heap_free(This->lines[1]);
265     heap_free(This->lines[2]);
266     heap_free(This->cancelMsg);
267     heap_free(This->title);
268     This->cs.DebugInfo->Spare[0] = 0;
269     DeleteCriticalSection(&This->cs);
270     heap_free(This);
271     BROWSEUI_refCount--;
272 }
273
274 static HRESULT WINAPI ProgressDialog_QueryInterface(IProgressDialog *iface, REFIID iid, LPVOID *ppvOut)
275 {
276     ProgressDialog *This = impl_from_IProgressDialog(iface);
277
278     TRACE("(%p, %s, %p)\n", iface, debugstr_guid(iid), ppvOut);
279     if (!ppvOut)
280         return E_POINTER;
281
282     *ppvOut = NULL;
283     if (IsEqualIID(iid, &IID_IUnknown) || IsEqualIID(iid, &IID_IProgressDialog))
284     {
285         *ppvOut = iface;
286     }
287     else if (IsEqualIID(iid, &IID_IOleWindow))
288     {
289         *ppvOut = &This->IOleWindow_iface;
290     }
291
292     if (*ppvOut)
293     {
294         IProgressDialog_AddRef(iface);
295         return S_OK;
296     }
297
298     WARN("unsupported interface: %s\n", debugstr_guid(iid));
299     return E_NOINTERFACE;
300 }
301
302 static ULONG WINAPI ProgressDialog_AddRef(IProgressDialog *iface)
303 {
304     ProgressDialog *This = impl_from_IProgressDialog(iface);
305     return InterlockedIncrement(&This->refCount);
306 }
307
308 static ULONG WINAPI ProgressDialog_Release(IProgressDialog *iface)
309 {
310     ProgressDialog *This = impl_from_IProgressDialog(iface);
311     ULONG ret;
312
313     ret = InterlockedDecrement(&This->refCount);
314     if (ret == 0)
315         ProgressDialog_Destructor(This);
316     return ret;
317 }
318
319 static HRESULT WINAPI ProgressDialog_StartProgressDialog(IProgressDialog *iface, HWND hwndParent, IUnknown *punkEnableModeless, DWORD dwFlags, LPCVOID reserved)
320 {
321     static const INITCOMMONCONTROLSEX init = { sizeof(init), ICC_ANIMATE_CLASS };
322     ProgressDialog *This = impl_from_IProgressDialog(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     InitCommonControlsEx( &init );
335
336     EnterCriticalSection(&This->cs);
337
338     if (This->hwnd)
339     {
340         LeaveCriticalSection(&This->cs);
341         return S_OK;  /* as on XP */
342     }
343     This->dwFlags = dwFlags;
344     params.This = This;
345     params.hwndParent = hwndParent;
346     params.hEvent = CreateEventW(NULL, TRUE, FALSE, NULL);
347
348     hThread = CreateThread(NULL, 0, dialog_thread, &params, 0, NULL);
349     WaitForSingleObject(params.hEvent, INFINITE);
350     CloseHandle(params.hEvent);
351     CloseHandle(hThread);
352
353     This->hwndDisabledParent = NULL;
354     if (hwndParent && (dwFlags & PROGDLG_MODAL))
355     {
356         HWND hwndDisable = GetAncestor(hwndParent, GA_ROOT);
357         if (EnableWindow(hwndDisable, FALSE))
358             This->hwndDisabledParent = hwndDisable;
359     }
360
361     LeaveCriticalSection(&This->cs);
362
363     return S_OK;
364 }
365
366 static HRESULT WINAPI ProgressDialog_StopProgressDialog(IProgressDialog *iface)
367 {
368     ProgressDialog *This = impl_from_IProgressDialog(iface);
369
370     EnterCriticalSection(&This->cs);
371     if (This->hwnd)
372         end_dialog(This);
373     LeaveCriticalSection(&This->cs);
374
375     return S_OK;
376 }
377
378 static HRESULT WINAPI ProgressDialog_SetTitle(IProgressDialog *iface, LPCWSTR pwzTitle)
379 {
380     ProgressDialog *This = impl_from_IProgressDialog(iface);
381     HWND hwnd;
382
383     TRACE("(%p, %s)\n", This, wine_dbgstr_w(pwzTitle));
384
385     EnterCriticalSection(&This->cs);
386     set_buffer(&This->title, pwzTitle);
387     This->dwUpdate |= UPDATE_TITLE;
388     hwnd = This->hwnd;
389     LeaveCriticalSection(&This->cs);
390
391     if (hwnd)
392         SendMessageW(hwnd, WM_DLG_UPDATE, 0, 0);
393
394     return S_OK;
395 }
396
397 static HRESULT WINAPI ProgressDialog_SetAnimation(IProgressDialog *iface, HINSTANCE hInstance, UINT uiResourceId)
398 {
399     FIXME("(%p, %p, %d) - stub\n", iface, hInstance, uiResourceId);
400     return S_OK;
401 }
402
403 static BOOL WINAPI ProgressDialog_HasUserCancelled(IProgressDialog *iface)
404 {
405     ProgressDialog *This = impl_from_IProgressDialog(iface);
406     return This->isCancelled;
407 }
408
409 static HRESULT WINAPI ProgressDialog_SetProgress64(IProgressDialog *iface, ULONGLONG ullCompleted, ULONGLONG ullTotal)
410 {
411     ProgressDialog *This = impl_from_IProgressDialog(iface);
412     HWND hwnd;
413
414     TRACE("(%p, 0x%s, 0x%s)\n", This, wine_dbgstr_longlong(ullCompleted), wine_dbgstr_longlong(ullTotal));
415
416     EnterCriticalSection(&This->cs);
417     This->ullTotal = ullTotal;
418     This->ullCompleted = ullCompleted;
419     This->dwUpdate |= UPDATE_PROGRESS;
420     hwnd = This->hwnd;
421     LeaveCriticalSection(&This->cs);
422
423     if (hwnd)
424         SendMessageW(hwnd, WM_DLG_UPDATE, 0, 0);
425
426     return S_OK;  /* Windows sometimes returns S_FALSE */
427 }
428
429 static HRESULT WINAPI ProgressDialog_SetProgress(IProgressDialog *iface, DWORD dwCompleted, DWORD dwTotal)
430 {
431     return IProgressDialog_SetProgress64(iface, dwCompleted, dwTotal);
432 }
433
434 static HRESULT WINAPI ProgressDialog_SetLine(IProgressDialog *iface, DWORD dwLineNum, LPCWSTR pwzLine, BOOL bPath, LPCVOID reserved)
435 {
436     ProgressDialog *This = impl_from_IProgressDialog(iface);
437     HWND hwnd;
438
439     TRACE("(%p, %d, %s, %d)\n", This, dwLineNum, wine_dbgstr_w(pwzLine), bPath);
440
441     if (reserved)
442         FIXME("reserved pointer not null (%p)\n", reserved);
443
444     dwLineNum--;
445     if (dwLineNum >= 3)  /* Windows seems to do something like that */
446         dwLineNum = 0;
447
448     EnterCriticalSection(&This->cs);
449     set_buffer(&This->lines[dwLineNum], pwzLine);
450     This->dwUpdate |= UPDATE_LINE1 << dwLineNum;
451     hwnd = (This->isCancelled ? NULL : This->hwnd); /* no sense to send the message if window cancelled */
452     LeaveCriticalSection(&This->cs);
453
454     if (hwnd)
455         SendMessageW(hwnd, WM_DLG_UPDATE, 0, 0);
456
457     return S_OK;
458 }
459
460 static HRESULT WINAPI ProgressDialog_SetCancelMsg(IProgressDialog *iface, LPCWSTR pwzMsg, LPCVOID reserved)
461 {
462     ProgressDialog *This = impl_from_IProgressDialog(iface);
463     HWND hwnd;
464
465     TRACE("(%p, %s)\n", This, wine_dbgstr_w(pwzMsg));
466
467     if (reserved)
468         FIXME("reserved pointer not null (%p)\n", reserved);
469
470     EnterCriticalSection(&This->cs);
471     set_buffer(&This->cancelMsg, pwzMsg);
472     This->dwUpdate |= UPDATE_LINE1 << CANCEL_MSG_LINE;
473     hwnd = (This->isCancelled ? This->hwnd : NULL); /* no sense to send the message if window not cancelled */
474     LeaveCriticalSection(&This->cs);
475
476     if (hwnd)
477         SendMessageW(hwnd, WM_DLG_UPDATE, 0, 0);
478
479     return S_OK;
480 }
481
482 static HRESULT WINAPI ProgressDialog_Timer(IProgressDialog *iface, DWORD dwTimerAction, LPCVOID reserved)
483 {
484     ProgressDialog *This = impl_from_IProgressDialog(iface);
485
486     FIXME("(%p, %d, %p) - stub\n", This, dwTimerAction, reserved);
487
488     if (reserved)
489         FIXME("Reserved field not NULL but %p\n", reserved);
490
491     return S_OK;
492 }
493
494 static const IProgressDialogVtbl ProgressDialogVtbl =
495 {
496     ProgressDialog_QueryInterface,
497     ProgressDialog_AddRef,
498     ProgressDialog_Release,
499
500     ProgressDialog_StartProgressDialog,
501     ProgressDialog_StopProgressDialog,
502     ProgressDialog_SetTitle,
503     ProgressDialog_SetAnimation,
504     ProgressDialog_HasUserCancelled,
505     ProgressDialog_SetProgress,
506     ProgressDialog_SetProgress64,
507     ProgressDialog_SetLine,
508     ProgressDialog_SetCancelMsg,
509     ProgressDialog_Timer
510 };
511
512 static HRESULT WINAPI OleWindow_QueryInterface(IOleWindow *iface, REFIID iid, LPVOID *ppvOut)
513 {
514     ProgressDialog *This = impl_from_IOleWindow(iface);
515     return ProgressDialog_QueryInterface(&This->IProgressDialog_iface, iid, ppvOut);
516 }
517
518 static ULONG WINAPI OleWindow_AddRef(IOleWindow *iface)
519 {
520     ProgressDialog *This = impl_from_IOleWindow(iface);
521     return ProgressDialog_AddRef(&This->IProgressDialog_iface);
522 }
523
524 static ULONG WINAPI OleWindow_Release(IOleWindow *iface)
525 {
526     ProgressDialog *This = impl_from_IOleWindow(iface);
527     return ProgressDialog_Release(&This->IProgressDialog_iface);
528 }
529
530 static HRESULT WINAPI OleWindow_GetWindow(IOleWindow* iface, HWND* phwnd)
531 {
532     ProgressDialog *This = impl_from_IOleWindow(iface);
533
534     TRACE("(%p, %p)\n", This, phwnd);
535     EnterCriticalSection(&This->cs);
536     *phwnd = This->hwnd;
537     LeaveCriticalSection(&This->cs);
538     return S_OK;
539 }
540
541 static HRESULT WINAPI OleWindow_ContextSensitiveHelp(IOleWindow* iface, BOOL fEnterMode)
542 {
543     ProgressDialog *This = impl_from_IOleWindow(iface);
544
545     FIXME("(%p, %d): stub\n", This, fEnterMode);
546     return E_NOTIMPL;
547 }
548
549 static const IOleWindowVtbl OleWindowVtbl =
550 {
551     OleWindow_QueryInterface,
552     OleWindow_AddRef,
553     OleWindow_Release,
554     OleWindow_GetWindow,
555     OleWindow_ContextSensitiveHelp
556 };
557
558
559 HRESULT ProgressDialog_Constructor(IUnknown *pUnkOuter, IUnknown **ppOut)
560 {
561     ProgressDialog *This;
562     if (pUnkOuter)
563         return CLASS_E_NOAGGREGATION;
564
565     This = heap_alloc_zero(sizeof(ProgressDialog));
566     if (This == NULL)
567         return E_OUTOFMEMORY;
568
569     This->IProgressDialog_iface.lpVtbl = &ProgressDialogVtbl;
570     This->IOleWindow_iface.lpVtbl = &OleWindowVtbl;
571     This->refCount = 1;
572     InitializeCriticalSection(&This->cs);
573     This->cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": ProgressDialog.cs");
574
575     TRACE("returning %p\n", This);
576     *ppOut = (IUnknown *)This;
577     BROWSEUI_refCount++;
578     return S_OK;
579 }