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