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