summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/video/windows/SDL_windowsmessagebox.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/SDL-3.2.8/src/video/windows/SDL_windowsmessagebox.c')
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowsmessagebox.c1086
1 files changed, 1086 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowsmessagebox.c b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsmessagebox.c
new file mode 100644
index 0000000..c174e3d
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsmessagebox.c
@@ -0,0 +1,1086 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#if defined(SDL_VIDEO_DRIVER_WINDOWS) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
24
25#ifdef HAVE_LIMITS_H
26#include <limits.h>
27#endif
28#ifndef SIZE_MAX
29#define SIZE_MAX ((size_t)-1)
30#endif
31
32#include "../../core/windows/SDL_windows.h"
33
34#include "SDL_windowsvideo.h"
35
36#ifndef SS_EDITCONTROL
37#define SS_EDITCONTROL 0x2000
38#endif
39
40#ifndef IDOK
41#define IDOK 1
42#endif
43
44#ifndef IDCANCEL
45#define IDCANCEL 2
46#endif
47
48// Custom dialog return codes
49#define IDCLOSED 20
50#define IDINVALPTRINIT 50
51#define IDINVALPTRCOMMAND 51
52#define IDINVALPTRSETFOCUS 52
53#define IDINVALPTRDLGITEM 53
54// First button ID
55#define IDBUTTONINDEX0 100
56
57#define DLGITEMTYPEBUTTON 0x0080
58#define DLGITEMTYPESTATIC 0x0082
59
60/* Windows only sends the lower 16 bits of the control ID when a button
61 * gets clicked. There are also some predefined and custom IDs that lower
62 * the available number further. 2^16 - 101 buttons should be enough for
63 * everyone, no need to make the code more complex.
64 */
65#define MAX_BUTTONS (0xffff - 100)
66
67// Display a Windows message box
68
69typedef HRESULT(CALLBACK *PFTASKDIALOGCALLBACK)(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, LONG_PTR lpRefData);
70
71enum _TASKDIALOG_FLAGS
72{
73 TDF_ENABLE_HYPERLINKS = 0x0001,
74 TDF_USE_HICON_MAIN = 0x0002,
75 TDF_USE_HICON_FOOTER = 0x0004,
76 TDF_ALLOW_DIALOG_CANCELLATION = 0x0008,
77 TDF_USE_COMMAND_LINKS = 0x0010,
78 TDF_USE_COMMAND_LINKS_NO_ICON = 0x0020,
79 TDF_EXPAND_FOOTER_AREA = 0x0040,
80 TDF_EXPANDED_BY_DEFAULT = 0x0080,
81 TDF_VERIFICATION_FLAG_CHECKED = 0x0100,
82 TDF_SHOW_PROGRESS_BAR = 0x0200,
83 TDF_SHOW_MARQUEE_PROGRESS_BAR = 0x0400,
84 TDF_CALLBACK_TIMER = 0x0800,
85 TDF_POSITION_RELATIVE_TO_WINDOW = 0x1000,
86 TDF_RTL_LAYOUT = 0x2000,
87 TDF_NO_DEFAULT_RADIO_BUTTON = 0x4000,
88 TDF_CAN_BE_MINIMIZED = 0x8000,
89 // #if (NTDDI_VERSION >= NTDDI_WIN8)
90 TDF_NO_SET_FOREGROUND = 0x00010000, // Don't call SetForegroundWindow() when activating the dialog
91 // #endif // (NTDDI_VERSION >= NTDDI_WIN8)
92 TDF_SIZE_TO_CONTENT = 0x01000000 // used by ShellMessageBox to emulate MessageBox sizing behavior
93};
94typedef int TASKDIALOG_FLAGS; // Note: _TASKDIALOG_FLAGS is an int
95
96typedef enum _TASKDIALOG_MESSAGES
97{
98 TDM_NAVIGATE_PAGE = WM_USER + 101,
99 TDM_CLICK_BUTTON = WM_USER + 102, // wParam = Button ID
100 TDM_SET_MARQUEE_PROGRESS_BAR = WM_USER + 103, // wParam = 0 (nonMarque) wParam != 0 (Marquee)
101 TDM_SET_PROGRESS_BAR_STATE = WM_USER + 104, // wParam = new progress state
102 TDM_SET_PROGRESS_BAR_RANGE = WM_USER + 105, // lParam = MAKELPARAM(nMinRange, nMaxRange)
103 TDM_SET_PROGRESS_BAR_POS = WM_USER + 106, // wParam = new position
104 TDM_SET_PROGRESS_BAR_MARQUEE = WM_USER + 107, // wParam = 0 (stop marquee), wParam != 0 (start marquee), lparam = speed (milliseconds between repaints)
105 TDM_SET_ELEMENT_TEXT = WM_USER + 108, // wParam = element (TASKDIALOG_ELEMENTS), lParam = new element text (LPCWSTR)
106 TDM_CLICK_RADIO_BUTTON = WM_USER + 110, // wParam = Radio Button ID
107 TDM_ENABLE_BUTTON = WM_USER + 111, // lParam = 0 (disable), lParam != 0 (enable), wParam = Button ID
108 TDM_ENABLE_RADIO_BUTTON = WM_USER + 112, // lParam = 0 (disable), lParam != 0 (enable), wParam = Radio Button ID
109 TDM_CLICK_VERIFICATION = WM_USER + 113, // wParam = 0 (unchecked), 1 (checked), lParam = 1 (set key focus)
110 TDM_UPDATE_ELEMENT_TEXT = WM_USER + 114, // wParam = element (TASKDIALOG_ELEMENTS), lParam = new element text (LPCWSTR)
111 TDM_SET_BUTTON_ELEVATION_REQUIRED_STATE = WM_USER + 115, // wParam = Button ID, lParam = 0 (elevation not required), lParam != 0 (elevation required)
112 TDM_UPDATE_ICON = WM_USER + 116 // wParam = icon element (TASKDIALOG_ICON_ELEMENTS), lParam = new icon (hIcon if TDF_USE_HICON_* was set, PCWSTR otherwise)
113} TASKDIALOG_MESSAGES;
114
115typedef enum _TASKDIALOG_NOTIFICATIONS
116{
117 TDN_CREATED = 0,
118 TDN_NAVIGATED = 1,
119 TDN_BUTTON_CLICKED = 2, // wParam = Button ID
120 TDN_HYPERLINK_CLICKED = 3, // lParam = (LPCWSTR)pszHREF
121 TDN_TIMER = 4, // wParam = Milliseconds since dialog created or timer reset
122 TDN_DESTROYED = 5,
123 TDN_RADIO_BUTTON_CLICKED = 6, // wParam = Radio Button ID
124 TDN_DIALOG_CONSTRUCTED = 7,
125 TDN_VERIFICATION_CLICKED = 8, // wParam = 1 if checkbox checked, 0 if not, lParam is unused and always 0
126 TDN_HELP = 9,
127 TDN_EXPANDO_BUTTON_CLICKED = 10 // wParam = 0 (dialog is now collapsed), wParam != 0 (dialog is now expanded)
128} TASKDIALOG_NOTIFICATIONS;
129
130typedef enum _TASKDIALOG_ELEMENTS
131{
132 TDE_CONTENT,
133 TDE_EXPANDED_INFORMATION,
134 TDE_FOOTER,
135 TDE_MAIN_INSTRUCTION
136} TASKDIALOG_ELEMENTS;
137
138typedef enum _TASKDIALOG_ICON_ELEMENTS
139{
140 TDIE_ICON_MAIN,
141 TDIE_ICON_FOOTER
142} TASKDIALOG_ICON_ELEMENTS;
143
144#define TD_WARNING_ICON MAKEINTRESOURCEW(-1)
145#define TD_ERROR_ICON MAKEINTRESOURCEW(-2)
146#define TD_INFORMATION_ICON MAKEINTRESOURCEW(-3)
147#define TD_SHIELD_ICON MAKEINTRESOURCEW(-4)
148
149enum _TASKDIALOG_COMMON_BUTTON_FLAGS
150{
151 TDCBF_OK_BUTTON = 0x0001, // selected control return value IDOK
152 TDCBF_YES_BUTTON = 0x0002, // selected control return value IDYES
153 TDCBF_NO_BUTTON = 0x0004, // selected control return value IDNO
154 TDCBF_CANCEL_BUTTON = 0x0008, // selected control return value IDCANCEL
155 TDCBF_RETRY_BUTTON = 0x0010, // selected control return value IDRETRY
156 TDCBF_CLOSE_BUTTON = 0x0020 // selected control return value IDCLOSE
157};
158typedef int TASKDIALOG_COMMON_BUTTON_FLAGS; // Note: _TASKDIALOG_COMMON_BUTTON_FLAGS is an int
159
160#pragma pack(push, 1)
161
162typedef struct _TASKDIALOG_BUTTON
163{
164 int nButtonID;
165 PCWSTR pszButtonText;
166} TASKDIALOG_BUTTON;
167
168typedef struct _TASKDIALOGCONFIG
169{
170 UINT cbSize;
171 HWND hwndParent; // incorrectly named, this is the owner window, not a parent.
172 HINSTANCE hInstance; // used for MAKEINTRESOURCE() strings
173 TASKDIALOG_FLAGS dwFlags; // TASKDIALOG_FLAGS (TDF_XXX) flags
174 TASKDIALOG_COMMON_BUTTON_FLAGS dwCommonButtons; // TASKDIALOG_COMMON_BUTTON (TDCBF_XXX) flags
175 PCWSTR pszWindowTitle; // string or MAKEINTRESOURCE()
176 union
177 {
178 HICON hMainIcon;
179 PCWSTR pszMainIcon;
180 } /*DUMMYUNIONNAME*/;
181 PCWSTR pszMainInstruction;
182 PCWSTR pszContent;
183 UINT cButtons;
184 const TASKDIALOG_BUTTON *pButtons;
185 int nDefaultButton;
186 UINT cRadioButtons;
187 const TASKDIALOG_BUTTON *pRadioButtons;
188 int nDefaultRadioButton;
189 PCWSTR pszVerificationText;
190 PCWSTR pszExpandedInformation;
191 PCWSTR pszExpandedControlText;
192 PCWSTR pszCollapsedControlText;
193 union
194 {
195 HICON hFooterIcon;
196 PCWSTR pszFooterIcon;
197 } /*DUMMYUNIONNAME2*/;
198 PCWSTR pszFooter;
199 PFTASKDIALOGCALLBACK pfCallback;
200 LONG_PTR lpCallbackData;
201 UINT cxWidth; // width of the Task Dialog's client area in DLU's. If 0, Task Dialog will calculate the ideal width.
202} TASKDIALOGCONFIG;
203
204typedef struct
205{
206 WORD dlgVer;
207 WORD signature;
208 DWORD helpID;
209 DWORD exStyle;
210 DWORD style;
211 WORD cDlgItems;
212 short x;
213 short y;
214 short cx;
215 short cy;
216} DLGTEMPLATEEX;
217
218typedef struct
219{
220 DWORD helpID;
221 DWORD exStyle;
222 DWORD style;
223 short x;
224 short y;
225 short cx;
226 short cy;
227 DWORD id;
228} DLGITEMTEMPLATEEX;
229
230#pragma pack(pop)
231
232typedef struct
233{
234 DLGTEMPLATEEX *lpDialog;
235 void *data;
236 size_t size;
237 size_t used;
238 WORD numbuttons;
239} WIN_DialogData;
240
241static bool GetButtonIndex(const SDL_MessageBoxData *messageboxdata, SDL_MessageBoxButtonFlags flags, size_t *i)
242{
243 for (*i = 0; *i < (size_t)messageboxdata->numbuttons; ++*i) {
244 if (messageboxdata->buttons[*i].flags & flags) {
245 return true;
246 }
247 }
248 return false;
249}
250
251static INT_PTR CALLBACK MessageBoxDialogProc(HWND hDlg, UINT iMessage, WPARAM wParam, LPARAM lParam)
252{
253 const SDL_MessageBoxData *messageboxdata;
254 size_t buttonindex;
255
256 switch (iMessage) {
257 case WM_INITDIALOG:
258 if (lParam == 0) {
259 EndDialog(hDlg, IDINVALPTRINIT);
260 return TRUE;
261 }
262 messageboxdata = (const SDL_MessageBoxData *)lParam;
263 SetWindowLongPtr(hDlg, GWLP_USERDATA, lParam);
264
265 if (GetButtonIndex(messageboxdata, SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, &buttonindex)) {
266 // Focus on the first default return-key button
267 HWND buttonctl = GetDlgItem(hDlg, (int)(IDBUTTONINDEX0 + buttonindex));
268 if (!buttonctl) {
269 EndDialog(hDlg, IDINVALPTRDLGITEM);
270 }
271 PostMessage(hDlg, WM_NEXTDLGCTL, (WPARAM)buttonctl, TRUE);
272 } else {
273 // Give the focus to the dialog window instead
274 SetFocus(hDlg);
275 }
276 return FALSE;
277 case WM_SETFOCUS:
278 messageboxdata = (const SDL_MessageBoxData *)GetWindowLongPtr(hDlg, GWLP_USERDATA);
279 if (!messageboxdata) {
280 EndDialog(hDlg, IDINVALPTRSETFOCUS);
281 return TRUE;
282 }
283
284 // Let the default button be focused if there is one. Otherwise, prevent any initial focus.
285 if (GetButtonIndex(messageboxdata, SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, &buttonindex)) {
286 return FALSE;
287 }
288 return TRUE;
289 case WM_COMMAND:
290 messageboxdata = (const SDL_MessageBoxData *)GetWindowLongPtr(hDlg, GWLP_USERDATA);
291 if (!messageboxdata) {
292 EndDialog(hDlg, IDINVALPTRCOMMAND);
293 return TRUE;
294 }
295
296 // Return the ID of the button that was pushed
297 if (wParam == IDOK) {
298 if (GetButtonIndex(messageboxdata, SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, &buttonindex)) {
299 EndDialog(hDlg, IDBUTTONINDEX0 + buttonindex);
300 }
301 } else if (wParam == IDCANCEL) {
302 if (GetButtonIndex(messageboxdata, SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, &buttonindex)) {
303 EndDialog(hDlg, IDBUTTONINDEX0 + buttonindex);
304 } else {
305 // Closing of window was requested by user or system. It would be rude not to comply.
306 EndDialog(hDlg, IDCLOSED);
307 }
308 } else if (wParam >= IDBUTTONINDEX0 && (int)wParam - IDBUTTONINDEX0 < messageboxdata->numbuttons) {
309 EndDialog(hDlg, wParam);
310 }
311 return TRUE;
312
313 default:
314 break;
315 }
316 return FALSE;
317}
318
319static bool ExpandDialogSpace(WIN_DialogData *dialog, size_t space)
320{
321 // Growing memory in 64 KiB steps.
322 const size_t sizestep = 0x10000;
323 size_t size = dialog->size;
324
325 if (size == 0) {
326 // Start with 4 KiB or a multiple of 64 KiB to fit the data.
327 size = 0x1000;
328 if (SIZE_MAX - sizestep < space) {
329 size = space;
330 } else if (space > size) {
331 size = (space + sizestep) & ~(sizestep - 1);
332 }
333 } else if (SIZE_MAX - dialog->used < space) {
334 SDL_OutOfMemory();
335 return false;
336 } else if (SIZE_MAX - (dialog->used + space) < sizestep) {
337 // Close to the maximum.
338 size = dialog->used + space;
339 } else if (size < dialog->used + space) {
340 // Round up to the next 64 KiB block.
341 size = dialog->used + space;
342 size += sizestep - size % sizestep;
343 }
344
345 if (size > dialog->size) {
346 void *data = SDL_realloc(dialog->data, size);
347 if (!data) {
348 return false;
349 }
350 dialog->data = data;
351 dialog->size = size;
352 dialog->lpDialog = (DLGTEMPLATEEX *)dialog->data;
353 }
354 return true;
355}
356
357static bool AlignDialogData(WIN_DialogData *dialog, size_t size)
358{
359 size_t padding = (dialog->used % size);
360
361 if (!ExpandDialogSpace(dialog, padding)) {
362 return false;
363 }
364
365 dialog->used += padding;
366
367 return true;
368}
369
370static bool AddDialogData(WIN_DialogData *dialog, const void *data, size_t size)
371{
372 if (!ExpandDialogSpace(dialog, size)) {
373 return false;
374 }
375
376 SDL_memcpy((Uint8 *)dialog->data + dialog->used, data, size);
377 dialog->used += size;
378
379 return true;
380}
381
382static bool AddDialogString(WIN_DialogData *dialog, const char *string)
383{
384 WCHAR *wstring;
385 WCHAR *p;
386 size_t count;
387 bool status;
388
389 if (!string) {
390 string = "";
391 }
392
393 wstring = WIN_UTF8ToStringW(string);
394 if (!wstring) {
395 return false;
396 }
397
398 // Find out how many characters we have, including null terminator
399 count = 0;
400 for (p = wstring; *p; ++p) {
401 ++count;
402 }
403 ++count;
404
405 status = AddDialogData(dialog, wstring, count * sizeof(WCHAR));
406 SDL_free(wstring);
407 return status;
408}
409
410static int s_BaseUnitsX;
411static int s_BaseUnitsY;
412static void Vec2ToDLU(short *x, short *y)
413{
414 SDL_assert(s_BaseUnitsX != 0); // we init in WIN_ShowMessageBox(), which is the only public function...
415
416 *x = (short)MulDiv(*x, 4, s_BaseUnitsX);
417 *y = (short)MulDiv(*y, 8, s_BaseUnitsY);
418}
419
420static bool AddDialogControl(WIN_DialogData *dialog, WORD type, DWORD style, DWORD exStyle, int x, int y, int w, int h, int id, const char *caption, WORD ordinal)
421{
422 DLGITEMTEMPLATEEX item;
423 WORD marker = 0xFFFF;
424 WORD extraData = 0;
425
426 SDL_zero(item);
427 item.style = style;
428 item.exStyle = exStyle;
429 item.x = (short)x;
430 item.y = (short)y;
431 item.cx = (short)w;
432 item.cy = (short)h;
433 item.id = id;
434
435 Vec2ToDLU(&item.x, &item.y);
436 Vec2ToDLU(&item.cx, &item.cy);
437
438 if (!AlignDialogData(dialog, sizeof(DWORD))) {
439 return false;
440 }
441 if (!AddDialogData(dialog, &item, sizeof(item))) {
442 return false;
443 }
444 if (!AddDialogData(dialog, &marker, sizeof(marker))) {
445 return false;
446 }
447 if (!AddDialogData(dialog, &type, sizeof(type))) {
448 return false;
449 }
450 if (type == DLGITEMTYPEBUTTON || (type == DLGITEMTYPESTATIC && caption)) {
451 if (!AddDialogString(dialog, caption)) {
452 return false;
453 }
454 } else {
455 if (!AddDialogData(dialog, &marker, sizeof(marker))) {
456 return false;
457 }
458 if (!AddDialogData(dialog, &ordinal, sizeof(ordinal))) {
459 return false;
460 }
461 }
462 if (!AddDialogData(dialog, &extraData, sizeof(extraData))) {
463 return false;
464 }
465 if (type == DLGITEMTYPEBUTTON) {
466 dialog->numbuttons++;
467 }
468 ++dialog->lpDialog->cDlgItems;
469
470 return true;
471}
472
473static bool AddDialogStaticText(WIN_DialogData *dialog, int x, int y, int w, int h, const char *text)
474{
475 DWORD style = WS_VISIBLE | WS_CHILD | SS_LEFT | SS_NOPREFIX | SS_EDITCONTROL | WS_GROUP;
476 return AddDialogControl(dialog, DLGITEMTYPESTATIC, style, 0, x, y, w, h, -1, text, 0);
477}
478
479static bool AddDialogStaticIcon(WIN_DialogData *dialog, int x, int y, int w, int h, Uint16 ordinal)
480{
481 DWORD style = WS_VISIBLE | WS_CHILD | SS_ICON | WS_GROUP;
482 return AddDialogControl(dialog, DLGITEMTYPESTATIC, style, 0, x, y, w, h, -2, NULL, ordinal);
483}
484
485static bool AddDialogButton(WIN_DialogData *dialog, int x, int y, int w, int h, const char *text, int id, bool isDefault)
486{
487 DWORD style = WS_VISIBLE | WS_CHILD | WS_TABSTOP;
488 if (isDefault) {
489 style |= BS_DEFPUSHBUTTON;
490 } else {
491 style |= BS_PUSHBUTTON;
492 }
493 // The first button marks the start of the group.
494 if (dialog->numbuttons == 0) {
495 style |= WS_GROUP;
496 }
497 return AddDialogControl(dialog, DLGITEMTYPEBUTTON, style, 0, x, y, w, h, id, text, 0);
498}
499
500static void FreeDialogData(WIN_DialogData *dialog)
501{
502 SDL_free(dialog->data);
503 SDL_free(dialog);
504}
505
506static WIN_DialogData *CreateDialogData(int w, int h, const char *caption)
507{
508 WIN_DialogData *dialog;
509 DLGTEMPLATEEX dialogTemplate;
510 WORD WordToPass;
511
512 SDL_zero(dialogTemplate);
513 dialogTemplate.dlgVer = 1;
514 dialogTemplate.signature = 0xffff;
515 dialogTemplate.style = (WS_CAPTION | DS_CENTER | DS_SHELLFONT);
516 dialogTemplate.x = 0;
517 dialogTemplate.y = 0;
518 dialogTemplate.cx = (short)w;
519 dialogTemplate.cy = (short)h;
520 Vec2ToDLU(&dialogTemplate.cx, &dialogTemplate.cy);
521
522 dialog = (WIN_DialogData *)SDL_calloc(1, sizeof(*dialog));
523 if (!dialog) {
524 return NULL;
525 }
526
527 if (!AddDialogData(dialog, &dialogTemplate, sizeof(dialogTemplate))) {
528 FreeDialogData(dialog);
529 return NULL;
530 }
531
532 // No menu
533 WordToPass = 0;
534 if (!AddDialogData(dialog, &WordToPass, 2)) {
535 FreeDialogData(dialog);
536 return NULL;
537 }
538
539 // No custom class
540 if (!AddDialogData(dialog, &WordToPass, 2)) {
541 FreeDialogData(dialog);
542 return NULL;
543 }
544
545 // title
546 if (!AddDialogString(dialog, caption)) {
547 FreeDialogData(dialog);
548 return NULL;
549 }
550
551 // Font stuff
552 {
553 /*
554 * We want to use the system messagebox font.
555 */
556 BYTE ToPass;
557
558 NONCLIENTMETRICSA NCM;
559 NCM.cbSize = sizeof(NCM);
560 SystemParametersInfoA(SPI_GETNONCLIENTMETRICS, 0, &NCM, 0);
561
562 // Font size - convert to logical font size for dialog parameter.
563 {
564 HDC ScreenDC = GetDC(NULL);
565 int LogicalPixelsY = GetDeviceCaps(ScreenDC, LOGPIXELSY);
566 if (!LogicalPixelsY) {
567 LogicalPixelsY = 72; // This can happen if the application runs out of GDI handles
568 }
569
570 WordToPass = (WORD)(-72 * NCM.lfMessageFont.lfHeight / LogicalPixelsY);
571 ReleaseDC(NULL, ScreenDC);
572 }
573
574 if (!AddDialogData(dialog, &WordToPass, 2)) {
575 FreeDialogData(dialog);
576 return NULL;
577 }
578
579 // Font weight
580 WordToPass = (WORD)NCM.lfMessageFont.lfWeight;
581 if (!AddDialogData(dialog, &WordToPass, 2)) {
582 FreeDialogData(dialog);
583 return NULL;
584 }
585
586 // italic?
587 ToPass = NCM.lfMessageFont.lfItalic;
588 if (!AddDialogData(dialog, &ToPass, 1)) {
589 FreeDialogData(dialog);
590 return NULL;
591 }
592
593 // charset?
594 ToPass = NCM.lfMessageFont.lfCharSet;
595 if (!AddDialogData(dialog, &ToPass, 1)) {
596 FreeDialogData(dialog);
597 return NULL;
598 }
599
600 // font typeface.
601 if (!AddDialogString(dialog, NCM.lfMessageFont.lfFaceName)) {
602 FreeDialogData(dialog);
603 return NULL;
604 }
605 }
606
607 return dialog;
608}
609
610/* Escaping ampersands is necessary to disable mnemonics in dialog controls.
611 * The caller provides a char** for dst and a size_t* for dstlen where the
612 * address of the work buffer and its size will be stored. Their values must be
613 * NULL and 0 on the first call. src is the string to be escaped. On error, the
614 * function returns NULL and, on success, returns a pointer to the escaped
615 * sequence as a read-only string that is valid until the next call or until the
616 * work buffer is freed. Once all strings have been processed, it's the caller's
617 * responsibility to free the work buffer with SDL_free, even on errors.
618 */
619static const char *EscapeAmpersands(char **dst, size_t *dstlen, const char *src)
620{
621 char *newdst;
622 size_t ampcount = 0;
623 size_t srclen = 0;
624
625 if (!src) {
626 return NULL;
627 }
628
629 while (src[srclen]) {
630 if (src[srclen] == '&') {
631 ampcount++;
632 }
633 srclen++;
634 }
635 srclen++;
636
637 if (ampcount == 0) {
638 // Nothing to do.
639 return src;
640 }
641 if (SIZE_MAX - srclen < ampcount) {
642 return NULL;
643 }
644 if (!*dst || *dstlen < srclen + ampcount) {
645 // Allocating extra space in case the next strings are a bit longer.
646 size_t extraspace = SIZE_MAX - (srclen + ampcount);
647 if (extraspace > 512) {
648 extraspace = 512;
649 }
650 *dstlen = srclen + ampcount + extraspace;
651 SDL_free(*dst);
652 *dst = NULL;
653 newdst = (char *)SDL_malloc(*dstlen);
654 if (!newdst) {
655 return NULL;
656 }
657 *dst = newdst;
658 } else {
659 newdst = *dst;
660 }
661
662 // The escape character is the ampersand itself.
663 while (srclen--) {
664 if (*src == '&') {
665 *newdst++ = '&';
666 }
667 *newdst++ = *src++;
668 }
669
670 return *dst;
671}
672
673static float WIN_GetContentScale(void)
674{
675 int dpi = 0;
676
677#if 0 // We don't know what monitor the dialog will be shown on
678 UINT hdpi_uint, vdpi_uint;
679 if (GetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &hdpi_uint, &vdpi_uint) == S_OK) {
680 dpi = (int)hdpi_uint;
681 }
682#endif
683 if (dpi == 0) {
684 // Window 8.0 and below: same DPI for all monitors
685 HDC hdc = GetDC(NULL);
686 if (hdc) {
687 dpi = GetDeviceCaps(hdc, LOGPIXELSX);
688 ReleaseDC(NULL, hdc);
689 }
690 }
691 if (dpi == 0) {
692 // Safe default
693 dpi = USER_DEFAULT_SCREEN_DPI;
694 }
695 return dpi / (float)USER_DEFAULT_SCREEN_DPI;
696}
697
698// This function is called if a Task Dialog is unsupported.
699static bool WIN_ShowOldMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID)
700{
701 WIN_DialogData *dialog;
702 int i, x, y;
703 HFONT DialogFont;
704 SIZE Size;
705 RECT TextSize;
706 wchar_t *wmessage;
707 TEXTMETRIC TM;
708 HDC FontDC;
709 INT_PTR rc;
710 char *ampescape = NULL;
711 size_t ampescapesize = 0;
712 Uint16 defbuttoncount = 0;
713 Uint16 icon = 0;
714 bool result;
715
716 HWND ParentWindow = NULL;
717
718 const float scale = WIN_GetContentScale();
719 const int ButtonWidth = (int)SDL_roundf(88 * scale);
720 const int ButtonHeight = (int)SDL_roundf(26 * scale);
721 const int TextMargin = (int)SDL_roundf(16 * scale);
722 const int ButtonMargin = (int)SDL_roundf(12 * scale);
723 const int IconWidth = GetSystemMetrics(SM_CXICON);
724 const int IconHeight = GetSystemMetrics(SM_CYICON);
725 const int IconMargin = (int)SDL_roundf(20 * scale);
726
727 if (messageboxdata->numbuttons > MAX_BUTTONS) {
728 return SDL_SetError("Number of buttons exceeds limit of %d", MAX_BUTTONS);
729 }
730
731 switch (messageboxdata->flags & (SDL_MESSAGEBOX_ERROR | SDL_MESSAGEBOX_WARNING | SDL_MESSAGEBOX_INFORMATION)) {
732 case SDL_MESSAGEBOX_ERROR:
733 icon = (Uint16)(size_t)IDI_ERROR;
734 break;
735 case SDL_MESSAGEBOX_WARNING:
736 icon = (Uint16)(size_t)IDI_WARNING;
737 break;
738 case SDL_MESSAGEBOX_INFORMATION:
739 icon = (Uint16)(size_t)IDI_INFORMATION;
740 break;
741 }
742
743 /* Jan 25th, 2013 - dant@fleetsa.com
744 *
745 * I've tried to make this more reasonable, but I've run in to a lot
746 * of nonsense.
747 *
748 * The original issue is the code was written in pixels and not
749 * dialog units (DLUs). All DialogBox functions use DLUs, which
750 * vary based on the selected font (yay).
751 *
752 * According to MSDN, the most reliable way to convert is via
753 * MapDialogUnits, which requires an HWND, which we don't have
754 * at time of template creation.
755 *
756 * We do however have:
757 * The system font (DLU width 8 for me)
758 * The font we select for the dialog (DLU width 6 for me)
759 *
760 * Based on experimentation, *neither* of these return the value
761 * actually used. Stepping in to MapDialogUnits(), the conversion
762 * is fairly clear, and uses 7 for me.
763 *
764 * As a result, some of this is hacky to ensure the sizing is
765 * somewhat correct.
766 *
767 * Honestly, a long term solution is to use CreateWindow, not CreateDialog.
768 *
769 * In order to get text dimensions we need to have a DC with the desired font.
770 * I'm assuming a dialog box in SDL is rare enough we can to the create.
771 */
772 FontDC = CreateCompatibleDC(0);
773
774 {
775 // Create a duplicate of the font used in system message boxes.
776 LOGFONT lf;
777 NONCLIENTMETRICS NCM;
778 NCM.cbSize = sizeof(NCM);
779 SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &NCM, 0);
780 lf = NCM.lfMessageFont;
781 DialogFont = CreateFontIndirect(&lf);
782 }
783
784 // Select the font in to our DC
785 SelectObject(FontDC, DialogFont);
786
787 {
788 // Get the metrics to try and figure our DLU conversion.
789 GetTextMetrics(FontDC, &TM);
790
791 /* Calculation from the following documentation:
792 * https://support.microsoft.com/en-gb/help/125681/how-to-calculate-dialog-base-units-with-non-system-based-font
793 * This fixes bug 2137, dialog box calculation with a fixed-width system font
794 */
795 {
796 SIZE extent;
797 GetTextExtentPoint32A(FontDC, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 52, &extent);
798 s_BaseUnitsX = (extent.cx / 26 + 1) / 2;
799 }
800 // s_BaseUnitsX = TM.tmAveCharWidth + 1;
801 s_BaseUnitsY = TM.tmHeight;
802 }
803
804 /* Measure the *pixel* size of the string. */
805 wmessage = WIN_UTF8ToStringW(messageboxdata->message);
806 SDL_zero(TextSize);
807 DrawTextW(FontDC, wmessage, -1, &TextSize, DT_CALCRECT | DT_LEFT | DT_NOPREFIX | DT_EDITCONTROL);
808
809 // Add margins and some padding for hangs, etc.
810 TextSize.left += TextMargin;
811 TextSize.right += TextMargin + 2;
812 TextSize.top += TextMargin;
813 TextSize.bottom += TextMargin + 2;
814
815 // Done with the DC, and the string
816 DeleteDC(FontDC);
817 SDL_free(wmessage);
818
819 // Increase the size of the dialog by some border spacing around the text.
820 Size.cx = TextSize.right - TextSize.left;
821 Size.cy = TextSize.bottom - TextSize.top;
822 Size.cx += TextMargin * 2;
823 Size.cy += TextMargin * 2;
824
825 // Make dialog wider and shift text over for the icon.
826 if (icon) {
827 Size.cx += IconMargin + IconWidth;
828 TextSize.left += IconMargin + IconWidth;
829 TextSize.right += IconMargin + IconWidth;
830 }
831
832 // Ensure the size is wide enough for all of the buttons.
833 if (Size.cx < (LONG)messageboxdata->numbuttons * (ButtonWidth + ButtonMargin) + ButtonMargin) {
834 Size.cx = (LONG)messageboxdata->numbuttons * (ButtonWidth + ButtonMargin) + ButtonMargin;
835 }
836
837 // Reset the height to the icon size if it is actually bigger than the text.
838 if (icon && Size.cy < (LONG)IconMargin * 2 + IconHeight) {
839 Size.cy = (LONG)IconMargin * 2 + IconHeight;
840 }
841
842 // Add vertical space for the buttons and border.
843 Size.cy += ButtonHeight + TextMargin;
844
845 dialog = CreateDialogData(Size.cx, Size.cy, messageboxdata->title);
846 if (!dialog) {
847 return false;
848 }
849
850 if (icon && !AddDialogStaticIcon(dialog, IconMargin, IconMargin, IconWidth, IconHeight, icon)) {
851 FreeDialogData(dialog);
852 return false;
853 }
854
855 if (!AddDialogStaticText(dialog, TextSize.left, TextSize.top, TextSize.right - TextSize.left, TextSize.bottom - TextSize.top, messageboxdata->message)) {
856 FreeDialogData(dialog);
857 return false;
858 }
859
860 // Align the buttons to the right/bottom.
861 x = Size.cx - (ButtonWidth + ButtonMargin) * messageboxdata->numbuttons;
862 y = Size.cy - ButtonHeight - ButtonMargin;
863 for (i = 0; i < messageboxdata->numbuttons; i++) {
864 bool isdefault = false;
865 const char *buttontext;
866 const SDL_MessageBoxButtonData *sdlButton;
867
868 /* We always have to create the dialog buttons from left to right
869 * so that the tab order is correct. Select the info to use
870 * depending on which order was requested. */
871 if (messageboxdata->flags & SDL_MESSAGEBOX_BUTTONS_LEFT_TO_RIGHT) {
872 sdlButton = &messageboxdata->buttons[i];
873 } else {
874 sdlButton = &messageboxdata->buttons[messageboxdata->numbuttons - 1 - i];
875 }
876
877 if (sdlButton->flags & SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT) {
878 defbuttoncount++;
879 if (defbuttoncount == 1) {
880 isdefault = true;
881 }
882 }
883
884 buttontext = EscapeAmpersands(&ampescape, &ampescapesize, sdlButton->text);
885 /* Make sure to provide the correct ID to keep buttons indexed in the
886 * same order as how they are in messageboxdata. */
887 if (!buttontext || !AddDialogButton(dialog, x, y, ButtonWidth, ButtonHeight, buttontext, IDBUTTONINDEX0 + (int)(sdlButton - messageboxdata->buttons), isdefault)) {
888 FreeDialogData(dialog);
889 SDL_free(ampescape);
890 return false;
891 }
892
893 x += ButtonWidth + ButtonMargin;
894 }
895 SDL_free(ampescape);
896
897 /* If we have a parent window, get the Instance and HWND for them
898 * so that our little dialog gets exclusive focus at all times. */
899 if (messageboxdata->window) {
900 ParentWindow = messageboxdata->window->internal->hwnd;
901 }
902
903 rc = DialogBoxIndirectParam(NULL, (DLGTEMPLATE *)dialog->lpDialog, ParentWindow, MessageBoxDialogProc, (LPARAM)messageboxdata);
904 if (rc >= IDBUTTONINDEX0 && rc - IDBUTTONINDEX0 < messageboxdata->numbuttons) {
905 *buttonID = messageboxdata->buttons[rc - IDBUTTONINDEX0].buttonID;
906 result = true;
907 } else if (rc == IDCLOSED) {
908 // Dialog window closed by user or system.
909 // This could use a special return code.
910 result = true;
911 *buttonID = -1;
912 } else {
913 if (rc == 0) {
914 SDL_SetError("Invalid parent window handle");
915 } else if (rc == -1) {
916 SDL_SetError("The message box encountered an error.");
917 } else if (rc == IDINVALPTRINIT || rc == IDINVALPTRSETFOCUS || rc == IDINVALPTRCOMMAND) {
918 SDL_SetError("Invalid message box pointer in dialog procedure");
919 } else if (rc == IDINVALPTRDLGITEM) {
920 SDL_SetError("Couldn't find dialog control of the default enter-key button");
921 } else {
922 SDL_SetError("An unknown error occurred");
923 }
924 result = false;
925 }
926
927 FreeDialogData(dialog);
928 return result;
929}
930
931/* TaskDialogIndirect procedure
932 * This is because SDL targets Windows XP (0x501), so this is not defined in the platform SDK.
933 */
934/* *INDENT-OFF* */ // clang-format off
935typedef HRESULT (FAR WINAPI *TASKDIALOGINDIRECTPROC)(const TASKDIALOGCONFIG *pTaskConfig, int *pnButton, int *pnRadioButton, BOOL *pfVerificationFlagChecked);
936/* *INDENT-ON* */ // clang-format on
937
938bool WIN_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID)
939{
940 HWND ParentWindow = NULL;
941 wchar_t *wmessage;
942 wchar_t *wtitle;
943 TASKDIALOGCONFIG TaskConfig;
944 TASKDIALOG_BUTTON *pButtons;
945 TASKDIALOG_BUTTON *pButton;
946 HMODULE hComctl32;
947 TASKDIALOGINDIRECTPROC pfnTaskDialogIndirect;
948 HRESULT hr;
949 char *ampescape = NULL;
950 size_t ampescapesize = 0;
951 int nButton;
952 int nCancelButton;
953 int i;
954 bool result = false;
955
956 if (SIZE_MAX / sizeof(TASKDIALOG_BUTTON) < messageboxdata->numbuttons) {
957 return SDL_OutOfMemory();
958 }
959
960 HMODULE hUser32 = GetModuleHandle(TEXT("user32.dll"));
961 typedef DPI_AWARENESS_CONTEXT (WINAPI * SetThreadDpiAwarenessContext_t)(DPI_AWARENESS_CONTEXT);
962 SetThreadDpiAwarenessContext_t SetThreadDpiAwarenessContextFunc = (SetThreadDpiAwarenessContext_t)GetProcAddress(hUser32, "SetThreadDpiAwarenessContext");
963 DPI_AWARENESS_CONTEXT previous_context = DPI_AWARENESS_CONTEXT_UNAWARE;
964 if (SetThreadDpiAwarenessContextFunc) {
965 previous_context = SetThreadDpiAwarenessContextFunc(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
966 }
967
968 // If we cannot load comctl32.dll use the old messagebox!
969 hComctl32 = LoadLibrary(TEXT("comctl32.dll"));
970 if (!hComctl32) {
971 result = WIN_ShowOldMessageBox(messageboxdata, buttonID);
972 goto done;
973 }
974
975 /* If TaskDialogIndirect doesn't exist use the old messagebox!
976 This will fail prior to Windows Vista.
977 The manifest file in the application may require targeting version 6 of comctl32.dll, even
978 when we use LoadLibrary here!
979 If you don't want to bother with manifests, put this #pragma in your app's source code somewhere:
980 #pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
981 */
982 pfnTaskDialogIndirect = (TASKDIALOGINDIRECTPROC)GetProcAddress(hComctl32, "TaskDialogIndirect");
983 if (!pfnTaskDialogIndirect) {
984 FreeLibrary(hComctl32);
985 result = WIN_ShowOldMessageBox(messageboxdata, buttonID);
986 goto done;
987 }
988
989 /* If we have a parent window, get the Instance and HWND for them
990 so that our little dialog gets exclusive focus at all times. */
991 if (messageboxdata->window) {
992 ParentWindow = messageboxdata->window->internal->hwnd;
993 }
994
995 wmessage = WIN_UTF8ToStringW(messageboxdata->message);
996 wtitle = WIN_UTF8ToStringW(messageboxdata->title);
997
998 SDL_zero(TaskConfig);
999 TaskConfig.cbSize = sizeof(TASKDIALOGCONFIG);
1000 TaskConfig.hwndParent = ParentWindow;
1001 TaskConfig.dwFlags = TDF_SIZE_TO_CONTENT;
1002 TaskConfig.pszWindowTitle = wtitle;
1003 if (messageboxdata->flags & SDL_MESSAGEBOX_ERROR) {
1004 TaskConfig.pszMainIcon = TD_ERROR_ICON;
1005 } else if (messageboxdata->flags & SDL_MESSAGEBOX_WARNING) {
1006 TaskConfig.pszMainIcon = TD_WARNING_ICON;
1007 } else if (messageboxdata->flags & SDL_MESSAGEBOX_INFORMATION) {
1008 TaskConfig.pszMainIcon = TD_INFORMATION_ICON;
1009 } else {
1010 TaskConfig.pszMainIcon = NULL;
1011 }
1012
1013 TaskConfig.pszContent = wmessage;
1014 TaskConfig.cButtons = messageboxdata->numbuttons;
1015 pButtons = (TASKDIALOG_BUTTON *)SDL_malloc(sizeof(TASKDIALOG_BUTTON) * messageboxdata->numbuttons);
1016 TaskConfig.nDefaultButton = 0;
1017 nCancelButton = 0;
1018 for (i = 0; i < messageboxdata->numbuttons; i++) {
1019 const char *buttontext;
1020 if (messageboxdata->flags & SDL_MESSAGEBOX_BUTTONS_LEFT_TO_RIGHT) {
1021 pButton = &pButtons[i];
1022 } else {
1023 pButton = &pButtons[messageboxdata->numbuttons - 1 - i];
1024 }
1025 if (messageboxdata->buttons[i].flags & SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT) {
1026 nCancelButton = messageboxdata->buttons[i].buttonID;
1027 pButton->nButtonID = IDCANCEL;
1028 } else {
1029 pButton->nButtonID = IDBUTTONINDEX0 + i;
1030 }
1031 buttontext = EscapeAmpersands(&ampescape, &ampescapesize, messageboxdata->buttons[i].text);
1032 if (!buttontext) {
1033 int j;
1034 FreeLibrary(hComctl32);
1035 SDL_free(ampescape);
1036 SDL_free(wmessage);
1037 SDL_free(wtitle);
1038 for (j = 0; j < i; j++) {
1039 SDL_free((wchar_t *)pButtons[j].pszButtonText);
1040 }
1041 SDL_free(pButtons);
1042 return false;
1043 }
1044 pButton->pszButtonText = WIN_UTF8ToStringW(buttontext);
1045 if (messageboxdata->buttons[i].flags & SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT) {
1046 TaskConfig.nDefaultButton = pButton->nButtonID;
1047 }
1048 }
1049 TaskConfig.pButtons = pButtons;
1050
1051 // Show the Task Dialog
1052 hr = pfnTaskDialogIndirect(&TaskConfig, &nButton, NULL, NULL);
1053
1054 // Free everything
1055 FreeLibrary(hComctl32);
1056 SDL_free(ampescape);
1057 SDL_free(wmessage);
1058 SDL_free(wtitle);
1059 for (i = 0; i < messageboxdata->numbuttons; i++) {
1060 SDL_free((wchar_t *)pButtons[i].pszButtonText);
1061 }
1062 SDL_free(pButtons);
1063
1064 // Check the Task Dialog was successful and give the result
1065 if (SUCCEEDED(hr)) {
1066 if (nButton == IDCANCEL) {
1067 *buttonID = nCancelButton;
1068 } else if (nButton >= IDBUTTONINDEX0 && nButton < IDBUTTONINDEX0 + messageboxdata->numbuttons) {
1069 *buttonID = messageboxdata->buttons[nButton - IDBUTTONINDEX0].buttonID;
1070 } else {
1071 *buttonID = -1;
1072 }
1073 result = true;
1074 } else {
1075 // We failed showing the Task Dialog, use the old message box!
1076 result = WIN_ShowOldMessageBox(messageboxdata, buttonID);
1077 }
1078
1079done:
1080 if (SetThreadDpiAwarenessContextFunc) {
1081 SetThreadDpiAwarenessContextFunc(previous_context);
1082 }
1083 return result;
1084}
1085
1086#endif // SDL_VIDEO_DRIVER_WINDOWS