1 /* 2 * Task dialog control 3 * 4 * Copyright 2017 Fabian Maurer 5 * Copyright 2018 Zhiyi Zhang 6 * 7 * This library is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU Lesser General Public 9 * License as published by the Free Software Foundation; either 10 * version 2.1 of the License, or (at your option) any later version. 11 * 12 * This library is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with this library; if not, write to the Free Software 19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 20 * 21 */ 22 23 #include <stdarg.h> 24 #include <stdlib.h> 25 #include <string.h> 26 27 #define NONAMELESSUNION 28 29 #include "windef.h" 30 #include "winbase.h" 31 #include "wingdi.h" 32 #include "winuser.h" 33 #include "commctrl.h" 34 #include "winerror.h" 35 #include "comctl32.h" 36 37 #include "wine/debug.h" 38 39 WINE_DEFAULT_DEBUG_CHANNEL(taskdialog); 40 41 static const UINT DIALOG_MIN_WIDTH = 240; 42 static const UINT DIALOG_SPACING = 5; 43 static const UINT DIALOG_BUTTON_WIDTH = 50; 44 static const UINT DIALOG_BUTTON_HEIGHT = 14; 45 static const UINT DIALOG_EXPANDO_ICON_WIDTH = 10; 46 static const UINT DIALOG_EXPANDO_ICON_HEIGHT = 10; 47 static const UINT DIALOG_TIMER_MS = 200; 48 49 static const UINT ID_TIMER = 1; 50 51 struct taskdialog_info 52 { 53 HWND hwnd; 54 const TASKDIALOGCONFIG *taskconfig; 55 DWORD last_timer_tick; 56 HFONT font; 57 HFONT main_instruction_font; 58 /* Control handles */ 59 HWND main_icon; 60 HWND main_instruction; 61 HWND content; 62 HWND progress_bar; 63 HWND *radio_buttons; 64 INT radio_button_count; 65 HWND *command_links; 66 INT command_link_count; 67 HWND expanded_info; 68 HWND expando_button; 69 HWND verification_box; 70 HWND footer_icon; 71 HWND footer_text; 72 HWND *buttons; 73 INT button_count; 74 HWND default_button; 75 /* Dialog metrics */ 76 struct 77 { 78 LONG x_baseunit; 79 LONG y_baseunit; 80 LONG h_spacing; 81 LONG v_spacing; 82 } m; 83 INT selected_radio_id; 84 BOOL verification_checked; 85 BOOL expanded; 86 BOOL has_cancel; 87 WCHAR *expanded_text; 88 WCHAR *collapsed_text; 89 }; 90 91 struct button_layout_info 92 { 93 LONG width; 94 LONG line; 95 }; 96 97 static HRESULT taskdialog_notify(struct taskdialog_info *dialog_info, UINT notification, WPARAM wparam, LPARAM lparam); 98 static void taskdialog_on_button_click(struct taskdialog_info *dialog_info, HWND hwnd, WORD id); 99 static void taskdialog_layout(struct taskdialog_info *dialog_info); 100 101 static void taskdialog_du_to_px(struct taskdialog_info *dialog_info, LONG *width, LONG *height) 102 { 103 if (width) *width = MulDiv(*width, dialog_info->m.x_baseunit, 4); 104 if (height) *height = MulDiv(*height, dialog_info->m.y_baseunit, 8); 105 } 106 107 static void template_write_data(char **ptr, const void *src, unsigned int size) 108 { 109 memcpy(*ptr, src, size); 110 *ptr += size; 111 } 112 113 static unsigned int taskdialog_get_reference_rect(const TASKDIALOGCONFIG *taskconfig, RECT *ret) 114 { 115 HMONITOR monitor = MonitorFromWindow(taskconfig->hwndParent ? taskconfig->hwndParent : GetActiveWindow(), 116 MONITOR_DEFAULTTOPRIMARY); 117 MONITORINFO info; 118 119 info.cbSize = sizeof(info); 120 GetMonitorInfoW(monitor, &info); 121 122 if ((taskconfig->dwFlags & TDF_POSITION_RELATIVE_TO_WINDOW) && taskconfig->hwndParent) 123 GetWindowRect(taskconfig->hwndParent, ret); 124 else 125 *ret = info.rcWork; 126 127 return info.rcWork.right - info.rcWork.left; 128 } 129 130 static WCHAR *taskdialog_get_exe_name(WCHAR *name, DWORD length) 131 { 132 DWORD len = GetModuleFileNameW(NULL, name, length); 133 if (len && len < length) 134 { 135 WCHAR *p; 136 if ((p = wcsrchr(name, '/'))) name = p + 1; 137 if ((p = wcsrchr(name, '\\'))) name = p + 1; 138 return name; 139 } 140 else 141 return NULL; 142 } 143 144 static DLGTEMPLATE *create_taskdialog_template(const TASKDIALOGCONFIG *taskconfig) 145 { 146 unsigned int size, title_size; 147 static const WORD fontsize = 0x7fff; 148 static const WCHAR emptyW[] = { 0 }; 149 const WCHAR *titleW = NULL; 150 DLGTEMPLATE *template; 151 WCHAR pathW[MAX_PATH]; 152 char *ptr; 153 154 /* Window title */ 155 if (!taskconfig->pszWindowTitle) 156 titleW = taskdialog_get_exe_name(pathW, ARRAY_SIZE(pathW)); 157 else if (IS_INTRESOURCE(taskconfig->pszWindowTitle)) 158 { 159 if (!LoadStringW(taskconfig->hInstance, LOWORD(taskconfig->pszWindowTitle), (WCHAR *)&titleW, 0)) 160 titleW = taskdialog_get_exe_name(pathW, ARRAY_SIZE(pathW)); 161 } 162 else 163 titleW = taskconfig->pszWindowTitle; 164 if (!titleW) 165 titleW = emptyW; 166 title_size = (lstrlenW(titleW) + 1) * sizeof(WCHAR); 167 168 size = sizeof(DLGTEMPLATE) + 2 * sizeof(WORD); 169 size += title_size; 170 size += 2; /* font size */ 171 172 template = Alloc(size); 173 if (!template) return NULL; 174 175 template->style = DS_MODALFRAME | DS_SETFONT | WS_CAPTION | WS_VISIBLE | WS_SYSMENU; 176 if (taskconfig->dwFlags & TDF_CAN_BE_MINIMIZED) template->style |= WS_MINIMIZEBOX; 177 if (!(taskconfig->dwFlags & TDF_NO_SET_FOREGROUND)) template->style |= DS_SETFOREGROUND; 178 if (taskconfig->dwFlags & TDF_RTL_LAYOUT) template->dwExtendedStyle = WS_EX_LAYOUTRTL | WS_EX_RIGHT | WS_EX_RTLREADING; 179 180 ptr = (char *)(template + 1); 181 ptr += 2; /* menu */ 182 ptr += 2; /* class */ 183 template_write_data(&ptr, titleW, title_size); 184 template_write_data(&ptr, &fontsize, sizeof(fontsize)); 185 186 return template; 187 } 188 189 static HWND taskdialog_find_button(HWND *buttons, INT count, INT id) 190 { 191 INT button_id; 192 INT i; 193 194 for (i = 0; i < count; i++) 195 { 196 button_id = GetWindowLongW(buttons[i], GWLP_ID); 197 if (button_id == id) return buttons[i]; 198 } 199 200 return NULL; 201 } 202 203 static void taskdialog_enable_button(const struct taskdialog_info *dialog_info, INT id, BOOL enable) 204 { 205 HWND hwnd = taskdialog_find_button(dialog_info->command_links, dialog_info->command_link_count, id); 206 if (!hwnd) hwnd = taskdialog_find_button(dialog_info->buttons, dialog_info->button_count, id); 207 if (hwnd) EnableWindow(hwnd, enable); 208 } 209 210 static void taskdialog_click_button(struct taskdialog_info *dialog_info, INT id) 211 { 212 if (taskdialog_notify(dialog_info, TDN_BUTTON_CLICKED, id, 0) == S_OK) EndDialog(dialog_info->hwnd, id); 213 } 214 215 static void taskdialog_button_set_shield(const struct taskdialog_info *dialog_info, INT id, BOOL elevate) 216 { 217 HWND hwnd = taskdialog_find_button(dialog_info->command_links, dialog_info->command_link_count, id); 218 if (!hwnd) hwnd = taskdialog_find_button(dialog_info->buttons, dialog_info->button_count, id); 219 if (hwnd) SendMessageW(hwnd, BCM_SETSHIELD, 0, elevate); 220 } 221 222 static void taskdialog_enable_radio_button(const struct taskdialog_info *dialog_info, INT id, BOOL enable) 223 { 224 HWND hwnd = taskdialog_find_button(dialog_info->radio_buttons, dialog_info->radio_button_count, id); 225 if (hwnd) EnableWindow(hwnd, enable); 226 } 227 228 static void taskdialog_click_radio_button(const struct taskdialog_info *dialog_info, INT id) 229 { 230 HWND hwnd = taskdialog_find_button(dialog_info->radio_buttons, dialog_info->radio_button_count, id); 231 if (hwnd) SendMessageW(hwnd, BM_CLICK, 0, 0); 232 } 233 234 static HRESULT taskdialog_notify(struct taskdialog_info *dialog_info, UINT notification, WPARAM wparam, LPARAM lparam) 235 { 236 const TASKDIALOGCONFIG *taskconfig = dialog_info->taskconfig; 237 return taskconfig->pfCallback 238 ? taskconfig->pfCallback(dialog_info->hwnd, notification, wparam, lparam, taskconfig->lpCallbackData) 239 : S_OK; 240 } 241 242 static void taskdialog_move_controls_vertically(HWND parent, HWND *controls, INT count, INT offset) 243 { 244 RECT rect; 245 POINT pt; 246 INT i; 247 248 for (i = 0; i < count; i++) 249 { 250 if (!controls[i]) continue; 251 252 GetWindowRect(controls[i], &rect); 253 pt.x = rect.left; 254 pt.y = rect.top; 255 MapWindowPoints(HWND_DESKTOP, parent, &pt, 1); 256 SetWindowPos(controls[i], 0, pt.x, pt.y + offset, 0, 0, SWP_NOSIZE | SWP_NOZORDER); 257 } 258 } 259 260 static void taskdialog_toggle_expando_control(struct taskdialog_info *dialog_info) 261 { 262 const TASKDIALOGCONFIG *taskconfig = dialog_info->taskconfig; 263 const WCHAR *text; 264 RECT info_rect, rect; 265 INT height, offset; 266 267 dialog_info->expanded = !dialog_info->expanded; 268 text = dialog_info->expanded ? dialog_info->expanded_text : dialog_info->collapsed_text; 269 SendMessageW(dialog_info->expando_button, WM_SETTEXT, 0, (LPARAM)text); 270 ShowWindow(dialog_info->expanded_info, dialog_info->expanded ? SW_SHOWDEFAULT : SW_HIDE); 271 272 GetWindowRect(dialog_info->expanded_info, &info_rect); 273 /* If expanded information starts up not expanded, call taskdialog_layout() 274 * to to set size for expanded information control at least once */ 275 if (IsRectEmpty(&info_rect)) 276 { 277 taskdialog_layout(dialog_info); 278 return; 279 } 280 height = info_rect.bottom - info_rect.top + dialog_info->m.v_spacing; 281 offset = dialog_info->expanded ? height : -height; 282 283 /* Update vertical layout, move all controls after expanded information */ 284 /* Move dialog */ 285 GetWindowRect(dialog_info->hwnd, &rect); 286 SetWindowPos(dialog_info->hwnd, 0, 0, 0, rect.right - rect.left, rect.bottom - rect.top + offset, 287 SWP_NOMOVE | SWP_NOZORDER); 288 /* Move controls */ 289 if (!(taskconfig->dwFlags & TDF_EXPAND_FOOTER_AREA)) 290 { 291 taskdialog_move_controls_vertically(dialog_info->hwnd, &dialog_info->progress_bar, 1, offset); 292 taskdialog_move_controls_vertically(dialog_info->hwnd, &dialog_info->expando_button, 1, offset); 293 taskdialog_move_controls_vertically(dialog_info->hwnd, &dialog_info->verification_box, 1, offset); 294 taskdialog_move_controls_vertically(dialog_info->hwnd, &dialog_info->footer_icon, 1, offset); 295 taskdialog_move_controls_vertically(dialog_info->hwnd, &dialog_info->footer_text, 1, offset); 296 taskdialog_move_controls_vertically(dialog_info->hwnd, dialog_info->buttons, dialog_info->button_count, offset); 297 taskdialog_move_controls_vertically(dialog_info->hwnd, dialog_info->radio_buttons, 298 dialog_info->radio_button_count, offset); 299 taskdialog_move_controls_vertically(dialog_info->hwnd, dialog_info->command_links, 300 dialog_info->command_link_count, offset); 301 } 302 } 303 304 static void taskdialog_on_button_click(struct taskdialog_info *dialog_info, HWND hwnd, WORD id) 305 { 306 INT command_id; 307 HWND button, radio_button; 308 309 /* Prefer the id from hwnd because the id from WM_COMMAND is truncated to WORD */ 310 command_id = hwnd ? GetWindowLongW(hwnd, GWLP_ID) : id; 311 312 if (hwnd && hwnd == dialog_info->expando_button) 313 { 314 taskdialog_toggle_expando_control(dialog_info); 315 taskdialog_notify(dialog_info, TDN_EXPANDO_BUTTON_CLICKED, dialog_info->expanded, 0); 316 return; 317 } 318 319 if (hwnd && hwnd == dialog_info->verification_box) 320 { 321 dialog_info->verification_checked = !dialog_info->verification_checked; 322 taskdialog_notify(dialog_info, TDN_VERIFICATION_CLICKED, dialog_info->verification_checked, 0); 323 return; 324 } 325 326 radio_button = taskdialog_find_button(dialog_info->radio_buttons, dialog_info->radio_button_count, command_id); 327 if (radio_button) 328 { 329 dialog_info->selected_radio_id = command_id; 330 taskdialog_notify(dialog_info, TDN_RADIO_BUTTON_CLICKED, command_id, 0); 331 return; 332 } 333 334 button = taskdialog_find_button(dialog_info->command_links, dialog_info->command_link_count, command_id); 335 if (!button) button = taskdialog_find_button(dialog_info->buttons, dialog_info->button_count, command_id); 336 if (!button && command_id == IDOK) 337 { 338 button = dialog_info->command_link_count > 0 ? dialog_info->command_links[0] : dialog_info->buttons[0]; 339 command_id = GetWindowLongW(button, GWLP_ID); 340 } 341 342 if (button && taskdialog_notify(dialog_info, TDN_BUTTON_CLICKED, command_id, 0) == S_OK) 343 EndDialog(dialog_info->hwnd, command_id); 344 } 345 346 static WCHAR *taskdialog_gettext(struct taskdialog_info *dialog_info, BOOL user_resource, const WCHAR *text) 347 { 348 const WCHAR *textW = NULL; 349 INT length; 350 WCHAR *ret; 351 352 if (IS_INTRESOURCE(text)) 353 { 354 if (!(length = LoadStringW(user_resource ? dialog_info->taskconfig->hInstance : COMCTL32_hModule, 355 (UINT_PTR)text, (WCHAR *)&textW, 0))) 356 return NULL; 357 } 358 else 359 { 360 textW = text; 361 length = lstrlenW(textW); 362 } 363 364 ret = Alloc((length + 1) * sizeof(WCHAR)); 365 if (ret) memcpy(ret, textW, length * sizeof(WCHAR)); 366 367 return ret; 368 } 369 370 static BOOL taskdialog_hyperlink_enabled(struct taskdialog_info *dialog_info) 371 { 372 return dialog_info->taskconfig->dwFlags & TDF_ENABLE_HYPERLINKS; 373 } 374 375 static BOOL taskdialog_use_command_link(struct taskdialog_info *dialog_info) 376 { 377 return dialog_info->taskconfig->dwFlags & (TDF_USE_COMMAND_LINKS | TDF_USE_COMMAND_LINKS_NO_ICON); 378 } 379 380 static void taskdialog_get_label_size(struct taskdialog_info *dialog_info, HWND hwnd, LONG max_width, SIZE *size, 381 BOOL syslink) 382 { 383 DWORD style = DT_EXPANDTABS | DT_CALCRECT | DT_WORDBREAK; 384 HFONT hfont, old_hfont; 385 HDC hdc; 386 RECT rect = {0}; 387 WCHAR *text; 388 INT text_length; 389 390 if (syslink) 391 { 392 SendMessageW(hwnd, LM_GETIDEALSIZE, max_width, (LPARAM)size); 393 return; 394 } 395 396 if (dialog_info->taskconfig->dwFlags & TDF_RTL_LAYOUT) 397 style |= DT_RIGHT | DT_RTLREADING; 398 else 399 style |= DT_LEFT; 400 401 hfont = (HFONT)SendMessageW(hwnd, WM_GETFONT, 0, 0); 402 text_length = GetWindowTextLengthW(hwnd); 403 text = Alloc((text_length + 1) * sizeof(WCHAR)); 404 if (!text) 405 { 406 size->cx = 0; 407 size->cy = 0; 408 return; 409 } 410 GetWindowTextW(hwnd, text, text_length + 1); 411 hdc = GetDC(hwnd); 412 old_hfont = SelectObject(hdc, hfont); 413 rect.right = max_width; 414 size->cy = DrawTextW(hdc, text, text_length, &rect, style); 415 size->cx = min(max_width, rect.right - rect.left); 416 if (old_hfont) SelectObject(hdc, old_hfont); 417 ReleaseDC(hwnd, hdc); 418 Free(text); 419 } 420 421 static void taskdialog_get_button_size(HWND hwnd, LONG max_width, SIZE *size) 422 { 423 size->cx = max_width; 424 size->cy = 0; 425 SendMessageW(hwnd, BCM_GETIDEALSIZE, 0, (LPARAM)size); 426 } 427 428 static void taskdialog_get_expando_size(struct taskdialog_info *dialog_info, HWND hwnd, SIZE *size) 429 { 430 DWORD style = DT_EXPANDTABS | DT_CALCRECT | DT_WORDBREAK; 431 HFONT hfont, old_hfont; 432 HDC hdc; 433 RECT rect = {0}; 434 LONG icon_width, icon_height, text_offset; 435 LONG max_width, max_text_height; 436 437 hdc = GetDC(hwnd); 438 hfont = (HFONT)SendMessageW(hwnd, WM_GETFONT, 0, 0); 439 old_hfont = SelectObject(hdc, hfont); 440 441 icon_width = DIALOG_EXPANDO_ICON_WIDTH; 442 icon_height = DIALOG_EXPANDO_ICON_HEIGHT; 443 taskdialog_du_to_px(dialog_info, &icon_width, &icon_height); 444 445 GetCharWidthW(hdc, '0', '0', &text_offset); 446 text_offset /= 2; 447 448 if (dialog_info->taskconfig->dwFlags & TDF_RTL_LAYOUT) 449 style |= DT_RIGHT | DT_RTLREADING; 450 else 451 style |= DT_LEFT; 452 453 max_width = DIALOG_MIN_WIDTH / 2; 454 taskdialog_du_to_px(dialog_info, &max_width, NULL); 455 456 rect.right = max_width - icon_width - text_offset; 457 max_text_height = DrawTextW(hdc, dialog_info->expanded_text, -1, &rect, style); 458 size->cy = max(max_text_height, icon_height); 459 size->cx = rect.right - rect.left; 460 461 rect.right = max_width - icon_width - text_offset; 462 max_text_height = DrawTextW(hdc, dialog_info->collapsed_text, -1, &rect, style); 463 size->cy = max(size->cy, max_text_height); 464 size->cx = max(size->cx, rect.right - rect.left); 465 size->cx = min(size->cx, max_width); 466 467 if (old_hfont) SelectObject(hdc, old_hfont); 468 ReleaseDC(hwnd, hdc); 469 } 470 471 static ULONG_PTR taskdialog_get_standard_icon(LPCWSTR icon) 472 { 473 if (icon == TD_WARNING_ICON) 474 return IDI_WARNING; 475 else if (icon == TD_ERROR_ICON) 476 return IDI_ERROR; 477 else if (icon == TD_INFORMATION_ICON) 478 return IDI_INFORMATION; 479 else if (icon == TD_SHIELD_ICON) 480 return IDI_SHIELD; 481 else 482 return (ULONG_PTR)icon; 483 } 484 485 static void taskdialog_set_icon(struct taskdialog_info *dialog_info, INT element, HICON icon) 486 { 487 DWORD flags = dialog_info->taskconfig->dwFlags; 488 INT cx = 0, cy = 0; 489 HICON hicon; 490 491 if (!icon) return; 492 493 if (((flags & TDF_USE_HICON_MAIN) && element == TDIE_ICON_MAIN) 494 || ((flags & TDF_USE_HICON_FOOTER) && element == TDIE_ICON_FOOTER)) 495 hicon = icon; 496 else 497 { 498 if (element == TDIE_ICON_FOOTER) 499 { 500 cx = GetSystemMetrics(SM_CXSMICON); 501 cy = GetSystemMetrics(SM_CYSMICON); 502 } 503 hicon = LoadImageW(dialog_info->taskconfig->hInstance, (LPCWSTR)icon, IMAGE_ICON, cx, cy, LR_SHARED | LR_DEFAULTSIZE); 504 if (!hicon) 505 hicon = LoadImageW(NULL, (LPCWSTR)taskdialog_get_standard_icon((LPCWSTR)icon), IMAGE_ICON, cx, cy, 506 LR_SHARED | LR_DEFAULTSIZE); 507 } 508 509 if (!hicon) return; 510 511 if (element == TDIE_ICON_MAIN) 512 { 513 SendMessageW(dialog_info->hwnd, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)hicon); 514 SendMessageW(dialog_info->main_icon, STM_SETICON, (WPARAM)hicon, 0); 515 } 516 else if (element == TDIE_ICON_FOOTER) 517 SendMessageW(dialog_info->footer_icon, STM_SETICON, (WPARAM)hicon, 0); 518 } 519 520 static void taskdialog_set_element_text(struct taskdialog_info *dialog_info, TASKDIALOG_ELEMENTS element, 521 const WCHAR *text) 522 { 523 HWND hwnd = NULL; 524 WCHAR *textW; 525 526 if (element == TDE_CONTENT) 527 hwnd = dialog_info->content; 528 else if (element == TDE_EXPANDED_INFORMATION) 529 hwnd = dialog_info->expanded_info; 530 else if (element == TDE_FOOTER) 531 hwnd = dialog_info->footer_text; 532 else if (element == TDE_MAIN_INSTRUCTION) 533 hwnd = dialog_info->main_instruction; 534 535 if (!hwnd) return; 536 537 textW = taskdialog_gettext(dialog_info, TRUE, text); 538 SendMessageW(hwnd, WM_SETTEXT, 0, (LPARAM)textW); 539 Free(textW); 540 } 541 542 static void taskdialog_check_default_radio_buttons(struct taskdialog_info *dialog_info) 543 { 544 const TASKDIALOGCONFIG *taskconfig = dialog_info->taskconfig; 545 HWND default_button; 546 547 if (!dialog_info->radio_button_count) return; 548 549 default_button = taskdialog_find_button(dialog_info->radio_buttons, dialog_info->radio_button_count, 550 taskconfig->nDefaultRadioButton); 551 552 if (!default_button && !(taskconfig->dwFlags & TDF_NO_DEFAULT_RADIO_BUTTON)) 553 default_button = dialog_info->radio_buttons[0]; 554 555 if (default_button) 556 { 557 SendMessageW(default_button, BM_SETCHECK, BST_CHECKED, 0); 558 taskdialog_on_button_click(dialog_info, default_button, 0); 559 } 560 } 561 562 static void taskdialog_add_main_icon(struct taskdialog_info *dialog_info) 563 { 564 if (!dialog_info->taskconfig->u.hMainIcon) return; 565 566 dialog_info->main_icon = 567 CreateWindowW(WC_STATICW, NULL, WS_CHILD | WS_VISIBLE | SS_ICON, 0, 0, 0, 0, dialog_info->hwnd, NULL, 0, NULL); 568 taskdialog_set_icon(dialog_info, TDIE_ICON_MAIN, dialog_info->taskconfig->u.hMainIcon); 569 } 570 571 static HWND taskdialog_create_label(struct taskdialog_info *dialog_info, const WCHAR *text, HFONT font, BOOL syslink) 572 { 573 WCHAR *textW; 574 HWND hwnd; 575 const WCHAR *class; 576 DWORD style = WS_CHILD | WS_VISIBLE; 577 578 if (!text) return NULL; 579 580 class = syslink ? WC_LINK : WC_STATICW; 581 if (syslink) style |= WS_TABSTOP; 582 textW = taskdialog_gettext(dialog_info, TRUE, text); 583 hwnd = CreateWindowW(class, textW, style, 0, 0, 0, 0, dialog_info->hwnd, NULL, 0, NULL); 584 Free(textW); 585 586 SendMessageW(hwnd, WM_SETFONT, (WPARAM)font, 0); 587 return hwnd; 588 } 589 590 static void taskdialog_add_main_instruction(struct taskdialog_info *dialog_info) 591 { 592 const TASKDIALOGCONFIG *taskconfig = dialog_info->taskconfig; 593 NONCLIENTMETRICSW ncm; 594 595 if (!taskconfig->pszMainInstruction) return; 596 597 ncm.cbSize = sizeof(ncm); 598 SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0); 599 /* 1.25 times the height */ 600 ncm.lfMessageFont.lfHeight = ncm.lfMessageFont.lfHeight * 5 / 4; 601 ncm.lfMessageFont.lfWeight = FW_BOLD; 602 dialog_info->main_instruction_font = CreateFontIndirectW(&ncm.lfMessageFont); 603 604 dialog_info->main_instruction = 605 taskdialog_create_label(dialog_info, taskconfig->pszMainInstruction, dialog_info->main_instruction_font, FALSE); 606 } 607 608 static void taskdialog_add_content(struct taskdialog_info *dialog_info) 609 { 610 dialog_info->content = taskdialog_create_label(dialog_info, dialog_info->taskconfig->pszContent, dialog_info->font, 611 taskdialog_hyperlink_enabled(dialog_info)); 612 } 613 614 static void taskdialog_add_progress_bar(struct taskdialog_info *dialog_info) 615 { 616 const TASKDIALOGCONFIG *taskconfig = dialog_info->taskconfig; 617 DWORD style = PBS_SMOOTH | PBS_SMOOTHREVERSE | WS_CHILD | WS_VISIBLE; 618 619 if (!(taskconfig->dwFlags & (TDF_SHOW_PROGRESS_BAR | TDF_SHOW_MARQUEE_PROGRESS_BAR))) return; 620 if (taskconfig->dwFlags & TDF_SHOW_MARQUEE_PROGRESS_BAR) style |= PBS_MARQUEE; 621 dialog_info->progress_bar = 622 CreateWindowW(PROGRESS_CLASSW, NULL, style, 0, 0, 0, 0, dialog_info->hwnd, NULL, 0, NULL); 623 } 624 625 static void taskdialog_add_radio_buttons(struct taskdialog_info *dialog_info) 626 { 627 const TASKDIALOGCONFIG *taskconfig = dialog_info->taskconfig; 628 static const DWORD style = BS_AUTORADIOBUTTON | BS_MULTILINE | BS_TOP | WS_CHILD | WS_VISIBLE | WS_TABSTOP; 629 WCHAR *textW; 630 INT i; 631 632 if (!taskconfig->cRadioButtons || !taskconfig->pRadioButtons) return; 633 634 dialog_info->radio_buttons = Alloc(taskconfig->cRadioButtons * sizeof(*dialog_info->radio_buttons)); 635 if (!dialog_info->radio_buttons) return; 636 637 dialog_info->radio_button_count = taskconfig->cRadioButtons; 638 for (i = 0; i < dialog_info->radio_button_count; i++) 639 { 640 textW = taskdialog_gettext(dialog_info, TRUE, taskconfig->pRadioButtons[i].pszButtonText); 641 dialog_info->radio_buttons[i] = 642 CreateWindowW(WC_BUTTONW, textW, i == 0 ? style | WS_GROUP : style, 0, 0, 0, 0, dialog_info->hwnd, 643 LongToHandle(taskconfig->pRadioButtons[i].nButtonID), 0, NULL); 644 SendMessageW(dialog_info->radio_buttons[i], WM_SETFONT, (WPARAM)dialog_info->font, 0); 645 Free(textW); 646 } 647 } 648 649 static void taskdialog_add_command_links(struct taskdialog_info *dialog_info) 650 { 651 const TASKDIALOGCONFIG *taskconfig = dialog_info->taskconfig; 652 DWORD default_style = BS_MULTILINE | BS_LEFT | BS_TOP | WS_CHILD | WS_VISIBLE | WS_TABSTOP, style; 653 BOOL is_default; 654 WCHAR *textW; 655 INT i; 656 657 if (!taskconfig->cButtons || !taskconfig->pButtons || !taskdialog_use_command_link(dialog_info)) return; 658 659 dialog_info->command_links = Alloc(taskconfig->cButtons * sizeof(*dialog_info->command_links)); 660 if (!dialog_info->command_links) return; 661 662 dialog_info->command_link_count = taskconfig->cButtons; 663 for (i = 0; i < dialog_info->command_link_count; i++) 664 { 665 is_default = taskconfig->pButtons[i].nButtonID == taskconfig->nDefaultButton; 666 style = is_default ? default_style | BS_DEFCOMMANDLINK : default_style | BS_COMMANDLINK; 667 textW = taskdialog_gettext(dialog_info, TRUE, taskconfig->pButtons[i].pszButtonText); 668 dialog_info->command_links[i] = CreateWindowW(WC_BUTTONW, textW, style, 0, 0, 0, 0, dialog_info->hwnd, 669 LongToHandle(taskconfig->pButtons[i].nButtonID), 0, NULL); 670 SendMessageW(dialog_info->command_links[i], WM_SETFONT, (WPARAM)dialog_info->font, 0); 671 Free(textW); 672 673 if (is_default && !dialog_info->default_button) dialog_info->default_button = dialog_info->command_links[i]; 674 } 675 } 676 677 static void taskdialog_add_expanded_info(struct taskdialog_info *dialog_info) 678 { 679 const TASKDIALOGCONFIG *taskconfig = dialog_info->taskconfig; 680 681 if (!taskconfig->pszExpandedInformation) return; 682 683 dialog_info->expanded = taskconfig->dwFlags & TDF_EXPANDED_BY_DEFAULT; 684 dialog_info->expanded_info = taskdialog_create_label(dialog_info, taskconfig->pszExpandedInformation, 685 dialog_info->font, taskdialog_hyperlink_enabled(dialog_info)); 686 ShowWindow(dialog_info->expanded_info, dialog_info->expanded ? SW_SHOWDEFAULT : SW_HIDE); 687 } 688 689 static void taskdialog_add_expando_button(struct taskdialog_info *dialog_info) 690 { 691 const TASKDIALOGCONFIG *taskconfig = dialog_info->taskconfig; 692 const WCHAR *textW; 693 694 if (!taskconfig->pszExpandedInformation) return; 695 696 if (!taskconfig->pszCollapsedControlText && !taskconfig->pszExpandedControlText) 697 { 698 dialog_info->expanded_text = taskdialog_gettext(dialog_info, FALSE, MAKEINTRESOURCEW(IDS_TD_EXPANDED)); 699 dialog_info->collapsed_text = taskdialog_gettext(dialog_info, FALSE, MAKEINTRESOURCEW(IDS_TD_COLLAPSED)); 700 } 701 else 702 { 703 textW = taskconfig->pszExpandedControlText ? taskconfig->pszExpandedControlText 704 : taskconfig->pszCollapsedControlText; 705 dialog_info->expanded_text = taskdialog_gettext(dialog_info, TRUE, textW); 706 textW = taskconfig->pszCollapsedControlText ? taskconfig->pszCollapsedControlText 707 : taskconfig->pszExpandedControlText; 708 dialog_info->collapsed_text = taskdialog_gettext(dialog_info, TRUE, textW); 709 } 710 711 textW = dialog_info->expanded ? dialog_info->expanded_text : dialog_info->collapsed_text; 712 713 dialog_info->expando_button = CreateWindowW(WC_BUTTONW, textW, WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_OWNERDRAW, 0, 714 0, 0, 0, dialog_info->hwnd, 0, 0, 0); 715 SendMessageW(dialog_info->expando_button, WM_SETFONT, (WPARAM)dialog_info->font, 0); 716 } 717 718 static void taskdialog_add_verification_box(struct taskdialog_info *dialog_info) 719 { 720 const TASKDIALOGCONFIG *taskconfig = dialog_info->taskconfig; 721 static const DWORD style = BS_AUTOCHECKBOX | BS_MULTILINE | BS_LEFT | BS_TOP | WS_CHILD | WS_VISIBLE | WS_TABSTOP; 722 WCHAR *textW; 723 724 /* TDF_VERIFICATION_FLAG_CHECKED works even if pszVerificationText is not set */ 725 if (taskconfig->dwFlags & TDF_VERIFICATION_FLAG_CHECKED) dialog_info->verification_checked = TRUE; 726 727 if (!taskconfig->pszVerificationText) return; 728 729 textW = taskdialog_gettext(dialog_info, TRUE, taskconfig->pszVerificationText); 730 dialog_info->verification_box = CreateWindowW(WC_BUTTONW, textW, style, 0, 0, 0, 0, dialog_info->hwnd, 0, 0, 0); 731 SendMessageW(dialog_info->verification_box, WM_SETFONT, (WPARAM)dialog_info->font, 0); 732 Free(textW); 733 734 if (taskconfig->dwFlags & TDF_VERIFICATION_FLAG_CHECKED) 735 SendMessageW(dialog_info->verification_box, BM_SETCHECK, BST_CHECKED, 0); 736 } 737 738 static void taskdialog_add_button(struct taskdialog_info *dialog_info, HWND *button, INT_PTR id, const WCHAR *text, 739 BOOL custom_button) 740 { 741 const TASKDIALOGCONFIG *taskconfig = dialog_info->taskconfig; 742 WCHAR *textW; 743 744 textW = taskdialog_gettext(dialog_info, custom_button, text); 745 *button = CreateWindowW(WC_BUTTONW, textW, WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0, 0, 0, 0, dialog_info->hwnd, 746 (HMENU)id, 0, NULL); 747 Free(textW); 748 SendMessageW(*button, WM_SETFONT, (WPARAM)dialog_info->font, 0); 749 750 if (id == taskconfig->nDefaultButton && !dialog_info->default_button) dialog_info->default_button = *button; 751 } 752 753 static void taskdialog_add_buttons(struct taskdialog_info *dialog_info) 754 { 755 const TASKDIALOGCONFIG *taskconfig = dialog_info->taskconfig; 756 BOOL use_command_links = taskdialog_use_command_link(dialog_info); 757 DWORD flags = taskconfig->dwCommonButtons; 758 INT count, max_count; 759 760 /* Allocate enough memory for the custom and the default buttons. Maximum 6 default buttons possible. */ 761 max_count = 6; 762 if (!use_command_links && taskconfig->cButtons && taskconfig->pButtons) max_count += taskconfig->cButtons; 763 764 dialog_info->buttons = Alloc(max_count * sizeof(*dialog_info->buttons)); 765 if (!dialog_info->buttons) return; 766 767 for (count = 0; !use_command_links && count < taskconfig->cButtons; count++) 768 taskdialog_add_button(dialog_info, &dialog_info->buttons[count], taskconfig->pButtons[count].nButtonID, 769 taskconfig->pButtons[count].pszButtonText, TRUE); 770 771 #define TASKDIALOG_INIT_COMMON_BUTTON(id) \ 772 do \ 773 { \ 774 taskdialog_add_button(dialog_info, &dialog_info->buttons[count++], ID##id, MAKEINTRESOURCEW(IDS_BUTTON_##id), \ 775 FALSE); \ 776 } while (0) 777 778 if (flags & TDCBF_OK_BUTTON) TASKDIALOG_INIT_COMMON_BUTTON(OK); 779 if (flags & TDCBF_YES_BUTTON) TASKDIALOG_INIT_COMMON_BUTTON(YES); 780 if (flags & TDCBF_NO_BUTTON) TASKDIALOG_INIT_COMMON_BUTTON(NO); 781 if (flags & TDCBF_RETRY_BUTTON) TASKDIALOG_INIT_COMMON_BUTTON(RETRY); 782 if (flags & TDCBF_CANCEL_BUTTON) TASKDIALOG_INIT_COMMON_BUTTON(CANCEL); 783 if (flags & TDCBF_CLOSE_BUTTON) TASKDIALOG_INIT_COMMON_BUTTON(CLOSE); 784 785 if (!count && !dialog_info->command_link_count) TASKDIALOG_INIT_COMMON_BUTTON(OK); 786 #undef TASKDIALOG_INIT_COMMON_BUTTON 787 788 dialog_info->button_count = count; 789 } 790 791 static void taskdialog_add_footer_icon(struct taskdialog_info *dialog_info) 792 { 793 if (!dialog_info->taskconfig->u2.hFooterIcon) return; 794 795 dialog_info->footer_icon = 796 CreateWindowW(WC_STATICW, NULL, WS_CHILD | WS_VISIBLE | SS_ICON, 0, 0, 0, 0, dialog_info->hwnd, NULL, 0, 0); 797 taskdialog_set_icon(dialog_info, TDIE_ICON_FOOTER, dialog_info->taskconfig->u2.hFooterIcon); 798 } 799 800 static void taskdialog_add_footer_text(struct taskdialog_info *dialog_info) 801 { 802 dialog_info->footer_text = taskdialog_create_label(dialog_info, dialog_info->taskconfig->pszFooter, 803 dialog_info->font, taskdialog_hyperlink_enabled(dialog_info)); 804 } 805 806 static LONG taskdialog_get_dialog_width(struct taskdialog_info *dialog_info) 807 { 808 const TASKDIALOGCONFIG *taskconfig = dialog_info->taskconfig; 809 BOOL syslink = taskdialog_hyperlink_enabled(dialog_info); 810 LONG max_width, main_icon_width, screen_width; 811 RECT rect; 812 SIZE size; 813 814 screen_width = taskdialog_get_reference_rect(taskconfig, &rect); 815 if ((taskconfig->dwFlags & TDF_SIZE_TO_CONTENT) && !taskconfig->cxWidth) 816 { 817 max_width = DIALOG_MIN_WIDTH; 818 taskdialog_du_to_px(dialog_info, &max_width, NULL); 819 main_icon_width = dialog_info->m.h_spacing; 820 if (dialog_info->main_icon) main_icon_width += GetSystemMetrics(SM_CXICON); 821 if (dialog_info->content) 822 { 823 taskdialog_get_label_size(dialog_info, dialog_info->content, 0, &size, syslink); 824 max_width = max(max_width, size.cx + main_icon_width + dialog_info->m.h_spacing * 2); 825 } 826 } 827 else 828 { 829 max_width = max(taskconfig->cxWidth, DIALOG_MIN_WIDTH); 830 taskdialog_du_to_px(dialog_info, &max_width, NULL); 831 } 832 max_width = min(max_width, screen_width); 833 return max_width; 834 } 835 836 static void taskdialog_label_layout(struct taskdialog_info *dialog_info, HWND hwnd, INT start_x, LONG dialog_width, 837 LONG *dialog_height, BOOL syslink) 838 { 839 LONG x, y, max_width; 840 SIZE size; 841 842 if (!hwnd) return; 843 844 x = start_x + dialog_info->m.h_spacing; 845 y = *dialog_height + dialog_info->m.v_spacing; 846 max_width = dialog_width - x - dialog_info->m.h_spacing; 847 taskdialog_get_label_size(dialog_info, hwnd, max_width, &size, syslink); 848 SetWindowPos(hwnd, 0, x, y, size.cx, size.cy, SWP_NOZORDER); 849 *dialog_height = y + size.cy; 850 } 851 852 static void taskdialog_layout(struct taskdialog_info *dialog_info) 853 { 854 const TASKDIALOGCONFIG *taskconfig = dialog_info->taskconfig; 855 BOOL syslink = taskdialog_hyperlink_enabled(dialog_info); 856 static BOOL first_time = TRUE; 857 RECT ref_rect; 858 LONG dialog_width, dialog_height = 0; 859 LONG h_spacing, v_spacing; 860 LONG main_icon_right, main_icon_bottom; 861 LONG expando_right, expando_bottom; 862 struct button_layout_info *button_layout_infos; 863 LONG button_min_width, button_height; 864 LONG *line_widths, line_count, align; 865 LONG footer_icon_right, footer_icon_bottom; 866 LONG x, y; 867 SIZE size; 868 INT i; 869 870 taskdialog_get_reference_rect(dialog_info->taskconfig, &ref_rect); 871 dialog_width = taskdialog_get_dialog_width(dialog_info); 872 873 h_spacing = dialog_info->m.h_spacing; 874 v_spacing = dialog_info->m.v_spacing; 875 876 /* Main icon */ 877 main_icon_right = 0; 878 main_icon_bottom = 0; 879 if (dialog_info->main_icon) 880 { 881 x = h_spacing; 882 y = dialog_height + v_spacing; 883 size.cx = GetSystemMetrics(SM_CXICON); 884 size.cy = GetSystemMetrics(SM_CYICON); 885 SetWindowPos(dialog_info->main_icon, 0, x, y, size.cx, size.cy, SWP_NOZORDER); 886 main_icon_right = x + size.cx; 887 main_icon_bottom = y + size.cy; 888 } 889 890 /* Main instruction */ 891 taskdialog_label_layout(dialog_info, dialog_info->main_instruction, main_icon_right, dialog_width, &dialog_height, 892 FALSE); 893 894 /* Content */ 895 taskdialog_label_layout(dialog_info, dialog_info->content, main_icon_right, dialog_width, &dialog_height, syslink); 896 897 /* Expanded information */ 898 if (!(taskconfig->dwFlags & TDF_EXPAND_FOOTER_AREA) && dialog_info->expanded) 899 taskdialog_label_layout(dialog_info, dialog_info->expanded_info, main_icon_right, dialog_width, &dialog_height, 900 syslink); 901 902 /* Progress bar */ 903 if (dialog_info->progress_bar) 904 { 905 x = main_icon_right + h_spacing; 906 y = dialog_height + v_spacing; 907 size.cx = dialog_width - x - h_spacing; 908 size.cy = GetSystemMetrics(SM_CYVSCROLL); 909 SetWindowPos(dialog_info->progress_bar, 0, x, y, size.cx, size.cy, SWP_NOZORDER); 910 dialog_height = y + size.cy; 911 } 912 913 /* Radio buttons */ 914 for (i = 0; i < dialog_info->radio_button_count; i++) 915 { 916 x = main_icon_right + h_spacing; 917 y = dialog_height + v_spacing; 918 taskdialog_get_button_size(dialog_info->radio_buttons[i], dialog_width - x - h_spacing, &size); 919 size.cx = dialog_width - x - h_spacing; 920 SetWindowPos(dialog_info->radio_buttons[i], 0, x, y, size.cx, size.cy, SWP_NOZORDER); 921 dialog_height = y + size.cy; 922 } 923 924 /* Command links */ 925 for (i = 0; i < dialog_info->command_link_count; i++) 926 { 927 x = main_icon_right + h_spacing; 928 y = dialog_height; 929 /* Only add spacing for the first command links. There is no vertical spacing between command links */ 930 if (!i) 931 y += v_spacing; 932 taskdialog_get_button_size(dialog_info->command_links[i], dialog_width - x - h_spacing, &size); 933 size.cx = dialog_width - x - h_spacing; 934 /* Add spacing */ 935 size.cy += 4; 936 SetWindowPos(dialog_info->command_links[i], 0, x, y, size.cx, size.cy, SWP_NOZORDER); 937 dialog_height = y + size.cy; 938 } 939 940 dialog_height = max(dialog_height, main_icon_bottom); 941 942 expando_right = 0; 943 expando_bottom = dialog_height; 944 /* Expando control */ 945 if (dialog_info->expando_button) 946 { 947 x = h_spacing; 948 y = dialog_height + v_spacing; 949 taskdialog_get_expando_size(dialog_info, dialog_info->expando_button, &size); 950 SetWindowPos(dialog_info->expando_button, 0, x, y, size.cx, size.cy, SWP_NOZORDER); 951 expando_right = x + size.cx; 952 expando_bottom = y + size.cy; 953 } 954 955 /* Verification box */ 956 if (dialog_info->verification_box) 957 { 958 x = h_spacing; 959 y = expando_bottom + v_spacing; 960 size.cx = DIALOG_MIN_WIDTH / 2; 961 taskdialog_du_to_px(dialog_info, &size.cx, NULL); 962 taskdialog_get_button_size(dialog_info->verification_box, size.cx, &size); 963 SetWindowPos(dialog_info->verification_box, 0, x, y, size.cx, size.cy, SWP_NOZORDER); 964 expando_right = max(expando_right, x + size.cx); 965 expando_bottom = y + size.cy; 966 } 967 968 /* Common and custom buttons */ 969 button_layout_infos = Alloc(dialog_info->button_count * sizeof(*button_layout_infos)); 970 line_widths = Alloc(dialog_info->button_count * sizeof(*line_widths)); 971 972 button_min_width = DIALOG_BUTTON_WIDTH; 973 button_height = DIALOG_BUTTON_HEIGHT; 974 taskdialog_du_to_px(dialog_info, &button_min_width, &button_height); 975 for (i = 0; i < dialog_info->button_count; i++) 976 { 977 taskdialog_get_button_size(dialog_info->buttons[i], dialog_width - expando_right - h_spacing * 2, &size); 978 button_layout_infos[i].width = max(size.cx, button_min_width); 979 } 980 981 /* Separate buttons into lines */ 982 x = expando_right + h_spacing; 983 for (i = 0, line_count = 0; i < dialog_info->button_count; i++) 984 { 985 button_layout_infos[i].line = line_count; 986 x += button_layout_infos[i].width + h_spacing; 987 line_widths[line_count] += button_layout_infos[i].width + h_spacing; 988 989 if ((i + 1 < dialog_info->button_count) && (x + button_layout_infos[i + 1].width + h_spacing >= dialog_width)) 990 { 991 x = expando_right + h_spacing; 992 line_count++; 993 } 994 } 995 line_count++; 996 997 /* Try to balance lines so they are about the same size */ 998 for (i = 1; i < line_count - 1; i++) 999 { 1000 int diff_now = abs(line_widths[i] - line_widths[i - 1]); 1001 unsigned int j, last_button = 0; 1002 int diff_changed; 1003 1004 for (j = 0; j < dialog_info->button_count; j++) 1005 if (button_layout_infos[j].line == i - 1) last_button = j; 1006 1007 /* Difference in length of both lines if we wrapped the last button from the last line into this one */ 1008 diff_changed = abs(2 * button_layout_infos[last_button].width + line_widths[i] - line_widths[i - 1]); 1009 1010 if (diff_changed < diff_now) 1011 { 1012 button_layout_infos[last_button].line = i; 1013 line_widths[i] += button_layout_infos[last_button].width; 1014 line_widths[i - 1] -= button_layout_infos[last_button].width; 1015 } 1016 } 1017 1018 /* Calculate left alignment so all lines are as far right as possible. */ 1019 align = dialog_width - h_spacing; 1020 for (i = 0; i < line_count; i++) 1021 { 1022 int new_alignment = dialog_width - line_widths[i]; 1023 if (new_alignment < align) align = new_alignment; 1024 } 1025 1026 /* Now that we got them all positioned, move all buttons */ 1027 x = align; 1028 size.cy = button_height; 1029 for (i = 0; i < dialog_info->button_count; i++) 1030 { 1031 /* New line */ 1032 if (i > 0 && button_layout_infos[i].line != button_layout_infos[i - 1].line) 1033 { 1034 x = align; 1035 dialog_height += size.cy + v_spacing; 1036 } 1037 1038 y = dialog_height + v_spacing; 1039 size.cx = button_layout_infos[i].width; 1040 SetWindowPos(dialog_info->buttons[i], 0, x, y, size.cx, size.cy, SWP_NOZORDER); 1041 x += button_layout_infos[i].width + h_spacing; 1042 } 1043 1044 /* Add height for last row button and spacing */ 1045 dialog_height += size.cy + v_spacing; 1046 dialog_height = max(dialog_height, expando_bottom); 1047 1048 Free(button_layout_infos); 1049 Free(line_widths); 1050 1051 /* Footer icon */ 1052 footer_icon_right = 0; 1053 footer_icon_bottom = dialog_height; 1054 if (dialog_info->footer_icon) 1055 { 1056 x = h_spacing; 1057 y = dialog_height + v_spacing; 1058 size.cx = GetSystemMetrics(SM_CXSMICON); 1059 size.cy = GetSystemMetrics(SM_CYSMICON); 1060 SetWindowPos(dialog_info->footer_icon, 0, x, y, size.cx, size.cy, SWP_NOZORDER); 1061 footer_icon_right = x + size.cx; 1062 footer_icon_bottom = y + size.cy; 1063 } 1064 1065 /* Footer text */ 1066 taskdialog_label_layout(dialog_info, dialog_info->footer_text, footer_icon_right, dialog_width, &dialog_height, 1067 syslink); 1068 dialog_height = max(dialog_height, footer_icon_bottom); 1069 1070 /* Expanded information */ 1071 if ((taskconfig->dwFlags & TDF_EXPAND_FOOTER_AREA) && dialog_info->expanded) 1072 taskdialog_label_layout(dialog_info, dialog_info->expanded_info, 0, dialog_width, &dialog_height, syslink); 1073 1074 /* Add height for spacing, title height and frame height */ 1075 dialog_height += v_spacing; 1076 dialog_height += GetSystemMetrics(SM_CYCAPTION); 1077 dialog_height += GetSystemMetrics(SM_CXDLGFRAME); 1078 1079 if (first_time) 1080 { 1081 x = (ref_rect.left + ref_rect.right - dialog_width) / 2; 1082 y = (ref_rect.top + ref_rect.bottom - dialog_height) / 2; 1083 SetWindowPos(dialog_info->hwnd, 0, x, y, dialog_width, dialog_height, SWP_NOZORDER); 1084 first_time = FALSE; 1085 } 1086 else 1087 SetWindowPos(dialog_info->hwnd, 0, 0, 0, dialog_width, dialog_height, SWP_NOMOVE | SWP_NOZORDER); 1088 } 1089 1090 static void taskdialog_draw_expando_control(struct taskdialog_info *dialog_info, LPDRAWITEMSTRUCT dis) 1091 { 1092 HWND hwnd; 1093 HDC hdc; 1094 RECT rect = {0}; 1095 WCHAR *text; 1096 LONG icon_width, icon_height, text_offset; 1097 UINT style = DFCS_FLAT; 1098 BOOL draw_focus; 1099 1100 hdc = dis->hDC; 1101 hwnd = dis->hwndItem; 1102 1103 SendMessageW(hwnd, WM_ERASEBKGND, (WPARAM)hdc, 0); 1104 1105 icon_width = DIALOG_EXPANDO_ICON_WIDTH; 1106 icon_height = DIALOG_EXPANDO_ICON_HEIGHT; 1107 taskdialog_du_to_px(dialog_info, &icon_width, &icon_height); 1108 rect.right = icon_width; 1109 rect.bottom = icon_height; 1110 style |= dialog_info->expanded ? DFCS_SCROLLUP : DFCS_SCROLLDOWN; 1111 DrawFrameControl(hdc, &rect, DFC_SCROLL, style); 1112 1113 GetCharWidthW(hdc, '0', '0', &text_offset); 1114 text_offset /= 2; 1115 1116 rect = dis->rcItem; 1117 rect.left += icon_width + text_offset; 1118 text = dialog_info->expanded ? dialog_info->expanded_text : dialog_info->collapsed_text; 1119 DrawTextW(hdc, text, -1, &rect, DT_WORDBREAK | DT_END_ELLIPSIS | DT_EXPANDTABS); 1120 1121 draw_focus = (dis->itemState & ODS_FOCUS) && !(dis->itemState & ODS_NOFOCUSRECT); 1122 if(draw_focus) DrawFocusRect(hdc, &rect); 1123 } 1124 1125 static void taskdialog_init(struct taskdialog_info *dialog_info, HWND hwnd) 1126 { 1127 const TASKDIALOGCONFIG *taskconfig = dialog_info->taskconfig; 1128 NONCLIENTMETRICSW ncm; 1129 HDC hdc; 1130 INT id; 1131 1132 ncm.cbSize = sizeof(ncm); 1133 SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0); 1134 1135 memset(dialog_info, 0, sizeof(*dialog_info)); 1136 dialog_info->taskconfig = taskconfig; 1137 dialog_info->hwnd = hwnd; 1138 dialog_info->font = CreateFontIndirectW(&ncm.lfMessageFont); 1139 1140 hdc = GetDC(dialog_info->hwnd); 1141 SelectObject(hdc, dialog_info->font); 1142 dialog_info->m.x_baseunit = GdiGetCharDimensions(hdc, NULL, &dialog_info->m.y_baseunit); 1143 ReleaseDC(dialog_info->hwnd, hdc); 1144 1145 dialog_info->m.h_spacing = DIALOG_SPACING; 1146 dialog_info->m.v_spacing = DIALOG_SPACING; 1147 taskdialog_du_to_px(dialog_info, &dialog_info->m.h_spacing, &dialog_info->m.v_spacing); 1148 1149 if (taskconfig->dwFlags & TDF_CALLBACK_TIMER) 1150 { 1151 SetTimer(hwnd, ID_TIMER, DIALOG_TIMER_MS, NULL); 1152 dialog_info->last_timer_tick = GetTickCount(); 1153 } 1154 1155 taskdialog_add_main_icon(dialog_info); 1156 taskdialog_add_main_instruction(dialog_info); 1157 taskdialog_add_content(dialog_info); 1158 taskdialog_add_expanded_info(dialog_info); 1159 taskdialog_add_progress_bar(dialog_info); 1160 taskdialog_add_radio_buttons(dialog_info); 1161 taskdialog_add_command_links(dialog_info); 1162 taskdialog_add_expando_button(dialog_info); 1163 taskdialog_add_verification_box(dialog_info); 1164 taskdialog_add_buttons(dialog_info); 1165 taskdialog_add_footer_icon(dialog_info); 1166 taskdialog_add_footer_text(dialog_info); 1167 1168 /* Set default button */ 1169 if (!dialog_info->default_button && dialog_info->command_links) 1170 dialog_info->default_button = dialog_info->command_links[0]; 1171 if (!dialog_info->default_button) dialog_info->default_button = dialog_info->buttons[0]; 1172 SendMessageW(dialog_info->hwnd, WM_NEXTDLGCTL, (WPARAM)dialog_info->default_button, TRUE); 1173 id = GetWindowLongW(dialog_info->default_button, GWLP_ID); 1174 SendMessageW(dialog_info->hwnd, DM_SETDEFID, id, 0); 1175 1176 dialog_info->has_cancel = 1177 (taskconfig->dwFlags & TDF_ALLOW_DIALOG_CANCELLATION) 1178 || taskdialog_find_button(dialog_info->command_links, dialog_info->command_link_count, IDCANCEL) 1179 || taskdialog_find_button(dialog_info->buttons, dialog_info->button_count, IDCANCEL); 1180 1181 if (!dialog_info->has_cancel) DeleteMenu(GetSystemMenu(hwnd, FALSE), SC_CLOSE, MF_BYCOMMAND); 1182 1183 taskdialog_layout(dialog_info); 1184 } 1185 1186 static BOOL CALLBACK takdialog_destroy_control(HWND hwnd, LPARAM lParam) 1187 { 1188 DestroyWindow(hwnd); 1189 return TRUE; 1190 } 1191 1192 static void taskdialog_destroy(struct taskdialog_info *dialog_info) 1193 { 1194 EnumChildWindows(dialog_info->hwnd, takdialog_destroy_control, 0); 1195 1196 if (dialog_info->taskconfig->dwFlags & TDF_CALLBACK_TIMER) KillTimer(dialog_info->hwnd, ID_TIMER); 1197 if (dialog_info->font) DeleteObject(dialog_info->font); 1198 if (dialog_info->main_instruction_font) DeleteObject(dialog_info->main_instruction_font); 1199 Free(dialog_info->buttons); 1200 Free(dialog_info->radio_buttons); 1201 Free(dialog_info->command_links); 1202 Free(dialog_info->expanded_text); 1203 Free(dialog_info->collapsed_text); 1204 } 1205 1206 static INT_PTR CALLBACK taskdialog_proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) 1207 { 1208 static const WCHAR taskdialog_info_propnameW[] = {'T','a','s','k','D','i','a','l','o','g','I','n','f','o',0}; 1209 struct taskdialog_info *dialog_info; 1210 LRESULT result; 1211 1212 TRACE("hwnd=%p msg=0x%04x wparam=%lx lparam=%lx\n", hwnd, msg, wParam, lParam); 1213 1214 if (msg != WM_INITDIALOG) 1215 dialog_info = GetPropW(hwnd, taskdialog_info_propnameW); 1216 1217 switch (msg) 1218 { 1219 case TDM_NAVIGATE_PAGE: 1220 dialog_info->taskconfig = (const TASKDIALOGCONFIG *)lParam; 1221 taskdialog_destroy(dialog_info); 1222 taskdialog_init(dialog_info, hwnd); 1223 taskdialog_notify(dialog_info, TDN_DIALOG_CONSTRUCTED, 0, 0); 1224 /* Default radio button click notification is sent before TDN_NAVIGATED */ 1225 taskdialog_check_default_radio_buttons(dialog_info); 1226 taskdialog_notify(dialog_info, TDN_NAVIGATED, 0, 0); 1227 break; 1228 case TDM_CLICK_BUTTON: 1229 taskdialog_click_button(dialog_info, wParam); 1230 break; 1231 case TDM_ENABLE_BUTTON: 1232 taskdialog_enable_button(dialog_info, wParam, lParam); 1233 break; 1234 case TDM_SET_MARQUEE_PROGRESS_BAR: 1235 { 1236 BOOL marquee = wParam; 1237 LONG style; 1238 if(!dialog_info->progress_bar) break; 1239 style = GetWindowLongW(dialog_info->progress_bar, GWL_STYLE); 1240 style = marquee ? style | PBS_MARQUEE : style & (~PBS_MARQUEE); 1241 SetWindowLongW(dialog_info->progress_bar, GWL_STYLE, style); 1242 break; 1243 } 1244 case TDM_SET_PROGRESS_BAR_STATE: 1245 result = SendMessageW(dialog_info->progress_bar, PBM_SETSTATE, wParam, 0); 1246 SetWindowLongPtrW(hwnd, DWLP_MSGRESULT, result); 1247 break; 1248 case TDM_SET_PROGRESS_BAR_RANGE: 1249 result = SendMessageW(dialog_info->progress_bar, PBM_SETRANGE, 0, lParam); 1250 SetWindowLongPtrW(hwnd, DWLP_MSGRESULT, result); 1251 break; 1252 case TDM_SET_PROGRESS_BAR_POS: 1253 result = 0; 1254 if (dialog_info->progress_bar) 1255 { 1256 LONG style = GetWindowLongW(dialog_info->progress_bar, GWL_STYLE); 1257 if (!(style & PBS_MARQUEE)) result = SendMessageW(dialog_info->progress_bar, PBM_SETPOS, wParam, 0); 1258 } 1259 SetWindowLongPtrW(hwnd, DWLP_MSGRESULT, result); 1260 break; 1261 case TDM_SET_PROGRESS_BAR_MARQUEE: 1262 SendMessageW(dialog_info->progress_bar, PBM_SETMARQUEE, wParam, lParam); 1263 break; 1264 case TDM_SET_ELEMENT_TEXT: 1265 taskdialog_set_element_text(dialog_info, wParam, (const WCHAR *)lParam); 1266 taskdialog_layout(dialog_info); 1267 break; 1268 case TDM_UPDATE_ELEMENT_TEXT: 1269 taskdialog_set_element_text(dialog_info, wParam, (const WCHAR *)lParam); 1270 break; 1271 case TDM_CLICK_RADIO_BUTTON: 1272 taskdialog_click_radio_button(dialog_info, wParam); 1273 break; 1274 case TDM_ENABLE_RADIO_BUTTON: 1275 taskdialog_enable_radio_button(dialog_info, wParam, lParam); 1276 break; 1277 case TDM_CLICK_VERIFICATION: 1278 { 1279 BOOL checked = (BOOL)wParam; 1280 BOOL focused = (BOOL)lParam; 1281 dialog_info->verification_checked = checked; 1282 if (dialog_info->verification_box) 1283 { 1284 SendMessageW(dialog_info->verification_box, BM_SETCHECK, checked ? BST_CHECKED : BST_UNCHECKED, 0); 1285 taskdialog_notify(dialog_info, TDN_VERIFICATION_CLICKED, checked, 0); 1286 if (focused) SetFocus(dialog_info->verification_box); 1287 } 1288 break; 1289 } 1290 case TDM_SET_BUTTON_ELEVATION_REQUIRED_STATE: 1291 taskdialog_button_set_shield(dialog_info, wParam, lParam); 1292 break; 1293 case TDM_UPDATE_ICON: 1294 taskdialog_set_icon(dialog_info, wParam, (HICON)lParam); 1295 break; 1296 case WM_INITDIALOG: 1297 dialog_info = (struct taskdialog_info *)lParam; 1298 1299 taskdialog_init(dialog_info, hwnd); 1300 1301 SetPropW(hwnd, taskdialog_info_propnameW, dialog_info); 1302 taskdialog_notify(dialog_info, TDN_DIALOG_CONSTRUCTED, 0, 0); 1303 taskdialog_notify(dialog_info, TDN_CREATED, 0, 0); 1304 /* Default radio button click notification sent after TDN_CREATED */ 1305 taskdialog_check_default_radio_buttons(dialog_info); 1306 return FALSE; 1307 case WM_COMMAND: 1308 if (HIWORD(wParam) == BN_CLICKED) 1309 { 1310 taskdialog_on_button_click(dialog_info, (HWND)lParam, LOWORD(wParam)); 1311 break; 1312 } 1313 return FALSE; 1314 case WM_HELP: 1315 taskdialog_notify(dialog_info, TDN_HELP, 0, 0); 1316 break; 1317 case WM_TIMER: 1318 if (ID_TIMER == wParam) 1319 { 1320 DWORD elapsed = GetTickCount() - dialog_info->last_timer_tick; 1321 if (taskdialog_notify(dialog_info, TDN_TIMER, elapsed, 0) == S_FALSE) 1322 dialog_info->last_timer_tick = GetTickCount(); 1323 } 1324 break; 1325 case WM_NOTIFY: 1326 { 1327 PNMLINK pnmLink = (PNMLINK)lParam; 1328 HWND hwndFrom = pnmLink->hdr.hwndFrom; 1329 if ((taskdialog_hyperlink_enabled(dialog_info)) 1330 && (hwndFrom == dialog_info->content || hwndFrom == dialog_info->expanded_info 1331 || hwndFrom == dialog_info->footer_text) 1332 && (pnmLink->hdr.code == NM_CLICK || pnmLink->hdr.code == NM_RETURN)) 1333 { 1334 taskdialog_notify(dialog_info, TDN_HYPERLINK_CLICKED, 0, (LPARAM)pnmLink->item.szUrl); 1335 break; 1336 } 1337 return FALSE; 1338 } 1339 case WM_DRAWITEM: 1340 { 1341 LPDRAWITEMSTRUCT dis = (LPDRAWITEMSTRUCT)lParam; 1342 if (dis->hwndItem == dialog_info->expando_button) 1343 { 1344 taskdialog_draw_expando_control(dialog_info, dis); 1345 SetWindowLongPtrW(hwnd, DWLP_MSGRESULT, TRUE); 1346 break; 1347 } 1348 return FALSE; 1349 } 1350 case WM_DESTROY: 1351 taskdialog_notify(dialog_info, TDN_DESTROYED, 0, 0); 1352 RemovePropW(hwnd, taskdialog_info_propnameW); 1353 taskdialog_destroy(dialog_info); 1354 break; 1355 case WM_CLOSE: 1356 if (dialog_info->has_cancel) 1357 { 1358 if(taskdialog_notify(dialog_info, TDN_BUTTON_CLICKED, IDCANCEL, 0) == S_OK) 1359 EndDialog(hwnd, IDCANCEL); 1360 SetWindowLongPtrW(hwnd, DWLP_MSGRESULT, 0); 1361 break; 1362 } 1363 return FALSE; 1364 default: 1365 return FALSE; 1366 } 1367 return TRUE; 1368 } 1369 1370 /*********************************************************************** 1371 * TaskDialogIndirect [COMCTL32.@] 1372 */ 1373 HRESULT WINAPI TaskDialogIndirect(const TASKDIALOGCONFIG *taskconfig, int *button, 1374 int *radio_button, BOOL *verification_flag_checked) 1375 { 1376 struct taskdialog_info dialog_info; 1377 DLGTEMPLATE *template; 1378 INT ret; 1379 1380 TRACE("%p, %p, %p, %p\n", taskconfig, button, radio_button, verification_flag_checked); 1381 1382 if (!taskconfig || taskconfig->cbSize != sizeof(TASKDIALOGCONFIG)) 1383 return E_INVALIDARG; 1384 1385 dialog_info.taskconfig = taskconfig; 1386 1387 template = create_taskdialog_template(taskconfig); 1388 ret = (short)DialogBoxIndirectParamW(taskconfig->hInstance, template, taskconfig->hwndParent, 1389 taskdialog_proc, (LPARAM)&dialog_info); 1390 Free(template); 1391 1392 if (button) *button = ret; 1393 if (radio_button) *radio_button = dialog_info.selected_radio_id; 1394 if (verification_flag_checked) *verification_flag_checked = dialog_info.verification_checked; 1395 1396 return S_OK; 1397 } 1398 1399 /*********************************************************************** 1400 * TaskDialog [COMCTL32.@] 1401 */ 1402 HRESULT WINAPI TaskDialog(HWND owner, HINSTANCE hinst, const WCHAR *title, const WCHAR *main_instruction, 1403 const WCHAR *content, TASKDIALOG_COMMON_BUTTON_FLAGS common_buttons, const WCHAR *icon, int *button) 1404 { 1405 TASKDIALOGCONFIG taskconfig; 1406 1407 TRACE("%p, %p, %s, %s, %s, %#x, %s, %p\n", owner, hinst, debugstr_w(title), debugstr_w(main_instruction), 1408 debugstr_w(content), common_buttons, debugstr_w(icon), button); 1409 1410 memset(&taskconfig, 0, sizeof(taskconfig)); 1411 taskconfig.cbSize = sizeof(taskconfig); 1412 taskconfig.hwndParent = owner; 1413 taskconfig.hInstance = hinst; 1414 taskconfig.dwCommonButtons = common_buttons; 1415 taskconfig.pszWindowTitle = title; 1416 taskconfig.u.pszMainIcon = icon; 1417 taskconfig.pszMainInstruction = main_instruction; 1418 taskconfig.pszContent = content; 1419 return TaskDialogIndirect(&taskconfig, button, NULL, NULL); 1420 } 1421