1 /* 2 * Task dialog control 3 * 4 * Copyright 2017 Fabian Maurer 5 * 6 * This library is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU Lesser General Public 8 * License as published by the Free Software Foundation; either 9 * version 2.1 of the License, or (at your option) any later version. 10 * 11 * This library is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 * Lesser General Public License for more details. 15 * 16 * You should have received a copy of the GNU Lesser General Public 17 * License along with this library; if not, write to the Free Software 18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 19 * 20 */ 21 22 #include <stdarg.h> 23 #include <stdlib.h> 24 #include <string.h> 25 26 #define NONAMELESSUNION 27 28 #include "windef.h" 29 #include "winbase.h" 30 #include "wingdi.h" 31 #include "winuser.h" 32 #include "commctrl.h" 33 #include "winerror.h" 34 #include "comctl32.h" 35 36 #include "wine/debug.h" 37 #include "wine/list.h" 38 #include "wine/unicode.h" 39 40 WINE_DEFAULT_DEBUG_CHANNEL(taskdialog); 41 42 #define ALIGNED_LENGTH(_Len, _Align) (((_Len)+(_Align))&~(_Align)) 43 #define ALIGNED_POINTER(_Ptr, _Align) ((LPVOID)ALIGNED_LENGTH((ULONG_PTR)(_Ptr), _Align)) 44 #define ALIGN_LENGTH(_Len, _Align) _Len = ALIGNED_LENGTH(_Len, _Align) 45 #define ALIGN_POINTER(_Ptr, _Align) _Ptr = ALIGNED_POINTER(_Ptr, _Align) 46 47 static const UINT DIALOG_MIN_WIDTH = 240; 48 static const UINT DIALOG_SPACING = 5; 49 static const UINT DIALOG_BUTTON_WIDTH = 50; 50 static const UINT DIALOG_BUTTON_HEIGHT = 14; 51 52 static const UINT ID_MAIN_INSTRUCTION = 0xf000; 53 static const UINT ID_CONTENT = 0xf001; 54 55 struct taskdialog_control 56 { 57 struct list entry; 58 DLGITEMTEMPLATE *template; 59 unsigned int template_size; 60 }; 61 62 struct taskdialog_button_desc 63 { 64 int id; 65 const WCHAR *text; 66 unsigned int width; 67 unsigned int line; 68 HINSTANCE hinst; 69 }; 70 71 struct taskdialog_template_desc 72 { 73 const TASKDIALOGCONFIG *taskconfig; 74 unsigned int dialog_height; 75 unsigned int dialog_width; 76 struct list controls; 77 WORD control_count; 78 LONG x_baseunit; 79 LONG y_baseunit; 80 HFONT font; 81 struct taskdialog_button_desc *default_button; 82 }; 83 84 struct taskdialog_info 85 { 86 HWND hwnd; 87 PFTASKDIALOGCALLBACK callback; 88 LONG_PTR callback_data; 89 }; 90 91 static void pixels_to_dialogunits(const struct taskdialog_template_desc *desc, LONG *width, LONG *height) 92 { 93 if (width) 94 *width = MulDiv(*width, 4, desc->x_baseunit); 95 if (height) 96 *height = MulDiv(*height, 8, desc->y_baseunit); 97 } 98 99 static void dialogunits_to_pixels(const struct taskdialog_template_desc *desc, LONG *width, LONG *height) 100 { 101 if (width) 102 *width = MulDiv(*width, desc->x_baseunit, 4); 103 if (height) 104 *height = MulDiv(*height, desc->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 /* used to calculate size for the controls */ 114 static void taskdialog_get_text_extent(const struct taskdialog_template_desc *desc, const WCHAR *text, 115 BOOL user_resource, SIZE *sz) 116 { 117 RECT rect = { 0, 0, desc->dialog_width - DIALOG_SPACING * 2, 0}; /* padding left and right of the control */ 118 const WCHAR *textW = NULL; 119 static const WCHAR nulW; 120 unsigned int length; 121 HFONT oldfont; 122 HDC hdc; 123 124 if (IS_INTRESOURCE(text)) 125 { 126 if (!(length = LoadStringW(user_resource ? desc->taskconfig->hInstance : COMCTL32_hModule, 127 (UINT_PTR)text, (WCHAR *)&textW, 0))) 128 { 129 WARN("Failed to load text\n"); 130 textW = &nulW; 131 length = 0; 132 } 133 } 134 else 135 { 136 textW = text; 137 length = strlenW(textW); 138 } 139 140 hdc = GetDC(0); 141 oldfont = SelectObject(hdc, desc->font); 142 143 dialogunits_to_pixels(desc, &rect.right, NULL); 144 DrawTextW(hdc, textW, length, &rect, DT_LEFT | DT_EXPANDTABS | DT_CALCRECT | DT_WORDBREAK); 145 pixels_to_dialogunits(desc, &rect.right, &rect.bottom); 146 147 SelectObject(hdc, oldfont); 148 ReleaseDC(0, hdc); 149 150 sz->cx = rect.right - rect.left; 151 sz->cy = rect.bottom - rect.top; 152 } 153 154 static unsigned int taskdialog_add_control(struct taskdialog_template_desc *desc, WORD id, const WCHAR *class, 155 HINSTANCE hInstance, const WCHAR *text, DWORD style, short x, short y, short cx, short cy) 156 { 157 struct taskdialog_control *control = Alloc(sizeof(*control)); 158 unsigned int size, class_size, text_size; 159 DLGITEMTEMPLATE *template; 160 static const WCHAR nulW; 161 const WCHAR *textW; 162 char *ptr; 163 164 class_size = (strlenW(class) + 1) * sizeof(WCHAR); 165 166 if (IS_INTRESOURCE(text)) 167 text_size = LoadStringW(hInstance, (UINT_PTR)text, (WCHAR *)&textW, 0) * sizeof(WCHAR); 168 else 169 { 170 textW = text; 171 text_size = strlenW(textW) * sizeof(WCHAR); 172 } 173 174 size = sizeof(DLGITEMTEMPLATE); 175 size += class_size; 176 size += text_size + sizeof(WCHAR); 177 size += sizeof(WORD); /* creation data */ 178 179 control->template = template = Alloc(size); 180 control->template_size = size; 181 182 template->style = WS_VISIBLE | style; 183 template->dwExtendedStyle = 0; 184 template->x = x; 185 template->y = y; 186 template->cx = cx; 187 template->cy = cy; 188 template->id = id; 189 ptr = (char *)(template + 1); 190 template_write_data(&ptr, class, class_size); 191 template_write_data(&ptr, textW, text_size); 192 template_write_data(&ptr, &nulW, sizeof(nulW)); 193 194 list_add_tail(&desc->controls, &control->entry); 195 desc->control_count++; 196 return ALIGNED_LENGTH(size, 3); 197 } 198 199 static unsigned int taskdialog_add_static_label(struct taskdialog_template_desc *desc, WORD id, const WCHAR *str) 200 { 201 unsigned int size; 202 SIZE sz; 203 204 if (!str) 205 return 0; 206 207 taskdialog_get_text_extent(desc, str, TRUE, &sz); 208 209 desc->dialog_height += DIALOG_SPACING; 210 size = taskdialog_add_control(desc, id, WC_STATICW, desc->taskconfig->hInstance, str, 0, DIALOG_SPACING, 211 desc->dialog_height, sz.cx, sz.cy); 212 desc->dialog_height += sz.cy + DIALOG_SPACING; 213 return size; 214 } 215 216 static unsigned int taskdialog_add_main_instruction(struct taskdialog_template_desc *desc) 217 { 218 return taskdialog_add_static_label(desc, ID_MAIN_INSTRUCTION, desc->taskconfig->pszMainInstruction); 219 } 220 221 static unsigned int taskdialog_add_content(struct taskdialog_template_desc *desc) 222 { 223 return taskdialog_add_static_label(desc, ID_CONTENT, desc->taskconfig->pszContent); 224 } 225 226 static void taskdialog_init_button(struct taskdialog_button_desc *button, struct taskdialog_template_desc *desc, 227 int id, const WCHAR *text, BOOL custom_button) 228 { 229 SIZE sz; 230 231 taskdialog_get_text_extent(desc, text, custom_button, &sz); 232 233 button->id = id; 234 button->text = text; 235 button->width = max(DIALOG_BUTTON_WIDTH, sz.cx + DIALOG_SPACING * 2); 236 button->line = 0; 237 button->hinst = custom_button ? desc->taskconfig->hInstance : COMCTL32_hModule; 238 239 if (id == desc->taskconfig->nDefaultButton) 240 desc->default_button = button; 241 } 242 243 static void taskdialog_init_common_buttons(struct taskdialog_template_desc *desc, struct taskdialog_button_desc *buttons, 244 unsigned int *button_count) 245 { 246 DWORD flags = desc->taskconfig->dwCommonButtons; 247 248 #define TASKDIALOG_INIT_COMMON_BUTTON(id) \ 249 do { \ 250 taskdialog_init_button(&buttons[(*button_count)++], desc, ID##id, MAKEINTRESOURCEW(IDS_BUTTON_##id), FALSE); \ 251 } while(0) 252 253 if (flags & TDCBF_OK_BUTTON) 254 TASKDIALOG_INIT_COMMON_BUTTON(OK); 255 if (flags & TDCBF_YES_BUTTON) 256 TASKDIALOG_INIT_COMMON_BUTTON(YES); 257 if (flags & TDCBF_NO_BUTTON) 258 TASKDIALOG_INIT_COMMON_BUTTON(NO); 259 if (flags & TDCBF_RETRY_BUTTON) 260 TASKDIALOG_INIT_COMMON_BUTTON(RETRY); 261 if (flags & TDCBF_CANCEL_BUTTON) 262 TASKDIALOG_INIT_COMMON_BUTTON(CANCEL); 263 if (flags & TDCBF_CLOSE_BUTTON) 264 TASKDIALOG_INIT_COMMON_BUTTON(CLOSE); 265 266 #undef TASKDIALOG_INIT_COMMON_BUTTON 267 } 268 269 static unsigned int taskdialog_add_buttons(struct taskdialog_template_desc *desc) 270 { 271 unsigned int count = 0, buttons_size, i, line_count, size = 0; 272 unsigned int location_x, *line_widths, alignment = ~0u; 273 const TASKDIALOGCONFIG *taskconfig = desc->taskconfig; 274 struct taskdialog_button_desc *buttons; 275 276 /* Allocate enough memory for the custom and the default buttons. Maximum 6 default buttons possible. */ 277 buttons_size = 6; 278 if (taskconfig->cButtons && taskconfig->pButtons) 279 buttons_size += taskconfig->cButtons; 280 281 if (!(buttons = Alloc(buttons_size * sizeof(*buttons)))) 282 return 0; 283 284 /* Custom buttons */ 285 if (taskconfig->cButtons && taskconfig->pButtons) 286 for (i = 0; i < taskconfig->cButtons; i++) 287 taskdialog_init_button(&buttons[count++], desc, taskconfig->pButtons[i].nButtonID, 288 taskconfig->pButtons[i].pszButtonText, TRUE); 289 290 /* Common buttons */ 291 taskdialog_init_common_buttons(desc, buttons, &count); 292 293 /* There must be at least one button */ 294 if (count == 0) 295 taskdialog_init_button(&buttons[count++], desc, IDOK, MAKEINTRESOURCEW(IDS_BUTTON_OK), FALSE); 296 297 if (!desc->default_button) 298 desc->default_button = &buttons[0]; 299 300 /* For easy handling just allocate as many lines as buttons, the worst case. */ 301 line_widths = Alloc(count * sizeof(*line_widths)); 302 303 /* Separate buttons into lines */ 304 location_x = DIALOG_SPACING; 305 for (i = 0, line_count = 0; i < count; i++) 306 { 307 if (location_x + buttons[i].width + DIALOG_SPACING > desc->dialog_width) 308 { 309 location_x = DIALOG_SPACING; 310 line_count++; 311 } 312 313 buttons[i].line = line_count; 314 315 location_x += buttons[i].width + DIALOG_SPACING; 316 line_widths[line_count] += buttons[i].width + DIALOG_SPACING; 317 } 318 line_count++; 319 320 /* Try to balance lines so they are about the same size */ 321 for (i = 1; i < line_count - 1; i++) 322 { 323 int diff_now = abs(line_widths[i] - line_widths[i - 1]); 324 unsigned int j, last_button = 0; 325 int diff_changed; 326 327 for (j = 0; j < count; j++) 328 if (buttons[j].line == i - 1) 329 last_button = j; 330 331 /* Difference in length of both lines if we wrapped the last button from the last line into this one */ 332 diff_changed = abs(2 * buttons[last_button].width + line_widths[i] - line_widths[i - 1]); 333 334 if (diff_changed < diff_now) 335 { 336 buttons[last_button].line = i; 337 line_widths[i] += buttons[last_button].width; 338 line_widths[i - 1] -= buttons[last_button].width; 339 } 340 } 341 342 /* Calculate left alignment so all lines are as far right as possible. */ 343 for (i = 0; i < line_count; i++) 344 { 345 int new_alignment = desc->dialog_width - line_widths[i]; 346 if (new_alignment < alignment) 347 alignment = new_alignment; 348 } 349 350 /* Now that we got them all positioned, create all buttons */ 351 location_x = alignment; 352 for (i = 0; i < count; i++) 353 { 354 DWORD style = &buttons[i] == desc->default_button ? BS_DEFPUSHBUTTON : BS_PUSHBUTTON; 355 356 if (i > 0 && buttons[i].line != buttons[i - 1].line) /* New line */ 357 { 358 location_x = alignment; 359 desc->dialog_height += DIALOG_BUTTON_HEIGHT + DIALOG_SPACING; 360 } 361 362 size += taskdialog_add_control(desc, buttons[i].id, WC_BUTTONW, buttons[i].hinst, buttons[i].text, style, 363 location_x, desc->dialog_height, buttons[i].width, DIALOG_BUTTON_HEIGHT); 364 365 location_x += buttons[i].width + DIALOG_SPACING; 366 } 367 368 /* Add height for last row and spacing */ 369 desc->dialog_height += DIALOG_BUTTON_HEIGHT + DIALOG_SPACING; 370 371 Free(line_widths); 372 Free(buttons); 373 374 return size; 375 } 376 377 static void taskdialog_clear_controls(struct list *controls) 378 { 379 struct taskdialog_control *control, *control2; 380 381 LIST_FOR_EACH_ENTRY_SAFE(control, control2, controls, struct taskdialog_control, entry) 382 { 383 list_remove(&control->entry); 384 Free(control->template); 385 Free(control); 386 } 387 } 388 389 static unsigned int taskdialog_get_reference_rect(const struct taskdialog_template_desc *desc, RECT *ret) 390 { 391 HMONITOR monitor = MonitorFromWindow(desc->taskconfig->hwndParent ? desc->taskconfig->hwndParent : GetActiveWindow(), 392 MONITOR_DEFAULTTOPRIMARY); 393 MONITORINFO info; 394 395 info.cbSize = sizeof(info); 396 GetMonitorInfoW(monitor, &info); 397 398 if (desc->taskconfig->dwFlags & TDF_POSITION_RELATIVE_TO_WINDOW && desc->taskconfig->hwndParent) 399 GetWindowRect(desc->taskconfig->hwndParent, ret); 400 else 401 *ret = info.rcWork; 402 403 pixels_to_dialogunits(desc, &ret->left, &ret->top); 404 pixels_to_dialogunits(desc, &ret->right, &ret->bottom); 405 406 pixels_to_dialogunits(desc, &info.rcWork.left, &info.rcWork.top); 407 pixels_to_dialogunits(desc, &info.rcWork.right, &info.rcWork.bottom); 408 return info.rcWork.right - info.rcWork.left; 409 } 410 411 static WCHAR *taskdialog_get_exe_name(const TASKDIALOGCONFIG *taskconfig, WCHAR *name, DWORD length) 412 { 413 DWORD len = GetModuleFileNameW(NULL, name, length); 414 if (len && len < length) 415 { 416 WCHAR *p; 417 if ((p = strrchrW(name, '/'))) name = p + 1; 418 if ((p = strrchrW(name, '\\'))) name = p + 1; 419 return name; 420 } 421 else 422 return NULL; 423 } 424 425 static DLGTEMPLATE *create_taskdialog_template(const TASKDIALOGCONFIG *taskconfig) 426 { 427 struct taskdialog_control *control, *control2; 428 unsigned int size, title_size, screen_width; 429 struct taskdialog_template_desc desc; 430 static const WORD fontsize = 0x7fff; 431 static const WCHAR emptyW[] = { 0 }; 432 const WCHAR *titleW = NULL; 433 DLGTEMPLATE *template; 434 NONCLIENTMETRICSW ncm; 435 WCHAR pathW[MAX_PATH]; 436 RECT ref_rect; 437 char *ptr; 438 HDC hdc; 439 440 /* Window title */ 441 if (!taskconfig->pszWindowTitle) 442 titleW = taskdialog_get_exe_name(taskconfig, pathW, ARRAY_SIZE(pathW)); 443 else if (IS_INTRESOURCE(taskconfig->pszWindowTitle)) 444 { 445 if (!LoadStringW(taskconfig->hInstance, LOWORD(taskconfig->pszWindowTitle), (WCHAR *)&titleW, 0)) 446 titleW = taskdialog_get_exe_name(taskconfig, pathW, ARRAY_SIZE(pathW)); 447 } 448 else 449 titleW = taskconfig->pszWindowTitle; 450 if (!titleW) 451 titleW = emptyW; 452 title_size = (strlenW(titleW) + 1) * sizeof(WCHAR); 453 454 size = sizeof(DLGTEMPLATE) + 2 * sizeof(WORD); 455 size += title_size; 456 size += 2; /* font size */ 457 458 list_init(&desc.controls); 459 desc.taskconfig = taskconfig; 460 desc.control_count = 0; 461 462 ncm.cbSize = sizeof(ncm); 463 SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0); 464 desc.font = CreateFontIndirectW(&ncm.lfMessageFont); 465 466 hdc = GetDC(0); 467 SelectObject(hdc, desc.font); 468 desc.x_baseunit = GdiGetCharDimensions(hdc, NULL, &desc.y_baseunit); 469 ReleaseDC(0, hdc); 470 471 screen_width = taskdialog_get_reference_rect(&desc, &ref_rect); 472 473 desc.dialog_height = 0; 474 desc.dialog_width = max(taskconfig->cxWidth, DIALOG_MIN_WIDTH); 475 desc.dialog_width = min(desc.dialog_width, screen_width); 476 desc.default_button = NULL; 477 478 size += taskdialog_add_main_instruction(&desc); 479 size += taskdialog_add_content(&desc); 480 size += taskdialog_add_buttons(&desc); 481 482 template = Alloc(size); 483 if (!template) 484 { 485 taskdialog_clear_controls(&desc.controls); 486 DeleteObject(desc.font); 487 return NULL; 488 } 489 490 template->style = DS_MODALFRAME | DS_SETFONT | WS_CAPTION | WS_VISIBLE | WS_SYSMENU; 491 template->cdit = desc.control_count; 492 template->x = (ref_rect.left + ref_rect.right + desc.dialog_width) / 2; 493 template->y = (ref_rect.top + ref_rect.bottom + desc.dialog_height) / 2; 494 template->cx = desc.dialog_width; 495 template->cy = desc.dialog_height; 496 497 ptr = (char *)(template + 1); 498 ptr += 2; /* menu */ 499 ptr += 2; /* class */ 500 template_write_data(&ptr, titleW, title_size); 501 template_write_data(&ptr, &fontsize, sizeof(fontsize)); 502 503 /* write control entries */ 504 LIST_FOR_EACH_ENTRY_SAFE(control, control2, &desc.controls, struct taskdialog_control, entry) 505 { 506 ALIGN_POINTER(ptr, 3); 507 508 template_write_data(&ptr, control->template, control->template_size); 509 510 /* list item won't be needed later */ 511 list_remove(&control->entry); 512 Free(control->template); 513 Free(control); 514 } 515 516 DeleteObject(desc.font); 517 return template; 518 } 519 520 static HRESULT taskdialog_notify(struct taskdialog_info *dialog_info, UINT notification, WPARAM wparam, LPARAM lparam) 521 { 522 return dialog_info->callback ? dialog_info->callback(dialog_info->hwnd, notification, wparam, lparam, 523 dialog_info->callback_data) : S_OK; 524 } 525 526 static void taskdialog_on_button_click(struct taskdialog_info *dialog_info, WORD command_id) 527 { 528 if (taskdialog_notify(dialog_info, TDN_BUTTON_CLICKED, command_id, 0) == S_OK) 529 EndDialog(dialog_info->hwnd, command_id); 530 } 531 532 static INT_PTR CALLBACK taskdialog_proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) 533 { 534 static const WCHAR taskdialog_info_propnameW[] = {'T','a','s','k','D','i','a','l','o','g','I','n','f','o',0}; 535 struct taskdialog_info *dialog_info; 536 537 TRACE("hwnd=%p msg=0x%04x wparam=%lx lparam=%lx\n", hwnd, msg, wParam, lParam); 538 539 if (msg != WM_INITDIALOG) 540 dialog_info = GetPropW(hwnd, taskdialog_info_propnameW); 541 542 switch (msg) 543 { 544 case TDM_CLICK_BUTTON: 545 taskdialog_on_button_click(dialog_info, LOWORD(wParam)); 546 break; 547 case WM_INITDIALOG: 548 dialog_info = (struct taskdialog_info *)lParam; 549 dialog_info->hwnd = hwnd; 550 SetPropW(hwnd, taskdialog_info_propnameW, dialog_info); 551 552 taskdialog_notify(dialog_info, TDN_DIALOG_CONSTRUCTED, 0, 0); 553 break; 554 case WM_SHOWWINDOW: 555 taskdialog_notify(dialog_info, TDN_CREATED, 0, 0); 556 break; 557 case WM_COMMAND: 558 if (HIWORD(wParam) == BN_CLICKED) 559 { 560 taskdialog_on_button_click(dialog_info, LOWORD(wParam)); 561 return TRUE; 562 } 563 break; 564 case WM_DESTROY: 565 taskdialog_notify(dialog_info, TDN_DESTROYED, 0, 0); 566 RemovePropW(hwnd, taskdialog_info_propnameW); 567 break; 568 } 569 return FALSE; 570 } 571 572 /*********************************************************************** 573 * TaskDialogIndirect [COMCTL32.@] 574 */ 575 HRESULT WINAPI TaskDialogIndirect(const TASKDIALOGCONFIG *taskconfig, int *button, 576 int *radio_button, BOOL *verification_flag_checked) 577 { 578 struct taskdialog_info dialog_info; 579 DLGTEMPLATE *template; 580 INT ret; 581 582 TRACE("%p, %p, %p, %p\n", taskconfig, button, radio_button, verification_flag_checked); 583 584 if (!taskconfig || taskconfig->cbSize != sizeof(TASKDIALOGCONFIG)) 585 return E_INVALIDARG; 586 587 dialog_info.callback = taskconfig->pfCallback; 588 dialog_info.callback_data = taskconfig->lpCallbackData; 589 590 template = create_taskdialog_template(taskconfig); 591 ret = (short)DialogBoxIndirectParamW(taskconfig->hInstance, template, taskconfig->hwndParent, 592 taskdialog_proc, (LPARAM)&dialog_info); 593 Free(template); 594 595 if (button) *button = ret; 596 if (radio_button) *radio_button = taskconfig->nDefaultButton; 597 if (verification_flag_checked) *verification_flag_checked = TRUE; 598 599 return S_OK; 600 } 601 602 /*********************************************************************** 603 * TaskDialog [COMCTL32.@] 604 */ 605 HRESULT WINAPI TaskDialog(HWND owner, HINSTANCE hinst, const WCHAR *title, const WCHAR *main_instruction, 606 const WCHAR *content, TASKDIALOG_COMMON_BUTTON_FLAGS common_buttons, const WCHAR *icon, int *button) 607 { 608 TASKDIALOGCONFIG taskconfig; 609 610 TRACE("%p, %p, %s, %s, %s, %#x, %s, %p\n", owner, hinst, debugstr_w(title), debugstr_w(main_instruction), 611 debugstr_w(content), common_buttons, debugstr_w(icon), button); 612 613 memset(&taskconfig, 0, sizeof(taskconfig)); 614 taskconfig.cbSize = sizeof(taskconfig); 615 taskconfig.hwndParent = owner; 616 taskconfig.hInstance = hinst; 617 taskconfig.dwCommonButtons = common_buttons; 618 taskconfig.pszWindowTitle = title; 619 taskconfig.u.pszMainIcon = icon; 620 taskconfig.pszMainInstruction = main_instruction; 621 taskconfig.pszContent = content; 622 return TaskDialogIndirect(&taskconfig, button, NULL, NULL); 623 } 624