comctl32/taskdialog: Added support for custom buttons.

Signed-off-by: Nikolay Sivov <nsivov@codeweavers.com>
Signed-off-by: Alexandre Julliard <julliard@winehq.org>
oldstable
Fabian Maurer 2017-06-11 22:11:46 +03:00 committed by Alexandre Julliard
parent df07533a80
commit 5b06498b93
1 changed files with 192 additions and 57 deletions

View File

@ -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;
if (flags & TDCBF_OK_BUTTON)
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)