| /* | |
| * Progress dialog | |
| * | |
| * Copyright 2007 Mikolaj Zalewski | |
| * | |
| * This library is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU Lesser General Public | |
| * License as published by the Free Software Foundation; either | |
| * version 2.1 of the License, or (at your option) any later version. | |
| * | |
| * This library is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| * Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; if not, write to the Free Software | |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA | |
| */ | |
| #include "config.h" | |
| #include <stdarg.h> | |
| #define COBJMACROS | |
| #include "wine/debug.h" | |
| #include "windef.h" | |
| #include "winbase.h" | |
| #include "winreg.h" | |
| #include "winuser.h" | |
| #include "shlwapi.h" | |
| #include "winerror.h" | |
| #include "objbase.h" | |
| #include "shlguid.h" | |
| #include "shlobj.h" | |
| #include "wine/unicode.h" | |
| #include "browseui.h" | |
| #include "resids.h" | |
| WINE_DEFAULT_DEBUG_CHANNEL(browseui); | |
| #define CANCEL_MSG_LINE 2 | |
| /* Note: to avoid a deadlock we don't want to send messages to the dialog | |
| * with the critical section held. Instead we only mark what fields should be | |
| * updated and the dialog proc does the update */ | |
| #define UPDATE_PROGRESS 0x1 | |
| #define UPDATE_TITLE 0x2 | |
| #define UPDATE_LINE1 0x4 | |
| #define UPDATE_LINE2 (UPDATE_LINE1<<1) | |
| #define UPDATE_LINE3 (UPDATE_LINE2<<2) | |
| #define WM_DLG_UPDATE (WM_APP+1) /* set to the dialog when it should update */ | |
| #define WM_DLG_DESTROY (WM_APP+2) /* DestroyWindow must be called from the owning thread */ | |
| typedef struct tagProgressDialog { | |
| IProgressDialog IProgressDialog_iface; | |
| IOleWindow IOleWindow_iface; | |
| LONG refCount; | |
| CRITICAL_SECTION cs; | |
| HWND hwnd; | |
| DWORD dwFlags; | |
| DWORD dwUpdate; | |
| LPWSTR lines[3]; | |
| LPWSTR cancelMsg; | |
| LPWSTR title; | |
| BOOL isCancelled; | |
| ULONGLONG ullCompleted; | |
| ULONGLONG ullTotal; | |
| HWND hwndDisabledParent; /* For modal dialog: the parent that need to be re-enabled when the dialog ends */ | |
| } ProgressDialog; | |
| static inline ProgressDialog *impl_from_IProgressDialog(IProgressDialog *iface) | |
| { | |
| return CONTAINING_RECORD(iface, ProgressDialog, IProgressDialog_iface); | |
| } | |
| static inline ProgressDialog *impl_from_IOleWindow(IOleWindow *iface) | |
| { | |
| return CONTAINING_RECORD(iface, ProgressDialog, IOleWindow_iface); | |
| } | |
| static void set_buffer(LPWSTR *buffer, LPCWSTR string) | |
| { | |
| static const WCHAR empty_string[] = {0}; | |
| IMalloc *malloc; | |
| ULONG cb; | |
| if (string == NULL) | |
| string = empty_string; | |
| CoGetMalloc(MEMCTX_TASK, &malloc); | |
| cb = (strlenW(string) + 1)*sizeof(WCHAR); | |
| if (*buffer == NULL || cb > IMalloc_GetSize(malloc, *buffer)) | |
| *buffer = IMalloc_Realloc(malloc, *buffer, cb); | |
| memcpy(*buffer, string, cb); | |
| } | |
| struct create_params | |
| { | |
| ProgressDialog *This; | |
| HANDLE hEvent; | |
| HWND hwndParent; | |
| }; | |
| static LPWSTR load_string(HINSTANCE hInstance, UINT uiResourceId) | |
| { | |
| WCHAR string[256]; | |
| LPWSTR ret; | |
| LoadStringW(hInstance, uiResourceId, string, sizeof(string)/sizeof(string[0])); | |
| ret = HeapAlloc(GetProcessHeap(), 0, (strlenW(string) + 1) * sizeof(WCHAR)); | |
| strcpyW(ret, string); | |
| return ret; | |
| } | |
| static void set_progress_marquee(ProgressDialog *This) | |
| { | |
| HWND hProgress = GetDlgItem(This->hwnd, IDC_PROGRESS_BAR); | |
| SetWindowLongW(hProgress, GWL_STYLE, | |
| GetWindowLongW(hProgress, GWL_STYLE)|PBS_MARQUEE); | |
| } | |
| static void update_dialog(ProgressDialog *This, DWORD dwUpdate) | |
| { | |
| WCHAR empty[] = {0}; | |
| if (dwUpdate & UPDATE_TITLE) | |
| SetWindowTextW(This->hwnd, This->title); | |
| if (dwUpdate & UPDATE_LINE1) | |
| SetDlgItemTextW(This->hwnd, IDC_TEXT_LINE, (This->isCancelled ? empty : This->lines[0])); | |
| if (dwUpdate & UPDATE_LINE2) | |
| SetDlgItemTextW(This->hwnd, IDC_TEXT_LINE+1, (This->isCancelled ? empty : This->lines[1])); | |
| if (dwUpdate & UPDATE_LINE3) | |
| SetDlgItemTextW(This->hwnd, IDC_TEXT_LINE+2, (This->isCancelled ? This->cancelMsg : This->lines[2])); | |
| if (dwUpdate & UPDATE_PROGRESS) | |
| { | |
| ULONGLONG ullTotal = This->ullTotal; | |
| ULONGLONG ullCompleted = This->ullCompleted; | |
| /* progress bar requires 32-bit coordinates */ | |
| while (ullTotal >> 32) | |
| { | |
| ullTotal >>= 1; | |
| ullCompleted >>= 1; | |
| } | |
| SendDlgItemMessageW(This->hwnd, IDC_PROGRESS_BAR, PBM_SETRANGE32, 0, (DWORD)ullTotal); | |
| SendDlgItemMessageW(This->hwnd, IDC_PROGRESS_BAR, PBM_SETPOS, (DWORD)ullCompleted, 0); | |
| } | |
| } | |
| static void end_dialog(ProgressDialog *This) | |
| { | |
| SendMessageW(This->hwnd, WM_DLG_DESTROY, 0, 0); | |
| /* native doesn't re-enable the window? */ | |
| if (This->hwndDisabledParent) | |
| EnableWindow(This->hwndDisabledParent, TRUE); | |
| This->hwnd = NULL; | |
| } | |
| static INT_PTR CALLBACK dialog_proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) | |
| { | |
| ProgressDialog *This = (ProgressDialog *)GetWindowLongPtrW(hwnd, DWLP_USER); | |
| switch (msg) | |
| { | |
| case WM_INITDIALOG: | |
| { | |
| struct create_params *params = (struct create_params *)lParam; | |
| /* Note: until we set the hEvent, the object is protected by | |
| * the critical section held by StartProgress */ | |
| SetWindowLongPtrW(hwnd, DWLP_USER, (LONG_PTR)params->This); | |
| This = params->This; | |
| This->hwnd = hwnd; | |
| if (This->dwFlags & PROGDLG_NOPROGRESSBAR) | |
| ShowWindow(GetDlgItem(hwnd, IDC_PROGRESS_BAR), SW_HIDE); | |
| if (This->dwFlags & PROGDLG_NOCANCEL) | |
| ShowWindow(GetDlgItem(hwnd, IDCANCEL), SW_HIDE); | |
| if (This->dwFlags & PROGDLG_MARQUEEPROGRESS) | |
| set_progress_marquee(This); | |
| if (This->dwFlags & PROGDLG_NOMINIMIZE) | |
| SetWindowLongW(hwnd, GWL_STYLE, GetWindowLongW(hwnd, GWL_STYLE) & (~WS_MINIMIZEBOX)); | |
| update_dialog(This, 0xffffffff); | |
| This->dwUpdate = 0; | |
| This->isCancelled = FALSE; | |
| SetEvent(params->hEvent); | |
| return TRUE; | |
| } | |
| case WM_DLG_UPDATE: | |
| EnterCriticalSection(&This->cs); | |
| update_dialog(This, This->dwUpdate); | |
| This->dwUpdate = 0; | |
| LeaveCriticalSection(&This->cs); | |
| return TRUE; | |
| case WM_DLG_DESTROY: | |
| DestroyWindow(hwnd); | |
| PostThreadMessageW(GetCurrentThreadId(), WM_NULL, 0, 0); /* wake up the GetMessage */ | |
| return TRUE; | |
| case WM_CLOSE: | |
| case WM_COMMAND: | |
| if (msg == WM_CLOSE || wParam == IDCANCEL) | |
| { | |
| EnterCriticalSection(&This->cs); | |
| This->isCancelled = TRUE; | |
| if (!This->cancelMsg) | |
| This->cancelMsg = load_string(BROWSEUI_hinstance, IDS_CANCELLING); | |
| set_progress_marquee(This); | |
| EnableWindow(GetDlgItem(This->hwnd, IDCANCEL), FALSE); | |
| update_dialog(This, UPDATE_LINE1|UPDATE_LINE2|UPDATE_LINE3); | |
| LeaveCriticalSection(&This->cs); | |
| } | |
| return TRUE; | |
| } | |
| return FALSE; | |
| } | |
| static DWORD WINAPI dialog_thread(LPVOID lpParameter) | |
| { | |
| /* Note: until we set the hEvent in WM_INITDIALOG, the ProgressDialog object | |
| * is protected by the critical section held by StartProgress */ | |
| struct create_params *params = lpParameter; | |
| ProgressDialog *This = params->This; | |
| HWND hwnd; | |
| MSG msg; | |
| hwnd = CreateDialogParamW(BROWSEUI_hinstance, MAKEINTRESOURCEW(IDD_PROGRESS_DLG), | |
| params->hwndParent, dialog_proc, (LPARAM)params); | |
| while (GetMessageW(&msg, NULL, 0, 0) > 0) | |
| { | |
| if (!IsWindow(hwnd)) | |
| break; | |
| if(!IsDialogMessageW(hwnd, &msg)) | |
| { | |
| TranslateMessage(&msg); | |
| DispatchMessageW(&msg); | |
| } | |
| } | |
| IProgressDialog_Release(&This->IProgressDialog_iface); | |
| return 0; | |
| } | |
| static void ProgressDialog_Destructor(ProgressDialog *This) | |
| { | |
| TRACE("destroying %p\n", This); | |
| if (This->hwnd) | |
| end_dialog(This); | |
| heap_free(This->lines[0]); | |
| heap_free(This->lines[1]); | |
| heap_free(This->lines[2]); | |
| heap_free(This->cancelMsg); | |
| heap_free(This->title); | |
| This->cs.DebugInfo->Spare[0] = 0; | |
| DeleteCriticalSection(&This->cs); | |
| heap_free(This); | |
| InterlockedDecrement(&BROWSEUI_refCount); | |
| } | |
| static HRESULT WINAPI ProgressDialog_QueryInterface(IProgressDialog *iface, REFIID iid, LPVOID *ppvOut) | |
| { | |
| ProgressDialog *This = impl_from_IProgressDialog(iface); | |
| TRACE("(%p, %s, %p)\n", iface, debugstr_guid(iid), ppvOut); | |
| if (!ppvOut) | |
| return E_POINTER; | |
| *ppvOut = NULL; | |
| if (IsEqualIID(iid, &IID_IUnknown) || IsEqualIID(iid, &IID_IProgressDialog)) | |
| { | |
| *ppvOut = iface; | |
| } | |
| else if (IsEqualIID(iid, &IID_IOleWindow)) | |
| { | |
| *ppvOut = &This->IOleWindow_iface; | |
| } | |
| if (*ppvOut) | |
| { | |
| IProgressDialog_AddRef(iface); | |
| return S_OK; | |
| } | |
| WARN("unsupported interface: %s\n", debugstr_guid(iid)); | |
| return E_NOINTERFACE; | |
| } | |
| static ULONG WINAPI ProgressDialog_AddRef(IProgressDialog *iface) | |
| { | |
| ProgressDialog *This = impl_from_IProgressDialog(iface); | |
| return InterlockedIncrement(&This->refCount); | |
| } | |
| static ULONG WINAPI ProgressDialog_Release(IProgressDialog *iface) | |
| { | |
| ProgressDialog *This = impl_from_IProgressDialog(iface); | |
| ULONG ret; | |
| ret = InterlockedDecrement(&This->refCount); | |
| if (ret == 0) | |
| ProgressDialog_Destructor(This); | |
| return ret; | |
| } | |
| static HRESULT WINAPI ProgressDialog_StartProgressDialog(IProgressDialog *iface, HWND hwndParent, IUnknown *punkEnableModeless, DWORD dwFlags, LPCVOID reserved) | |
| { | |
| static const INITCOMMONCONTROLSEX init = { sizeof(init), ICC_ANIMATE_CLASS }; | |
| ProgressDialog *This = impl_from_IProgressDialog(iface); | |
| struct create_params params; | |
| HANDLE hThread; | |
| TRACE("(%p, %p, %x, %p)\n", iface, punkEnableModeless, dwFlags, reserved); | |
| if (punkEnableModeless || reserved) | |
| FIXME("Reserved parameters not null (%p, %p)\n", punkEnableModeless, reserved); | |
| if (dwFlags & PROGDLG_AUTOTIME) | |
| FIXME("Flags PROGDLG_AUTOTIME not supported\n"); | |
| if (dwFlags & PROGDLG_NOTIME) | |
| FIXME("Flags PROGDLG_NOTIME not supported\n"); | |
| InitCommonControlsEx( &init ); | |
| EnterCriticalSection(&This->cs); | |
| if (This->hwnd) | |
| { | |
| LeaveCriticalSection(&This->cs); | |
| return S_OK; /* as on XP */ | |
| } | |
| This->dwFlags = dwFlags; | |
| params.This = This; | |
| params.hwndParent = hwndParent; | |
| params.hEvent = CreateEventW(NULL, TRUE, FALSE, NULL); | |
| /* thread holds one reference to ensure clean shutdown */ | |
| IProgressDialog_AddRef(&This->IProgressDialog_iface); | |
| hThread = CreateThread(NULL, 0, dialog_thread, ¶ms, 0, NULL); | |
| WaitForSingleObject(params.hEvent, INFINITE); | |
| CloseHandle(params.hEvent); | |
| CloseHandle(hThread); | |
| This->hwndDisabledParent = NULL; | |
| if (hwndParent && (dwFlags & PROGDLG_MODAL)) | |
| { | |
| HWND hwndDisable = GetAncestor(hwndParent, GA_ROOT); | |
| if (EnableWindow(hwndDisable, FALSE)) | |
| This->hwndDisabledParent = hwndDisable; | |
| } | |
| LeaveCriticalSection(&This->cs); | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI ProgressDialog_StopProgressDialog(IProgressDialog *iface) | |
| { | |
| ProgressDialog *This = impl_from_IProgressDialog(iface); | |
| EnterCriticalSection(&This->cs); | |
| if (This->hwnd) | |
| end_dialog(This); | |
| LeaveCriticalSection(&This->cs); | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI ProgressDialog_SetTitle(IProgressDialog *iface, LPCWSTR pwzTitle) | |
| { | |
| ProgressDialog *This = impl_from_IProgressDialog(iface); | |
| HWND hwnd; | |
| TRACE("(%p, %s)\n", This, wine_dbgstr_w(pwzTitle)); | |
| EnterCriticalSection(&This->cs); | |
| set_buffer(&This->title, pwzTitle); | |
| This->dwUpdate |= UPDATE_TITLE; | |
| hwnd = This->hwnd; | |
| LeaveCriticalSection(&This->cs); | |
| if (hwnd) | |
| SendMessageW(hwnd, WM_DLG_UPDATE, 0, 0); | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI ProgressDialog_SetAnimation(IProgressDialog *iface, HINSTANCE hInstance, UINT uiResourceId) | |
| { | |
| FIXME("(%p, %p, %d) - stub\n", iface, hInstance, uiResourceId); | |
| return S_OK; | |
| } | |
| static BOOL WINAPI ProgressDialog_HasUserCancelled(IProgressDialog *iface) | |
| { | |
| ProgressDialog *This = impl_from_IProgressDialog(iface); | |
| return This->isCancelled; | |
| } | |
| static HRESULT WINAPI ProgressDialog_SetProgress64(IProgressDialog *iface, ULONGLONG ullCompleted, ULONGLONG ullTotal) | |
| { | |
| ProgressDialog *This = impl_from_IProgressDialog(iface); | |
| HWND hwnd; | |
| TRACE("(%p, 0x%s, 0x%s)\n", This, wine_dbgstr_longlong(ullCompleted), wine_dbgstr_longlong(ullTotal)); | |
| EnterCriticalSection(&This->cs); | |
| This->ullTotal = ullTotal; | |
| This->ullCompleted = ullCompleted; | |
| This->dwUpdate |= UPDATE_PROGRESS; | |
| hwnd = This->hwnd; | |
| LeaveCriticalSection(&This->cs); | |
| if (hwnd) | |
| SendMessageW(hwnd, WM_DLG_UPDATE, 0, 0); | |
| return S_OK; /* Windows sometimes returns S_FALSE */ | |
| } | |
| static HRESULT WINAPI ProgressDialog_SetProgress(IProgressDialog *iface, DWORD dwCompleted, DWORD dwTotal) | |
| { | |
| return IProgressDialog_SetProgress64(iface, dwCompleted, dwTotal); | |
| } | |
| static HRESULT WINAPI ProgressDialog_SetLine(IProgressDialog *iface, DWORD dwLineNum, LPCWSTR pwzLine, BOOL bPath, LPCVOID reserved) | |
| { | |
| ProgressDialog *This = impl_from_IProgressDialog(iface); | |
| HWND hwnd; | |
| TRACE("(%p, %d, %s, %d)\n", This, dwLineNum, wine_dbgstr_w(pwzLine), bPath); | |
| if (reserved) | |
| FIXME("reserved pointer not null (%p)\n", reserved); | |
| dwLineNum--; | |
| if (dwLineNum >= 3) /* Windows seems to do something like that */ | |
| dwLineNum = 0; | |
| EnterCriticalSection(&This->cs); | |
| set_buffer(&This->lines[dwLineNum], pwzLine); | |
| This->dwUpdate |= UPDATE_LINE1 << dwLineNum; | |
| hwnd = (This->isCancelled ? NULL : This->hwnd); /* no sense to send the message if window cancelled */ | |
| LeaveCriticalSection(&This->cs); | |
| if (hwnd) | |
| SendMessageW(hwnd, WM_DLG_UPDATE, 0, 0); | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI ProgressDialog_SetCancelMsg(IProgressDialog *iface, LPCWSTR pwzMsg, LPCVOID reserved) | |
| { | |
| ProgressDialog *This = impl_from_IProgressDialog(iface); | |
| HWND hwnd; | |
| TRACE("(%p, %s)\n", This, wine_dbgstr_w(pwzMsg)); | |
| if (reserved) | |
| FIXME("reserved pointer not null (%p)\n", reserved); | |
| EnterCriticalSection(&This->cs); | |
| set_buffer(&This->cancelMsg, pwzMsg); | |
| This->dwUpdate |= UPDATE_LINE1 << CANCEL_MSG_LINE; | |
| hwnd = (This->isCancelled ? This->hwnd : NULL); /* no sense to send the message if window not cancelled */ | |
| LeaveCriticalSection(&This->cs); | |
| if (hwnd) | |
| SendMessageW(hwnd, WM_DLG_UPDATE, 0, 0); | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI ProgressDialog_Timer(IProgressDialog *iface, DWORD dwTimerAction, LPCVOID reserved) | |
| { | |
| ProgressDialog *This = impl_from_IProgressDialog(iface); | |
| FIXME("(%p, %d, %p) - stub\n", This, dwTimerAction, reserved); | |
| if (reserved) | |
| FIXME("Reserved field not NULL but %p\n", reserved); | |
| return S_OK; | |
| } | |
| static const IProgressDialogVtbl ProgressDialogVtbl = | |
| { | |
| ProgressDialog_QueryInterface, | |
| ProgressDialog_AddRef, | |
| ProgressDialog_Release, | |
| ProgressDialog_StartProgressDialog, | |
| ProgressDialog_StopProgressDialog, | |
| ProgressDialog_SetTitle, | |
| ProgressDialog_SetAnimation, | |
| ProgressDialog_HasUserCancelled, | |
| ProgressDialog_SetProgress, | |
| ProgressDialog_SetProgress64, | |
| ProgressDialog_SetLine, | |
| ProgressDialog_SetCancelMsg, | |
| ProgressDialog_Timer | |
| }; | |
| static HRESULT WINAPI OleWindow_QueryInterface(IOleWindow *iface, REFIID iid, LPVOID *ppvOut) | |
| { | |
| ProgressDialog *This = impl_from_IOleWindow(iface); | |
| return ProgressDialog_QueryInterface(&This->IProgressDialog_iface, iid, ppvOut); | |
| } | |
| static ULONG WINAPI OleWindow_AddRef(IOleWindow *iface) | |
| { | |
| ProgressDialog *This = impl_from_IOleWindow(iface); | |
| return ProgressDialog_AddRef(&This->IProgressDialog_iface); | |
| } | |
| static ULONG WINAPI OleWindow_Release(IOleWindow *iface) | |
| { | |
| ProgressDialog *This = impl_from_IOleWindow(iface); | |
| return ProgressDialog_Release(&This->IProgressDialog_iface); | |
| } | |
| static HRESULT WINAPI OleWindow_GetWindow(IOleWindow* iface, HWND* phwnd) | |
| { | |
| ProgressDialog *This = impl_from_IOleWindow(iface); | |
| TRACE("(%p, %p)\n", This, phwnd); | |
| EnterCriticalSection(&This->cs); | |
| *phwnd = This->hwnd; | |
| LeaveCriticalSection(&This->cs); | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI OleWindow_ContextSensitiveHelp(IOleWindow* iface, BOOL fEnterMode) | |
| { | |
| ProgressDialog *This = impl_from_IOleWindow(iface); | |
| FIXME("(%p, %d): stub\n", This, fEnterMode); | |
| return E_NOTIMPL; | |
| } | |
| static const IOleWindowVtbl OleWindowVtbl = | |
| { | |
| OleWindow_QueryInterface, | |
| OleWindow_AddRef, | |
| OleWindow_Release, | |
| OleWindow_GetWindow, | |
| OleWindow_ContextSensitiveHelp | |
| }; | |
| HRESULT ProgressDialog_Constructor(IUnknown *pUnkOuter, IUnknown **ppOut) | |
| { | |
| ProgressDialog *This; | |
| if (pUnkOuter) | |
| return CLASS_E_NOAGGREGATION; | |
| This = heap_alloc_zero(sizeof(ProgressDialog)); | |
| if (This == NULL) | |
| return E_OUTOFMEMORY; | |
| This->IProgressDialog_iface.lpVtbl = &ProgressDialogVtbl; | |
| This->IOleWindow_iface.lpVtbl = &OleWindowVtbl; | |
| This->refCount = 1; | |
| InitializeCriticalSection(&This->cs); | |
| This->cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": ProgressDialog.cs"); | |
| TRACE("returning %p\n", This); | |
| *ppOut = (IUnknown *)&This->IProgressDialog_iface; | |
| InterlockedIncrement(&BROWSEUI_refCount); | |
| return S_OK; | |
| } |