[PATCH v3] comctl32/taskdialog: Implement buttons.

Nikolay Sivov nsivov at codeweavers.com
Sun Jun 11 07:09:03 CDT 2017


Thanks.

I attached what I believe to be improved version, while keeping your 
positioning logic. It fixes compiler warning, loading custom button text
from resources, naming issues, and minor formatting inconsistencies.

If you're okay with it, I'll submit under your name, or you can do it 
yourself.

Nikolay
-------------- next part --------------
From e445361b0a05e13c1ed15d20325564dd6b024cd5 Mon Sep 17 00:00:00 2001
From: Nikolay Sivov <nsivov at codeweavers.com>
Date: Sun, 11 Jun 2017 15:04:26 +0300
Subject: [PATCH] comctl32/taskdialog: Added support for custom buttons.

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..f1d055f507 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 - (DIALOG_SPACING + 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-devel mailing list