[PATCH] comctl32/taskdialog: Initial implementation of a minimal taskdialog

Nikolay Sivov nsivov at codeweavers.com
Tue Mar 21 09:31:20 CDT 2017


Signed-off-by: Nikolay Sivov <nsivov at codeweavers.com>
---

This is based on patches by Fabian Maurer <dark.shadow4 at web.de>. I made a lot of changes,
so I'm not sure if resending under original author name is appropriate in this case.
Let me know if you feel otherwise.

 dlls/comctl32/comctl32.h   |   8 ++
 dlls/comctl32/comctl32.rc  |  10 ++
 dlls/comctl32/taskdialog.c | 350 +++++++++++++++++++++++++++++++++++++++++----
 3 files changed, 343 insertions(+), 25 deletions(-)

diff --git a/dlls/comctl32/comctl32.h b/dlls/comctl32/comctl32.h
index b9b0574427..aa527d7ab8 100644
--- a/dlls/comctl32/comctl32.h
+++ b/dlls/comctl32/comctl32.h
@@ -99,6 +99,14 @@ extern HBRUSH  COMCTL32_hPattern55AABrush DECLSPEC_HIDDEN;
 #define IDI_TT_WARN_SM                   25
 #define IDI_TT_ERROR_SM                  28
 
+/* Taskdialog strings */
+#define IDS_BUTTON_YES    3000
+#define IDS_BUTTON_NO     3001
+#define IDS_BUTTON_RETRY  3002
+#define IDS_BUTTON_OK     3003
+#define IDS_BUTTON_CANCEL 3004
+#define IDS_BUTTON_CLOSE  3005
+
 typedef struct
 {
     COLORREF clrBtnHighlight;       /* COLOR_BTNHIGHLIGHT                  */
diff --git a/dlls/comctl32/comctl32.rc b/dlls/comctl32/comctl32.rc
index 87e2fd1f6c..cea815a347 100644
--- a/dlls/comctl32/comctl32.rc
+++ b/dlls/comctl32/comctl32.rc
@@ -46,6 +46,16 @@ STRINGTABLE
     HKY_NONE "#msgctxt#hotkey#None"
 }
 
+STRINGTABLE
+{
+    IDS_BUTTON_YES    "&Yes"
+    IDS_BUTTON_NO     "&No"
+    IDS_BUTTON_RETRY  "&Retry"
+    IDS_BUTTON_OK     "OK"
+    IDS_BUTTON_CANCEL "Cancel"
+    IDS_BUTTON_CLOSE  "&Close"
+}
+
 IDD_PROPSHEET DIALOG 0, 0, 220, 140
 STYLE DS_CONTEXTHELP | DS_MODALFRAME | DS_3DLOOK | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_VISIBLE
 CAPTION "Properties for %s"
diff --git a/dlls/comctl32/taskdialog.c b/dlls/comctl32/taskdialog.c
index 15824d3f5f..bd7f8bdab5 100644
--- a/dlls/comctl32/taskdialog.c
+++ b/dlls/comctl32/taskdialog.c
@@ -24,49 +24,349 @@
 
 #include "windef.h"
 #include "winbase.h"
+#include "wingdi.h"
 #include "winuser.h"
 #include "commctrl.h"
 #include "winerror.h"
 #include "comctl32.h"
+
 #include "wine/debug.h"
+#include "wine/list.h"
+#include "wine/unicode.h"
 
 WINE_DEFAULT_DEBUG_CHANNEL(taskdialog);
 
+#define ALIGNED_LENGTH(_Len, _Align) (((_Len)+(_Align))&~(_Align))
+#define ALIGNED_POINTER(_Ptr, _Align) ((LPVOID)ALIGNED_LENGTH((ULONG_PTR)(_Ptr), _Align))
+#define ALIGN_LENGTH(_Len, _Align) _Len = ALIGNED_LENGTH(_Len, _Align)
+#define ALIGN_POINTER(_Ptr, _Align) _Ptr = ALIGNED_POINTER(_Ptr, _Align)
+
+static const UINT DIALOG_MIN_WIDTH = 180;
+static const UINT DIALOG_SPACING = 5;
+static const UINT DIALOG_BUTTON_WIDTH = 50;
+static const UINT DIALOG_BUTTON_HEIGHT = 14;
+
+static const UINT ID_MAIN_INSTRUCTION = 0xf000;
+
+struct taskdialog_control
+{
+    struct list entry;
+    DLGITEMTEMPLATE *template;
+    unsigned int template_size;
+};
+
+struct taskdialog_template_desc
+{
+    const TASKDIALOGCONFIG *taskconfig;
+    unsigned int dialog_height;
+    unsigned int dialog_width;
+    struct list controls;
+    WORD control_count;
+    LONG x_baseunit;
+    LONG y_baseunit;
+    HFONT font;
+};
+
+static void pixels_to_dialogunits(const struct taskdialog_template_desc *desc, LONG *width, LONG *height)
+{
+    if (width)
+        *width = MulDiv(*width, 4, desc->x_baseunit);
+    if (height)
+        *height = MulDiv(*height, 8, desc->y_baseunit);
+}
+
+static void dialogunits_to_pixels(const struct taskdialog_template_desc *desc, LONG *width, LONG *height)
+{
+    if (width)
+        *width = MulDiv(*width, desc->x_baseunit, 4);
+    if (height)
+        *height = MulDiv(*height, desc->y_baseunit, 8);
+}
+
+static void template_write_data(char **ptr, const void *src, unsigned int size)
+{
+    memcpy(*ptr, src, size);
+    *ptr += size;
+}
+
+static unsigned int taskdialog_add_control(struct taskdialog_template_desc *desc, WORD id, const WCHAR *class,
+        HINSTANCE hInstance, const WCHAR *text, short x, short y, short cx, short cy)
+{
+    struct taskdialog_control *control = Alloc(sizeof(*control));
+    unsigned int size, class_size, text_size;
+    DLGITEMTEMPLATE *template;
+    static const WCHAR nulW;
+    const WCHAR *textW;
+    char *ptr;
+
+    class_size = (strlenW(class) + 1) * sizeof(WCHAR);
+
+    if (IS_INTRESOURCE(text))
+        text_size = LoadStringW(hInstance, (UINT_PTR)text, (WCHAR *)&textW, 0) * sizeof(WCHAR);
+    else
+    {
+        textW = text;
+        text_size = strlenW(textW) * sizeof(WCHAR);
+    }
+
+    size = sizeof(DLGITEMTEMPLATE);
+    size += class_size;
+    size += text_size + sizeof(WCHAR);
+    size += sizeof(WORD); /* creation data */
+
+    control->template = template = Alloc(size);
+    control->template_size = size;
+
+    template->style = WS_VISIBLE;
+    template->dwExtendedStyle = 0;
+    template->x = x;
+    template->y = y;
+    template->cx = cx;
+    template->cy = cy;
+    template->id = id;
+    ptr = (char *)(template + 1);
+    template_write_data(&ptr, class, class_size);
+    template_write_data(&ptr, textW, text_size);
+    template_write_data(&ptr, &nulW, sizeof(nulW));
+
+    list_add_tail(&desc->controls, &control->entry);
+    desc->control_count++;
+    return ALIGNED_LENGTH(size, 3);
+}
+
+static unsigned int taskdialog_add_main_instruction(struct taskdialog_template_desc *desc)
+{
+    RECT rect = { 0, 0, desc->dialog_width - DIALOG_SPACING * 2, 0}; /* padding left and right of the control */
+    const WCHAR *textW = NULL;
+    unsigned int size, length;
+    HFONT oldfont;
+    HDC hdc;
+
+    if (!desc->taskconfig->pszMainInstruction)
+        return 0;
+
+    if (IS_INTRESOURCE(desc->taskconfig->pszMainInstruction))
+    {
+        if (!(length = LoadStringW(desc->taskconfig->hInstance, (UINT_PTR)desc->taskconfig->pszMainInstruction,
+                (WCHAR *)&textW, 0)))
+        {
+            WARN("Failed to load main instruction text\n");
+            return 0;
+        }
+    }
+    else
+    {
+        textW = desc->taskconfig->pszMainInstruction;
+        length = strlenW(textW);
+    }
+
+    hdc = GetDC(0);
+    oldfont = SelectObject(hdc, desc->font);
+
+    dialogunits_to_pixels(desc, &rect.right, NULL);
+    DrawTextW(hdc, textW, length, &rect, DT_LEFT | DT_EXPANDTABS | DT_CALCRECT | DT_WORDBREAK);
+    pixels_to_dialogunits(desc, &rect.right, &rect.bottom);
+
+    SelectObject(hdc, oldfont);
+    DeleteDC(hdc);
+
+    size = taskdialog_add_control(desc, ID_MAIN_INSTRUCTION, WC_STATICW, desc->taskconfig->hInstance,
+            desc->taskconfig->pszMainInstruction, DIALOG_SPACING, desc->dialog_height,
+            rect.right, rect.bottom);
+    desc->dialog_height += rect.bottom;
+    return size;
+}
+
+static unsigned int taskdialog_add_common_buttons(struct taskdialog_template_desc *desc)
+{
+    short button_x = desc->dialog_width - DIALOG_BUTTON_WIDTH - DIALOG_SPACING;
+    DWORD flags = desc->taskconfig->dwCommonButtons;
+    unsigned int size = 0;
+
+#define TASKDIALOG_ADD_COMMON_BUTTON(id) \
+    { \
+        size += taskdialog_add_control(desc, ID##id, WC_BUTTONW, COMCTL32_hModule, MAKEINTRESOURCEW(IDS_BUTTON_##id), \
+            button_x, desc->dialog_height + DIALOG_SPACING, DIALOG_BUTTON_WIDTH, DIALOG_BUTTON_HEIGHT); \
+        button_x -= DIALOG_BUTTON_WIDTH + DIALOG_SPACING; \
+    }
+    if (flags & TDCBF_CLOSE_BUTTON)
+        TASKDIALOG_ADD_COMMON_BUTTON(CLOSE);
+    if (flags & TDCBF_CANCEL_BUTTON)
+        TASKDIALOG_ADD_COMMON_BUTTON(CANCEL);
+    if (flags & TDCBF_RETRY_BUTTON)
+        TASKDIALOG_ADD_COMMON_BUTTON(RETRY);
+    if (flags & TDCBF_NO_BUTTON)
+        TASKDIALOG_ADD_COMMON_BUTTON(NO);
+    if (flags & TDCBF_YES_BUTTON)
+        TASKDIALOG_ADD_COMMON_BUTTON(YES);
+    if (flags & TDCBF_OK_BUTTON)
+        TASKDIALOG_ADD_COMMON_BUTTON(OK);
+    /* Always add OK button */
+    if (list_empty(&desc->controls))
+        TASKDIALOG_ADD_COMMON_BUTTON(OK);
+#undef TASKDIALOG_ADD_COMMON_BUTTON
+
+    /* make room for common buttons row */
+    desc->dialog_height +=  DIALOG_BUTTON_HEIGHT + 2 * DIALOG_SPACING;
+    return size;
+}
+
+static void taskdialog_clear_controls(struct list *controls)
+{
+    struct taskdialog_control *control, *control2;
+
+    LIST_FOR_EACH_ENTRY_SAFE(control, control2, controls, struct taskdialog_control, entry)
+    {
+        list_remove(&control->entry);
+        Free(control->template);
+        Free(control);
+    }
+}
+
+static unsigned int taskdialog_get_reference_rect(const struct taskdialog_template_desc *desc, RECT *ret)
+{
+    HMONITOR monitor = MonitorFromWindow(desc->taskconfig->hwndParent ? desc->taskconfig->hwndParent : GetActiveWindow(),
+            MONITOR_DEFAULTTOPRIMARY);
+    MONITORINFO info;
+
+    info.cbSize = sizeof(info);
+    GetMonitorInfoW(monitor, &info);
+
+    if (desc->taskconfig->dwFlags & TDF_POSITION_RELATIVE_TO_WINDOW && desc->taskconfig->hwndParent)
+        GetWindowRect(desc->taskconfig->hwndParent, ret);
+    else
+        *ret = info.rcWork;
+
+    pixels_to_dialogunits(desc, &ret->left, &ret->top);
+    pixels_to_dialogunits(desc, &ret->right, &ret->bottom);
+
+    pixels_to_dialogunits(desc, &info.rcWork.left, &info.rcWork.top);
+    pixels_to_dialogunits(desc, &info.rcWork.right, &info.rcWork.bottom);
+    return info.rcWork.right - info.rcWork.left;
+}
+
+DLGTEMPLATE *create_taskdialog_template(const TASKDIALOGCONFIG *taskconfig)
+{
+    struct taskdialog_control *control, *control2;
+    unsigned int size, title_size, screen_width;
+    struct taskdialog_template_desc desc;
+    static const WORD fontsize = 0x7fff;
+    static const WCHAR emptyW[] = { 0 };
+    const WCHAR *titleW = NULL;
+    DLGTEMPLATE *template;
+    NONCLIENTMETRICSW ncm;
+    RECT ref_rect;
+    char *ptr;
+    HDC hdc;
+
+    /* Window title */
+    if (!taskconfig->pszWindowTitle)
+        FIXME("use executable name for window title\n");
+    else if (IS_INTRESOURCE(taskconfig->pszWindowTitle))
+        FIXME("load window title from resources\n");
+    else
+        titleW = taskconfig->pszWindowTitle;
+    if (!titleW)
+        titleW = emptyW;
+    title_size = (strlenW(titleW) + 1) * sizeof(WCHAR);
+
+    size = sizeof(DLGTEMPLATE) + 2 * sizeof(WORD);
+    if (titleW)
+        size += title_size;
+    size += 2; /* font size */
+
+    list_init(&desc.controls);
+    desc.taskconfig = taskconfig;
+    desc.control_count = 0;
+
+    ncm.cbSize = sizeof(ncm);
+    SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0);
+    desc.font = CreateFontIndirectW(&ncm.lfMessageFont);
+
+    hdc = GetDC(0);
+    SelectObject(hdc, desc.font);
+    desc.x_baseunit = GdiGetCharDimensions(hdc, NULL, &desc.y_baseunit);
+    DeleteDC(hdc);
+
+    screen_width = taskdialog_get_reference_rect(&desc, &ref_rect);
+
+    desc.dialog_height = DIALOG_SPACING;
+    desc.dialog_width = max(taskconfig->cxWidth, DIALOG_MIN_WIDTH);
+    desc.dialog_width = min(desc.dialog_width, screen_width);
+
+    size += taskdialog_add_main_instruction(&desc);
+    size += taskdialog_add_common_buttons(&desc);
+
+    template = Alloc(size);
+    if (!template)
+    {
+        taskdialog_clear_controls(&desc.controls);
+        DeleteObject(desc.font);
+        return NULL;
+    }
+
+    template->style = DS_MODALFRAME | DS_SETFONT | WS_CAPTION | WS_VISIBLE | WS_SYSMENU;
+    template->cdit = desc.control_count;
+    template->x = (ref_rect.left + ref_rect.right + desc.dialog_width) / 2;
+    template->y = (ref_rect.top + ref_rect.bottom + desc.dialog_height) / 2;
+    template->cx = desc.dialog_width;
+    template->cy = desc.dialog_height;
+
+    ptr = (char *)(template + 1);
+    ptr += 2; /* menu */
+    ptr += 2; /* class */
+    template_write_data(&ptr, titleW, title_size);
+    template_write_data(&ptr, &fontsize, sizeof(fontsize));
+
+    /* write control entries */
+    LIST_FOR_EACH_ENTRY_SAFE(control, control2, &desc.controls, struct taskdialog_control, entry)
+    {
+        ALIGN_POINTER(ptr, 3);
+
+        template_write_data(&ptr, control->template, control->template_size);
+
+        /* list item won't be needed later */
+        list_remove(&control->entry);
+        Free(control->template);
+        Free(control);
+    }
+
+    DeleteObject(desc.font);
+    return template;
+}
+
+static INT_PTR CALLBACK taskdialog_proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+    TRACE("hwnd=%p msg=0x%04x wparam=%lx lparam=%lx\n", hwnd, msg, wParam, lParam);
+
+    switch (msg)
+    {
+        case WM_COMMAND:
+            if (HIWORD(wParam) == BN_CLICKED)
+            {
+                WORD command_id = LOWORD(wParam);
+                EndDialog(hwnd, command_id);
+                return TRUE;
+            }
+            break;
+    }
+    return FALSE;
+}
+
 /***********************************************************************
  * TaskDialogIndirect [COMCTL32.@]
  */
 HRESULT WINAPI TaskDialogIndirect(const TASKDIALOGCONFIG *taskconfig, int *button,
                                   int *radio_button, BOOL *verification_flag_checked)
 {
-    UINT type = 0;
+    DLGTEMPLATE *template;
     INT ret;
 
     TRACE("%p, %p, %p, %p\n", taskconfig, button, radio_button, verification_flag_checked);
 
-    if (taskconfig->dwCommonButtons & TDCBF_YES_BUTTON &&
-        taskconfig->dwCommonButtons & TDCBF_NO_BUTTON &&
-        taskconfig->dwCommonButtons & TDCBF_CANCEL_BUTTON)
-        type |= MB_YESNOCANCEL;
-    else
-    if (taskconfig->dwCommonButtons & TDCBF_YES_BUTTON &&
-        taskconfig->dwCommonButtons & TDCBF_NO_BUTTON)
-        type |= MB_YESNO;
-    else
-    if (taskconfig->dwCommonButtons & TDCBF_RETRY_BUTTON &&
-        taskconfig->dwCommonButtons & TDCBF_CANCEL_BUTTON)
-        type |= MB_RETRYCANCEL;
-    else
-    if (taskconfig->dwCommonButtons & TDCBF_OK_BUTTON &&
-        taskconfig->dwCommonButtons & TDCBF_CANCEL_BUTTON)
-        type |= MB_OKCANCEL;
-    else
-    if (taskconfig->dwCommonButtons & TDCBF_OK_BUTTON)
-        type |= MB_OK;
-
-    ret = MessageBoxW(taskconfig->hwndParent, taskconfig->pszMainInstruction,
-        taskconfig->pszWindowTitle, type);
-
-    FIXME("dwCommonButtons=%x type=%x ret=%x\n", taskconfig->dwCommonButtons, type, ret);
+    template = create_taskdialog_template(taskconfig);
+    ret = DialogBoxIndirectParamW(taskconfig->hInstance, template, taskconfig->hwndParent, taskdialog_proc, 0);
+    Free(template);
 
     if (button) *button = ret;
     if (radio_button) *radio_button = taskconfig->nDefaultButton;
-- 
2.11.0




More information about the wine-patches mailing list