1 /*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2021 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 SDL_VIDEO_DRIVER_WINDOWS
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 #include "SDL_windowstaskdialog.h"
36
37 #ifndef SS_EDITCONTROL
38 #define SS_EDITCONTROL 0x2000
39 #endif
40
41 #ifndef IDOK
42 #define IDOK 1
43 #endif
44
45 #ifndef IDCANCEL
46 #define IDCANCEL 2
47 #endif
48
49 /* Custom dialog return codes */
50 #define IDCLOSED 20
51 #define IDINVALPTRINIT 50
52 #define IDINVALPTRCOMMAND 51
53 #define IDINVALPTRSETFOCUS 52
54 #define IDINVALPTRDLGITEM 53
55 /* First button ID */
56 #define IDBUTTONINDEX0 100
57
58 #define DLGITEMTYPEBUTTON 0x0080
59 #define DLGITEMTYPESTATIC 0x0082
60
61 /* Windows only sends the lower 16 bits of the control ID when a button
62 * gets clicked. There are also some predefined and custom IDs that lower
63 * the available number further. 2^16 - 101 buttons should be enough for
64 * everyone, no need to make the code more complex.
65 */
66 #define MAX_BUTTONS (0xffff - 100)
67
68
69 /* Display a Windows message box */
70
71 #pragma pack(push, 1)
72
73 typedef struct
74 {
75 WORD dlgVer;
76 WORD signature;
77 DWORD helpID;
78 DWORD exStyle;
79 DWORD style;
80 WORD cDlgItems;
81 short x;
82 short y;
83 short cx;
84 short cy;
85 } DLGTEMPLATEEX;
86
87 typedef struct
88 {
89 DWORD helpID;
90 DWORD exStyle;
91 DWORD style;
92 short x;
93 short y;
94 short cx;
95 short cy;
96 DWORD id;
97 } DLGITEMTEMPLATEEX;
98
99 #pragma pack(pop)
100
101 typedef struct
102 {
103 DLGTEMPLATEEX* lpDialog;
104 Uint8 *data;
105 size_t size;
106 size_t used;
107 WORD numbuttons;
108 } WIN_DialogData;
109
GetButtonIndex(const SDL_MessageBoxData * messageboxdata,Uint32 flags,size_t * i)110 static SDL_bool GetButtonIndex(const SDL_MessageBoxData *messageboxdata, Uint32 flags, size_t *i)
111 {
112 for (*i = 0; *i < (size_t)messageboxdata->numbuttons; ++*i) {
113 if (messageboxdata->buttons[*i].flags & flags) {
114 return SDL_TRUE;
115 }
116 }
117 return SDL_FALSE;
118 }
119
MessageBoxDialogProc(HWND hDlg,UINT iMessage,WPARAM wParam,LPARAM lParam)120 static INT_PTR CALLBACK MessageBoxDialogProc(HWND hDlg, UINT iMessage, WPARAM wParam, LPARAM lParam)
121 {
122 const SDL_MessageBoxData *messageboxdata;
123 size_t buttonindex;
124
125 switch ( iMessage ) {
126 case WM_INITDIALOG:
127 if (lParam == 0) {
128 EndDialog(hDlg, IDINVALPTRINIT);
129 return TRUE;
130 }
131 messageboxdata = (const SDL_MessageBoxData *)lParam;
132 SetWindowLongPtr(hDlg, GWLP_USERDATA, lParam);
133
134 if (GetButtonIndex(messageboxdata, SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, &buttonindex)) {
135 /* Focus on the first default return-key button */
136 HWND buttonctl = GetDlgItem(hDlg, (int)(IDBUTTONINDEX0 + buttonindex));
137 if (buttonctl == NULL) {
138 EndDialog(hDlg, IDINVALPTRDLGITEM);
139 }
140 PostMessage(hDlg, WM_NEXTDLGCTL, (WPARAM)buttonctl, TRUE);
141 } else {
142 /* Give the focus to the dialog window instead */
143 SetFocus(hDlg);
144 }
145 return FALSE;
146 case WM_SETFOCUS:
147 messageboxdata = (const SDL_MessageBoxData *)GetWindowLongPtr(hDlg, GWLP_USERDATA);
148 if (messageboxdata == NULL) {
149 EndDialog(hDlg, IDINVALPTRSETFOCUS);
150 return TRUE;
151 }
152
153 /* Let the default button be focused if there is one. Otherwise, prevent any initial focus. */
154 if (GetButtonIndex(messageboxdata, SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, &buttonindex)) {
155 return FALSE;
156 }
157 return TRUE;
158 case WM_COMMAND:
159 messageboxdata = (const SDL_MessageBoxData *)GetWindowLongPtr(hDlg, GWLP_USERDATA);
160 if (messageboxdata == NULL) {
161 EndDialog(hDlg, IDINVALPTRCOMMAND);
162 return TRUE;
163 }
164
165 /* Return the ID of the button that was pushed */
166 if (wParam == IDOK) {
167 if (GetButtonIndex(messageboxdata, SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, &buttonindex)) {
168 EndDialog(hDlg, IDBUTTONINDEX0 + buttonindex);
169 }
170 } else if (wParam == IDCANCEL) {
171 if (GetButtonIndex(messageboxdata, SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, &buttonindex)) {
172 EndDialog(hDlg, IDBUTTONINDEX0 + buttonindex);
173 } else {
174 /* Closing of window was requested by user or system. It would be rude not to comply. */
175 EndDialog(hDlg, IDCLOSED);
176 }
177 } else if (wParam >= IDBUTTONINDEX0 && (int)wParam - IDBUTTONINDEX0 < messageboxdata->numbuttons) {
178 EndDialog(hDlg, wParam);
179 }
180 return TRUE;
181
182 default:
183 break;
184 }
185 return FALSE;
186 }
187
ExpandDialogSpace(WIN_DialogData * dialog,size_t space)188 static SDL_bool ExpandDialogSpace(WIN_DialogData *dialog, size_t space)
189 {
190 /* Growing memory in 64 KiB steps. */
191 const size_t sizestep = 0x10000;
192 size_t size = dialog->size;
193
194 if (size == 0) {
195 /* Start with 4 KiB or a multiple of 64 KiB to fit the data. */
196 size = 0x1000;
197 if (SIZE_MAX - sizestep < space) {
198 size = space;
199 } else if (space > size) {
200 size = (space + sizestep) & ~(sizestep - 1);
201 }
202 } else if (SIZE_MAX - dialog->used < space) {
203 SDL_OutOfMemory();
204 return SDL_FALSE;
205 } else if (SIZE_MAX - (dialog->used + space) < sizestep) {
206 /* Close to the maximum. */
207 size = dialog->used + space;
208 } else if (size < dialog->used + space) {
209 /* Round up to the next 64 KiB block. */
210 size = dialog->used + space;
211 size += sizestep - size % sizestep;
212 }
213
214 if (size > dialog->size) {
215 void *data = SDL_realloc(dialog->data, size);
216 if (!data) {
217 SDL_OutOfMemory();
218 return SDL_FALSE;
219 }
220 dialog->data = data;
221 dialog->size = size;
222 dialog->lpDialog = (DLGTEMPLATEEX*)dialog->data;
223 }
224 return SDL_TRUE;
225 }
226
AlignDialogData(WIN_DialogData * dialog,size_t size)227 static SDL_bool AlignDialogData(WIN_DialogData *dialog, size_t size)
228 {
229 size_t padding = (dialog->used % size);
230
231 if (!ExpandDialogSpace(dialog, padding)) {
232 return SDL_FALSE;
233 }
234
235 dialog->used += padding;
236
237 return SDL_TRUE;
238 }
239
AddDialogData(WIN_DialogData * dialog,const void * data,size_t size)240 static SDL_bool AddDialogData(WIN_DialogData *dialog, const void *data, size_t size)
241 {
242 if (!ExpandDialogSpace(dialog, size)) {
243 return SDL_FALSE;
244 }
245
246 SDL_memcpy(dialog->data+dialog->used, data, size);
247 dialog->used += size;
248
249 return SDL_TRUE;
250 }
251
AddDialogString(WIN_DialogData * dialog,const char * string)252 static SDL_bool AddDialogString(WIN_DialogData *dialog, const char *string)
253 {
254 WCHAR *wstring;
255 WCHAR *p;
256 size_t count;
257 SDL_bool status;
258
259 if (!string) {
260 string = "";
261 }
262
263 wstring = WIN_UTF8ToStringW(string);
264 if (!wstring) {
265 return SDL_FALSE;
266 }
267
268 /* Find out how many characters we have, including null terminator */
269 count = 0;
270 for (p = wstring; *p; ++p) {
271 ++count;
272 }
273 ++count;
274
275 status = AddDialogData(dialog, wstring, count*sizeof(WCHAR));
276 SDL_free(wstring);
277 return status;
278 }
279
280 static int s_BaseUnitsX;
281 static int s_BaseUnitsY;
Vec2ToDLU(short * x,short * y)282 static void Vec2ToDLU(short *x, short *y)
283 {
284 SDL_assert(s_BaseUnitsX != 0); /* we init in WIN_ShowMessageBox(), which is the only public function... */
285
286 *x = MulDiv(*x, 4, s_BaseUnitsX);
287 *y = MulDiv(*y, 8, s_BaseUnitsY);
288 }
289
290
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)291 static SDL_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)
292 {
293 DLGITEMTEMPLATEEX item;
294 WORD marker = 0xFFFF;
295 WORD extraData = 0;
296
297 SDL_zero(item);
298 item.style = style;
299 item.exStyle = exStyle;
300 item.x = x;
301 item.y = y;
302 item.cx = w;
303 item.cy = h;
304 item.id = id;
305
306 Vec2ToDLU(&item.x, &item.y);
307 Vec2ToDLU(&item.cx, &item.cy);
308
309 if (!AlignDialogData(dialog, sizeof(DWORD))) {
310 return SDL_FALSE;
311 }
312 if (!AddDialogData(dialog, &item, sizeof(item))) {
313 return SDL_FALSE;
314 }
315 if (!AddDialogData(dialog, &marker, sizeof(marker))) {
316 return SDL_FALSE;
317 }
318 if (!AddDialogData(dialog, &type, sizeof(type))) {
319 return SDL_FALSE;
320 }
321 if (type == DLGITEMTYPEBUTTON || (type == DLGITEMTYPESTATIC && caption != NULL)) {
322 if (!AddDialogString(dialog, caption)) {
323 return SDL_FALSE;
324 }
325 } else {
326 if (!AddDialogData(dialog, &marker, sizeof(marker))) {
327 return SDL_FALSE;
328 }
329 if (!AddDialogData(dialog, &ordinal, sizeof(ordinal))) {
330 return SDL_FALSE;
331 }
332 }
333 if (!AddDialogData(dialog, &extraData, sizeof(extraData))) {
334 return SDL_FALSE;
335 }
336 if (type == DLGITEMTYPEBUTTON) {
337 dialog->numbuttons++;
338 }
339 ++dialog->lpDialog->cDlgItems;
340
341 return SDL_TRUE;
342 }
343
AddDialogStaticText(WIN_DialogData * dialog,int x,int y,int w,int h,const char * text)344 static SDL_bool AddDialogStaticText(WIN_DialogData *dialog, int x, int y, int w, int h, const char *text)
345 {
346 DWORD style = WS_VISIBLE | WS_CHILD | SS_LEFT | SS_NOPREFIX | SS_EDITCONTROL | WS_GROUP;
347 return AddDialogControl(dialog, DLGITEMTYPESTATIC, style, 0, x, y, w, h, -1, text, 0);
348 }
349
AddDialogStaticIcon(WIN_DialogData * dialog,int x,int y,int w,int h,Uint16 ordinal)350 static SDL_bool AddDialogStaticIcon(WIN_DialogData *dialog, int x, int y, int w, int h, Uint16 ordinal)
351 {
352 DWORD style = WS_VISIBLE | WS_CHILD | SS_ICON | WS_GROUP;
353 return AddDialogControl(dialog, DLGITEMTYPESTATIC, style, 0, x, y, w, h, -2, NULL, ordinal);
354 }
355
AddDialogButton(WIN_DialogData * dialog,int x,int y,int w,int h,const char * text,int id,SDL_bool isDefault)356 static SDL_bool AddDialogButton(WIN_DialogData *dialog, int x, int y, int w, int h, const char *text, int id, SDL_bool isDefault)
357 {
358 DWORD style = WS_VISIBLE | WS_CHILD | WS_TABSTOP;
359 if (isDefault) {
360 style |= BS_DEFPUSHBUTTON;
361 } else {
362 style |= BS_PUSHBUTTON;
363 }
364 /* The first button marks the start of the group. */
365 if (dialog->numbuttons == 0) {
366 style |= WS_GROUP;
367 }
368 return AddDialogControl(dialog, DLGITEMTYPEBUTTON, style, 0, x, y, w, h, id, text, 0);
369 }
370
FreeDialogData(WIN_DialogData * dialog)371 static void FreeDialogData(WIN_DialogData *dialog)
372 {
373 SDL_free(dialog->data);
374 SDL_free(dialog);
375 }
376
CreateDialogData(int w,int h,const char * caption)377 static WIN_DialogData *CreateDialogData(int w, int h, const char *caption)
378 {
379 WIN_DialogData *dialog;
380 DLGTEMPLATEEX dialogTemplate;
381 WORD WordToPass;
382
383 SDL_zero(dialogTemplate);
384 dialogTemplate.dlgVer = 1;
385 dialogTemplate.signature = 0xffff;
386 dialogTemplate.style = (WS_CAPTION | DS_CENTER | DS_SHELLFONT);
387 dialogTemplate.x = 0;
388 dialogTemplate.y = 0;
389 dialogTemplate.cx = w;
390 dialogTemplate.cy = h;
391 Vec2ToDLU(&dialogTemplate.cx, &dialogTemplate.cy);
392
393 dialog = (WIN_DialogData *)SDL_calloc(1, sizeof(*dialog));
394 if (!dialog) {
395 return NULL;
396 }
397
398 if (!AddDialogData(dialog, &dialogTemplate, sizeof(dialogTemplate))) {
399 FreeDialogData(dialog);
400 return NULL;
401 }
402
403 /* No menu */
404 WordToPass = 0;
405 if (!AddDialogData(dialog, &WordToPass, 2)) {
406 FreeDialogData(dialog);
407 return NULL;
408 }
409
410 /* No custom class */
411 if (!AddDialogData(dialog, &WordToPass, 2)) {
412 FreeDialogData(dialog);
413 return NULL;
414 }
415
416 /* title */
417 if (!AddDialogString(dialog, caption)) {
418 FreeDialogData(dialog);
419 return NULL;
420 }
421
422 /* Font stuff */
423 {
424 /*
425 * We want to use the system messagebox font.
426 */
427 BYTE ToPass;
428
429 NONCLIENTMETRICSA NCM;
430 NCM.cbSize = sizeof(NCM);
431 SystemParametersInfoA(SPI_GETNONCLIENTMETRICS, 0, &NCM, 0);
432
433 /* Font size - convert to logical font size for dialog parameter. */
434 {
435 HDC ScreenDC = GetDC(NULL);
436 int LogicalPixelsY = GetDeviceCaps(ScreenDC, LOGPIXELSY);
437 if (!LogicalPixelsY) /* This can happen if the application runs out of GDI handles */
438 LogicalPixelsY = 72;
439 WordToPass = (WORD)(-72 * NCM.lfMessageFont.lfHeight / LogicalPixelsY);
440 ReleaseDC(NULL, ScreenDC);
441 }
442
443 if (!AddDialogData(dialog, &WordToPass, 2)) {
444 FreeDialogData(dialog);
445 return NULL;
446 }
447
448 /* Font weight */
449 WordToPass = (WORD)NCM.lfMessageFont.lfWeight;
450 if (!AddDialogData(dialog, &WordToPass, 2)) {
451 FreeDialogData(dialog);
452 return NULL;
453 }
454
455 /* italic? */
456 ToPass = NCM.lfMessageFont.lfItalic;
457 if (!AddDialogData(dialog, &ToPass, 1)) {
458 FreeDialogData(dialog);
459 return NULL;
460 }
461
462 /* charset? */
463 ToPass = NCM.lfMessageFont.lfCharSet;
464 if (!AddDialogData(dialog, &ToPass, 1)) {
465 FreeDialogData(dialog);
466 return NULL;
467 }
468
469 /* font typeface. */
470 if (!AddDialogString(dialog, NCM.lfMessageFont.lfFaceName)) {
471 FreeDialogData(dialog);
472 return NULL;
473 }
474 }
475
476 return dialog;
477 }
478
479 /* Escaping ampersands is necessary to disable mnemonics in dialog controls.
480 * The caller provides a char** for dst and a size_t* for dstlen where the
481 * address of the work buffer and its size will be stored. Their values must be
482 * NULL and 0 on the first call. src is the string to be escaped. On error, the
483 * function returns NULL and, on success, returns a pointer to the escaped
484 * sequence as a read-only string that is valid until the next call or until the
485 * work buffer is freed. Once all strings have been processed, it's the caller's
486 * responsibilty to free the work buffer with SDL_free, even on errors.
487 */
EscapeAmpersands(char ** dst,size_t * dstlen,const char * src)488 static const char *EscapeAmpersands(char **dst, size_t *dstlen, const char *src)
489 {
490 char *newdst;
491 size_t ampcount = 0;
492 size_t srclen = 0;
493
494 if (src == NULL) {
495 return NULL;
496 }
497
498 while (src[srclen]) {
499 if (src[srclen] == '&') {
500 ampcount++;
501 }
502 srclen++;
503 }
504 srclen++;
505
506 if (ampcount == 0) {
507 /* Nothing to do. */
508 return src;
509 }
510 if (SIZE_MAX - srclen < ampcount) {
511 return NULL;
512 }
513 if (*dst == NULL || *dstlen < srclen + ampcount) {
514 /* Allocating extra space in case the next strings are a bit longer. */
515 size_t extraspace = SIZE_MAX - (srclen + ampcount);
516 if (extraspace > 512) {
517 extraspace = 512;
518 }
519 *dstlen = srclen + ampcount + extraspace;
520 SDL_free(*dst);
521 *dst = NULL;
522 newdst = SDL_malloc(*dstlen);
523 if (newdst == NULL) {
524 return NULL;
525 }
526 *dst = newdst;
527 } else {
528 newdst = *dst;
529 }
530
531 /* The escape character is the ampersand itself. */
532 while (srclen--) {
533 if (*src == '&') {
534 *newdst++ = '&';
535 }
536 *newdst++ = *src++;
537 }
538
539 return *dst;
540 }
541
542 /* This function is called if a Task Dialog is unsupported. */
543 static int
WIN_ShowOldMessageBox(const SDL_MessageBoxData * messageboxdata,int * buttonid)544 WIN_ShowOldMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)
545 {
546 WIN_DialogData *dialog;
547 int i, x, y, retval;
548 HFONT DialogFont;
549 SIZE Size;
550 RECT TextSize;
551 wchar_t* wmessage;
552 TEXTMETRIC TM;
553 HDC FontDC;
554 INT_PTR result;
555 char *ampescape = NULL;
556 size_t ampescapesize = 0;
557 Uint16 defbuttoncount = 0;
558 Uint16 icon = 0;
559
560 HWND ParentWindow = NULL;
561
562 const int ButtonWidth = 88;
563 const int ButtonHeight = 26;
564 const int TextMargin = 16;
565 const int ButtonMargin = 12;
566 const int IconWidth = GetSystemMetrics(SM_CXICON);
567 const int IconHeight = GetSystemMetrics(SM_CYICON);
568 const int IconMargin = 20;
569
570 if (messageboxdata->numbuttons > MAX_BUTTONS) {
571 return SDL_SetError("Number of butons exceeds limit of %d", MAX_BUTTONS);
572 }
573
574 switch (messageboxdata->flags) {
575 case SDL_MESSAGEBOX_ERROR:
576 icon = (Uint16)(size_t)IDI_ERROR;
577 break;
578 case SDL_MESSAGEBOX_WARNING:
579 icon = (Uint16)(size_t)IDI_WARNING;
580 break;
581 case SDL_MESSAGEBOX_INFORMATION:
582 icon = (Uint16)(size_t)IDI_INFORMATION;
583 break;
584 }
585
586 /* Jan 25th, 2013 - dant@fleetsa.com
587 *
588 * I've tried to make this more reasonable, but I've run in to a lot
589 * of nonsense.
590 *
591 * The original issue is the code was written in pixels and not
592 * dialog units (DLUs). All DialogBox functions use DLUs, which
593 * vary based on the selected font (yay).
594 *
595 * According to MSDN, the most reliable way to convert is via
596 * MapDialogUnits, which requires an HWND, which we don't have
597 * at time of template creation.
598 *
599 * We do however have:
600 * The system font (DLU width 8 for me)
601 * The font we select for the dialog (DLU width 6 for me)
602 *
603 * Based on experimentation, *neither* of these return the value
604 * actually used. Stepping in to MapDialogUnits(), the conversion
605 * is fairly clear, and uses 7 for me.
606 *
607 * As a result, some of this is hacky to ensure the sizing is
608 * somewhat correct.
609 *
610 * Honestly, a long term solution is to use CreateWindow, not CreateDialog.
611 *
612 * In order to get text dimensions we need to have a DC with the desired font.
613 * I'm assuming a dialog box in SDL is rare enough we can to the create.
614 */
615 FontDC = CreateCompatibleDC(0);
616
617 {
618 /* Create a duplicate of the font used in system message boxes. */
619 LOGFONT lf;
620 NONCLIENTMETRICS NCM;
621 NCM.cbSize = sizeof(NCM);
622 SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &NCM, 0);
623 lf = NCM.lfMessageFont;
624 DialogFont = CreateFontIndirect(&lf);
625 }
626
627 /* Select the font in to our DC */
628 SelectObject(FontDC, DialogFont);
629
630 {
631 /* Get the metrics to try and figure our DLU conversion. */
632 GetTextMetrics(FontDC, &TM);
633
634 /* Calculation from the following documentation:
635 * https://support.microsoft.com/en-gb/help/125681/how-to-calculate-dialog-base-units-with-non-system-based-font
636 * This fixes bug 2137, dialog box calculation with a fixed-width system font
637 */
638 {
639 SIZE extent;
640 GetTextExtentPoint32A(FontDC, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 52, &extent);
641 s_BaseUnitsX = (extent.cx / 26 + 1) / 2;
642 }
643 /*s_BaseUnitsX = TM.tmAveCharWidth + 1;*/
644 s_BaseUnitsY = TM.tmHeight;
645 }
646
647 /* Measure the *pixel* size of the string. */
648 wmessage = WIN_UTF8ToStringW(messageboxdata->message);
649 SDL_zero(TextSize);
650 DrawTextW(FontDC, wmessage, -1, &TextSize, DT_CALCRECT | DT_LEFT | DT_NOPREFIX | DT_EDITCONTROL);
651
652 /* Add margins and some padding for hangs, etc. */
653 TextSize.left += TextMargin;
654 TextSize.right += TextMargin + 2;
655 TextSize.top += TextMargin;
656 TextSize.bottom += TextMargin + 2;
657
658 /* Done with the DC, and the string */
659 DeleteDC(FontDC);
660 SDL_free(wmessage);
661
662 /* Increase the size of the dialog by some border spacing around the text. */
663 Size.cx = TextSize.right - TextSize.left;
664 Size.cy = TextSize.bottom - TextSize.top;
665 Size.cx += TextMargin * 2;
666 Size.cy += TextMargin * 2;
667
668 /* Make dialog wider and shift text over for the icon. */
669 if (icon) {
670 Size.cx += IconMargin + IconWidth;
671 TextSize.left += IconMargin + IconWidth;
672 TextSize.right += IconMargin + IconWidth;
673 }
674
675 /* Ensure the size is wide enough for all of the buttons. */
676 if (Size.cx < messageboxdata->numbuttons * (ButtonWidth + ButtonMargin) + ButtonMargin)
677 Size.cx = messageboxdata->numbuttons * (ButtonWidth + ButtonMargin) + ButtonMargin;
678
679 /* Reset the height to the icon size if it is actually bigger than the text. */
680 if (icon && Size.cy < IconMargin * 2 + IconHeight) {
681 Size.cy = IconMargin * 2 + IconHeight;
682 }
683
684 /* Add vertical space for the buttons and border. */
685 Size.cy += ButtonHeight + TextMargin;
686
687 dialog = CreateDialogData(Size.cx, Size.cy, messageboxdata->title);
688 if (!dialog) {
689 return -1;
690 }
691
692 if (icon && ! AddDialogStaticIcon(dialog, IconMargin, IconMargin, IconWidth, IconHeight, icon)) {
693 FreeDialogData(dialog);
694 return -1;
695 }
696
697 if (!AddDialogStaticText(dialog, TextSize.left, TextSize.top, TextSize.right - TextSize.left, TextSize.bottom - TextSize.top, messageboxdata->message)) {
698 FreeDialogData(dialog);
699 return -1;
700 }
701
702 /* Align the buttons to the right/bottom. */
703 x = Size.cx - (ButtonWidth + ButtonMargin) * messageboxdata->numbuttons;
704 y = Size.cy - ButtonHeight - ButtonMargin;
705 for (i = 0; i < messageboxdata->numbuttons; i++) {
706 SDL_bool isdefault = SDL_FALSE;
707 const char *buttontext;
708 const SDL_MessageBoxButtonData *sdlButton;
709
710 /* We always have to create the dialog buttons from left to right
711 * so that the tab order is correct. Select the info to use
712 * depending on which order was requested. */
713 if (messageboxdata->flags & SDL_MESSAGEBOX_BUTTONS_LEFT_TO_RIGHT) {
714 sdlButton = &messageboxdata->buttons[i];
715 } else {
716 sdlButton = &messageboxdata->buttons[messageboxdata->numbuttons - 1 - i];
717 }
718
719 if (sdlButton->flags & SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT) {
720 defbuttoncount++;
721 if (defbuttoncount == 1) {
722 isdefault = SDL_TRUE;
723 }
724 }
725
726 buttontext = EscapeAmpersands(&escape, &escapesize, sdlButton->text);
727 /* Make sure to provide the correct ID to keep buttons indexed in the
728 * same order as how they are in messageboxdata. */
729 if (buttontext == NULL || !AddDialogButton(dialog, x, y, ButtonWidth, ButtonHeight, buttontext, IDBUTTONINDEX0 + (int)(sdlButton - messageboxdata->buttons), isdefault)) {
730 FreeDialogData(dialog);
731 SDL_free(ampescape);
732 return -1;
733 }
734
735 x += ButtonWidth + ButtonMargin;
736 }
737 SDL_free(ampescape);
738
739 /* If we have a parent window, get the Instance and HWND for them
740 * so that our little dialog gets exclusive focus at all times. */
741 if (messageboxdata->window) {
742 ParentWindow = ((SDL_WindowData*)messageboxdata->window->driverdata)->hwnd;
743 }
744
745 result = DialogBoxIndirectParam(NULL, (DLGTEMPLATE*)dialog->lpDialog, ParentWindow, MessageBoxDialogProc, (LPARAM)messageboxdata);
746 if (result >= IDBUTTONINDEX0 && result - IDBUTTONINDEX0 < messageboxdata->numbuttons) {
747 *buttonid = messageboxdata->buttons[result - IDBUTTONINDEX0].buttonid;
748 retval = 0;
749 } else if (result == IDCLOSED) {
750 /* Dialog window closed by user or system. */
751 /* This could use a special return code. */
752 retval = 0;
753 *buttonid = -1;
754 } else {
755 if (result == 0) {
756 SDL_SetError("Invalid parent window handle");
757 } else if (result == -1) {
758 SDL_SetError("The message box encountered an error.");
759 } else if (result == IDINVALPTRINIT || result == IDINVALPTRSETFOCUS || result == IDINVALPTRCOMMAND) {
760 SDL_SetError("Invalid message box pointer in dialog procedure");
761 } else if (result == IDINVALPTRDLGITEM) {
762 SDL_SetError("Couldn't find dialog control of the default enter-key button");
763 } else {
764 SDL_SetError("An unknown error occured");
765 }
766 retval = -1;
767 }
768
769 FreeDialogData(dialog);
770 return retval;
771 }
772
773 /* TaskDialogIndirect procedure
774 * This is because SDL targets Windows XP (0x501), so this is not defined in the platform SDK.
775 */
776 typedef HRESULT(FAR WINAPI *TASKDIALOGINDIRECTPROC)(const TASKDIALOGCONFIG *pTaskConfig, int *pnButton, int *pnRadioButton, BOOL *pfVerificationFlagChecked);
777
778 int
WIN_ShowMessageBox(const SDL_MessageBoxData * messageboxdata,int * buttonid)779 WIN_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)
780 {
781 HWND ParentWindow = NULL;
782 wchar_t *wmessage;
783 wchar_t *wtitle;
784 TASKDIALOGCONFIG TaskConfig;
785 TASKDIALOG_BUTTON *pButtons;
786 TASKDIALOG_BUTTON *pButton;
787 HMODULE hComctl32;
788 TASKDIALOGINDIRECTPROC pfnTaskDialogIndirect;
789 HRESULT hr;
790 char *ampescape = NULL;
791 size_t ampescapesize = 0;
792 int nButton;
793 int nCancelButton;
794 int i;
795
796 if (SIZE_MAX / sizeof(TASKDIALOG_BUTTON) < messageboxdata->numbuttons) {
797 return SDL_OutOfMemory();
798 }
799
800 /* If we cannot load comctl32.dll use the old messagebox! */
801 hComctl32 = LoadLibrary(TEXT("comctl32.dll"));
802 if (hComctl32 == NULL) {
803 return WIN_ShowOldMessageBox(messageboxdata, buttonid);
804 }
805
806 /* If TaskDialogIndirect doesn't exist use the old messagebox!
807 This will fail prior to Windows Vista.
808 The manifest file in the application may require targeting version 6 of comctl32.dll, even
809 when we use LoadLibrary here!
810 If you don't want to bother with manifests, put this #pragma in your app's source code somewhere:
811 pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
812 */
813 pfnTaskDialogIndirect = (TASKDIALOGINDIRECTPROC) GetProcAddress(hComctl32, "TaskDialogIndirect");
814 if (pfnTaskDialogIndirect == NULL) {
815 FreeLibrary(hComctl32);
816 return WIN_ShowOldMessageBox(messageboxdata, buttonid);
817 }
818
819 /* If we have a parent window, get the Instance and HWND for them
820 so that our little dialog gets exclusive focus at all times. */
821 if (messageboxdata->window) {
822 ParentWindow = ((SDL_WindowData *) messageboxdata->window->driverdata)->hwnd;
823 }
824
825 wmessage = WIN_UTF8ToStringW(messageboxdata->message);
826 wtitle = WIN_UTF8ToStringW(messageboxdata->title);
827
828 SDL_zero(TaskConfig);
829 TaskConfig.cbSize = sizeof (TASKDIALOGCONFIG);
830 TaskConfig.hwndParent = ParentWindow;
831 TaskConfig.dwFlags = TDF_SIZE_TO_CONTENT;
832 TaskConfig.pszWindowTitle = wtitle;
833 if (messageboxdata->flags & SDL_MESSAGEBOX_ERROR) {
834 TaskConfig.pszMainIcon = TD_ERROR_ICON;
835 } else if (messageboxdata->flags & SDL_MESSAGEBOX_WARNING) {
836 TaskConfig.pszMainIcon = TD_WARNING_ICON;
837 } else if (messageboxdata->flags & SDL_MESSAGEBOX_INFORMATION) {
838 TaskConfig.pszMainIcon = TD_INFORMATION_ICON;
839 } else {
840 TaskConfig.pszMainIcon = NULL;
841 }
842
843 TaskConfig.pszContent = wmessage;
844 TaskConfig.cButtons = messageboxdata->numbuttons;
845 pButtons = SDL_malloc(sizeof (TASKDIALOG_BUTTON) * messageboxdata->numbuttons);
846 TaskConfig.nDefaultButton = 0;
847 nCancelButton = 0;
848 for (i = 0; i < messageboxdata->numbuttons; i++)
849 {
850 const char *buttontext;
851 if (messageboxdata->flags & SDL_MESSAGEBOX_BUTTONS_LEFT_TO_RIGHT) {
852 pButton = &pButtons[i];
853 } else {
854 pButton = &pButtons[messageboxdata->numbuttons - 1 - i];
855 }
856 if (messageboxdata->buttons[i].flags & SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT) {
857 nCancelButton = messageboxdata->buttons[i].buttonid;
858 pButton->nButtonID = IDCANCEL;
859 } else {
860 pButton->nButtonID = IDBUTTONINDEX0 + i;
861 }
862 buttontext = EscapeAmpersands(&escape, &escapesize, messageboxdata->buttons[i].text);
863 if (buttontext == NULL) {
864 int j;
865 FreeLibrary(hComctl32);
866 SDL_free(ampescape);
867 SDL_free(wmessage);
868 SDL_free(wtitle);
869 for (j = 0; j < i; j++) {
870 SDL_free((wchar_t *) pButtons[j].pszButtonText);
871 }
872 SDL_free(pButtons);
873 return -1;
874 }
875 pButton->pszButtonText = WIN_UTF8ToStringW(buttontext);
876 if (messageboxdata->buttons[i].flags & SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT) {
877 TaskConfig.nDefaultButton = pButton->nButtonID;
878 }
879 }
880 TaskConfig.pButtons = pButtons;
881
882 /* Show the Task Dialog */
883 hr = pfnTaskDialogIndirect(&TaskConfig, &nButton, NULL, NULL);
884
885 /* Free everything */
886 FreeLibrary(hComctl32);
887 SDL_free(ampescape);
888 SDL_free(wmessage);
889 SDL_free(wtitle);
890 for (i = 0; i < messageboxdata->numbuttons; i++) {
891 SDL_free((wchar_t *) pButtons[i].pszButtonText);
892 }
893 SDL_free(pButtons);
894
895 /* Check the Task Dialog was successful and give the result */
896 if (SUCCEEDED(hr)) {
897 if (nButton == IDCANCEL) {
898 *buttonid = nCancelButton;
899 } else if (nButton >= IDBUTTONINDEX0 && nButton < IDBUTTONINDEX0 + messageboxdata->numbuttons) {
900 *buttonid = messageboxdata->buttons[nButton - IDBUTTONINDEX0].buttonid;
901 } else {
902 *buttonid = -1;
903 }
904 return 0;
905 }
906
907 /* We failed showing the Task Dialog, use the old message box! */
908 return WIN_ShowOldMessageBox(messageboxdata, buttonid);
909 }
910
911 #endif /* SDL_VIDEO_DRIVER_WINDOWS */
912
913 /* vi: set ts=4 sw=4 expandtab: */
914