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
24 WINE_DEFAULT_DEBUG_CHANNEL(taskdialog
);
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)
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;
36 static const UINT ID_MAIN_INSTRUCTION
= 0xf000;
37 static const UINT ID_CONTENT
= 0xf001;
39 struct taskdialog_control
42 DLGITEMTEMPLATE
*template;
43 unsigned int template_size
;
46 struct taskdialog_template_desc
48 const TASKDIALOGCONFIG
*taskconfig
;
49 unsigned int dialog_height
;
50 unsigned int dialog_width
;
58 struct taskdialog_button_desc
67 static void pixels_to_dialogunits(const struct taskdialog_template_desc
*desc
, LONG
*width
, LONG
*height
)
70 *width
= MulDiv(*width
, 4, desc
->x_baseunit
);
72 *height
= MulDiv(*height
, 8, desc
->y_baseunit
);
75 static void dialogunits_to_pixels(const struct taskdialog_template_desc
*desc
, LONG
*width
, LONG
*height
)
78 *width
= MulDiv(*width
, desc
->x_baseunit
, 4);
80 *height
= MulDiv(*height
, desc
->y_baseunit
, 8);
83 static void template_write_data(char **ptr
, const void *src
, unsigned int size
)
85 memcpy(*ptr
, src
, size
);
89 /* used to calculate size for the controls */
90 static void taskdialog_get_text_extent(const struct taskdialog_template_desc
*desc
, const WCHAR
*text
,
91 BOOL user_resource
, SIZE
*sz
)
93 RECT rect
= { 0, 0, desc
->dialog_width
- DIALOG_SPACING
* 2, 0}; /* padding left and right of the control */
94 const WCHAR
*textW
= NULL
;
95 static const WCHAR nulW
;
100 if (IS_INTRESOURCE(text
))
102 if (!(length
= LoadStringW(user_resource
? desc
->taskconfig
->hInstance
: COMCTL32_hModule
,
103 (UINT_PTR
)text
, (WCHAR
*)&textW
, 0)))
105 WARN("Failed to load text\n");
113 length
= strlenW(textW
);
117 oldfont
= SelectObject(hdc
, desc
->font
);
119 dialogunits_to_pixels(desc
, &rect
.right
, NULL
);
120 DrawTextW(hdc
, textW
, length
, &rect
, DT_LEFT
| DT_EXPANDTABS
| DT_CALCRECT
| DT_WORDBREAK
);
121 pixels_to_dialogunits(desc
, &rect
.right
, &rect
.bottom
);
123 SelectObject(hdc
, oldfont
);
126 sz
->cx
= rect
.right
- rect
.left
;
127 sz
->cy
= rect
.bottom
- rect
.top
;
130 static unsigned int taskdialog_add_control(struct taskdialog_template_desc
*desc
, WORD id
, const WCHAR
*class,
131 HINSTANCE hInstance
, const WCHAR
*text
, short x
, short y
, short cx
, short cy
)
133 struct taskdialog_control
*control
= Alloc(sizeof(*control
));
134 unsigned int size
, class_size
, text_size
;
135 DLGITEMTEMPLATE
*template;
136 static const WCHAR nulW
;
140 class_size
= (strlenW(class) + 1) * sizeof(WCHAR
);
142 if (IS_INTRESOURCE(text
))
143 text_size
= LoadStringW(hInstance
, (UINT_PTR
)text
, (WCHAR
*)&textW
, 0) * sizeof(WCHAR
);
147 text_size
= strlenW(textW
) * sizeof(WCHAR
);
150 size
= sizeof(DLGITEMTEMPLATE
);
152 size
+= text_size
+ sizeof(WCHAR
);
153 size
+= sizeof(WORD
); /* creation data */
155 control
->template = template = Alloc(size
);
156 control
->template_size
= size
;
158 template->style
= WS_VISIBLE
;
159 template->dwExtendedStyle
= 0;
165 ptr
= (char *)(template + 1);
166 template_write_data(&ptr
, class, class_size
);
167 template_write_data(&ptr
, textW
, text_size
);
168 template_write_data(&ptr
, &nulW
, sizeof(nulW
));
170 list_add_tail(&desc
->controls
, &control
->entry
);
171 desc
->control_count
++;
172 return ALIGNED_LENGTH(size
, 3);
175 static unsigned int taskdialog_add_static_label(struct taskdialog_template_desc
*desc
, WORD id
, const WCHAR
*str
)
183 taskdialog_get_text_extent(desc
, str
, TRUE
, &sz
);
185 desc
->dialog_height
+= DIALOG_SPACING
;
186 size
= taskdialog_add_control(desc
, id
, WC_STATICW
, desc
->taskconfig
->hInstance
, str
, DIALOG_SPACING
,
187 desc
->dialog_height
, sz
.cx
, sz
.cy
);
188 desc
->dialog_height
+= sz
.cy
+ DIALOG_SPACING
;
192 static unsigned int taskdialog_add_main_instruction(struct taskdialog_template_desc
*desc
)
194 return taskdialog_add_static_label(desc
, ID_MAIN_INSTRUCTION
, desc
->taskconfig
->pszMainInstruction
);
197 static unsigned int taskdialog_add_content(struct taskdialog_template_desc
*desc
)
199 return taskdialog_add_static_label(desc
, ID_CONTENT
, desc
->taskconfig
->pszContent
);
202 static void taskdialog_init_button(struct taskdialog_button_desc
*button
, struct taskdialog_template_desc
*desc
,
203 int id
, const WCHAR
*text
, BOOL custom_button
)
207 taskdialog_get_text_extent(desc
, text
, custom_button
, &sz
);
211 button
->width
= max(DIALOG_BUTTON_WIDTH
, sz
.cx
+ DIALOG_SPACING
* 2);
213 button
->hinst
= custom_button
? desc
->taskconfig
->hInstance
: COMCTL32_hModule
;
216 static void taskdialog_init_common_buttons(struct taskdialog_template_desc
*desc
, struct taskdialog_button_desc
*buttons
,
217 unsigned int *button_count
)
219 DWORD flags
= desc
->taskconfig
->dwCommonButtons
;
221 #define TASKDIALOG_INIT_COMMON_BUTTON(id) \
223 taskdialog_init_button(&buttons[(*button_count)++], desc, ID##id, MAKEINTRESOURCEW(IDS_BUTTON_##id), FALSE); \
226 if (flags
& TDCBF_OK_BUTTON
)
227 TASKDIALOG_INIT_COMMON_BUTTON(OK
);
228 if (flags
& TDCBF_YES_BUTTON
)
229 TASKDIALOG_INIT_COMMON_BUTTON(YES
);
230 if (flags
& TDCBF_NO_BUTTON
)
231 TASKDIALOG_INIT_COMMON_BUTTON(NO
);
232 if (flags
& TDCBF_RETRY_BUTTON
)
233 TASKDIALOG_INIT_COMMON_BUTTON(RETRY
);
234 if (flags
& TDCBF_CANCEL_BUTTON
)
235 TASKDIALOG_INIT_COMMON_BUTTON(CANCEL
);
236 if (flags
& TDCBF_CLOSE_BUTTON
)
237 TASKDIALOG_INIT_COMMON_BUTTON(CLOSE
);
239 #undef TASKDIALOG_INIT_COMMON_BUTTON
242 static unsigned int taskdialog_add_buttons(struct taskdialog_template_desc
*desc
)
244 unsigned int count
= 0, buttons_size
, i
, line_count
, size
= 0;
245 unsigned int location_x
, *line_widths
, alignment
= ~0u;
246 const TASKDIALOGCONFIG
*taskconfig
= desc
->taskconfig
;
247 struct taskdialog_button_desc
*buttons
;
249 /* Allocate enough memory for the custom and the default buttons. Maximum 6 default buttons possible. */
251 if (taskconfig
->cButtons
&& taskconfig
->pButtons
)
252 buttons_size
+= taskconfig
->cButtons
;
254 if (!(buttons
= Alloc(buttons_size
* sizeof(*buttons
))))
258 if (taskconfig
->cButtons
&& taskconfig
->pButtons
)
259 for (i
= 0; i
< taskconfig
->cButtons
; i
++)
260 taskdialog_init_button(&buttons
[count
++], desc
, taskconfig
->pButtons
[i
].nButtonID
,
261 taskconfig
->pButtons
[i
].pszButtonText
, TRUE
);
264 taskdialog_init_common_buttons(desc
, buttons
, &count
);
266 /* There must be at least one button */
268 taskdialog_init_button(&buttons
[count
++], desc
, IDOK
, MAKEINTRESOURCEW(IDS_BUTTON_OK
), FALSE
);
270 /* For easy handling just allocate as many lines as buttons, the worst case. */
271 line_widths
= Alloc(count
* sizeof(*line_widths
));
273 /* Separate buttons into lines */
274 location_x
= DIALOG_SPACING
;
275 for (i
= 0, line_count
= 0; i
< count
; i
++)
277 if (location_x
+ buttons
[i
].width
+ DIALOG_SPACING
> desc
->dialog_width
)
279 location_x
= DIALOG_SPACING
;
283 buttons
[i
].line
= line_count
;
285 location_x
+= buttons
[i
].width
+ DIALOG_SPACING
;
286 line_widths
[line_count
] += buttons
[i
].width
+ DIALOG_SPACING
;
290 /* Try to balance lines so they are about the same size */
291 for (i
= 1; i
< line_count
- 1; i
++)
293 int diff_now
= abs(line_widths
[i
] - line_widths
[i
- 1]);
294 unsigned int j
, last_button
= 0;
297 for (j
= 0; j
< count
; j
++)
298 if (buttons
[j
].line
== i
- 1)
301 /* Difference in length of both lines if we wrapped the last button from the last line into this one */
302 diff_changed
= abs(2 * buttons
[last_button
].width
+ line_widths
[i
] - line_widths
[i
- 1]);
304 if (diff_changed
< diff_now
)
306 buttons
[last_button
].line
= i
;
307 line_widths
[i
] += buttons
[last_button
].width
;
308 line_widths
[i
- 1] -= buttons
[last_button
].width
;
312 /* Calculate left alignment so all lines are as far right as possible. */
313 for (i
= 0; i
< line_count
; i
++)
315 int new_alignment
= desc
->dialog_width
- line_widths
[i
];
316 if (new_alignment
< alignment
)
317 alignment
= new_alignment
;
320 /* Now that we got them all positioned, create all buttons */
321 location_x
= alignment
;
322 for (i
= 0; i
< count
; i
++)
324 if (i
> 0 && buttons
[i
].line
!= buttons
[i
- 1].line
) /* New line */
326 location_x
= alignment
;
327 desc
->dialog_height
+= DIALOG_BUTTON_HEIGHT
+ DIALOG_SPACING
;
330 size
+= taskdialog_add_control(desc
, buttons
[i
].id
, WC_BUTTONW
, buttons
[i
].hinst
, buttons
[i
].text
, location_x
,
331 desc
->dialog_height
, buttons
[i
].width
, DIALOG_BUTTON_HEIGHT
);
333 location_x
+= buttons
[i
].width
+ DIALOG_SPACING
;
336 /* Add height for last row and spacing */
337 desc
->dialog_height
+= DIALOG_BUTTON_HEIGHT
+ DIALOG_SPACING
;
345 static void taskdialog_clear_controls(struct list
*controls
)
347 struct taskdialog_control
*control
, *control2
;
349 LIST_FOR_EACH_ENTRY_SAFE(control
, control2
, controls
, struct taskdialog_control
, entry
)
351 list_remove(&control
->entry
);
352 Free(control
->template);
357 static unsigned int taskdialog_get_reference_rect(const struct taskdialog_template_desc
*desc
, RECT
*ret
)
359 HMONITOR monitor
= MonitorFromWindow(desc
->taskconfig
->hwndParent
? desc
->taskconfig
->hwndParent
: GetActiveWindow(),
360 MONITOR_DEFAULTTOPRIMARY
);
363 info
.cbSize
= sizeof(info
);
364 GetMonitorInfoW(monitor
, &info
);
366 if (desc
->taskconfig
->dwFlags
& TDF_POSITION_RELATIVE_TO_WINDOW
&& desc
->taskconfig
->hwndParent
)
367 GetWindowRect(desc
->taskconfig
->hwndParent
, ret
);
371 pixels_to_dialogunits(desc
, &ret
->left
, &ret
->top
);
372 pixels_to_dialogunits(desc
, &ret
->right
, &ret
->bottom
);
374 pixels_to_dialogunits(desc
, &info
.rcWork
.left
, &info
.rcWork
.top
);
375 pixels_to_dialogunits(desc
, &info
.rcWork
.right
, &info
.rcWork
.bottom
);
376 return info
.rcWork
.right
- info
.rcWork
.left
;
379 static DLGTEMPLATE
*create_taskdialog_template(const TASKDIALOGCONFIG
*taskconfig
)
381 struct taskdialog_control
*control
, *control2
;
382 unsigned int size
, title_size
, screen_width
;
383 struct taskdialog_template_desc desc
;
384 static const WORD fontsize
= 0x7fff;
385 static const WCHAR emptyW
[] = { 0 };
386 const WCHAR
*titleW
= NULL
;
387 DLGTEMPLATE
*template;
388 NONCLIENTMETRICSW ncm
;
394 if (!taskconfig
->pszWindowTitle
)
395 FIXME("use executable name for window title\n");
396 else if (IS_INTRESOURCE(taskconfig
->pszWindowTitle
))
397 FIXME("load window title from resources\n");
399 titleW
= taskconfig
->pszWindowTitle
;
402 title_size
= (strlenW(titleW
) + 1) * sizeof(WCHAR
);
404 size
= sizeof(DLGTEMPLATE
) + 2 * sizeof(WORD
);
406 size
+= 2; /* font size */
408 list_init(&desc
.controls
);
409 desc
.taskconfig
= taskconfig
;
410 desc
.control_count
= 0;
412 ncm
.cbSize
= sizeof(ncm
);
413 SystemParametersInfoW(SPI_GETNONCLIENTMETRICS
, ncm
.cbSize
, &ncm
, 0);
414 desc
.font
= CreateFontIndirectW(&ncm
.lfMessageFont
);
417 SelectObject(hdc
, desc
.font
);
418 desc
.x_baseunit
= GdiGetCharDimensions(hdc
, NULL
, &desc
.y_baseunit
);
421 screen_width
= taskdialog_get_reference_rect(&desc
, &ref_rect
);
423 desc
.dialog_height
= 0;
424 desc
.dialog_width
= max(taskconfig
->cxWidth
, DIALOG_MIN_WIDTH
);
425 desc
.dialog_width
= min(desc
.dialog_width
, screen_width
);
427 size
+= taskdialog_add_main_instruction(&desc
);
428 size
+= taskdialog_add_content(&desc
);
429 size
+= taskdialog_add_buttons(&desc
);
431 template = Alloc(size
);
434 taskdialog_clear_controls(&desc
.controls
);
435 DeleteObject(desc
.font
);
439 template->style
= DS_MODALFRAME
| DS_SETFONT
| WS_CAPTION
| WS_VISIBLE
| WS_SYSMENU
;
440 template->cdit
= desc
.control_count
;
441 template->x
= (ref_rect
.left
+ ref_rect
.right
+ desc
.dialog_width
) / 2;
442 template->y
= (ref_rect
.top
+ ref_rect
.bottom
+ desc
.dialog_height
) / 2;
443 template->cx
= desc
.dialog_width
;
444 template->cy
= desc
.dialog_height
;
446 ptr
= (char *)(template + 1);
448 ptr
+= 2; /* class */
449 template_write_data(&ptr
, titleW
, title_size
);
450 template_write_data(&ptr
, &fontsize
, sizeof(fontsize
));
452 /* write control entries */
453 LIST_FOR_EACH_ENTRY_SAFE(control
, control2
, &desc
.controls
, struct taskdialog_control
, entry
)
455 ALIGN_POINTER(ptr
, 3);
457 template_write_data(&ptr
, control
->template, control
->template_size
);
459 /* list item won't be needed later */
460 list_remove(&control
->entry
);
461 Free(control
->template);
465 DeleteObject(desc
.font
);
469 static INT_PTR CALLBACK
taskdialog_proc(HWND hwnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
471 TRACE("hwnd=%p msg=0x%04x wparam=%lx lparam=%lx\n", hwnd
, msg
, wParam
, lParam
);
476 if (HIWORD(wParam
) == BN_CLICKED
)
478 WORD command_id
= LOWORD(wParam
);
479 EndDialog(hwnd
, command_id
);
487 /***********************************************************************
488 * TaskDialogIndirect [COMCTL32.@]
490 HRESULT WINAPI
TaskDialogIndirect(const TASKDIALOGCONFIG
*taskconfig
, int *button
,
491 int *radio_button
, BOOL
*verification_flag_checked
)
493 DLGTEMPLATE
*template;
496 TRACE("%p, %p, %p, %p\n", taskconfig
, button
, radio_button
, verification_flag_checked
);
498 template = create_taskdialog_template(taskconfig
);
499 ret
= DialogBoxIndirectParamW(taskconfig
->hInstance
, template, taskconfig
->hwndParent
, taskdialog_proc
, 0);
502 if (button
) *button
= ret
;
503 if (radio_button
) *radio_button
= taskconfig
->nDefaultButton
;
504 if (verification_flag_checked
) *verification_flag_checked
= TRUE
;
509 /***********************************************************************
510 * TaskDialog [COMCTL32.@]
512 HRESULT WINAPI
TaskDialog(HWND owner
, HINSTANCE hinst
, const WCHAR
*title
, const WCHAR
*main_instruction
,
513 const WCHAR
*content
, TASKDIALOG_COMMON_BUTTON_FLAGS common_buttons
, const WCHAR
*icon
, int *button
)
515 TASKDIALOGCONFIG taskconfig
;
517 TRACE("%p, %p, %s, %s, %s, %#x, %s, %p\n", owner
, hinst
, debugstr_w(title
), debugstr_w(main_instruction
),
518 debugstr_w(content
), common_buttons
, debugstr_w(icon
), button
);
520 memset(&taskconfig
, 0, sizeof(taskconfig
));
521 taskconfig
.cbSize
= sizeof(taskconfig
);
522 taskconfig
.hwndParent
= owner
;
523 taskconfig
.hInstance
= hinst
;
524 taskconfig
.dwCommonButtons
= common_buttons
;
525 taskconfig
.pszWindowTitle
= title
;
526 taskconfig
.u
.pszMainIcon
= icon
;
527 taskconfig
.pszMainInstruction
= main_instruction
;
528 taskconfig
.pszContent
= content
;
529 return TaskDialogIndirect(&taskconfig
, button
, NULL
, NULL
);