[SHELL32] CDrivesFolder: Implement the eject and disconnect menu items. CORE-13841
[reactos.git] / dll / win32 / comctl32 / taskdialog.c
1 /*
2 * Task dialog control
3 *
4 * Copyright 2017 Fabian Maurer
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 *
20 */
21
22 #include "comctl32.h"
23
24 WINE_DEFAULT_DEBUG_CHANNEL(taskdialog);
25
26 #define ALIGNED_LENGTH(_Len, _Align) (((_Len)+(_Align))&~(_Align))
27 #define ALIGNED_POINTER(_Ptr, _Align) ((LPVOID)ALIGNED_LENGTH((ULONG_PTR)(_Ptr), _Align))
28 #define ALIGN_LENGTH(_Len, _Align) _Len = ALIGNED_LENGTH(_Len, _Align)
29 #define ALIGN_POINTER(_Ptr, _Align) _Ptr = ALIGNED_POINTER(_Ptr, _Align)
30
31 static const UINT DIALOG_MIN_WIDTH = 240;
32 static const UINT DIALOG_SPACING = 5;
33 static const UINT DIALOG_BUTTON_WIDTH = 50;
34 static const UINT DIALOG_BUTTON_HEIGHT = 14;
35
36 static const UINT ID_MAIN_INSTRUCTION = 0xf000;
37 static const UINT ID_CONTENT = 0xf001;
38
39 struct taskdialog_control
40 {
41 struct list entry;
42 DLGITEMTEMPLATE *template;
43 unsigned int template_size;
44 };
45
46 struct taskdialog_template_desc
47 {
48 const TASKDIALOGCONFIG *taskconfig;
49 unsigned int dialog_height;
50 unsigned int dialog_width;
51 struct list controls;
52 WORD control_count;
53 LONG x_baseunit;
54 LONG y_baseunit;
55 HFONT font;
56 };
57
58 struct taskdialog_button_desc
59 {
60 int id;
61 const WCHAR *text;
62 unsigned int width;
63 unsigned int line;
64 HINSTANCE hinst;
65 };
66
67 static void pixels_to_dialogunits(const struct taskdialog_template_desc *desc, LONG *width, LONG *height)
68 {
69 if (width)
70 *width = MulDiv(*width, 4, desc->x_baseunit);
71 if (height)
72 *height = MulDiv(*height, 8, desc->y_baseunit);
73 }
74
75 static void dialogunits_to_pixels(const struct taskdialog_template_desc *desc, LONG *width, LONG *height)
76 {
77 if (width)
78 *width = MulDiv(*width, desc->x_baseunit, 4);
79 if (height)
80 *height = MulDiv(*height, desc->y_baseunit, 8);
81 }
82
83 static void template_write_data(char **ptr, const void *src, unsigned int size)
84 {
85 memcpy(*ptr, src, size);
86 *ptr += size;
87 }
88
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)
92 {
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;
96 unsigned int length;
97 HFONT oldfont;
98 HDC hdc;
99
100 if (IS_INTRESOURCE(text))
101 {
102 if (!(length = LoadStringW(user_resource ? desc->taskconfig->hInstance : COMCTL32_hModule,
103 (UINT_PTR)text, (WCHAR *)&textW, 0)))
104 {
105 WARN("Failed to load text\n");
106 textW = &nulW;
107 length = 0;
108 }
109 }
110 else
111 {
112 textW = text;
113 length = strlenW(textW);
114 }
115
116 hdc = GetDC(0);
117 oldfont = SelectObject(hdc, desc->font);
118
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);
122
123 SelectObject(hdc, oldfont);
124 ReleaseDC(0, hdc);
125
126 sz->cx = rect.right - rect.left;
127 sz->cy = rect.bottom - rect.top;
128 }
129
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)
132 {
133 struct taskdialog_control *control = Alloc(sizeof(*control));
134 unsigned int size, class_size, text_size;
135 DLGITEMTEMPLATE *template;
136 static const WCHAR nulW;
137 const WCHAR *textW;
138 char *ptr;
139
140 class_size = (strlenW(class) + 1) * sizeof(WCHAR);
141
142 if (IS_INTRESOURCE(text))
143 text_size = LoadStringW(hInstance, (UINT_PTR)text, (WCHAR *)&textW, 0) * sizeof(WCHAR);
144 else
145 {
146 textW = text;
147 text_size = strlenW(textW) * sizeof(WCHAR);
148 }
149
150 size = sizeof(DLGITEMTEMPLATE);
151 size += class_size;
152 size += text_size + sizeof(WCHAR);
153 size += sizeof(WORD); /* creation data */
154
155 control->template = template = Alloc(size);
156 control->template_size = size;
157
158 template->style = WS_VISIBLE;
159 template->dwExtendedStyle = 0;
160 template->x = x;
161 template->y = y;
162 template->cx = cx;
163 template->cy = cy;
164 template->id = id;
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));
169
170 list_add_tail(&desc->controls, &control->entry);
171 desc->control_count++;
172 return ALIGNED_LENGTH(size, 3);
173 }
174
175 static unsigned int taskdialog_add_static_label(struct taskdialog_template_desc *desc, WORD id, const WCHAR *str)
176 {
177 unsigned int size;
178 SIZE sz;
179
180 if (!str)
181 return 0;
182
183 taskdialog_get_text_extent(desc, str, TRUE, &sz);
184
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;
189 return size;
190 }
191
192 static unsigned int taskdialog_add_main_instruction(struct taskdialog_template_desc *desc)
193 {
194 return taskdialog_add_static_label(desc, ID_MAIN_INSTRUCTION, desc->taskconfig->pszMainInstruction);
195 }
196
197 static unsigned int taskdialog_add_content(struct taskdialog_template_desc *desc)
198 {
199 return taskdialog_add_static_label(desc, ID_CONTENT, desc->taskconfig->pszContent);
200 }
201
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)
204 {
205 SIZE sz;
206
207 taskdialog_get_text_extent(desc, text, custom_button, &sz);
208
209 button->id = id;
210 button->text = text;
211 button->width = max(DIALOG_BUTTON_WIDTH, sz.cx + DIALOG_SPACING * 2);
212 button->line = 0;
213 button->hinst = custom_button ? desc->taskconfig->hInstance : COMCTL32_hModule;
214 }
215
216 static void taskdialog_init_common_buttons(struct taskdialog_template_desc *desc, struct taskdialog_button_desc *buttons,
217 unsigned int *button_count)
218 {
219 DWORD flags = desc->taskconfig->dwCommonButtons;
220
221 #define TASKDIALOG_INIT_COMMON_BUTTON(id) \
222 do { \
223 taskdialog_init_button(&buttons[(*button_count)++], desc, ID##id, MAKEINTRESOURCEW(IDS_BUTTON_##id), FALSE); \
224 } while(0)
225
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);
238
239 #undef TASKDIALOG_INIT_COMMON_BUTTON
240 }
241
242 static unsigned int taskdialog_add_buttons(struct taskdialog_template_desc *desc)
243 {
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;
248
249 /* Allocate enough memory for the custom and the default buttons. Maximum 6 default buttons possible. */
250 buttons_size = 6;
251 if (taskconfig->cButtons && taskconfig->pButtons)
252 buttons_size += taskconfig->cButtons;
253
254 if (!(buttons = Alloc(buttons_size * sizeof(*buttons))))
255 return 0;
256
257 /* Custom 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);
262
263 /* Common buttons */
264 taskdialog_init_common_buttons(desc, buttons, &count);
265
266 /* There must be at least one button */
267 if (count == 0)
268 taskdialog_init_button(&buttons[count++], desc, IDOK, MAKEINTRESOURCEW(IDS_BUTTON_OK), FALSE);
269
270 /* For easy handling just allocate as many lines as buttons, the worst case. */
271 line_widths = Alloc(count * sizeof(*line_widths));
272
273 /* Separate buttons into lines */
274 location_x = DIALOG_SPACING;
275 for (i = 0, line_count = 0; i < count; i++)
276 {
277 if (location_x + buttons[i].width + DIALOG_SPACING > desc->dialog_width)
278 {
279 location_x = DIALOG_SPACING;
280 line_count++;
281 }
282
283 buttons[i].line = line_count;
284
285 location_x += buttons[i].width + DIALOG_SPACING;
286 line_widths[line_count] += buttons[i].width + DIALOG_SPACING;
287 }
288 line_count++;
289
290 /* Try to balance lines so they are about the same size */
291 for (i = 1; i < line_count - 1; i++)
292 {
293 int diff_now = abs(line_widths[i] - line_widths[i - 1]);
294 unsigned int j, last_button = 0;
295 int diff_changed;
296
297 for (j = 0; j < count; j++)
298 if (buttons[j].line == i - 1)
299 last_button = j;
300
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]);
303
304 if (diff_changed < diff_now)
305 {
306 buttons[last_button].line = i;
307 line_widths[i] += buttons[last_button].width;
308 line_widths[i - 1] -= buttons[last_button].width;
309 }
310 }
311
312 /* Calculate left alignment so all lines are as far right as possible. */
313 for (i = 0; i < line_count; i++)
314 {
315 int new_alignment = desc->dialog_width - line_widths[i];
316 if (new_alignment < alignment)
317 alignment = new_alignment;
318 }
319
320 /* Now that we got them all positioned, create all buttons */
321 location_x = alignment;
322 for (i = 0; i < count; i++)
323 {
324 if (i > 0 && buttons[i].line != buttons[i - 1].line) /* New line */
325 {
326 location_x = alignment;
327 desc->dialog_height += DIALOG_BUTTON_HEIGHT + DIALOG_SPACING;
328 }
329
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);
332
333 location_x += buttons[i].width + DIALOG_SPACING;
334 }
335
336 /* Add height for last row and spacing */
337 desc->dialog_height += DIALOG_BUTTON_HEIGHT + DIALOG_SPACING;
338
339 Free(line_widths);
340 Free(buttons);
341
342 return size;
343 }
344
345 static void taskdialog_clear_controls(struct list *controls)
346 {
347 struct taskdialog_control *control, *control2;
348
349 LIST_FOR_EACH_ENTRY_SAFE(control, control2, controls, struct taskdialog_control, entry)
350 {
351 list_remove(&control->entry);
352 Free(control->template);
353 Free(control);
354 }
355 }
356
357 static unsigned int taskdialog_get_reference_rect(const struct taskdialog_template_desc *desc, RECT *ret)
358 {
359 HMONITOR monitor = MonitorFromWindow(desc->taskconfig->hwndParent ? desc->taskconfig->hwndParent : GetActiveWindow(),
360 MONITOR_DEFAULTTOPRIMARY);
361 MONITORINFO info;
362
363 info.cbSize = sizeof(info);
364 GetMonitorInfoW(monitor, &info);
365
366 if (desc->taskconfig->dwFlags & TDF_POSITION_RELATIVE_TO_WINDOW && desc->taskconfig->hwndParent)
367 GetWindowRect(desc->taskconfig->hwndParent, ret);
368 else
369 *ret = info.rcWork;
370
371 pixels_to_dialogunits(desc, &ret->left, &ret->top);
372 pixels_to_dialogunits(desc, &ret->right, &ret->bottom);
373
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;
377 }
378
379 static DLGTEMPLATE *create_taskdialog_template(const TASKDIALOGCONFIG *taskconfig)
380 {
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;
389 RECT ref_rect;
390 char *ptr;
391 HDC hdc;
392
393 /* Window title */
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");
398 else
399 titleW = taskconfig->pszWindowTitle;
400 if (!titleW)
401 titleW = emptyW;
402 title_size = (strlenW(titleW) + 1) * sizeof(WCHAR);
403
404 size = sizeof(DLGTEMPLATE) + 2 * sizeof(WORD);
405 size += title_size;
406 size += 2; /* font size */
407
408 list_init(&desc.controls);
409 desc.taskconfig = taskconfig;
410 desc.control_count = 0;
411
412 ncm.cbSize = sizeof(ncm);
413 SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0);
414 desc.font = CreateFontIndirectW(&ncm.lfMessageFont);
415
416 hdc = GetDC(0);
417 SelectObject(hdc, desc.font);
418 desc.x_baseunit = GdiGetCharDimensions(hdc, NULL, &desc.y_baseunit);
419 ReleaseDC(0, hdc);
420
421 screen_width = taskdialog_get_reference_rect(&desc, &ref_rect);
422
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);
426
427 size += taskdialog_add_main_instruction(&desc);
428 size += taskdialog_add_content(&desc);
429 size += taskdialog_add_buttons(&desc);
430
431 template = Alloc(size);
432 if (!template)
433 {
434 taskdialog_clear_controls(&desc.controls);
435 DeleteObject(desc.font);
436 return NULL;
437 }
438
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;
445
446 ptr = (char *)(template + 1);
447 ptr += 2; /* menu */
448 ptr += 2; /* class */
449 template_write_data(&ptr, titleW, title_size);
450 template_write_data(&ptr, &fontsize, sizeof(fontsize));
451
452 /* write control entries */
453 LIST_FOR_EACH_ENTRY_SAFE(control, control2, &desc.controls, struct taskdialog_control, entry)
454 {
455 ALIGN_POINTER(ptr, 3);
456
457 template_write_data(&ptr, control->template, control->template_size);
458
459 /* list item won't be needed later */
460 list_remove(&control->entry);
461 Free(control->template);
462 Free(control);
463 }
464
465 DeleteObject(desc.font);
466 return template;
467 }
468
469 static INT_PTR CALLBACK taskdialog_proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
470 {
471 TRACE("hwnd=%p msg=0x%04x wparam=%lx lparam=%lx\n", hwnd, msg, wParam, lParam);
472
473 switch (msg)
474 {
475 case WM_COMMAND:
476 if (HIWORD(wParam) == BN_CLICKED)
477 {
478 WORD command_id = LOWORD(wParam);
479 EndDialog(hwnd, command_id);
480 return TRUE;
481 }
482 break;
483 }
484 return FALSE;
485 }
486
487 /***********************************************************************
488 * TaskDialogIndirect [COMCTL32.@]
489 */
490 HRESULT WINAPI TaskDialogIndirect(const TASKDIALOGCONFIG *taskconfig, int *button,
491 int *radio_button, BOOL *verification_flag_checked)
492 {
493 DLGTEMPLATE *template;
494 INT ret;
495
496 TRACE("%p, %p, %p, %p\n", taskconfig, button, radio_button, verification_flag_checked);
497
498 template = create_taskdialog_template(taskconfig);
499 ret = DialogBoxIndirectParamW(taskconfig->hInstance, template, taskconfig->hwndParent, taskdialog_proc, 0);
500 Free(template);
501
502 if (button) *button = ret;
503 if (radio_button) *radio_button = taskconfig->nDefaultButton;
504 if (verification_flag_checked) *verification_flag_checked = TRUE;
505
506 return S_OK;
507 }
508
509 /***********************************************************************
510 * TaskDialog [COMCTL32.@]
511 */
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)
514 {
515 TASKDIALOGCONFIG taskconfig;
516
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);
519
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);
530 }