From df2e5f39759faaead4cc5a5e0df7d705ce145cb9 Mon Sep 17 00:00:00 2001 From: Zhiyi Zhang Date: Tue, 12 Jun 2018 12:10:32 +0800 Subject: [PATCH] comctl32/taskdialog: Support dynamic layout. Support dynamic layout since some controls might change their own layout after creation. Signed-off-by: Zhiyi Zhang Signed-off-by: Nikolay Sivov Signed-off-by: Alexandre Julliard --- dlls/comctl32/taskdialog.c | 418 +++++++++++++++++++++---------------- 1 file changed, 237 insertions(+), 181 deletions(-) diff --git a/dlls/comctl32/taskdialog.c b/dlls/comctl32/taskdialog.c index 549c7f2bf94..bd9e68b6048 100644 --- a/dlls/comctl32/taskdialog.c +++ b/dlls/comctl32/taskdialog.c @@ -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); }