comctl32/taskdialog: Support dynamic layout.

Support dynamic layout since some controls might change
their own layout after creation.

Signed-off-by: Zhiyi Zhang <zzhang@codeweavers.com>
Signed-off-by: Nikolay Sivov <nsivov@codeweavers.com>
Signed-off-by: Alexandre Julliard <julliard@winehq.org>
This commit is contained in:
Zhiyi Zhang 2018-06-12 12:10:32 +08:00 committed by Alexandre Julliard
parent dd90ed02d9
commit df2e5f3975
1 changed files with 237 additions and 181 deletions

View File

@ -66,21 +66,14 @@ struct taskdialog_button_desc
{
int id;
const WCHAR *text;
unsigned int width;
unsigned int line;
HINSTANCE hinst;
};
struct taskdialog_template_desc
{
const TASKDIALOGCONFIG *taskconfig;
unsigned int dialog_height;
unsigned int dialog_width;
struct list controls;
WORD control_count;
LONG x_baseunit;
LONG y_baseunit;
HFONT font;
struct taskdialog_button_desc *default_button;
};
@ -89,23 +82,30 @@ struct taskdialog_info
HWND hwnd;
const TASKDIALOGCONFIG *taskconfig;
DWORD last_timer_tick;
HFONT font;
HFONT main_instruction_font;
/* Dialog metrics */
struct
{
LONG x_baseunit;
LONG y_baseunit;
LONG h_spacing;
LONG v_spacing;
} m;
};
static void pixels_to_dialogunits(const struct taskdialog_template_desc *desc, LONG *width, LONG *height)
struct button_layout_info
{
if (width)
*width = MulDiv(*width, 4, desc->x_baseunit);
if (height)
*height = MulDiv(*height, 8, desc->y_baseunit);
}
INT id;
HWND hwnd;
LONG width;
LONG line;
};
static void dialogunits_to_pixels(const struct taskdialog_template_desc *desc, LONG *width, LONG *height)
static void taskdialog_du_to_px(struct taskdialog_info *dialog_info, LONG *width, LONG *height)
{
if (width)
*width = MulDiv(*width, desc->x_baseunit, 4);
if (height)
*height = MulDiv(*height, desc->y_baseunit, 8);
if (width) *width = MulDiv(*width, dialog_info->m.x_baseunit, 4);
if (height) *height = MulDiv(*height, dialog_info->m.y_baseunit, 8);
}
static void template_write_data(char **ptr, const void *src, unsigned int size)
@ -132,49 +132,8 @@ static void taskdialog_set_main_instruction_font(struct taskdialog_info *dialog_
SendMessageW(hwnd, WM_SETFONT, (WPARAM)dialog_info->main_instruction_font, TRUE);
}
/* 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, DWORD style, short x, short y, short cx, short cy)
HINSTANCE hInstance, const WCHAR *text, DWORD style)
{
struct taskdialog_control *control = Alloc(sizeof(*control));
unsigned int size, class_size, text_size;
@ -203,10 +162,6 @@ static unsigned int taskdialog_add_control(struct taskdialog_template_desc *desc
template->style = WS_VISIBLE | style;
template->dwExtendedStyle = 0;
template->x = x;
template->y = y;
template->cx = cx;
template->cy = cy;
template->id = id;
ptr = (char *)(template + 1);
template_write_data(&ptr, class, class_size);
@ -221,17 +176,11 @@ 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)
{
unsigned int size;
SIZE sz;
if (!str)
return 0;
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, 0, DIALOG_SPACING,
desc->dialog_height, sz.cx, sz.cy);
desc->dialog_height += sz.cy + DIALOG_SPACING;
size = taskdialog_add_control(desc, id, WC_STATICW, desc->taskconfig->hInstance, str, 0);
return size;
}
@ -248,14 +197,8 @@ static unsigned int taskdialog_add_content(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;
if (id == desc->taskconfig->nDefaultButton)
@ -290,8 +233,7 @@ static void taskdialog_init_common_buttons(struct taskdialog_template_desc *desc
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;
unsigned int count = 0, buttons_size, i, size = 0;
const TASKDIALOGCONFIG *taskconfig = desc->taskconfig;
struct taskdialog_button_desc *buttons;
@ -319,78 +261,13 @@ static unsigned int taskdialog_add_buttons(struct taskdialog_template_desc *desc
if (!desc->default_button)
desc->default_button = &buttons[0];
/* 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;
/* create all buttons */
for (i = 0; i < count; i++)
{
DWORD style = &buttons[i] == desc->default_button ? BS_DEFPUSHBUTTON : BS_PUSHBUTTON;
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, style,
location_x, desc->dialog_height, buttons[i].width, DIALOG_BUTTON_HEIGHT);
location_x += buttons[i].width + DIALOG_SPACING;
size += taskdialog_add_control(desc, buttons[i].id, WC_BUTTONW, buttons[i].hinst, buttons[i].text, style);
}
/* Add height for last row and spacing */
desc->dialog_height += DIALOG_BUTTON_HEIGHT + DIALOG_SPACING;
Free(line_widths);
Free(buttons);
return size;
@ -408,25 +285,20 @@ static void taskdialog_clear_controls(struct list *controls)
}
}
static unsigned int taskdialog_get_reference_rect(const struct taskdialog_template_desc *desc, RECT *ret)
static unsigned int taskdialog_get_reference_rect(const TASKDIALOGCONFIG *taskconfig, RECT *ret)
{
HMONITOR monitor = MonitorFromWindow(desc->taskconfig->hwndParent ? desc->taskconfig->hwndParent : GetActiveWindow(),
MONITOR_DEFAULTTOPRIMARY);
HMONITOR monitor = MonitorFromWindow(taskconfig->hwndParent ? taskconfig->hwndParent : GetActiveWindow(),
MONITOR_DEFAULTTOPRIMARY);
MONITORINFO info;
info.cbSize = sizeof(info);
GetMonitorInfoW(monitor, &info);
if (desc->taskconfig->dwFlags & TDF_POSITION_RELATIVE_TO_WINDOW && desc->taskconfig->hwndParent)
GetWindowRect(desc->taskconfig->hwndParent, ret);
if (taskconfig->dwFlags & TDF_POSITION_RELATIVE_TO_WINDOW && taskconfig->hwndParent)
GetWindowRect(taskconfig->hwndParent, ret);
else
*ret = info.rcWork;
pixels_to_dialogunits(desc, &ret->left, &ret->top);
pixels_to_dialogunits(desc, &ret->right, &ret->bottom);
pixels_to_dialogunits(desc, &info.rcWork.left, &info.rcWork.top);
pixels_to_dialogunits(desc, &info.rcWork.right, &info.rcWork.bottom);
return info.rcWork.right - info.rcWork.left;
}
@ -447,17 +319,14 @@ static WCHAR *taskdialog_get_exe_name(WCHAR *name, DWORD length)
static DLGTEMPLATE *create_taskdialog_template(const TASKDIALOGCONFIG *taskconfig)
{
struct taskdialog_control *control, *control2;
unsigned int size, title_size, screen_width;
unsigned int size, title_size;
struct taskdialog_template_desc desc;
static const WORD fontsize = 0x7fff;
static const WCHAR emptyW[] = { 0 };
const WCHAR *titleW = NULL;
DLGTEMPLATE *template;
NONCLIENTMETRICSW ncm;
WCHAR pathW[MAX_PATH];
RECT ref_rect;
char *ptr;
HDC hdc;
/* Window title */
if (!taskconfig->pszWindowTitle)
@ -480,21 +349,6 @@ static DLGTEMPLATE *create_taskdialog_template(const TASKDIALOGCONFIG *taskconfi
list_init(&desc.controls);
desc.taskconfig = taskconfig;
desc.control_count = 0;
ncm.cbSize = sizeof(ncm);
SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0);
desc.font = CreateFontIndirectW(&ncm.lfMessageFont);
hdc = GetDC(0);
SelectObject(hdc, desc.font);
desc.x_baseunit = GdiGetCharDimensions(hdc, NULL, &desc.y_baseunit);
ReleaseDC(0, hdc);
screen_width = taskdialog_get_reference_rect(&desc, &ref_rect);
desc.dialog_height = 0;
desc.dialog_width = max(taskconfig->cxWidth, DIALOG_MIN_WIDTH);
desc.dialog_width = min(desc.dialog_width, screen_width);
desc.default_button = NULL;
size += taskdialog_add_main_instruction(&desc);
@ -505,7 +359,6 @@ static DLGTEMPLATE *create_taskdialog_template(const TASKDIALOGCONFIG *taskconfi
if (!template)
{
taskdialog_clear_controls(&desc.controls);
DeleteObject(desc.font);
return NULL;
}
@ -514,10 +367,6 @@ static DLGTEMPLATE *create_taskdialog_template(const TASKDIALOGCONFIG *taskconfi
if (!(taskconfig->dwFlags & TDF_NO_SET_FOREGROUND)) template->style |= DS_SETFOREGROUND;
if (taskconfig->dwFlags & TDF_RTL_LAYOUT) template->dwExtendedStyle = WS_EX_LAYOUTRTL | WS_EX_RIGHT | WS_EX_RTLREADING;
template->cdit = desc.control_count;
template->x = (ref_rect.left + ref_rect.right + desc.dialog_width) / 2;
template->y = (ref_rect.top + ref_rect.bottom + desc.dialog_height) / 2;
template->cx = desc.dialog_width;
template->cy = desc.dialog_height;
ptr = (char *)(template + 1);
ptr += 2; /* menu */
@ -538,7 +387,6 @@ static DLGTEMPLATE *create_taskdialog_template(const TASKDIALOGCONFIG *taskconfi
Free(control);
}
DeleteObject(desc.font);
return template;
}
@ -556,13 +404,219 @@ static void taskdialog_on_button_click(struct taskdialog_info *dialog_info, WORD
EndDialog(dialog_info->hwnd, command_id);
}
static void taskdialog_get_label_size(struct taskdialog_info *dialog_info, HWND hwnd, LONG max_width, SIZE *size)
{
DWORD style = DT_EXPANDTABS | DT_CALCRECT | DT_WORDBREAK;
HFONT hfont, old_hfont;
HDC hdc;
RECT rect = {0};
WCHAR text[1024];
INT text_length;
if (dialog_info->taskconfig->dwFlags & TDF_RTL_LAYOUT)
style |= DT_RIGHT | DT_RTLREADING;
else
style |= DT_LEFT;
hfont = (HFONT)SendMessageW(hwnd, WM_GETFONT, 0, 0);
text_length = GetWindowTextW(hwnd, text, ARRAY_SIZE(text));
hdc = GetDC(hwnd);
old_hfont = SelectObject(hdc, hfont);
rect.right = max_width;
size->cy = DrawTextW(hdc, text, text_length, &rect, style);
size->cx = min(max_width, rect.right - rect.left);
if (old_hfont) SelectObject(hdc, old_hfont);
ReleaseDC(hwnd, hdc);
}
static void taskdialog_label_layout(struct taskdialog_info *dialog_info, HWND hwnd, INT start_x, LONG dialog_width,
LONG *dialog_height)
{
LONG x, y, max_width;
SIZE size;
if (!hwnd) return;
x = start_x + dialog_info->m.h_spacing;
y = *dialog_height + dialog_info->m.v_spacing;
max_width = dialog_width - x - dialog_info->m.h_spacing;
taskdialog_get_label_size(dialog_info, hwnd, max_width, &size);
SetWindowPos(hwnd, 0, x, y, size.cx, size.cy, SWP_NOZORDER);
*dialog_height = y + size.cy;
}
static void taskdialog_layout(struct taskdialog_info *dialog_info)
{
const TASKDIALOGCONFIG *taskconfig = dialog_info->taskconfig;
DWORD flags = taskconfig->dwCommonButtons;
static BOOL first_time = TRUE;
HWND hwnd;
RECT ref_rect;
LONG screen_width, dialog_width, dialog_height = 0;
LONG h_spacing, v_spacing;
INT button_max_count, button_count = 0;
struct button_layout_info *button_layout_infos;
LONG button_min_width, button_height;
LONG *line_widths, line_count, align;
LONG x, y;
SIZE size;
INT i;
screen_width = taskdialog_get_reference_rect(dialog_info->taskconfig, &ref_rect);
dialog_width = max(taskconfig->cxWidth, DIALOG_MIN_WIDTH);
taskdialog_du_to_px(dialog_info, &dialog_width, 0);
dialog_width = min(dialog_width, screen_width);
h_spacing = dialog_info->m.h_spacing;
v_spacing = dialog_info->m.v_spacing;
/* Main instruction */
hwnd = GetDlgItem(dialog_info->hwnd, ID_MAIN_INSTRUCTION);
taskdialog_label_layout(dialog_info, hwnd, 0, dialog_width, &dialog_height);
/* Content */
hwnd = GetDlgItem(dialog_info->hwnd, ID_CONTENT);
taskdialog_label_layout(dialog_info, hwnd, 0, dialog_width, &dialog_height);
/* Common and custom buttons */
/* Allocate enough memory for the custom and the default buttons. Maximum 6 default buttons possible. */
button_max_count = 6;
/* Custom buttons */
if (taskconfig->cButtons && taskconfig->pButtons) button_max_count += taskconfig->cButtons;
button_layout_infos = Alloc(button_max_count * sizeof(*button_layout_infos));
line_widths = Alloc(button_max_count * sizeof(*line_widths));
if (taskconfig->cButtons && taskconfig->pButtons)
for (button_count = 0; button_count < taskconfig->cButtons; button_count++)
button_layout_infos[button_count].id = taskconfig->pButtons[button_count].nButtonID;
/* Ok button may be added if no button is specified in taskconfig */
if (GetDlgItem(dialog_info->hwnd, IDOK)) button_layout_infos[button_count++].id = IDOK;
if (flags & TDCBF_YES_BUTTON) button_layout_infos[button_count++].id = IDYES;
if (flags & TDCBF_NO_BUTTON) button_layout_infos[button_count++].id = IDNO;
if (flags & TDCBF_RETRY_BUTTON) button_layout_infos[button_count++].id = IDRETRY;
if (flags & TDCBF_CANCEL_BUTTON) button_layout_infos[button_count++].id = IDCANCEL;
if (flags & TDCBF_CLOSE_BUTTON) button_layout_infos[button_count++].id = IDCLOSE;
button_min_width = DIALOG_BUTTON_WIDTH;
button_height = DIALOG_BUTTON_HEIGHT;
taskdialog_du_to_px(dialog_info, &button_min_width, &button_height);
for (i = 0; i < button_count; i++)
{
button_layout_infos[i].hwnd = GetDlgItem(dialog_info->hwnd, button_layout_infos[i].id);
taskdialog_get_label_size(dialog_info, button_layout_infos[i].hwnd, dialog_width - h_spacing * 2, &size);
button_layout_infos[i].width = max(size.cx, button_min_width);
}
/* Separate buttons into lines */
x = h_spacing;
for (i = 0, line_count = 0; i < button_count; i++)
{
if (x + button_layout_infos[i].width + h_spacing >= dialog_width)
{
x = h_spacing;
line_count++;
}
button_layout_infos[i].line = line_count;
x += button_layout_infos[i].width + h_spacing;
line_widths[line_count] += button_layout_infos[i].width + h_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 < button_count; j++)
if (button_layout_infos[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 * button_layout_infos[last_button].width + line_widths[i] - line_widths[i - 1]);
if (diff_changed < diff_now)
{
button_layout_infos[last_button].line = i;
line_widths[i] += button_layout_infos[last_button].width;
line_widths[i - 1] -= button_layout_infos[last_button].width;
}
}
/* Calculate left alignment so all lines are as far right as possible. */
align = dialog_width - h_spacing;
for (i = 0; i < line_count; i++)
{
int new_alignment = dialog_width - line_widths[i];
if (new_alignment < align) align = new_alignment;
}
/* Now that we got them all positioned, move all buttons */
x = align;
size.cy = button_height;
for (i = 0; i < button_count; i++)
{
/* New line */
if (i > 0 && button_layout_infos[i].line != button_layout_infos[i - 1].line)
{
x = align;
dialog_height += size.cy + v_spacing;
}
y = dialog_height + v_spacing;
size.cx = button_layout_infos[i].width;
SetWindowPos(button_layout_infos[i].hwnd, 0, x, y, size.cx, size.cy, SWP_NOZORDER);
x += button_layout_infos[i].width + h_spacing;
}
/* Add height for last row button and spacing */
dialog_height += size.cy + v_spacing;
Free(button_layout_infos);
Free(line_widths);
/* Add height for spacing, title height and frame height */
dialog_height += v_spacing;
dialog_height += GetSystemMetrics(SM_CYCAPTION);
dialog_height += GetSystemMetrics(SM_CXDLGFRAME);
if (first_time)
{
x = (ref_rect.left + ref_rect.right + dialog_width) / 2;
y = (ref_rect.top + ref_rect.bottom + dialog_height) / 2;
SetWindowPos(dialog_info->hwnd, 0, x, y, dialog_width, dialog_height, SWP_NOZORDER);
first_time = FALSE;
}
else
SetWindowPos(dialog_info->hwnd, 0, 0, 0, dialog_width, dialog_height, SWP_NOMOVE | SWP_NOZORDER);
}
static void taskdialog_init(struct taskdialog_info *dialog_info, HWND hwnd)
{
const TASKDIALOGCONFIG *taskconfig = dialog_info->taskconfig;
NONCLIENTMETRICSW ncm;
HDC hdc;
ncm.cbSize = sizeof(ncm);
SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0);
memset(dialog_info, 0, sizeof(*dialog_info));
dialog_info->taskconfig = taskconfig;
dialog_info->hwnd = hwnd;
dialog_info->font = CreateFontIndirectW(&ncm.lfMessageFont);
hdc = GetDC(dialog_info->hwnd);
SelectObject(hdc, dialog_info->font);
dialog_info->m.x_baseunit = GdiGetCharDimensions(hdc, NULL, &dialog_info->m.y_baseunit);
ReleaseDC(dialog_info->hwnd, hdc);
dialog_info->m.h_spacing = DIALOG_SPACING;
dialog_info->m.v_spacing = DIALOG_SPACING;
taskdialog_du_to_px(dialog_info, &dialog_info->m.h_spacing, &dialog_info->m.v_spacing);
if (taskconfig->dwFlags & TDF_CALLBACK_TIMER)
{
@ -571,11 +625,13 @@ static void taskdialog_init(struct taskdialog_info *dialog_info, HWND hwnd)
}
taskdialog_set_main_instruction_font(dialog_info);
taskdialog_layout(dialog_info);
}
static void taskdialog_destroy(struct taskdialog_info *dialog_info)
{
if (dialog_info->taskconfig->dwFlags & TDF_CALLBACK_TIMER) KillTimer(dialog_info->hwnd, ID_TIMER);
if (dialog_info->font) DeleteObject(dialog_info->font);
if (dialog_info->main_instruction_font) DeleteObject(dialog_info->main_instruction_font);
}