/* * Task dialog control * * Copyright 2017 Fabian Maurer * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA * */ #include #include #define NONAMELESSUNION #include "windef.h" #include "winbase.h" #include "wingdi.h" #include "winuser.h" #include "commctrl.h" #include "winerror.h" #include "comctl32.h" #include "wine/debug.h" #include "wine/list.h" #include "wine/unicode.h" WINE_DEFAULT_DEBUG_CHANNEL(taskdialog); #define ALIGNED_LENGTH(_Len, _Align) (((_Len)+(_Align))&~(_Align)) #define ALIGNED_POINTER(_Ptr, _Align) ((LPVOID)ALIGNED_LENGTH((ULONG_PTR)(_Ptr), _Align)) #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_SPACING = 5; static const UINT DIALOG_BUTTON_WIDTH = 50; static const UINT DIALOG_BUTTON_HEIGHT = 14; static const UINT ID_MAIN_INSTRUCTION = 0xf000; static const UINT ID_CONTENT = 0xf001; struct taskdialog_control { struct list entry; DLGITEMTEMPLATE *template; unsigned int template_size; }; 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; }; static void pixels_to_dialogunits(const struct taskdialog_template_desc *desc, LONG *width, LONG *height) { if (width) *width = MulDiv(*width, 4, desc->x_baseunit); if (height) *height = MulDiv(*height, 8, desc->y_baseunit); } static void dialogunits_to_pixels(const struct taskdialog_template_desc *desc, LONG *width, LONG *height) { if (width) *width = MulDiv(*width, desc->x_baseunit, 4); if (height) *height = MulDiv(*height, desc->y_baseunit, 8); } static void template_write_data(char **ptr, const void *src, unsigned int size) { memcpy(*ptr, src, size); *ptr += size; } 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) { struct taskdialog_control *control = Alloc(sizeof(*control)); unsigned int size, class_size, text_size; DLGITEMTEMPLATE *template; static const WCHAR nulW; const WCHAR *textW; char *ptr; class_size = (strlenW(class) + 1) * sizeof(WCHAR); if (IS_INTRESOURCE(text)) text_size = LoadStringW(hInstance, (UINT_PTR)text, (WCHAR *)&textW, 0) * sizeof(WCHAR); else { textW = text; text_size = strlenW(textW) * sizeof(WCHAR); } size = sizeof(DLGITEMTEMPLATE); size += class_size; size += text_size + sizeof(WCHAR); size += sizeof(WORD); /* creation data */ control->template = template = Alloc(size); control->template_size = size; template->style = WS_VISIBLE; 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); template_write_data(&ptr, textW, text_size); template_write_data(&ptr, &nulW, sizeof(nulW)); list_add_tail(&desc->controls, &control->entry); desc->control_count++; return ALIGNED_LENGTH(size, 3); } 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; 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); 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; return size; } static unsigned int taskdialog_add_main_instruction(struct taskdialog_template_desc *desc) { return taskdialog_add_static_label(desc, ID_MAIN_INSTRUCTION, desc->taskconfig->pszMainInstruction); } 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) { 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) \ 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; \ } 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; return size; } static void taskdialog_clear_controls(struct list *controls) { struct taskdialog_control *control, *control2; LIST_FOR_EACH_ENTRY_SAFE(control, control2, controls, struct taskdialog_control, entry) { list_remove(&control->entry); Free(control->template); Free(control); } } static unsigned int taskdialog_get_reference_rect(const struct taskdialog_template_desc *desc, RECT *ret) { HMONITOR monitor = MonitorFromWindow(desc->taskconfig->hwndParent ? desc->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); 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; } static DLGTEMPLATE *create_taskdialog_template(const TASKDIALOGCONFIG *taskconfig) { struct taskdialog_control *control, *control2; unsigned int size, title_size, screen_width; struct taskdialog_template_desc desc; static const WORD fontsize = 0x7fff; static const WCHAR emptyW[] = { 0 }; const WCHAR *titleW = NULL; DLGTEMPLATE *template; NONCLIENTMETRICSW ncm; RECT ref_rect; char *ptr; HDC hdc; /* Window title */ if (!taskconfig->pszWindowTitle) FIXME("use executable name for window title\n"); else if (IS_INTRESOURCE(taskconfig->pszWindowTitle)) FIXME("load window title from resources\n"); else titleW = taskconfig->pszWindowTitle; if (!titleW) titleW = emptyW; title_size = (strlenW(titleW) + 1) * sizeof(WCHAR); size = sizeof(DLGTEMPLATE) + 2 * sizeof(WORD); size += title_size; size += 2; /* font size */ 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); size += taskdialog_add_main_instruction(&desc); size += taskdialog_add_content(&desc); size += taskdialog_add_common_buttons(&desc); template = Alloc(size); if (!template) { taskdialog_clear_controls(&desc.controls); DeleteObject(desc.font); return NULL; } template->style = DS_MODALFRAME | DS_SETFONT | WS_CAPTION | WS_VISIBLE | WS_SYSMENU; 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 */ ptr += 2; /* class */ template_write_data(&ptr, titleW, title_size); template_write_data(&ptr, &fontsize, sizeof(fontsize)); /* write control entries */ LIST_FOR_EACH_ENTRY_SAFE(control, control2, &desc.controls, struct taskdialog_control, entry) { ALIGN_POINTER(ptr, 3); template_write_data(&ptr, control->template, control->template_size); /* list item won't be needed later */ list_remove(&control->entry); Free(control->template); Free(control); } DeleteObject(desc.font); return template; } static INT_PTR CALLBACK taskdialog_proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { TRACE("hwnd=%p msg=0x%04x wparam=%lx lparam=%lx\n", hwnd, msg, wParam, lParam); switch (msg) { case WM_COMMAND: if (HIWORD(wParam) == BN_CLICKED) { WORD command_id = LOWORD(wParam); EndDialog(hwnd, command_id); return TRUE; } break; } return FALSE; } /*********************************************************************** * TaskDialogIndirect [COMCTL32.@] */ HRESULT WINAPI TaskDialogIndirect(const TASKDIALOGCONFIG *taskconfig, int *button, int *radio_button, BOOL *verification_flag_checked) { DLGTEMPLATE *template; INT ret; TRACE("%p, %p, %p, %p\n", taskconfig, button, radio_button, verification_flag_checked); template = create_taskdialog_template(taskconfig); ret = DialogBoxIndirectParamW(taskconfig->hInstance, template, taskconfig->hwndParent, taskdialog_proc, 0); Free(template); if (button) *button = ret; if (radio_button) *radio_button = taskconfig->nDefaultButton; if (verification_flag_checked) *verification_flag_checked = TRUE; return S_OK; } /*********************************************************************** * TaskDialog [COMCTL32.@] */ HRESULT WINAPI TaskDialog(HWND owner, HINSTANCE hinst, const WCHAR *title, const WCHAR *main_instruction, const WCHAR *content, TASKDIALOG_COMMON_BUTTON_FLAGS common_buttons, const WCHAR *icon, int *button) { TASKDIALOGCONFIG taskconfig; TRACE("%p, %p, %s, %s, %s, %#x, %s, %p\n", owner, hinst, debugstr_w(title), debugstr_w(main_instruction), debugstr_w(content), common_buttons, debugstr_w(icon), button); memset(&taskconfig, 0, sizeof(taskconfig)); taskconfig.cbSize = sizeof(taskconfig); taskconfig.hwndParent = owner; taskconfig.hInstance = hinst; taskconfig.dwCommonButtons = common_buttons; taskconfig.pszWindowTitle = title; taskconfig.u.pszMainIcon = icon; taskconfig.pszMainInstruction = main_instruction; taskconfig.pszContent = content; return TaskDialogIndirect(&taskconfig, button, NULL, NULL); }