From: Fabian Maurer Subject: [03/18] comctl32: Added basic implementation for task dialogs and add tests Message-Id: <20170224200412.12968-3-dark.shadow4@web.de> Date: Fri, 24 Feb 2017 21:03:57 +0100 In-Reply-To: <20170224200412.12968-1-dark.shadow4@web.de> References: <20170224200412.12968-1-dark.shadow4@web.de> The dialog doesn't show text and can basically do nothing, but it sets the foundation. Signed-off-by: Fabian Maurer --- dlls/comctl32/taskdialog.c | 212 +++++++++++++++++++++++++++++++++------ dlls/comctl32/tests/taskdialog.c | 40 +++++++- 2 files changed, 223 insertions(+), 29 deletions(-) diff --git a/dlls/comctl32/taskdialog.c b/dlls/comctl32/taskdialog.c index c26d53bac9..d31e7d0b95 100644 --- a/dlls/comctl32/taskdialog.c +++ b/dlls/comctl32/taskdialog.c @@ -28,45 +28,201 @@ #include "commctrl.h" #include "winerror.h" #include "comctl32.h" + +#include "wine/list.h" #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(commctrl); +typedef struct +{ + DLGITEMTEMPLATE template; + const WCHAR *class; /* For simplicity, we don't use ordinals but only the class name */ + const WCHAR *text; + WORD creation_data; + + /* not part of actual template */ + UINT text_size; /* Length in bytes including null-terminator */ + UINT class_size; /* Length in bytes including null-terminator */ + struct list entry; +}control_info; + +typedef struct +{ + DLGTEMPLATE template; + WORD menu; + WORD class; + const WCHAR *title; + + /* Not part of actual template */ + UINT titleSize; /* Length in bytes including null-terminator */ +}dialog_header; + +#define MEMCPY_MOVEPTR(target, source, size) memcpy(target, source, size); target += size; +#define STR_SIZE(str) ((lstrlenW(str) + 1) * sizeof(WCHAR)) + +static void* align_word(void *ptr) +{ + return (void *)(((UINT_PTR)ptr + 1 ) & ~1); +} + +static void* align_dword(void *ptr) +{ + return (void *)(((UINT_PTR)ptr + 3 ) & ~3); +} + +/* Functions for turning our dialog structures into a usable dialog template + * We don't load the dialog template from a resource, we instead create it in memory + * This way we can easily handle variable control numbers */ +LPDLGTEMPLATEW dialog_template_create(dialog_header header, struct list *controls) +{ + control_info *control; + void *template_data_start; + char *template_data; + int data_size = sizeof(WORD); /* Alignment at WORD boundaries is needed for strings, allocate a bit of padding */ + + data_size += sizeof(DLGTEMPLATE); + data_size += sizeof(WORD)*2 + header.titleSize; + LIST_FOR_EACH_ENTRY(control, controls, control_info, entry) + { + data_size += sizeof(DWORD); /* Each item is aligned on DWORD boundary, allocate a bit of padding */ + data_size += sizeof(DLGITEMTEMPLATE); + data_size += control->text_size; + data_size += control->class_size; + data_size += sizeof(WORD); + } + + template_data_start = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, data_size); + template_data = template_data_start; + + /* Align on WORD boundary for the strings */ + template_data = align_word(template_data); + + /* Copy header data */ + MEMCPY_MOVEPTR(template_data, &header.template, sizeof(DLGTEMPLATE)); + MEMCPY_MOVEPTR(template_data, &header.menu, sizeof(WORD)); + MEMCPY_MOVEPTR(template_data, &header.class, sizeof(WORD)); + MEMCPY_MOVEPTR(template_data, header.title, header.titleSize); + + /* Copy dialog member data */ + LIST_FOR_EACH_ENTRY(control, controls, control_info, entry) + { + /* Align on DWORD boundary for each new control */ + template_data = align_dword(template_data); + + MEMCPY_MOVEPTR(template_data, &control->template, sizeof(DLGITEMTEMPLATE)); + MEMCPY_MOVEPTR(template_data, control->class, control->class_size); + MEMCPY_MOVEPTR(template_data, control->text, control->text_size); + MEMCPY_MOVEPTR(template_data, &control->creation_data, sizeof(WORD)); + } + + return template_data_start; +} + +static void dialog_template_destroy(LPDLGTEMPLATEW template_data) +{ + HeapFree(GetProcessHeap(), 0, template_data); +} + +/* Functions to handle the dialog control list */ + +static void controls_init(struct list **controls) +{ + *controls = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(struct list)); + list_init(*controls); +} + +static void controls_destroy(struct list *controls) +{ + control_info *control; + LIST_FOR_EACH_ENTRY(control, controls, control_info, entry) + { + HeapFree(GetProcessHeap(), 0, control); + } + HeapFree(GetProcessHeap(), 0, controls); +} + +/* Adds a control for the TaskDialog into our list */ +static void controls_add(struct list *controls, WORD id, const WCHAR *class, const WCHAR *text, + DWORD style, short x, short y, short cx, short cy) +{ + control_info *data = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(control_info)); + + data->template.x = x; + data->template.y = y; + data->template.cx = cx; + data->template.cy = cy; + data->template.id = id; + data->template.style = style; + + data->class = class; + data->class_size = STR_SIZE(class); + data->text = text; + data->text_size = STR_SIZE(text); + + list_add_tail(controls, &data->entry); +} + +static INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_COMMAND: + if(HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == IDOK) + { + EndDialog(hwndDlg, 0); + return TRUE; + } + break; + } + return FALSE; +} + /*********************************************************************** * TaskDialogIndirect [COMCTL32.@] */ HRESULT WINAPI TaskDialogIndirect(const TASKDIALOGCONFIG *pTaskConfig, int *pnButton, int *pnRadioButton, BOOL *pfVerificationFlagChecked) { - UINT uType = 0; - INT ret; + static const WCHAR empty_string[] = {0}; + static const WCHAR class_button[] = {'B','u','t','t','o','n',0}; + static const WCHAR text_ok[] = {'O','K',0}; + LPDLGTEMPLATEW template_data; + dialog_header header = {0}; + struct list *controls; + TRACE("%p, %p, %p, %p\n", pTaskConfig, pnButton, pnRadioButton, pfVerificationFlagChecked); - if (pTaskConfig->dwCommonButtons & TDCBF_YES_BUTTON && - pTaskConfig->dwCommonButtons & TDCBF_NO_BUTTON && - pTaskConfig->dwCommonButtons & TDCBF_CANCEL_BUTTON) - uType |= MB_YESNOCANCEL; - else - if (pTaskConfig->dwCommonButtons & TDCBF_YES_BUTTON && - pTaskConfig->dwCommonButtons & TDCBF_NO_BUTTON) - uType |= MB_YESNO; - else - if (pTaskConfig->dwCommonButtons & TDCBF_RETRY_BUTTON && - pTaskConfig->dwCommonButtons & TDCBF_CANCEL_BUTTON) - uType |= MB_RETRYCANCEL; - else - if (pTaskConfig->dwCommonButtons & TDCBF_OK_BUTTON && - pTaskConfig->dwCommonButtons & TDCBF_CANCEL_BUTTON) - uType |= MB_OKCANCEL; - else - if (pTaskConfig->dwCommonButtons & TDCBF_OK_BUTTON) - uType |= MB_OK; - ret = MessageBoxW(pTaskConfig->hwndParent, pTaskConfig->pszMainInstruction, - pTaskConfig->pszWindowTitle, uType); - FIXME("dwCommonButtons=%x uType=%x ret=%x\n", pTaskConfig->dwCommonButtons, uType, ret); - - if (pnButton) *pnButton = ret; - if (pnRadioButton) *pnRadioButton = pTaskConfig->nDefaultButton; - if (pfVerificationFlagChecked) *pfVerificationFlagChecked = TRUE; + if (!pTaskConfig || pTaskConfig->cbSize != sizeof(TASKDIALOGCONFIG)) + return E_INVALIDARG; + + controls_init(&controls); + + /* Start creating controls */ + + controls_add(controls, IDOK, class_button, text_ok, WS_CHILD | WS_VISIBLE, 105, 85, 40, 10); + + header.title = pTaskConfig->pszWindowTitle; + if(!header.title) + header.title = empty_string; /* FIXME: Set to exe path instead */ + header.titleSize = STR_SIZE(header.title); + + header.template.style = DS_MODALFRAME | WS_CAPTION | WS_VISIBLE; + header.template.cdit = list_count(controls); + header.template.x = 10; + header.template.y = 10; + header.template.cx = 150; + header.template.cy = 100; + + /* Turn template information into a dialog template to display it */ + template_data = dialog_template_create(header, controls); + + DialogBoxIndirectParamW(pTaskConfig->hInstance, template_data, pTaskConfig->hwndParent, DialogProc, 0); + + /* Cleanup */ + + dialog_template_destroy(template_data); + controls_destroy(controls); + return S_OK; } diff --git a/dlls/comctl32/tests/taskdialog.c b/dlls/comctl32/tests/taskdialog.c index 332fa23db0..54c75ed061 100644 --- a/dlls/comctl32/tests/taskdialog.c +++ b/dlls/comctl32/tests/taskdialog.c @@ -29,6 +29,40 @@ static HRESULT (WINAPI *pTaskDialogIndirect)(const TASKDIALOGCONFIG *, int *, int *, BOOL *); +static HRESULT CALLBACK TaskDialogCallbackProc(HWND hwnd, UINT uNotification, WPARAM wParam, + LPARAM lParam, LONG_PTR dwRefData) +{ + if(uNotification == TDN_CREATED) + { + PostMessageW(hwnd, WM_KEYDOWN, VK_RETURN, 0); + } + return S_OK; +} + +static void test_TaskDialogIndirect(void) +{ + TASKDIALOGCONFIG info = {0}; + HRESULT ret; + + ret = pTaskDialogIndirect(NULL, NULL, NULL, NULL); + ok(ret == E_INVALIDARG, "Expected E_INVALIDARG, got %x\n", ret); + + ret = pTaskDialogIndirect(&info, NULL, NULL, NULL); + ok(ret == E_INVALIDARG, "Expected E_INVALIDARG, got %x\n", ret); + + info.cbSize = sizeof(TASKDIALOGCONFIG); + info.pfCallback = TaskDialogCallbackProc; + + /* Skip this test on wine, because it doesn't really fail, + * it would displays a dialog that doesn't automatically close */ + if (strcmp(winetest_platform, "wine")) + { + ret = pTaskDialogIndirect(&info, NULL, NULL, NULL); + ok(ret == S_OK, "Expected S_OK, got %x\n", ret); + } + +} + START_TEST(taskdialog) { ULONG_PTR ctx_cookie; @@ -46,7 +80,11 @@ START_TEST(taskdialog) ok(pTaskDialogIndirect != NULL, "TaskDialogIndirect not exported by name.\n"); ptr_ordinal = GetProcAddress(hinst, (const CHAR*)345); - ok(pTaskDialogIndirect == ptr_ordinal, "got wrong pointer for ordinal 345, %p expected %p\n", ptr_ordinal, pTaskDialogIndirect); + ok(pTaskDialogIndirect == ptr_ordinal, "got wrong pointer for ordinal 345, %p expected %p\n", + ptr_ordinal, pTaskDialogIndirect); + + if(pTaskDialogIndirect) + test_TaskDialogIndirect(); unload_v6_module(ctx_cookie, hCtx); } -- 2.11.1