xref: /reactos/dll/win32/comctl32/taskdialog.c (revision 0707475f)
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 
taskdialog_du_to_px(struct taskdialog_info * dialog_info,LONG * width,LONG * height)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 
template_write_data(char ** ptr,const void * src,unsigned int size)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 
taskdialog_get_reference_rect(const TASKDIALOGCONFIG * taskconfig,RECT * ret)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 
taskdialog_get_exe_name(WCHAR * name,DWORD length)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 
create_taskdialog_template(const TASKDIALOGCONFIG * taskconfig)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 
taskdialog_find_button(HWND * buttons,INT count,INT id)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 
taskdialog_enable_button(const struct taskdialog_info * dialog_info,INT id,BOOL enable)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 
taskdialog_click_button(struct taskdialog_info * dialog_info,INT id)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 
taskdialog_button_set_shield(const struct taskdialog_info * dialog_info,INT id,BOOL elevate)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 
taskdialog_enable_radio_button(const struct taskdialog_info * dialog_info,INT id,BOOL enable)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 
taskdialog_click_radio_button(const struct taskdialog_info * dialog_info,INT id)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 
taskdialog_notify(struct taskdialog_info * dialog_info,UINT notification,WPARAM wparam,LPARAM lparam)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 
taskdialog_move_controls_vertically(HWND parent,HWND * controls,INT count,INT offset)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 
taskdialog_toggle_expando_control(struct taskdialog_info * dialog_info)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 
taskdialog_on_button_click(struct taskdialog_info * dialog_info,HWND hwnd,WORD id)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 
taskdialog_gettext(struct taskdialog_info * dialog_info,BOOL user_resource,const WCHAR * text)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 
taskdialog_hyperlink_enabled(struct taskdialog_info * dialog_info)370 static BOOL taskdialog_hyperlink_enabled(struct taskdialog_info *dialog_info)
371 {
372     return dialog_info->taskconfig->dwFlags & TDF_ENABLE_HYPERLINKS;
373 }
374 
taskdialog_use_command_link(struct taskdialog_info * dialog_info)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 
taskdialog_get_label_size(struct taskdialog_info * dialog_info,HWND hwnd,LONG max_width,SIZE * size,BOOL syslink)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 
taskdialog_get_button_size(HWND hwnd,LONG max_width,SIZE * size)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 
taskdialog_get_expando_size(struct taskdialog_info * dialog_info,HWND hwnd,SIZE * size)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 
taskdialog_get_standard_icon(LPCWSTR icon)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 
taskdialog_set_icon(struct taskdialog_info * dialog_info,INT element,HICON icon)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 
taskdialog_set_element_text(struct taskdialog_info * dialog_info,TASKDIALOG_ELEMENTS element,const WCHAR * text)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 
taskdialog_check_default_radio_buttons(struct taskdialog_info * dialog_info)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 
taskdialog_add_main_icon(struct taskdialog_info * dialog_info)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 
taskdialog_create_label(struct taskdialog_info * dialog_info,const WCHAR * text,HFONT font,BOOL syslink)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 
taskdialog_add_main_instruction(struct taskdialog_info * dialog_info)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 
taskdialog_add_content(struct taskdialog_info * dialog_info)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 
taskdialog_add_progress_bar(struct taskdialog_info * dialog_info)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 
taskdialog_add_radio_buttons(struct taskdialog_info * dialog_info)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 
taskdialog_add_command_links(struct taskdialog_info * dialog_info)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 
taskdialog_add_expanded_info(struct taskdialog_info * dialog_info)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 
taskdialog_add_expando_button(struct taskdialog_info * dialog_info)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 
taskdialog_add_verification_box(struct taskdialog_info * dialog_info)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 
taskdialog_add_button(struct taskdialog_info * dialog_info,HWND * button,INT_PTR id,const WCHAR * text,BOOL custom_button)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 
taskdialog_add_buttons(struct taskdialog_info * dialog_info)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 
taskdialog_add_footer_icon(struct taskdialog_info * dialog_info)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 
taskdialog_add_footer_text(struct taskdialog_info * dialog_info)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 
taskdialog_get_dialog_width(struct taskdialog_info * dialog_info)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 
taskdialog_label_layout(struct taskdialog_info * dialog_info,HWND hwnd,INT start_x,LONG dialog_width,LONG * dialog_height,BOOL syslink)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 
taskdialog_layout(struct taskdialog_info * dialog_info)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 
taskdialog_draw_expando_control(struct taskdialog_info * dialog_info,LPDRAWITEMSTRUCT dis)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 
taskdialog_init(struct taskdialog_info * dialog_info,HWND hwnd)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 
takdialog_destroy_control(HWND hwnd,LPARAM lParam)1186 static BOOL CALLBACK takdialog_destroy_control(HWND hwnd, LPARAM lParam)
1187 {
1188     DestroyWindow(hwnd);
1189     return TRUE;
1190 }
1191 
taskdialog_destroy(struct taskdialog_info * dialog_info)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 
taskdialog_proc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)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  */
TaskDialogIndirect(const TASKDIALOGCONFIG * taskconfig,int * button,int * radio_button,BOOL * verification_flag_checked)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  */
TaskDialog(HWND owner,HINSTANCE hinst,const WCHAR * title,const WCHAR * main_instruction,const WCHAR * content,TASKDIALOG_COMMON_BUTTON_FLAGS common_buttons,const WCHAR * icon,int * button)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