[v4 PATCH] comctl32/taskdialog: Added support for custom buttons.

Nikolay Sivov nsivov at codeweavers.com
Sun Jun 11 14:11:46 CDT 2017


From: Fabian Maurer <dark.shadow4 at web.de>

Signed-off-by: Nikolay Sivov <nsivov at codeweavers.com>
---
 dlls/comctl32/taskdialog.c | 249 ++++++++++++++++++++++++++++++++++-----------
 1 file changed, 192 insertions(+), 57 deletions(-)

diff --git a/dlls/comctl32/taskdialog.c b/dlls/comctl32/taskdialog.c
index dc90160e44..a14a2230a9 100644
--- a/dlls/comctl32/taskdialog.c
+++ b/dlls/comctl32/taskdialog.c
@@ -20,6 +20,7 @@
  */
 
 #include <stdarg.h>
+#include <stdlib.h>
 #include <string.h>
 
 #define NONAMELESSUNION
@@ -43,7 +44,7 @@ WINE_DEFAULT_DEBUG_CHANNEL(taskdialog);
 #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_MIN_WIDTH = 240;
 static const UINT DIALOG_SPACING = 5;
 static const UINT DIALOG_BUTTON_WIDTH = 50;
 static const UINT DIALOG_BUTTON_HEIGHT = 14;
@@ -70,6 +71,15 @@ struct taskdialog_template_desc
     HFONT font;
 };
 
+struct taskdialog_button_desc
+{
+    int id;
+    const WCHAR *text;
+    unsigned int width;
+    unsigned int line;
+    HINSTANCE hinst;
+};
+
 static void pixels_to_dialogunits(const struct taskdialog_template_desc *desc, LONG *width, LONG *height)
 {
     if (width)
@@ -92,6 +102,47 @@ static void template_write_data(char **ptr, const void *src, unsigned int size)
     *ptr += size;
 }
 
+/* used to calculate size for the controls */
+static void taskdialog_get_text_extent(const struct taskdialog_template_desc *desc, const WCHAR *text,
+        BOOL user_resource, SIZE *sz)
+{
+    RECT rect = { 0, 0, desc->dialog_width - DIALOG_SPACING * 2, 0}; /* padding left and right of the control */
+    const WCHAR *textW = NULL;
+    static const WCHAR nulW;
+    unsigned int length;
+    HFONT oldfont;
+    HDC hdc;
+
+    if (IS_INTRESOURCE(text))
+    {
+        if (!(length = LoadStringW(user_resource ? desc->taskconfig->hInstance : COMCTL32_hModule,
+                (UINT_PTR)text, (WCHAR *)&textW, 0)))
+        {
+            WARN("Failed to load text\n");
+            textW = &nulW;
+            length = 0;
+        }
+    }
+    else
+    {
+        textW = text;
+        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);
+    ReleaseDC(0, hdc);
+
+    sz->cx = rect.right - rect.left;
+    sz->cy = rect.bottom - rect.top;
+}
+
 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)
 {
@@ -139,43 +190,18 @@ static unsigned int taskdialog_add_control(struct taskdialog_template_desc *desc
 
 static unsigned int taskdialog_add_static_label(struct taskdialog_template_desc *desc, WORD id, const WCHAR *str)
 {
-    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;
+    unsigned int size;
+    SIZE sz;
 
     if (!str)
         return 0;
 
-    if (IS_INTRESOURCE(str))
-    {
-        if (!(length = LoadStringW(desc->taskconfig->hInstance, (UINT_PTR)str, (WCHAR *)&textW, 0)))
-        {
-            WARN("Failed to load static text %s, id %#x\n", debugstr_w(str), id);
-            return 0;
-        }
-    }
-    else
-    {
-        textW = str;
-        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);
-    ReleaseDC(0, hdc);
+    taskdialog_get_text_extent(desc, str, TRUE, &sz);
 
     desc->dialog_height += DIALOG_SPACING;
     size = taskdialog_add_control(desc, id, WC_STATICW, desc->taskconfig->hInstance, str, DIALOG_SPACING,
-            desc->dialog_height, rect.right, rect.bottom);
-    desc->dialog_height += rect.bottom;
+            desc->dialog_height, sz.cx, sz.cy);
+    desc->dialog_height += sz.cy + DIALOG_SPACING;
     return size;
 }
 
@@ -189,37 +215,146 @@ static unsigned int taskdialog_add_content(struct taskdialog_template_desc *desc
     return taskdialog_add_static_label(desc, ID_CONTENT, desc->taskconfig->pszContent);
 }
 
-static unsigned int taskdialog_add_common_buttons(struct taskdialog_template_desc *desc)
+static void taskdialog_init_button(struct taskdialog_button_desc *button, struct taskdialog_template_desc *desc,
+        int id, const WCHAR *text, BOOL custom_button)
+{
+    SIZE sz;
+
+    taskdialog_get_text_extent(desc, text, custom_button, &sz);
+
+    button->id = id;
+    button->text = text;
+    button->width = max(DIALOG_BUTTON_WIDTH, sz.cx + DIALOG_SPACING * 2);
+    button->line = 0;
+    button->hinst = custom_button ? desc->taskconfig->hInstance : COMCTL32_hModule;
+}
+
+static void taskdialog_init_common_buttons(struct taskdialog_template_desc *desc, struct taskdialog_button_desc *buttons,
+    unsigned int *button_count)
 {
-    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) \
+#define TASKDIALOG_INIT_COMMON_BUTTON(id) \
     do { \
-        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; \
+        taskdialog_init_button(&buttons[(*button_count)++], desc, ID##id, MAKEINTRESOURCEW(IDS_BUTTON_##id), FALSE); \
     } while(0)
-    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;
+        TASKDIALOG_INIT_COMMON_BUTTON(OK);
+    if (flags & TDCBF_YES_BUTTON)
+        TASKDIALOG_INIT_COMMON_BUTTON(YES);
+    if (flags & TDCBF_NO_BUTTON)
+        TASKDIALOG_INIT_COMMON_BUTTON(NO);
+    if (flags & TDCBF_RETRY_BUTTON)
+        TASKDIALOG_INIT_COMMON_BUTTON(RETRY);
+    if (flags & TDCBF_CANCEL_BUTTON)
+        TASKDIALOG_INIT_COMMON_BUTTON(CANCEL);
+    if (flags & TDCBF_CLOSE_BUTTON)
+        TASKDIALOG_INIT_COMMON_BUTTON(CLOSE);
+
+#undef TASKDIALOG_INIT_COMMON_BUTTON
+}
+
+static unsigned int taskdialog_add_buttons(struct taskdialog_template_desc *desc)
+{
+    unsigned int count = 0, buttons_size, i, line_count, size = 0;
+    unsigned int location_x, *line_widths, alignment = ~0u;
+    const TASKDIALOGCONFIG *taskconfig = desc->taskconfig;
+    struct taskdialog_button_desc *buttons;
+
+    /* Allocate enough memory for the custom and the default buttons. Maximum 6 default buttons possible. */
+    buttons_size = 6;
+    if (taskconfig->cButtons && taskconfig->pButtons)
+        buttons_size += taskconfig->cButtons;
+
+    if (!(buttons = Alloc(buttons_size * sizeof(*buttons))))
+        return 0;
+
+    /* Custom buttons */
+    if (taskconfig->cButtons && taskconfig->pButtons)
+        for (i = 0; i < taskconfig->cButtons; i++)
+            taskdialog_init_button(&buttons[count++], desc, taskconfig->pButtons[i].nButtonID,
+                    taskconfig->pButtons[i].pszButtonText, TRUE);
+
+    /* Common buttons */
+    taskdialog_init_common_buttons(desc, buttons, &count);
+
+    /* There must be at least one button */
+    if (count == 0)
+        taskdialog_init_button(&buttons[count++], desc, IDOK, MAKEINTRESOURCEW(IDS_BUTTON_OK), FALSE);
+
+    /* For easy handling just allocate as many lines as buttons, the worst case. */
+    line_widths = Alloc(count * sizeof(*line_widths));
+
+    /* Separate buttons into lines */
+    location_x = DIALOG_SPACING;
+    for (i = 0, line_count = 0; i < count; i++)
+    {
+        if (location_x + buttons[i].width + DIALOG_SPACING > desc->dialog_width)
+        {
+            location_x = DIALOG_SPACING;
+            line_count++;
+        }
+
+        buttons[i].line = line_count;
+
+        location_x += buttons[i].width + DIALOG_SPACING;
+        line_widths[line_count] += buttons[i].width + DIALOG_SPACING;
+    }
+    line_count++;
+
+    /* Try to balance lines so they are about the same size */
+    for (i = 1; i < line_count - 1; i++)
+    {
+        int diff_now = abs(line_widths[i] - line_widths[i - 1]);
+        unsigned int j, last_button = 0;
+        int diff_changed;
+
+        for (j = 0; j < count; j++)
+            if (buttons[j].line == i - 1)
+                last_button = j;
+
+        /* Difference in length of both lines if we wrapped the last button from the last line into this one */
+        diff_changed = abs(2 * buttons[last_button].width + line_widths[i] - line_widths[i - 1]);
+
+        if (diff_changed < diff_now)
+        {
+            buttons[last_button].line = i;
+            line_widths[i] += buttons[last_button].width;
+            line_widths[i - 1] -= buttons[last_button].width;
+        }
+    }
+
+    /* Calculate left alignment so all lines are as far right as possible. */
+    for (i = 0; i < line_count; i++)
+    {
+        int new_alignment = desc->dialog_width - line_widths[i];
+        if (new_alignment < alignment)
+            alignment = new_alignment;
+    }
+
+    /* Now that we got them all positioned, create all buttons */
+    location_x = alignment;
+    for (i = 0; i < count; i++)
+    {
+        if (i > 0 && buttons[i].line != buttons[i - 1].line) /* New line */
+        {
+            location_x = alignment;
+            desc->dialog_height += DIALOG_BUTTON_HEIGHT + DIALOG_SPACING;
+        }
+
+        size += taskdialog_add_control(desc, buttons[i].id, WC_BUTTONW, buttons[i].hinst, buttons[i].text, location_x,
+                desc->dialog_height, buttons[i].width, DIALOG_BUTTON_HEIGHT);
+
+        location_x += buttons[i].width + DIALOG_SPACING;
+    }
+
+    /* Add height for last row and spacing */
+    desc->dialog_height += DIALOG_BUTTON_HEIGHT + DIALOG_SPACING;
+
+    Free(line_widths);
+    Free(buttons);
+
     return size;
 }
 
@@ -307,7 +442,7 @@ static DLGTEMPLATE *create_taskdialog_template(const TASKDIALOGCONFIG *taskconfi
 
     size += taskdialog_add_main_instruction(&desc);
     size += taskdialog_add_content(&desc);
-    size += taskdialog_add_common_buttons(&desc);
+    size += taskdialog_add_buttons(&desc);
 
     template = Alloc(size);
     if (!template)
-- 
2.11.0




More information about the wine-patches mailing list