4 * Copyright 2017 Fabian Maurer
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.
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.
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
26 #define NONAMELESSUNION
36 #include "wine/debug.h"
37 #include "wine/list.h"
38 #include "wine/unicode.h"
40 WINE_DEFAULT_DEBUG_CHANNEL(taskdialog
);
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)
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;
52 static const UINT ID_MAIN_INSTRUCTION
= 0xf000;
53 static const UINT ID_CONTENT
= 0xf001;
55 struct taskdialog_control
58 DLGITEMTEMPLATE
*template;
59 unsigned int template_size
;
62 struct taskdialog_button_desc
71 struct taskdialog_template_desc
73 const TASKDIALOGCONFIG
*taskconfig
;
74 unsigned int dialog_height
;
75 unsigned int dialog_width
;
81 struct taskdialog_button_desc
*default_button
;
84 struct taskdialog_info
87 PFTASKDIALOGCALLBACK callback
;
88 LONG_PTR callback_data
;
91 static void pixels_to_dialogunits(const struct taskdialog_template_desc
*desc
, LONG
*width
, LONG
*height
)
94 *width
= MulDiv(*width
, 4, desc
->x_baseunit
);
96 *height
= MulDiv(*height
, 8, desc
->y_baseunit
);
99 static void dialogunits_to_pixels(const struct taskdialog_template_desc
*desc
, LONG
*width
, LONG
*height
)
102 *width
= MulDiv(*width
, desc
->x_baseunit
, 4);
104 *height
= MulDiv(*height
, desc
->y_baseunit
, 8);
107 static void template_write_data(char **ptr
, const void *src
, unsigned int size
)
109 memcpy(*ptr
, src
, size
);
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
)
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
;
124 if (IS_INTRESOURCE(text
))
126 if (!(length
= LoadStringW(user_resource
? desc
->taskconfig
->hInstance
: COMCTL32_hModule
,
127 (UINT_PTR
)text
, (WCHAR
*)&textW
, 0)))
129 WARN("Failed to load text\n");
137 length
= strlenW(textW
);
141 oldfont
= SelectObject(hdc
, desc
->font
);
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
);
147 SelectObject(hdc
, oldfont
);
150 sz
->cx
= rect
.right
- rect
.left
;
151 sz
->cy
= rect
.bottom
- rect
.top
;
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
)
157 struct taskdialog_control
*control
= Alloc(sizeof(*control
));
158 unsigned int size
, class_size
, text_size
;
159 DLGITEMTEMPLATE
*template;
160 static const WCHAR nulW
;
164 class_size
= (strlenW(class) + 1) * sizeof(WCHAR
);
166 if (IS_INTRESOURCE(text
))
167 text_size
= LoadStringW(hInstance
, (UINT_PTR
)text
, (WCHAR
*)&textW
, 0) * sizeof(WCHAR
);
171 text_size
= strlenW(textW
) * sizeof(WCHAR
);
174 size
= sizeof(DLGITEMTEMPLATE
);
176 size
+= text_size
+ sizeof(WCHAR
);
177 size
+= sizeof(WORD
); /* creation data */
179 control
->template = template = Alloc(size
);
180 control
->template_size
= size
;
182 template->style
= WS_VISIBLE
| style
;
183 template->dwExtendedStyle
= 0;
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
));
194 list_add_tail(&desc
->controls
, &control
->entry
);
195 desc
->control_count
++;
196 return ALIGNED_LENGTH(size
, 3);
199 static unsigned int taskdialog_add_static_label(struct taskdialog_template_desc
*desc
, WORD id
, const WCHAR
*str
)
207 taskdialog_get_text_extent(desc
, str
, TRUE
, &sz
);
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
;
216 static unsigned int taskdialog_add_main_instruction(struct taskdialog_template_desc
*desc
)
218 return taskdialog_add_static_label(desc
, ID_MAIN_INSTRUCTION
, desc
->taskconfig
->pszMainInstruction
);
221 static unsigned int taskdialog_add_content(struct taskdialog_template_desc
*desc
)
223 return taskdialog_add_static_label(desc
, ID_CONTENT
, desc
->taskconfig
->pszContent
);
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
)
231 taskdialog_get_text_extent(desc
, text
, custom_button
, &sz
);
235 button
->width
= max(DIALOG_BUTTON_WIDTH
, sz
.cx
+ DIALOG_SPACING
* 2);
237 button
->hinst
= custom_button
? desc
->taskconfig
->hInstance
: COMCTL32_hModule
;
239 if (id
== desc
->taskconfig
->nDefaultButton
)
240 desc
->default_button
= button
;
243 static void taskdialog_init_common_buttons(struct taskdialog_template_desc
*desc
, struct taskdialog_button_desc
*buttons
,
244 unsigned int *button_count
)
246 DWORD flags
= desc
->taskconfig
->dwCommonButtons
;
248 #define TASKDIALOG_INIT_COMMON_BUTTON(id) \
250 taskdialog_init_button(&buttons[(*button_count)++], desc, ID##id, MAKEINTRESOURCEW(IDS_BUTTON_##id), FALSE); \
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
);
266 #undef TASKDIALOG_INIT_COMMON_BUTTON
269 static unsigned int taskdialog_add_buttons(struct taskdialog_template_desc
*desc
)
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
;
276 /* Allocate enough memory for the custom and the default buttons. Maximum 6 default buttons possible. */
278 if (taskconfig
->cButtons
&& taskconfig
->pButtons
)
279 buttons_size
+= taskconfig
->cButtons
;
281 if (!(buttons
= Alloc(buttons_size
* sizeof(*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
);
291 taskdialog_init_common_buttons(desc
, buttons
, &count
);
293 /* There must be at least one button */
295 taskdialog_init_button(&buttons
[count
++], desc
, IDOK
, MAKEINTRESOURCEW(IDS_BUTTON_OK
), FALSE
);
297 if (!desc
->default_button
)
298 desc
->default_button
= &buttons
[0];
300 /* For easy handling just allocate as many lines as buttons, the worst case. */
301 line_widths
= Alloc(count
* sizeof(*line_widths
));
303 /* Separate buttons into lines */
304 location_x
= DIALOG_SPACING
;
305 for (i
= 0, line_count
= 0; i
< count
; i
++)
307 if (location_x
+ buttons
[i
].width
+ DIALOG_SPACING
> desc
->dialog_width
)
309 location_x
= DIALOG_SPACING
;
313 buttons
[i
].line
= line_count
;
315 location_x
+= buttons
[i
].width
+ DIALOG_SPACING
;
316 line_widths
[line_count
] += buttons
[i
].width
+ DIALOG_SPACING
;
320 /* Try to balance lines so they are about the same size */
321 for (i
= 1; i
< line_count
- 1; i
++)
323 int diff_now
= abs(line_widths
[i
] - line_widths
[i
- 1]);
324 unsigned int j
, last_button
= 0;
327 for (j
= 0; j
< count
; j
++)
328 if (buttons
[j
].line
== i
- 1)
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]);
334 if (diff_changed
< diff_now
)
336 buttons
[last_button
].line
= i
;
337 line_widths
[i
] += buttons
[last_button
].width
;
338 line_widths
[i
- 1] -= buttons
[last_button
].width
;
342 /* Calculate left alignment so all lines are as far right as possible. */
343 for (i
= 0; i
< line_count
; i
++)
345 int new_alignment
= desc
->dialog_width
- line_widths
[i
];
346 if (new_alignment
< alignment
)
347 alignment
= new_alignment
;
350 /* Now that we got them all positioned, create all buttons */
351 location_x
= alignment
;
352 for (i
= 0; i
< count
; i
++)
354 DWORD style
= &buttons
[i
] == desc
->default_button
? BS_DEFPUSHBUTTON
: BS_PUSHBUTTON
;
356 if (i
> 0 && buttons
[i
].line
!= buttons
[i
- 1].line
) /* New line */
358 location_x
= alignment
;
359 desc
->dialog_height
+= DIALOG_BUTTON_HEIGHT
+ DIALOG_SPACING
;
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
);
365 location_x
+= buttons
[i
].width
+ DIALOG_SPACING
;
368 /* Add height for last row and spacing */
369 desc
->dialog_height
+= DIALOG_BUTTON_HEIGHT
+ DIALOG_SPACING
;
377 static void taskdialog_clear_controls(struct list
*controls
)
379 struct taskdialog_control
*control
, *control2
;
381 LIST_FOR_EACH_ENTRY_SAFE(control
, control2
, controls
, struct taskdialog_control
, entry
)
383 list_remove(&control
->entry
);
384 Free(control
->template);
389 static unsigned int taskdialog_get_reference_rect(const struct taskdialog_template_desc
*desc
, RECT
*ret
)
391 HMONITOR monitor
= MonitorFromWindow(desc
->taskconfig
->hwndParent
? desc
->taskconfig
->hwndParent
: GetActiveWindow(),
392 MONITOR_DEFAULTTOPRIMARY
);
395 info
.cbSize
= sizeof(info
);
396 GetMonitorInfoW(monitor
, &info
);
398 if (desc
->taskconfig
->dwFlags
& TDF_POSITION_RELATIVE_TO_WINDOW
&& desc
->taskconfig
->hwndParent
)
399 GetWindowRect(desc
->taskconfig
->hwndParent
, ret
);
403 pixels_to_dialogunits(desc
, &ret
->left
, &ret
->top
);
404 pixels_to_dialogunits(desc
, &ret
->right
, &ret
->bottom
);
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
;
411 static WCHAR
*taskdialog_get_exe_name(const TASKDIALOGCONFIG
*taskconfig
, WCHAR
*name
, DWORD length
)
413 DWORD len
= GetModuleFileNameW(NULL
, name
, length
);
414 if (len
&& len
< length
)
417 if ((p
= strrchrW(name
, '/'))) name
= p
+ 1;
418 if ((p
= strrchrW(name
, '\\'))) name
= p
+ 1;
425 static DLGTEMPLATE
*create_taskdialog_template(const TASKDIALOGCONFIG
*taskconfig
)
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
];
441 if (!taskconfig
->pszWindowTitle
)
442 titleW
= taskdialog_get_exe_name(taskconfig
, pathW
, ARRAY_SIZE(pathW
));
443 else if (IS_INTRESOURCE(taskconfig
->pszWindowTitle
))
445 if (!LoadStringW(taskconfig
->hInstance
, LOWORD(taskconfig
->pszWindowTitle
), (WCHAR
*)&titleW
, 0))
446 titleW
= taskdialog_get_exe_name(taskconfig
, pathW
, ARRAY_SIZE(pathW
));
449 titleW
= taskconfig
->pszWindowTitle
;
452 title_size
= (strlenW(titleW
) + 1) * sizeof(WCHAR
);
454 size
= sizeof(DLGTEMPLATE
) + 2 * sizeof(WORD
);
456 size
+= 2; /* font size */
458 list_init(&desc
.controls
);
459 desc
.taskconfig
= taskconfig
;
460 desc
.control_count
= 0;
462 ncm
.cbSize
= sizeof(ncm
);
463 SystemParametersInfoW(SPI_GETNONCLIENTMETRICS
, ncm
.cbSize
, &ncm
, 0);
464 desc
.font
= CreateFontIndirectW(&ncm
.lfMessageFont
);
467 SelectObject(hdc
, desc
.font
);
468 desc
.x_baseunit
= GdiGetCharDimensions(hdc
, NULL
, &desc
.y_baseunit
);
471 screen_width
= taskdialog_get_reference_rect(&desc
, &ref_rect
);
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
;
478 size
+= taskdialog_add_main_instruction(&desc
);
479 size
+= taskdialog_add_content(&desc
);
480 size
+= taskdialog_add_buttons(&desc
);
482 template = Alloc(size
);
485 taskdialog_clear_controls(&desc
.controls
);
486 DeleteObject(desc
.font
);
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
;
497 ptr
= (char *)(template + 1);
499 ptr
+= 2; /* class */
500 template_write_data(&ptr
, titleW
, title_size
);
501 template_write_data(&ptr
, &fontsize
, sizeof(fontsize
));
503 /* write control entries */
504 LIST_FOR_EACH_ENTRY_SAFE(control
, control2
, &desc
.controls
, struct taskdialog_control
, entry
)
506 ALIGN_POINTER(ptr
, 3);
508 template_write_data(&ptr
, control
->template, control
->template_size
);
510 /* list item won't be needed later */
511 list_remove(&control
->entry
);
512 Free(control
->template);
516 DeleteObject(desc
.font
);
520 static HRESULT
taskdialog_notify(struct taskdialog_info
*dialog_info
, UINT notification
, WPARAM wparam
, LPARAM lparam
)
522 return dialog_info
->callback
? dialog_info
->callback(dialog_info
->hwnd
, notification
, wparam
, lparam
,
523 dialog_info
->callback_data
) : S_OK
;
526 static void taskdialog_on_button_click(struct taskdialog_info
*dialog_info
, WORD command_id
)
528 if (taskdialog_notify(dialog_info
, TDN_BUTTON_CLICKED
, command_id
, 0) == S_OK
)
529 EndDialog(dialog_info
->hwnd
, command_id
);
532 static INT_PTR CALLBACK
taskdialog_proc(HWND hwnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
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
;
537 TRACE("hwnd=%p msg=0x%04x wparam=%lx lparam=%lx\n", hwnd
, msg
, wParam
, lParam
);
539 if (msg
!= WM_INITDIALOG
)
540 dialog_info
= GetPropW(hwnd
, taskdialog_info_propnameW
);
544 case TDM_CLICK_BUTTON
:
545 taskdialog_on_button_click(dialog_info
, LOWORD(wParam
));
548 dialog_info
= (struct taskdialog_info
*)lParam
;
549 dialog_info
->hwnd
= hwnd
;
550 SetPropW(hwnd
, taskdialog_info_propnameW
, dialog_info
);
552 taskdialog_notify(dialog_info
, TDN_DIALOG_CONSTRUCTED
, 0, 0);
555 taskdialog_notify(dialog_info
, TDN_CREATED
, 0, 0);
558 if (HIWORD(wParam
) == BN_CLICKED
)
560 taskdialog_on_button_click(dialog_info
, LOWORD(wParam
));
565 taskdialog_notify(dialog_info
, TDN_DESTROYED
, 0, 0);
566 RemovePropW(hwnd
, taskdialog_info_propnameW
);
572 /***********************************************************************
573 * TaskDialogIndirect [COMCTL32.@]
575 HRESULT WINAPI
TaskDialogIndirect(const TASKDIALOGCONFIG
*taskconfig
, int *button
,
576 int *radio_button
, BOOL
*verification_flag_checked
)
578 struct taskdialog_info dialog_info
;
579 DLGTEMPLATE
*template;
582 TRACE("%p, %p, %p, %p\n", taskconfig
, button
, radio_button
, verification_flag_checked
);
584 if (!taskconfig
|| taskconfig
->cbSize
!= sizeof(TASKDIALOGCONFIG
))
587 dialog_info
.callback
= taskconfig
->pfCallback
;
588 dialog_info
.callback_data
= taskconfig
->lpCallbackData
;
590 template = create_taskdialog_template(taskconfig
);
591 ret
= (short)DialogBoxIndirectParamW(taskconfig
->hInstance
, template, taskconfig
->hwndParent
,
592 taskdialog_proc
, (LPARAM
)&dialog_info
);
595 if (button
) *button
= ret
;
596 if (radio_button
) *radio_button
= taskconfig
->nDefaultButton
;
597 if (verification_flag_checked
) *verification_flag_checked
= TRUE
;
602 /***********************************************************************
603 * TaskDialog [COMCTL32.@]
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
)
608 TASKDIALOGCONFIG taskconfig
;
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
);
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
);