Signed-off-by: Zhiyi Zhang zzhang@codeweavers.com --- dlls/comctl32/taskdialog.c | 58 +++++++++++++- dlls/comctl32/tests/taskdialog.c | 130 ++++++++++++++++++++++++------- 2 files changed, 160 insertions(+), 28 deletions(-)
diff --git a/dlls/comctl32/taskdialog.c b/dlls/comctl32/taskdialog.c index 645739fdc2..c38e973fb6 100644 --- a/dlls/comctl32/taskdialog.c +++ b/dlls/comctl32/taskdialog.c @@ -66,6 +66,7 @@ struct taskdialog_info INT command_link_count; HWND expanded_info; HWND expando_button; + HWND verification_box; HWND *buttons; INT button_count; HWND default_button; @@ -78,6 +79,7 @@ struct taskdialog_info LONG v_spacing; } m; INT selected_radio_id; + BOOL verification_checked; BOOL expanded; WCHAR *expanded_text; WCHAR *collapsed_text; @@ -247,6 +249,13 @@ static void taskdialog_on_button_click(struct taskdialog_info *dialog_info, HWND return; }
+ if (hwnd == dialog_info->verification_box) + { + dialog_info->verification_checked = !dialog_info->verification_checked; + taskdialog_notify(dialog_info, TDN_VERIFICATION_CLICKED, dialog_info->verification_checked, 0); + return; + } + radio_button = taskdialog_find_button(dialog_info->radio_buttons, dialog_info->radio_button_count, command_id); if (radio_button) { @@ -636,6 +645,26 @@ static void taskdialog_add_expando_button(struct taskdialog_info *dialog_info) SendMessageW(dialog_info->expando_button, WM_SETFONT, (WPARAM)dialog_info->font, 0); }
+static void taskdialog_add_verification_box(struct taskdialog_info *dialog_info) +{ + const TASKDIALOGCONFIG *taskconfig = dialog_info->taskconfig; + static const DWORD style = BS_AUTOCHECKBOX | BS_MULTILINE | BS_LEFT | BS_TOP | WS_CHILD | WS_VISIBLE | WS_TABSTOP; + WCHAR *textW; + + if (!taskconfig->pszVerificationText) return; + + textW = taskdialog_gettext(dialog_info, TRUE, taskconfig->pszVerificationText); + dialog_info->verification_box = CreateWindowW(WC_BUTTONW, textW, style, 0, 0, 0, 0, dialog_info->hwnd, 0, 0, 0); + SendMessageW(dialog_info->verification_box, WM_SETFONT, (WPARAM)dialog_info->font, 0); + Free(textW); + + if (taskconfig->dwFlags & TDF_VERIFICATION_FLAG_CHECKED) + { + dialog_info->verification_checked = TRUE; + SendMessageW(dialog_info->verification_box, BM_SETCHECK, BST_CHECKED, 0); + } +} + static void taskdialog_add_button(struct taskdialog_info *dialog_info, HWND *button, INT_PTR id, const WCHAR *text, BOOL custom_button) { @@ -806,6 +835,19 @@ static void taskdialog_layout(struct taskdialog_info *dialog_info) expando_bottom = y + size.cy; }
+ /* Verification box */ + if (dialog_info->verification_box) + { + x = h_spacing; + y = expando_bottom + v_spacing; + size.cx = DIALOG_MIN_WIDTH / 2; + taskdialog_du_to_px(dialog_info, &size.cx, NULL); + taskdialog_get_radio_button_size(dialog_info, dialog_info->verification_box, size.cx, &size); + SetWindowPos(dialog_info->verification_box, 0, x, y, size.cx, size.cy, SWP_NOZORDER); + expando_right = max(expando_right, x + size.cx); + expando_bottom = y + size.cy; + } + /* Common and custom buttons */ button_layout_infos = Alloc(dialog_info->button_count * sizeof(*button_layout_infos)); line_widths = Alloc(dialog_info->button_count * sizeof(*line_widths)); @@ -983,6 +1025,7 @@ static void taskdialog_init(struct taskdialog_info *dialog_info, HWND hwnd) taskdialog_add_radio_buttons(dialog_info); taskdialog_add_command_links(dialog_info); taskdialog_add_expando_button(dialog_info); + taskdialog_add_verification_box(dialog_info); taskdialog_add_buttons(dialog_info);
/* Set default button */ @@ -1063,6 +1106,19 @@ static INT_PTR CALLBACK taskdialog_proc(HWND hwnd, UINT msg, WPARAM wParam, LPAR case TDM_ENABLE_RADIO_BUTTON: taskdialog_enable_radio_button(dialog_info, wParam, lParam); break; + case TDM_CLICK_VERIFICATION: + { + BOOL checked = (BOOL)wParam; + BOOL focused = (BOOL)lParam; + dialog_info->verification_checked = checked; + if (dialog_info->verification_box) + { + SendMessageW(dialog_info->verification_box, BM_SETCHECK, checked ? BST_CHECKED : BST_UNCHECKED, 0); + taskdialog_notify(dialog_info, TDN_VERIFICATION_CLICKED, checked, 0); + if (focused) SetFocus(dialog_info->verification_box); + } + break; + } case WM_INITDIALOG: dialog_info = (struct taskdialog_info *)lParam;
@@ -1151,7 +1207,7 @@ HRESULT WINAPI TaskDialogIndirect(const TASKDIALOGCONFIG *taskconfig, int *butto
if (button) *button = ret; if (radio_button) *radio_button = dialog_info.selected_radio_id; - if (verification_flag_checked) *verification_flag_checked = TRUE; + if (verification_flag_checked) *verification_flag_checked = dialog_info.verification_checked;
return S_OK; } diff --git a/dlls/comctl32/tests/taskdialog.c b/dlls/comctl32/tests/taskdialog.c index 7fb8f3dcfc..07a80a2c2e 100644 --- a/dlls/comctl32/tests/taskdialog.c +++ b/dlls/comctl32/tests/taskdialog.c @@ -229,6 +229,48 @@ static const struct message_info msg_return_press_negative_id_radio_button[] = { 0 } };
+static const struct message_info msg_return_default_verification_unchecked[] = +{ + { TDN_CREATED, 0, 0, S_OK, msg_send_click_ok }, + { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL }, + { 0 } +}; + +static const struct message_info msg_return_default_verification_checked[] = +{ + { TDN_CREATED, 0, 0, S_OK, msg_send_click_ok }, + { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL }, + { 0 } +}; + +static const struct message_info msg_uncheck_verification[] = +{ + { TDM_CLICK_VERIFICATION, FALSE, 0 }, + { 0 } +}; + +static const struct message_info msg_return_verification_unchecked[] = +{ + { TDN_CREATED, 0, 0, S_OK, msg_uncheck_verification }, + { TDN_VERIFICATION_CLICKED, FALSE, 0, S_OK, msg_send_click_ok }, + { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL }, + { 0 } +}; + +static const struct message_info msg_check_verification[] = +{ + { TDM_CLICK_VERIFICATION, TRUE, 0 }, + { 0 } +}; + +static const struct message_info msg_return_verification_checked[] = +{ + { TDN_CREATED, 0, 0, S_OK, msg_check_verification }, + { TDN_VERIFICATION_CLICKED, TRUE, 0, S_OK, msg_send_click_ok }, + { TDN_BUTTON_CLICKED, IDOK, 0, S_OK, NULL }, + { 0 } +}; + static void init_test_message(UINT message, WPARAM wParam, LPARAM lParam, struct message *msg) { msg->message = WM_TD_CALLBACK; @@ -239,17 +281,18 @@ static void init_test_message(UINT message, WPARAM wParam, LPARAM lParam, struct msg->stage = 0; }
-#define run_test(info, expect_button, expect_radio_button, seq, context) \ - run_test_(info, expect_button, expect_radio_button, seq, context, \ - ARRAY_SIZE(seq) - 1, __FILE__, __LINE__) +#define run_test(info, expect_button, expect_radio_button, verification_checked, seq, context) \ + run_test_(info, expect_button, expect_radio_button, verification_checked, seq, context, \ + ARRAY_SIZE(seq) - 1, __FILE__, __LINE__)
-static void run_test_(TASKDIALOGCONFIG *info, int expect_button, int expect_radio_button, +static void run_test_(TASKDIALOGCONFIG *info, int expect_button, int expect_radio_button, BOOL verification_checked, const struct message_info *test_messages, const char *context, int test_messages_len, const char *file, int line) { struct message *msg, *msg_start; int ret_button = 0; int ret_radio = 0; + BOOL ret_verification = FALSE; HRESULT hr; int i;
@@ -266,7 +309,7 @@ static void run_test_(TASKDIALOGCONFIG *info, int expect_button, int expect_radi current_message_info = test_messages; flush_sequences(sequences, NUM_MSG_SEQUENCES);
- hr = pTaskDialogIndirect(info, &ret_button, &ret_radio, NULL); + hr = pTaskDialogIndirect(info, &ret_button, &ret_radio, &ret_verification); ok_(file, line)(hr == S_OK, "TaskDialogIndirect() failed, got %#x.\n", hr);
ok_sequence_(sequences, TASKDIALOG_SEQ_INDEX, msg_start, context, FALSE, file, line); @@ -331,7 +374,7 @@ static void test_callback(void) info.pfCallback = taskdialog_callback_proc; info.lpCallbackData = test_ref_data;
- run_test(&info, IDOK, 0, msg_return_press_ok, "Press VK_RETURN."); + run_test(&info, IDOK, 0, FALSE, msg_return_press_ok, "Press VK_RETURN."); }
static void test_buttons(void) @@ -376,16 +419,16 @@ static void test_buttons(void) info.nDefaultButton = 0; /* Should default to first created button */ info.dwCommonButtons = TDCBF_OK_BUTTON | TDCBF_YES_BUTTON | TDCBF_NO_BUTTON | TDCBF_CANCEL_BUTTON | TDCBF_RETRY_BUTTON | TDCBF_CLOSE_BUTTON; - run_test(&info, IDOK, 0, msg_return_press_ok, "default button: unset default"); + run_test(&info, IDOK, 0, FALSE, msg_return_press_ok, "default button: unset default"); info.dwCommonButtons = TDCBF_YES_BUTTON | TDCBF_NO_BUTTON | TDCBF_CANCEL_BUTTON | TDCBF_RETRY_BUTTON | TDCBF_CLOSE_BUTTON; - run_test(&info, IDYES, 0, msg_return_press_yes, "default button: unset default"); + run_test(&info, IDYES, 0, FALSE, msg_return_press_yes, "default button: unset default"); info.dwCommonButtons = TDCBF_NO_BUTTON | TDCBF_CANCEL_BUTTON | TDCBF_RETRY_BUTTON | TDCBF_CLOSE_BUTTON; - run_test(&info, IDNO, 0, msg_return_press_no, "default button: unset default"); + run_test(&info, IDNO, 0, FALSE, msg_return_press_no, "default button: unset default"); info.dwCommonButtons = TDCBF_CANCEL_BUTTON | TDCBF_RETRY_BUTTON | TDCBF_CLOSE_BUTTON; - run_test(&info, IDRETRY, 0, msg_return_press_retry, "default button: unset default"); + run_test(&info, IDRETRY, 0, FALSE, msg_return_press_retry, "default button: unset default"); info.dwCommonButtons = TDCBF_CANCEL_BUTTON | TDCBF_CLOSE_BUTTON; - run_test(&info, IDCANCEL, 0, msg_return_press_cancel, "default button: unset default"); + run_test(&info, IDCANCEL, 0, FALSE, msg_return_press_cancel, "default button: unset default");
/* Custom buttons could be command links */ for (i = 0; i < ARRAY_SIZE(command_link_flags); i++) @@ -396,30 +439,30 @@ static void test_buttons(void) info.nDefaultButton = 0xff; /* Random ID, should also default to first created button */ info.cButtons = TEST_NUM_BUTTONS; info.pButtons = custom_buttons; - run_test(&info, ID_START_BUTTON, 0, msg_return_press_custom1, + run_test(&info, ID_START_BUTTON, 0, FALSE, msg_return_press_custom1, "default button: invalid default, with common buttons - 1");
info.nDefaultButton = -1; /* Should work despite button ID -1 */ - run_test(&info, -1, 0, msg_return_press_custom10, "default button: invalid default, with common buttons - 2"); + run_test(&info, -1, 0, FALSE, msg_return_press_custom10, "default button: invalid default, with common buttons - 2");
info.nDefaultButton = -2; /* Should also default to first created button */ - run_test(&info, ID_START_BUTTON, 0, msg_return_press_custom1, + run_test(&info, ID_START_BUTTON, 0, FALSE, msg_return_press_custom1, "default button: invalid default, with common buttons - 3");
/* Test with only custom buttons and invalid default ID */ info.dwCommonButtons = 0; - run_test(&info, ID_START_BUTTON, 0, msg_return_press_custom1, + run_test(&info, ID_START_BUTTON, 0, FALSE, msg_return_press_custom1, "default button: invalid default, no common buttons");
/* Test with common and custom buttons and valid default ID */ info.dwCommonButtons = TDCBF_OK_BUTTON | TDCBF_YES_BUTTON | TDCBF_NO_BUTTON | TDCBF_CANCEL_BUTTON | TDCBF_RETRY_BUTTON | TDCBF_CLOSE_BUTTON; info.nDefaultButton = IDRETRY; - run_test(&info, IDRETRY, 0, msg_return_press_retry, "default button: valid default - 1"); + run_test(&info, IDRETRY, 0, FALSE, msg_return_press_retry, "default button: valid default - 1");
/* Test with common and custom buttons and valid default ID */ info.nDefaultButton = ID_START_BUTTON + 3; - run_test(&info, ID_START_BUTTON + 3, 0, msg_return_press_custom4, "default button: valid default - 2"); + run_test(&info, ID_START_BUTTON + 3, 0, FALSE, msg_return_press_custom4, "default button: valid default - 2"); }
/* Test radio buttons */ @@ -431,44 +474,52 @@ static void test_buttons(void) info.pRadioButtons = radio_buttons;
/* Test default first radio button */ - run_test(&info, IDOK, ID_START_RADIO_BUTTON, msg_return_default_radio_button_1, "default radio button: default first radio button"); + run_test(&info, IDOK, ID_START_RADIO_BUTTON, FALSE, msg_return_default_radio_button_1, + "default radio button: default first radio button");
/* Test default radio button */ info.nDefaultRadioButton = ID_START_RADIO_BUTTON + 1; - run_test(&info, IDOK, info.nDefaultRadioButton, msg_return_default_radio_button_2, "default radio button: default radio button"); + run_test(&info, IDOK, info.nDefaultRadioButton, FALSE, msg_return_default_radio_button_2, + "default radio button: default radio button");
/* Test default radio button with -2 */ info.nDefaultRadioButton = -2; - run_test(&info, IDOK, info.nDefaultRadioButton, msg_return_default_radio_button_3, "default radio button: default radio button with id -2"); + run_test(&info, IDOK, info.nDefaultRadioButton, FALSE, msg_return_default_radio_button_3, + "default radio button: default radio button with id -2");
/* Test default radio button after clicking the first, messages still work even radio button is disabled */ info.nDefaultRadioButton = ID_START_RADIO_BUTTON + 1; - run_test(&info, IDOK, ID_START_RADIO_BUTTON, msg_return_first_radio_button, "default radio button: radio button after clicking"); + run_test(&info, IDOK, ID_START_RADIO_BUTTON, FALSE, msg_return_first_radio_button, + "default radio button: radio button after clicking");
/* Test radio button after disabling and clicking the first */ info.nDefaultRadioButton = ID_START_RADIO_BUTTON + 1; - run_test(&info, IDOK, ID_START_RADIO_BUTTON, msg_return_default_radio_button_clicking_disabled, "default radio button: disable radio button before clicking"); + run_test(&info, IDOK, ID_START_RADIO_BUTTON, FALSE, msg_return_default_radio_button_clicking_disabled, + "default radio button: disable radio button before clicking");
/* Test no default radio button, TDF_NO_DEFAULT_RADIO_BUTTON is set, TDN_RADIO_BUTTON_CLICKED will still be received, just radio button not selected */ info.nDefaultRadioButton = ID_START_RADIO_BUTTON; info.dwFlags = TDF_NO_DEFAULT_RADIO_BUTTON; - run_test(&info, IDOK, info.nDefaultRadioButton, msg_return_no_default_radio_button_flag, "default radio button: no default radio flag"); + run_test(&info, IDOK, info.nDefaultRadioButton, FALSE, msg_return_no_default_radio_button_flag, + "default radio button: no default radio flag");
/* Test no default radio button, TDF_NO_DEFAULT_RADIO_BUTTON is set and nDefaultRadioButton is 0. * TDN_RADIO_BUTTON_CLICKED will not be sent, and just radio button not selected */ info.nDefaultRadioButton = 0; info.dwFlags = TDF_NO_DEFAULT_RADIO_BUTTON; - run_test(&info, IDOK, 0, msg_return_no_default_radio_button_id_and_flag, "default radio button: no default radio id and flag"); + run_test(&info, IDOK, 0, FALSE, msg_return_no_default_radio_button_id_and_flag, + "default radio button: no default radio id and flag");
/* Test no default radio button, TDF_NO_DEFAULT_RADIO_BUTTON is set and nDefaultRadioButton is invalid. * TDN_RADIO_BUTTON_CLICKED will not be sent, and just radio button not selected */ info.nDefaultRadioButton = 0xff; info.dwFlags = TDF_NO_DEFAULT_RADIO_BUTTON; - run_test(&info, IDOK, 0, msg_return_no_default_radio_button_id_and_flag, "default radio button: no default flag, invalid id"); + run_test(&info, IDOK, 0, FALSE, msg_return_no_default_radio_button_id_and_flag, + "default radio button: no default flag, invalid id");
info.nDefaultRadioButton = 0; info.dwFlags = TDF_NO_DEFAULT_RADIO_BUTTON; - run_test(&info, IDOK, -2, msg_return_press_negative_id_radio_button, + run_test(&info, IDOK, -2, FALSE, msg_return_press_negative_id_radio_button, "radio button: manually click radio button with negative id"); }
@@ -481,7 +532,7 @@ static void test_help(void) info.lpCallbackData = test_ref_data; info.dwCommonButtons = TDCBF_OK_BUTTON;
- run_test(&info, IDOK, 0, msg_got_tdn_help, "send f1"); + run_test(&info, IDOK, 0, FALSE, msg_got_tdn_help, "send f1"); }
struct timer_notification_data @@ -601,6 +652,30 @@ static void test_progress_bar(void) pTaskDialogIndirect(&info, NULL, NULL, NULL); }
+static void test_verification_box(void) +{ + TASKDIALOGCONFIG info = {0}; + WCHAR textW[] = {'t', 'e', 'x', 't', 0}; + + info.cbSize = sizeof(TASKDIALOGCONFIG); + info.pfCallback = taskdialog_callback_proc; + info.lpCallbackData = test_ref_data; + info.dwCommonButtons = TDCBF_OK_BUTTON; + info.pszVerificationText = textW; + + run_test(&info, IDOK, 0, FALSE, msg_return_default_verification_unchecked, "default verification box: unchecked"); + + info.dwFlags = TDF_VERIFICATION_FLAG_CHECKED; + run_test(&info, IDOK, 0, FALSE, msg_return_default_verification_checked, "default verification box: checked"); + + run_test(&info, IDOK, 0, FALSE, msg_return_verification_unchecked, + "default verification box: default checked and then unchecked"); + + info.dwFlags = 0; + run_test(&info, IDOK, 0, FALSE, msg_return_verification_checked, + "default verification box: default unchecked and then checked"); +} + START_TEST(taskdialog) { ULONG_PTR ctx_cookie; @@ -640,6 +715,7 @@ START_TEST(taskdialog) test_help(); test_timer(); test_progress_bar(); + test_verification_box();
unload_v6_module(ctx_cookie, hCtx); }
On 07/16/2018 10:39 AM, Zhiyi Zhang wrote:
- if (hwnd == dialog_info->verification_box)
- {
dialog_info->verification_checked = !dialog_info->verification_checked;
taskdialog_notify(dialog_info, TDN_VERIFICATION_CLICKED, dialog_info->verification_checked, 0);
return;
- }
Is it necessary to keep this in sync? That probably depends on how
TDF_VERIFICATION_FLAG_CHECKED works if pszVerificationText is not set. Do we have a test for that?
On Wed 7 18 18:05, Nikolay Sivov wrote:
On 07/16/2018 10:39 AM, Zhiyi Zhang wrote:
+ if (hwnd == dialog_info->verification_box) + { + dialog_info->verification_checked = !dialog_info->verification_checked; + taskdialog_notify(dialog_info, TDN_VERIFICATION_CLICKED, dialog_info->verification_checked, 0); + return; + }
Is it necessary to keep this in sync? That probably depends on how
No. we can get it via BM_GETCHECK. It's just that using BM_GETCHECK would require syncing the check state at every verification button clicks and in WM_DESTROY. syncing the state to dialog_info->verification_checked seems easier and more efficient. And since we have to have an verification_checked variable anyway, it doesn't require extra space.
TDF_VERIFICATION_FLAG_CHECKED works if pszVerificationText is not set. Do we have a test for that?
TDF_VERIFICATION_FLAG_CHECKED works even if pszVerificationText is not set. I'll add a test for that. I think I'll stick to syncing the check state, otherwise we also have to check if verification box is valid before syncing the state.
On 07/18/2018 02:30 PM, Zhiyi Zhang wrote:
On Wed 7 18 18:05, Nikolay Sivov wrote:
On 07/16/2018 10:39 AM, Zhiyi Zhang wrote:
+ if (hwnd == dialog_info->verification_box) + { + dialog_info->verification_checked = !dialog_info->verification_checked; + taskdialog_notify(dialog_info, TDN_VERIFICATION_CLICKED, dialog_info->verification_checked, 0); + return; + }
Is it necessary to keep this in sync? That probably depends on how
No. we can get it via BM_GETCHECK. It's just that using BM_GETCHECK would require syncing the check state at every verification button clicks and in WM_DESTROY. syncing the state to dialog_info->verification_checked seems easier and more efficient. And since we have to have an verification_checked variable anyway, it doesn't require extra space.
TDF_VERIFICATION_FLAG_CHECKED works if pszVerificationText is not set. Do we have a test for that?
TDF_VERIFICATION_FLAG_CHECKED works even if pszVerificationText is not set. I'll add a test for that. I think I'll stick to syncing the check state, otherwise we also have to check if verification box is valid before syncing the state.
Yes, in this case the way you did it with a separate flag is better.