[PATCH] comctl32/taskdialog: Add callback and tests

Fabian Maurer dark.shadow4 at web.de
Fri Jul 28 11:53:06 CDT 2017


Signed-off-by: Fabian Maurer <dark.shadow4 at web.de>
---
 dlls/comctl32/taskdialog.c       |  62 +++++++++-
 dlls/comctl32/tests/taskdialog.c | 240 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 299 insertions(+), 3 deletions(-)

diff --git a/dlls/comctl32/taskdialog.c b/dlls/comctl32/taskdialog.c
index a14a2230a9..535aa412f2 100644
--- a/dlls/comctl32/taskdialog.c
+++ b/dlls/comctl32/taskdialog.c
@@ -80,6 +80,13 @@ struct taskdialog_button_desc
     HINSTANCE hinst;
 };
 
+struct taskdialog_info
+{
+    HWND hwnd;
+    PFTASKDIALOGCALLBACK callback;
+    LONG_PTR callback_data;
+};
+
 static void pixels_to_dialogunits(const struct taskdialog_template_desc *desc, LONG *width, LONG *height)
 {
     if (width)
@@ -482,20 +489,62 @@ static DLGTEMPLATE *create_taskdialog_template(const TASKDIALOGCONFIG *taskconfi
     return template;
 }
 
+static HRESULT callback(struct taskdialog_info *dialog_info, UINT uNotification, WPARAM wParam, LPARAM lParam)
+{
+    if(dialog_info->callback)
+        return dialog_info->callback(dialog_info->hwnd, uNotification, wParam, lParam, dialog_info->callback_data);
+    return S_OK;
+}
+
+static void click_button(struct taskdialog_info *dialog_info, WORD command_id)
+{
+    HRESULT ret_callback;
+
+    ret_callback = callback(dialog_info, TDN_BUTTON_CLICKED, command_id, 0);
+    if(ret_callback == S_OK)
+    {
+        EndDialog(dialog_info->hwnd, command_id);
+    }
+}
+
 static INT_PTR CALLBACK taskdialog_proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
 {
+    static const WCHAR taskdialog_info_propnameW[] = {'T','a','s','k','D','i','a','l','o','g','I','n','f','o',0};
+    struct taskdialog_info *dialog_info;
+
     TRACE("hwnd=%p msg=0x%04x wparam=%lx lparam=%lx\n", hwnd, msg, wParam, lParam);
 
+    if(msg != WM_INITDIALOG && msg != WM_NCDESTROY)
+        dialog_info = GetPropW(hwnd, taskdialog_info_propnameW);
+
     switch (msg)
     {
+        case WM_INITDIALOG:
+            dialog_info = (struct taskdialog_info *)lParam;
+            dialog_info->hwnd = hwnd;
+            SetPropW(hwnd, taskdialog_info_propnameW, dialog_info);
+
+            callback(dialog_info, TDN_DIALOG_CONSTRUCTED, 0, 0);
+            callback(dialog_info, TDN_CREATED, 0, 0);
+            break;
         case WM_COMMAND:
             if (HIWORD(wParam) == BN_CLICKED)
             {
                 WORD command_id = LOWORD(wParam);
-                EndDialog(hwnd, command_id);
+                click_button(dialog_info, command_id);
                 return TRUE;
             }
             break;
+        case WM_DESTROY:
+            callback(dialog_info, TDN_DESTROYED, 0, 0);
+            RemovePropW(hwnd, taskdialog_info_propnameW);
+            break;
+
+        /* Custom messages*/
+
+        case TDM_CLICK_BUTTON:
+            click_button(dialog_info, wParam);
+            break;
     }
     return FALSE;
 }
@@ -507,17 +556,24 @@ HRESULT WINAPI TaskDialogIndirect(const TASKDIALOGCONFIG *taskconfig, int *butto
                                   int *radio_button, BOOL *verification_flag_checked)
 {
     DLGTEMPLATE *template;
+    struct taskdialog_info dialog_info;
     INT ret;
 
     TRACE("%p, %p, %p, %p\n", taskconfig, button, radio_button, verification_flag_checked);
 
+    if(!taskconfig || taskconfig->cbSize != sizeof(TASKDIALOGCONFIG))
+        return E_INVALIDARG;
+
+    dialog_info.callback = taskconfig->pfCallback;
+    dialog_info.callback_data = taskconfig->lpCallbackData;
+
     template = create_taskdialog_template(taskconfig);
-    ret = DialogBoxIndirectParamW(taskconfig->hInstance, template, taskconfig->hwndParent, taskdialog_proc, 0);
+    ret = DialogBoxIndirectParamW(taskconfig->hInstance, template, taskconfig->hwndParent, taskdialog_proc, (LPARAM)&dialog_info);
     Free(template);
 
     if (button) *button = ret;
     if (radio_button) *radio_button = taskconfig->nDefaultButton;
-    if (verification_flag_checked) *verification_flag_checked = TRUE;
+    if (verification_flag_checked) *verification_flag_checked = FALSE;
 
     return S_OK;
 }
diff --git a/dlls/comctl32/tests/taskdialog.c b/dlls/comctl32/tests/taskdialog.c
index c937127897..867361617c 100644
--- a/dlls/comctl32/tests/taskdialog.c
+++ b/dlls/comctl32/tests/taskdialog.c
@@ -24,13 +24,250 @@
 #include "winuser.h"
 #include "commctrl.h"
 
+#include "wine/list.h"
 #include "wine/test.h"
 #include "v6util.h"
+#include "msg.h"
+
+#define WM_TD_CALLBACK (WM_APP) /* Custom dummy message to wrap callback notifications */
+
+#define NUM_MSG_SEQUENCES     1
+#define TASKDIALOG_SEQ_INDEX  0
+
+#define TEST_NUM_BUTTONS 20 /* Number of custom buttons to test with */
+
+#define ID_START 20 /* Lower IDs might be used by the system */
+#define ID_START_BUTTON (ID_START + 0)
 
 static HRESULT (WINAPI *pTaskDialogIndirect)(const TASKDIALOGCONFIG *, int *, int *, BOOL *);
 static HRESULT (WINAPI *pTaskDialog)(HWND, HINSTANCE, const WCHAR *, const WCHAR *, const WCHAR *,
         TASKDIALOG_COMMON_BUTTON_FLAGS, const WCHAR *, int *);
 
+static struct msg_sequence *sequences[NUM_MSG_SEQUENCES];
+
+/* Message lists to test against */
+
+struct message_send_info
+{
+    UINT send_message;
+    WPARAM send_wparam;
+    LPARAM send_lparam;
+
+    BOOL post; /* post instead of send */
+    const CHAR *title_target; /* control text, 0 means it's send to the dialog form instead */
+};
+
+struct message_info
+{
+    UINT recv_message; /* Message the callback receives */
+    WPARAM recv_wparam;
+    LPARAM recv_lparam;
+
+    HRESULT ret; /* Value the callback should return */
+
+    struct message_send_info send[9];  /* Message to send to trigger the next callback message */
+};
+
+static const struct message_info *current_message_info;
+
+static const struct message_info mes_return_press_ok[] = {
+    { TDN_CREATED, 0, 0, S_OK, {
+        { WM_KEYDOWN, VK_RETURN, 0, TRUE },
+        { 0 }}},
+    { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, {{ 0 }}},
+    { 0 }
+};
+
+static const struct message_info mes_cancel_button_press[] = {
+    { TDN_CREATED, 0, 0, S_OK, {
+        { TDM_CLICK_BUTTON, IDOK, 0, TRUE },
+        { TDM_CLICK_BUTTON, IDOK, 0, TRUE },
+        { TDM_CLICK_BUTTON, IDOK, 0, TRUE },
+        { 0 }}},
+    { TDN_BUTTON_CLICKED, IDOK, 0, S_FALSE, {{ 0 }}},
+    { TDN_BUTTON_CLICKED, IDOK, 0, 0xFF, {{ 0 }}}, /* Random return value tested here */
+    { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, {{ 0 }}},
+    { 0 }
+};
+
+static const struct message_info mes_send_button_directly[] = {
+    { TDN_CREATED, 0, 0, S_OK, {
+        { WM_LBUTTONDOWN, MK_LBUTTON, 0, TRUE, "02" },
+        { WM_LBUTTONUP,   0, 0, TRUE, "02" },
+        { 0 }}},
+    { TDN_BUTTON_CLICKED, ID_START + 2, 0, S_OK, {{ 0 }}},
+    { 0 }
+};
+
+/* Create a message to test against */
+static struct message create_test_message(UINT message, WPARAM wParam, LPARAM lParam)
+{
+    struct message mes;
+
+    mes.message = WM_TD_CALLBACK;
+    mes.id = message;
+    mes.flags = sent|wparam|lparam|id;
+    mes.wParam = wParam;
+    mes.lParam = lParam;
+
+    return mes;
+}
+
+/* Our only way to get a button handle, since GetDlgItem and FindWindowEx don't work for the official taskdialog */
+
+static HWND taskdialog_child;
+BOOL CALLBACK enum_taskdialog_children_proc(HWND hwnd, LPARAM lParam)
+{
+    CHAR text[100];
+    const CHAR *title = (const CHAR *)lParam;
+
+    GetWindowTextA(hwnd, text, sizeof(text));
+
+    if(lstrcmpA(text, title) == 0)
+    {
+        taskdialog_child = hwnd;
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+static HWND get_child_from_title(HWND hwnd_parent, const CHAR *title)
+{
+    taskdialog_child = NULL;
+    EnumChildWindows(hwnd_parent, enum_taskdialog_children_proc, (LPARAM)title);
+    return taskdialog_child;
+}
+
+#define run_test(info, expect_button, expect_radio, expect_checkbox, seq, context) \
+        run_test_(info, expect_button, expect_radio, expect_checkbox, seq, context, \
+                  sizeof(seq)/sizeof(seq[0]) - 1 , __FILE__, __LINE__)
+
+void run_test_(TASKDIALOGCONFIG *info, int expect_button, int expect_radio, BOOL expect_checkbox,
+               const struct message_info *test_messages, const char* context,
+               int test_messages_len, const char *file, int line)
+{
+    HRESULT ret;
+    int ret_button = 0;
+    int ret_radio = 0;
+    BOOL ret_checkbox = 0;
+
+    struct message *mes, *mes_start;
+    int i;
+
+    /* Allocate messages to test against, plus 2 implicit and 1 empty */
+    mes_start = mes = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(struct message) * (test_messages_len + 3));
+
+    *mes++ = create_test_message(TDN_DIALOG_CONSTRUCTED, 0, 0); /* Always needed, thus made implicit */
+    for(i = 0; i < test_messages_len; i++)
+    {
+        *mes++ = create_test_message(test_messages[i].recv_message,
+                                     test_messages[i].recv_wparam, test_messages[i].recv_lparam);
+    }
+    *mes++ = create_test_message(TDN_DESTROYED, 0, 0); /* Always needed, thus made implicit */
+
+    current_message_info = test_messages;
+    flush_sequences(sequences, NUM_MSG_SEQUENCES);
+    ret = pTaskDialogIndirect(info, &ret_button, &ret_radio, &ret_checkbox);
+
+    ok_( file, line)(ret == S_OK, "Expected S_OK, got %x\n", ret);
+    ok_sequence_(sequences, TASKDIALOG_SEQ_INDEX, mes_start, context, FALSE, file, line);
+    ok_( file, line)(ret_button == expect_button,
+                     "Wrong button. Expected %d, got %d\n", expect_button, ret_button);
+    ok_( file, line)(ret_radio == expect_radio,
+                     "Wrong radio button. Expected %d, got %d\n", expect_radio, ret_radio);
+    ok_( file, line)(ret_checkbox == expect_checkbox,
+                     "Wrong checkbox state. Expected %d, got %d\n", expect_checkbox, ret_checkbox);
+
+    HeapFree(GetProcessHeap(), 0, mes_start);
+}
+
+static LONG_PTR backup_ref_data; /* Copy of dwRefData to test against */
+
+static HRESULT CALLBACK TaskDialogCallbackProc(HWND hwnd, UINT uNotification, WPARAM wParam,
+                                               LPARAM lParam, LONG_PTR dwRefData)
+{
+    struct message msg;
+
+    ok(backup_ref_data == dwRefData, "dwRefData is wrong, expected %lu, got %lu\n", backup_ref_data, dwRefData);
+
+    msg = create_test_message(uNotification, wParam, lParam);
+    add_message(sequences, TASKDIALOG_SEQ_INDEX, &msg);
+
+    if(uNotification != TDN_DIALOG_CONSTRUCTED && uNotification != TDN_DESTROYED) /* Skip implicit messages */
+    {
+        int mes_pos = sequences[TASKDIALOG_SEQ_INDEX]->count - 2; /* Skip implicit message and the current one */
+        const struct message_send_info *msg_send = current_message_info[mes_pos].send;
+        while(msg_send->send_message)
+        {
+            HWND hwnd_receiver = hwnd;
+            if(msg_send->title_target)
+            {
+                hwnd_receiver = get_child_from_title(hwnd, msg_send->title_target);
+                ok(hwnd_receiver != NULL, "Can't find child window.\n");
+            }
+
+            if(msg_send->post)
+                PostMessageW(hwnd_receiver, msg_send->send_message, msg_send->send_wparam, msg_send->send_lparam);
+            else
+                SendMessageW(hwnd_receiver, msg_send->send_message, msg_send->send_wparam, msg_send->send_lparam);
+
+            msg_send++;
+        }
+
+        return current_message_info[mes_pos].ret;
+    }
+
+    return S_OK;
+}
+
+static TASKDIALOG_BUTTON* buttons_make(void)
+{
+    static const WCHAR str_format[] = {'%','0','2','d',0};
+    static TASKDIALOG_BUTTON buttons[TEST_NUM_BUTTONS];
+    static WCHAR titles[TEST_NUM_BUTTONS * 3]; /* Each button has two digits as title, plus null-terminator */
+    int i;
+
+    for(i = 0; i < TEST_NUM_BUTTONS; i++)
+    {
+        WCHAR *text = &titles[i * 3];
+        wsprintfW(text, str_format, i);
+
+        buttons[i].pszButtonText = text;
+        buttons[i].nButtonID = ID_START_BUTTON + i;
+    }
+    return buttons;
+}
+
+static void test_TaskDialogIndirect(void)
+{
+    TASKDIALOGCONFIG info = {0};
+    TASKDIALOG_BUTTON *custom_buttons;
+    HRESULT ret;
+
+    custom_buttons = buttons_make();
+
+    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;
+    info.lpCallbackData = backup_ref_data = 0x12345678; /* Set data for callback tests */
+
+    run_test(&info, IDOK, 0, FALSE, mes_return_press_ok, "Simple test with parameters null");
+    run_test(&info, IDOK, 0, FALSE, mes_cancel_button_press, "Simple test for canceling button press");
+
+    /* Test sending message to button directly */
+    info.dwCommonButtons = TDCBF_OK_BUTTON | TDCBF_YES_BUTTON | TDCBF_NO_BUTTON
+                           | TDCBF_CANCEL_BUTTON | TDCBF_RETRY_BUTTON | TDCBF_CLOSE_BUTTON;
+    info.cButtons = 10;
+    info.pButtons = custom_buttons;
+    run_test(&info, ID_START + 2, 0, FALSE, mes_send_button_directly, "Sending message to button directly");
+}
+
 START_TEST(taskdialog)
 {
     ULONG_PTR ctx_cookie;
@@ -62,5 +299,8 @@ START_TEST(taskdialog)
     ok(pTaskDialogIndirect == ptr_ordinal, "got wrong pointer for ordinal 345, %p expected %p\n",
                                             ptr_ordinal, pTaskDialogIndirect);
 
+    init_msg_sequences(sequences, NUM_MSG_SEQUENCES);
+    test_TaskDialogIndirect();
+
     unload_v6_module(ctx_cookie, hCtx);
 }
-- 
2.13.3




More information about the wine-patches mailing list