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(&ampescape, &ampescapesize, 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(&ampescape, &ampescapesize, 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