From: Fabian Maurer Subject: [v4 5/5] comctl32: TaskDialog - Implement buttons Message-Id: <20170315201323.29623-5-dark.shadow4@web.de> Date: Wed, 15 Mar 2017 21:13:23 +0100 In-Reply-To: <20170315201323.29623-1-dark.shadow4@web.de> References: <20170315201323.29623-1-dark.shadow4@web.de> v3: Rewrite to implement Nikolay Sivov's suggestions v4: Rewrite to fix a bunch of issues and make patchset smaller They are properly placed but aren't working yet. Signed-off-by: Fabian Maurer --- dlls/comctl32/comctl32.h | 8 +++ dlls/comctl32/comctl32.rc | 10 +++ dlls/comctl32/taskdialog.c | 163 +++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 175 insertions(+), 6 deletions(-) diff --git a/dlls/comctl32/comctl32.h b/dlls/comctl32/comctl32.h index b9b0574427..d6d347f7e9 100644 --- a/dlls/comctl32/comctl32.h +++ b/dlls/comctl32/comctl32.h @@ -52,6 +52,14 @@ extern HBRUSH COMCTL32_hPattern55AABrush DECLSPEC_HIDDEN; #define IDS_CLOSE 4160 +/* Taskdialog */ +#define IDS_OK 1000 +#define IDS_AMP_YES 1001 +#define IDS_AMP_NO 1002 +#define IDS_AMP_RETRY 1003 +#define IDS_CANCEL 1004 +#define IDS_AMP_CLOSE 1005 + /* Toolbar customization dialog */ #define IDD_TBCUSTOMIZE 200 diff --git a/dlls/comctl32/comctl32.rc b/dlls/comctl32/comctl32.rc index 87e2fd1f6c..40d6b27d54 100644 --- a/dlls/comctl32/comctl32.rc +++ b/dlls/comctl32/comctl32.rc @@ -32,6 +32,16 @@ STRINGTABLE STRINGTABLE { + IDS_OK "OK" + IDS_AMP_YES "&Yes" + IDS_AMP_NO "&No" + IDS_AMP_RETRY "&Retry" + IDS_CANCEL "Cancel" + IDS_AMP_CLOSE "&Close" +} + +STRINGTABLE +{ IDM_TODAY "Today:" IDM_GOTODAY "Go to today" } diff --git a/dlls/comctl32/taskdialog.c b/dlls/comctl32/taskdialog.c index 72e72d5d34..ad6b2bbfb3 100644 --- a/dlls/comctl32/taskdialog.c +++ b/dlls/comctl32/taskdialog.c @@ -45,12 +45,18 @@ WINE_DEFAULT_DEBUG_CHANNEL(taskdlg); static const UINT DIALOG_DEFAULT_WIDTH = 180; static const UINT DIALOG_SPACING = 5; +static const UINT DIALOG_SPACING_BUTTONS_LEFT = 40; /* minimum distance from the left dialog border */ +static const UINT DIALOG_SPACING_BUTTON_H = 1; /* Distance between buttons */ +static const UINT DIALOG_SPACING_BUTTON_W = 4; /* Distance between buttons */ +static const UINT DIALOG_BUTTON_HEIGHT = 10; #define ID_START 0xF000 static const int ID_TEXTMAIN = ID_START + 1; static const int ID_TEXTCONTENT = ID_START + 2; +static const int MIN_SIZE_BUTTON = 30; + typedef struct { DLGITEMTEMPLATE template; @@ -81,6 +87,15 @@ typedef struct HFONT font_default, font_main; } taskdialog_info; +typedef struct +{ + int id; + const WCHAR *text; + UINT width; + UINT x; + UINT y; +}button_info; + #define MEMCPY_MOVEPTR(target, source, size) memcpy(target, source, size); target = (void *)((char *)target + size); #define ALIGN_POINTER(ptr, boundary) ptr = (void *)(((UINT_PTR)(ptr) + (boundary)) & ~(boundary)) #define STR_EMPTY(str) (str == NULL || str[0] == 0) @@ -193,7 +208,7 @@ static void controls_destroy(struct list *controls) } /* Adds a control for the TaskDialog into our list */ -static void controls_add(struct list *controls, WORD id, const WCHAR *class, const WCHAR *text, +static DLGITEMTEMPLATE* controls_add(struct list *controls, WORD id, const WCHAR *class, const WCHAR *text, DWORD style, short x, short y, short cx, short cy) { control_info *data = Alloc(sizeof(control_info)); @@ -211,6 +226,8 @@ static void controls_add(struct list *controls, WORD id, const WCHAR *class, con data->text_size = (lstrlenW(text) + 1) * sizeof(WCHAR); list_add_tail(controls, &data->entry); + + return &data->template; } /* DialogProc and helper functions */ @@ -292,6 +309,130 @@ static void taskdialog_info_destroy(taskdialog_info *dialog_info) DeleteObject(dialog_info->font_default); } +static button_info make_button(HDC hdc, UINT dialog_width, int id, const WCHAR *text) +{ + RECT rect; + button_info button; + + button.id = id; + button.text = text; + rect = text_get_rect(hdc, text, dialog_width); + button.width = rect.right + 10; + if (button.width < MIN_SIZE_BUTTON) + button.width = MIN_SIZE_BUTTON; + + return button; +} + +static UINT add_buttons(HDC hdc, const TASKDIALOGCONFIG *task_config, struct list *controls, + UINT dialog_width, UINT dialog_height) +{ + static const WCHAR class_button[] = WC_BUTTONW; /* Can't use WC_BUTTONW directy, need to store it into a static variable since it goes out of scope */ + static WCHAR text_ok [20] = {0}; + static WCHAR text_yes [20] = {0}; + static WCHAR text_no [20] = {0}; + static WCHAR text_cancel[20] = {0}; + static WCHAR text_retry [20] = {0}; + static WCHAR text_close [20] = {0}; + static BOOL text_initialized = 0; + UINT alignment = DIALOG_SPACING_BUTTONS_LEFT; /* minimum distance from the left dialog border */ + UINT location_x; + BOOL first_row = TRUE; + button_info *buttons; + int count = 0; + int i; + + if (!text_initialized) + { + text_initialized = TRUE; + LoadStringW(COMCTL32_hModule, IDS_OK, text_ok, sizeof(text_ok) / sizeof(WCHAR)); + LoadStringW(COMCTL32_hModule, IDS_AMP_YES, text_yes, sizeof(text_yes) / sizeof(WCHAR)); + LoadStringW(COMCTL32_hModule, IDS_AMP_NO, text_no, sizeof(text_no) / sizeof(WCHAR)); + LoadStringW(COMCTL32_hModule, IDS_CANCEL, text_cancel, sizeof(text_cancel) / sizeof(WCHAR)); + LoadStringW(COMCTL32_hModule, IDS_AMP_RETRY, text_retry, sizeof(text_retry) / sizeof(WCHAR)); + LoadStringW(COMCTL32_hModule, IDS_AMP_CLOSE, text_close, sizeof(text_close) / sizeof(WCHAR)); + } + + /* Allocate enough memory for the custom and the default buttons */ + if (task_config->cButtons && task_config->pButtons) + buttons = HeapAlloc(GetProcessHeap(), 0, (task_config->cButtons + 6) * sizeof(button_info)); + else + { + buttons = HeapAlloc(GetProcessHeap(), 0, 6 * sizeof(button_info)); + } + + /* Custom buttons */ + if (task_config->cButtons && task_config->pButtons) + { + for (i = 0; i < task_config->cButtons; i++) + { + buttons[count++] = make_button(hdc, dialog_width, task_config->pButtons[i].nButtonID, + task_config->pButtons[i].pszButtonText); + } + } + + /* Default buttons */ + if (task_config->dwCommonButtons & TDCBF_OK_BUTTON) + buttons[count++] = make_button(hdc, dialog_width, IDOK, text_ok); + if (task_config->dwCommonButtons & TDCBF_YES_BUTTON) + buttons[count++] = make_button(hdc, dialog_width, IDYES, text_yes); + if (task_config->dwCommonButtons & TDCBF_NO_BUTTON) + buttons[count++] = make_button(hdc, dialog_width, IDNO, text_no); + if (task_config->dwCommonButtons & TDCBF_RETRY_BUTTON) + buttons[count++] = make_button(hdc, dialog_width, IDRETRY, text_retry); + if (task_config->dwCommonButtons & TDCBF_CANCEL_BUTTON) + buttons[count++] = make_button(hdc, dialog_width, IDCANCEL, text_cancel); + if (task_config->dwCommonButtons & TDCBF_CLOSE_BUTTON) + buttons[count++] = make_button(hdc, dialog_width, IDCLOSE, text_close); + + /* There must be at least one button */ + if (!(task_config->cButtons && task_config->pButtons) && !task_config->dwCommonButtons) + buttons[count++] = make_button(hdc, dialog_width, IDOK, text_ok); + + /* Position buttons */ + location_x = alignment; + for (i = 0; i < count; i++) + { + if (location_x + buttons[i].width + DIALOG_SPACING_BUTTON_W > dialog_width) /* When beginning new row, align the first */ + { + if (first_row) + { + int diff = dialog_width - location_x; + + first_row = FALSE; + for (int j = 0; j < i; j++) /* Align first row to the right */ + buttons[j].x += diff; + alignment = buttons[0].x; /* left-align all coming rows to the first row */ + } + location_x = alignment; + dialog_height += DIALOG_BUTTON_HEIGHT + DIALOG_SPACING_BUTTON_H; + } + + buttons[i].x = location_x; + buttons[i].y = dialog_height; + + location_x += buttons[i].width + DIALOG_SPACING_BUTTON_W; + } + if (first_row) /* Always align first row to the right */ + { + int diff = dialog_width - (buttons[count - 1].x + buttons[count - 1].width + DIALOG_SPACING_BUTTON_W); + for (int i = 0; i < count; i++) + buttons[i].x += diff; + } + + /* Now that we got them all positioned, create all buttons */ + for (i = 0; i < count; i++) + { + controls_add(controls, buttons[i].id, class_button, buttons[i].text, WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, + buttons[i].x, buttons[i].y, buttons[i].width, DIALOG_BUTTON_HEIGHT); + } + + dialog_height += DIALOG_BUTTON_HEIGHT * 2; + + HeapFree(GetProcessHeap(), 0, buttons); + return dialog_height; +} + /*********************************************************************** * TaskDialogIndirect [COMCTL32.@] */ @@ -299,7 +440,6 @@ HRESULT WINAPI TaskDialogIndirect(const TASKDIALOGCONFIG *pTaskConfig, int *pnBu int *pnRadioButton, BOOL *pfVerificationFlagChecked) { static const WCHAR empty_string[] = {0}; - static const WCHAR text_ok[] = {'O','K',0}; RECT desktop; UINT dialog_width; /* In dialog units */ UINT dialog_height; /* In dialog units */ @@ -308,6 +448,7 @@ HRESULT WINAPI TaskDialogIndirect(const TASKDIALOGCONFIG *pTaskConfig, int *pnBu struct list controls; taskdialog_info dialog_info; HDC dc_dummy; + int ret; TRACE("%p, %p, %p, %p\n", pTaskConfig, pnButton, pnRadioButton, pfVerificationFlagChecked); @@ -357,9 +498,10 @@ HRESULT WINAPI TaskDialogIndirect(const TASKDIALOGCONFIG *pTaskConfig, int *pnBu dialog_height += rect.bottom; } - controls_add(&controls, IDOK, WC_BUTTONW, text_ok, - WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, dialog_width - 40 - 10, dialog_height, 40, 10); - dialog_height += DIALOG_SPACING * 2; + dialog_height += DIALOG_SPACING; + + /* Create buttons */ + dialog_height = add_buttons(dc_dummy, pTaskConfig, &controls, dialog_width, dialog_height); header.title = pTaskConfig->pszWindowTitle; if (!header.title) @@ -378,7 +520,16 @@ HRESULT WINAPI TaskDialogIndirect(const TASKDIALOGCONFIG *pTaskConfig, int *pnBu /* Turn template information into a dialog template to display it */ template_data = dialog_template_create(header, &controls); - DialogBoxIndirectParamW(pTaskConfig->hInstance, template_data, pTaskConfig->hwndParent, DialogProc, (LPARAM)&dialog_info); + ret = DialogBoxIndirectParamW(pTaskConfig->hInstance, template_data, pTaskConfig->hwndParent, DialogProc, (LPARAM)&dialog_info); + + if (pnButton) + *pnButton = ret; + + if (pnRadioButton) + *pnRadioButton = 0; + + if (pfVerificationFlagChecked) + *pfVerificationFlagChecked = FALSE; /* Cleanup */ -- 2.12.0