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
33 #include "wine/debug.h"
34 #include "wine/list.h"
35 #include "wine/unicode.h"
37 WINE_DEFAULT_DEBUG_CHANNEL(taskdialog
);
39 #define ALIGNED_LENGTH(_Len, _Align) (((_Len)+(_Align))&~(_Align))
40 #define ALIGNED_POINTER(_Ptr, _Align) ((LPVOID)ALIGNED_LENGTH((ULONG_PTR)(_Ptr), _Align))
41 #define ALIGN_LENGTH(_Len, _Align) _Len = ALIGNED_LENGTH(_Len, _Align)
42 #define ALIGN_POINTER(_Ptr, _Align) _Ptr = ALIGNED_POINTER(_Ptr, _Align)
44 static const UINT DIALOG_MIN_WIDTH
= 180;
45 static const UINT DIALOG_SPACING
= 5;
46 static const UINT DIALOG_BUTTON_WIDTH
= 50;
47 static const UINT DIALOG_BUTTON_HEIGHT
= 14;
49 static const UINT ID_MAIN_INSTRUCTION
= 0xf000;
51 struct taskdialog_control
54 DLGITEMTEMPLATE
*template;
55 unsigned int template_size
;
58 struct taskdialog_template_desc
60 const TASKDIALOGCONFIG
*taskconfig
;
61 unsigned int dialog_height
;
62 unsigned int dialog_width
;
70 static void pixels_to_dialogunits(const struct taskdialog_template_desc
*desc
, LONG
*width
, LONG
*height
)
73 *width
= MulDiv(*width
, 4, desc
->x_baseunit
);
75 *height
= MulDiv(*height
, 8, desc
->y_baseunit
);
78 static void dialogunits_to_pixels(const struct taskdialog_template_desc
*desc
, LONG
*width
, LONG
*height
)
81 *width
= MulDiv(*width
, desc
->x_baseunit
, 4);
83 *height
= MulDiv(*height
, desc
->y_baseunit
, 8);
86 static void template_write_data(char **ptr
, const void *src
, unsigned int size
)
88 memcpy(*ptr
, src
, size
);
92 static unsigned int taskdialog_add_control(struct taskdialog_template_desc
*desc
, WORD id
, const WCHAR
*class,
93 HINSTANCE hInstance
, const WCHAR
*text
, short x
, short y
, short cx
, short cy
)
95 struct taskdialog_control
*control
= Alloc(sizeof(*control
));
96 unsigned int size
, class_size
, text_size
;
97 DLGITEMTEMPLATE
*template;
98 static const WCHAR nulW
;
102 class_size
= (strlenW(class) + 1) * sizeof(WCHAR
);
104 if (IS_INTRESOURCE(text
))
105 text_size
= LoadStringW(hInstance
, (UINT_PTR
)text
, (WCHAR
*)&textW
, 0) * sizeof(WCHAR
);
109 text_size
= strlenW(textW
) * sizeof(WCHAR
);
112 size
= sizeof(DLGITEMTEMPLATE
);
114 size
+= text_size
+ sizeof(WCHAR
);
115 size
+= sizeof(WORD
); /* creation data */
117 control
->template = template = Alloc(size
);
118 control
->template_size
= size
;
120 template->style
= WS_VISIBLE
;
121 template->dwExtendedStyle
= 0;
127 ptr
= (char *)(template + 1);
128 template_write_data(&ptr
, class, class_size
);
129 template_write_data(&ptr
, textW
, text_size
);
130 template_write_data(&ptr
, &nulW
, sizeof(nulW
));
132 list_add_tail(&desc
->controls
, &control
->entry
);
133 desc
->control_count
++;
134 return ALIGNED_LENGTH(size
, 3);
137 static unsigned int taskdialog_add_main_instruction(struct taskdialog_template_desc
*desc
)
139 RECT rect
= { 0, 0, desc
->dialog_width
- DIALOG_SPACING
* 2, 0}; /* padding left and right of the control */
140 const WCHAR
*textW
= NULL
;
141 unsigned int size
, length
;
145 if (!desc
->taskconfig
->pszMainInstruction
)
148 if (IS_INTRESOURCE(desc
->taskconfig
->pszMainInstruction
))
150 if (!(length
= LoadStringW(desc
->taskconfig
->hInstance
, (UINT_PTR
)desc
->taskconfig
->pszMainInstruction
,
151 (WCHAR
*)&textW
, 0)))
153 WARN("Failed to load main instruction text\n");
159 textW
= desc
->taskconfig
->pszMainInstruction
;
160 length
= strlenW(textW
);
164 oldfont
= SelectObject(hdc
, desc
->font
);
166 dialogunits_to_pixels(desc
, &rect
.right
, NULL
);
167 DrawTextW(hdc
, textW
, length
, &rect
, DT_LEFT
| DT_EXPANDTABS
| DT_CALCRECT
| DT_WORDBREAK
);
168 pixels_to_dialogunits(desc
, &rect
.right
, &rect
.bottom
);
170 SelectObject(hdc
, oldfont
);
173 size
= taskdialog_add_control(desc
, ID_MAIN_INSTRUCTION
, WC_STATICW
, desc
->taskconfig
->hInstance
,
174 desc
->taskconfig
->pszMainInstruction
, DIALOG_SPACING
, desc
->dialog_height
,
175 rect
.right
, rect
.bottom
);
176 desc
->dialog_height
+= rect
.bottom
;
180 static unsigned int taskdialog_add_common_buttons(struct taskdialog_template_desc
*desc
)
182 short button_x
= desc
->dialog_width
- DIALOG_BUTTON_WIDTH
- DIALOG_SPACING
;
183 DWORD flags
= desc
->taskconfig
->dwCommonButtons
;
184 unsigned int size
= 0;
186 #define TASKDIALOG_ADD_COMMON_BUTTON(id) \
188 size += taskdialog_add_control(desc, ID##id, WC_BUTTONW, COMCTL32_hModule, MAKEINTRESOURCEW(IDS_BUTTON_##id), \
189 button_x, desc->dialog_height + DIALOG_SPACING, DIALOG_BUTTON_WIDTH, DIALOG_BUTTON_HEIGHT); \
190 button_x -= DIALOG_BUTTON_WIDTH + DIALOG_SPACING; \
192 if (flags
& TDCBF_CLOSE_BUTTON
)
193 TASKDIALOG_ADD_COMMON_BUTTON(CLOSE
);
194 if (flags
& TDCBF_CANCEL_BUTTON
)
195 TASKDIALOG_ADD_COMMON_BUTTON(CANCEL
);
196 if (flags
& TDCBF_RETRY_BUTTON
)
197 TASKDIALOG_ADD_COMMON_BUTTON(RETRY
);
198 if (flags
& TDCBF_NO_BUTTON
)
199 TASKDIALOG_ADD_COMMON_BUTTON(NO
);
200 if (flags
& TDCBF_YES_BUTTON
)
201 TASKDIALOG_ADD_COMMON_BUTTON(YES
);
202 if (flags
& TDCBF_OK_BUTTON
)
203 TASKDIALOG_ADD_COMMON_BUTTON(OK
);
204 /* Always add OK button */
205 if (list_empty(&desc
->controls
))
206 TASKDIALOG_ADD_COMMON_BUTTON(OK
);
207 #undef TASKDIALOG_ADD_COMMON_BUTTON
209 /* make room for common buttons row */
210 desc
->dialog_height
+= DIALOG_BUTTON_HEIGHT
+ 2 * DIALOG_SPACING
;
214 static void taskdialog_clear_controls(struct list
*controls
)
216 struct taskdialog_control
*control
, *control2
;
218 LIST_FOR_EACH_ENTRY_SAFE(control
, control2
, controls
, struct taskdialog_control
, entry
)
220 list_remove(&control
->entry
);
221 Free(control
->template);
226 static unsigned int taskdialog_get_reference_rect(const struct taskdialog_template_desc
*desc
, RECT
*ret
)
228 HMONITOR monitor
= MonitorFromWindow(desc
->taskconfig
->hwndParent
? desc
->taskconfig
->hwndParent
: GetActiveWindow(),
229 MONITOR_DEFAULTTOPRIMARY
);
232 info
.cbSize
= sizeof(info
);
233 GetMonitorInfoW(monitor
, &info
);
235 if (desc
->taskconfig
->dwFlags
& TDF_POSITION_RELATIVE_TO_WINDOW
&& desc
->taskconfig
->hwndParent
)
236 GetWindowRect(desc
->taskconfig
->hwndParent
, ret
);
240 pixels_to_dialogunits(desc
, &ret
->left
, &ret
->top
);
241 pixels_to_dialogunits(desc
, &ret
->right
, &ret
->bottom
);
243 pixels_to_dialogunits(desc
, &info
.rcWork
.left
, &info
.rcWork
.top
);
244 pixels_to_dialogunits(desc
, &info
.rcWork
.right
, &info
.rcWork
.bottom
);
245 return info
.rcWork
.right
- info
.rcWork
.left
;
248 static DLGTEMPLATE
*create_taskdialog_template(const TASKDIALOGCONFIG
*taskconfig
)
250 struct taskdialog_control
*control
, *control2
;
251 unsigned int size
, title_size
, screen_width
;
252 struct taskdialog_template_desc desc
;
253 static const WORD fontsize
= 0x7fff;
254 static const WCHAR emptyW
[] = { 0 };
255 const WCHAR
*titleW
= NULL
;
256 DLGTEMPLATE
*template;
257 NONCLIENTMETRICSW ncm
;
263 if (!taskconfig
->pszWindowTitle
)
264 FIXME("use executable name for window title\n");
265 else if (IS_INTRESOURCE(taskconfig
->pszWindowTitle
))
266 FIXME("load window title from resources\n");
268 titleW
= taskconfig
->pszWindowTitle
;
271 title_size
= (strlenW(titleW
) + 1) * sizeof(WCHAR
);
273 size
= sizeof(DLGTEMPLATE
) + 2 * sizeof(WORD
);
276 size
+= 2; /* font size */
278 list_init(&desc
.controls
);
279 desc
.taskconfig
= taskconfig
;
280 desc
.control_count
= 0;
282 ncm
.cbSize
= sizeof(ncm
);
283 SystemParametersInfoW(SPI_GETNONCLIENTMETRICS
, ncm
.cbSize
, &ncm
, 0);
284 desc
.font
= CreateFontIndirectW(&ncm
.lfMessageFont
);
287 SelectObject(hdc
, desc
.font
);
288 desc
.x_baseunit
= GdiGetCharDimensions(hdc
, NULL
, &desc
.y_baseunit
);
291 screen_width
= taskdialog_get_reference_rect(&desc
, &ref_rect
);
293 desc
.dialog_height
= DIALOG_SPACING
;
294 desc
.dialog_width
= max(taskconfig
->cxWidth
, DIALOG_MIN_WIDTH
);
295 desc
.dialog_width
= min(desc
.dialog_width
, screen_width
);
297 size
+= taskdialog_add_main_instruction(&desc
);
298 size
+= taskdialog_add_common_buttons(&desc
);
300 template = Alloc(size
);
303 taskdialog_clear_controls(&desc
.controls
);
304 DeleteObject(desc
.font
);
308 template->style
= DS_MODALFRAME
| DS_SETFONT
| WS_CAPTION
| WS_VISIBLE
| WS_SYSMENU
;
309 template->cdit
= desc
.control_count
;
310 template->x
= (ref_rect
.left
+ ref_rect
.right
+ desc
.dialog_width
) / 2;
311 template->y
= (ref_rect
.top
+ ref_rect
.bottom
+ desc
.dialog_height
) / 2;
312 template->cx
= desc
.dialog_width
;
313 template->cy
= desc
.dialog_height
;
315 ptr
= (char *)(template + 1);
317 ptr
+= 2; /* class */
318 template_write_data(&ptr
, titleW
, title_size
);
319 template_write_data(&ptr
, &fontsize
, sizeof(fontsize
));
321 /* write control entries */
322 LIST_FOR_EACH_ENTRY_SAFE(control
, control2
, &desc
.controls
, struct taskdialog_control
, entry
)
324 ALIGN_POINTER(ptr
, 3);
326 template_write_data(&ptr
, control
->template, control
->template_size
);
328 /* list item won't be needed later */
329 list_remove(&control
->entry
);
330 Free(control
->template);
334 DeleteObject(desc
.font
);
338 static INT_PTR CALLBACK
taskdialog_proc(HWND hwnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
340 TRACE("hwnd=%p msg=0x%04x wparam=%lx lparam=%lx\n", hwnd
, msg
, wParam
, lParam
);
345 if (HIWORD(wParam
) == BN_CLICKED
)
347 WORD command_id
= LOWORD(wParam
);
348 EndDialog(hwnd
, command_id
);
356 /***********************************************************************
357 * TaskDialogIndirect [COMCTL32.@]
359 HRESULT WINAPI
TaskDialogIndirect(const TASKDIALOGCONFIG
*taskconfig
, int *button
,
360 int *radio_button
, BOOL
*verification_flag_checked
)
362 DLGTEMPLATE
*template;
365 TRACE("%p, %p, %p, %p\n", taskconfig
, button
, radio_button
, verification_flag_checked
);
367 template = create_taskdialog_template(taskconfig
);
368 ret
= DialogBoxIndirectParamW(taskconfig
->hInstance
, template, taskconfig
->hwndParent
, taskdialog_proc
, 0);
371 if (button
) *button
= ret
;
372 if (radio_button
) *radio_button
= taskconfig
->nDefaultButton
;
373 if (verification_flag_checked
) *verification_flag_checked
= TRUE
;