src/video/windows/SDL_windowsmessagebox.c
author Sam Lantinga <slouken@libsdl.org>
Sat, 24 Mar 2018 10:26:40 -0700
changeset 11937 f21a2cd69acf
parent 11920 c8b4a5166613
child 12042 23bc0cb86dc6
permissions -rw-r--r--
Fixed bug 3804 - Message box on Windows truncates button ID

Simon Hug

I just wanted to fix a simple compiler warning in SDL_ShowMessageBox on Windows (which Sam fixed recently) and ended up finding some issues.

Attached patch fixes these issues:

- Because Windows only reports the lower 16 bits of the control identifier that was pushed, the button IDs used by SDL (C type int, most likely 32 bits) can get cut off.

- The documentation states (somewhat ambiguously) that the button ID will be -1 if the dialog was closed, but the current code sets 0. For SDL 2.1, I think this should be a return code of SDL_ShowMessageBox itself. That will free up the button ID and it seems a more appropriate place for signaling this event.

- Ampersands in controls will create mnemonics on Windows (underlined letters that, if combined with the Alt key, will push the button). I was thinking of adding a hint or flag to let the users enable it, but that might have unexpected results.

- When the size of the text gets calculated, it doesn't use the same parameters as the static control. This can cut off text or wrap it weirdly.

- On Windows, the Tab key is used to switch between control groups and sometimes between buttons in dialogs. This didn't seem to work correctly.

Attached patch also adds:

- Icons. Just the system ones that can be loaded with the ordinals IDI_ERROR, IDI_WARNING and IDI_INFORMATION.

- A button limit of 2^16 - 101.

- Some more specific error messages, but they never reach the user because how SDL_ShowMessageBox handles them if an implementation returns with an error.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2018 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 
   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 
   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 
   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 
   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 
   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 
   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;
   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 
   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 
   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 
   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 
   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, IDBUTTONINDEX0 + dialog->numbuttons, text, 0);
   371 }
   372 
   373 static void FreeDialogData(WIN_DialogData *dialog)
   374 {
   375     SDL_free(dialog->data);
   376     SDL_free(dialog);
   377 }
   378 
   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  */
   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
   546 WIN_ShowOldMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)
   547 {
   548     WIN_DialogData *dialog;
   549     int i, x, y, retval;
   550     const SDL_MessageBoxButtonData *buttons = messageboxdata->buttons;
   551     HFONT DialogFont;
   552     SIZE Size;
   553     RECT TextSize;
   554     wchar_t* wmessage;
   555     TEXTMETRIC TM;
   556     HDC FontDC;
   557     INT_PTR result;
   558     char *ampescape = NULL;
   559     size_t ampescapesize = 0;
   560     Uint16 defbuttoncount = 0;
   561     Uint16 icon = 0;
   562 
   563     HWND ParentWindow = NULL;
   564 
   565     const int ButtonWidth = 88;
   566     const int ButtonHeight = 26;
   567     const int TextMargin = 16;
   568     const int ButtonMargin = 12;
   569     const int IconWidth = GetSystemMetrics(SM_CXICON);
   570     const int IconHeight = GetSystemMetrics(SM_CYICON);
   571     const int IconMargin = 20;
   572 
   573     if (messageboxdata->numbuttons > MAX_BUTTONS) {
   574         return SDL_SetError("Number of butons exceeds limit of %d", MAX_BUTTONS);
   575     }
   576 
   577     switch (messageboxdata->flags) {
   578     case SDL_MESSAGEBOX_ERROR:
   579         icon = (Uint16)IDI_ERROR;
   580         break;
   581     case SDL_MESSAGEBOX_WARNING:
   582         icon = (Uint16)IDI_WARNING;
   583         break;
   584     case SDL_MESSAGEBOX_INFORMATION:
   585         icon = (Uint16)IDI_INFORMATION;
   586         break;
   587     }
   588 
   589     /* Jan 25th, 2013 - dant@fleetsa.com
   590      *
   591      *
   592      * I've tried to make this more reasonable, but I've run in to a lot
   593      * of nonsense.
   594      *
   595      * The original issue is the code was written in pixels and not
   596      * dialog units (DLUs). All DialogBox functions use DLUs, which
   597      * vary based on the selected font (yay).
   598      *
   599      * According to MSDN, the most reliable way to convert is via
   600      * MapDialogUnits, which requires an HWND, which we don't have
   601      * at time of template creation.
   602      *
   603      * We do however have:
   604      *  The system font (DLU width 8 for me)
   605      *  The font we select for the dialog (DLU width 6 for me)
   606      *
   607      * Based on experimentation, *neither* of these return the value
   608      * actually used. Stepping in to MapDialogUnits(), the conversion
   609      * is fairly clear, and uses 7 for me.
   610      *
   611      * As a result, some of this is hacky to ensure the sizing is
   612      * somewhat correct.
   613      *
   614      * Honestly, a long term solution is to use CreateWindow, not CreateDialog.
   615      *
   616 
   617      *
   618      * In order to get text dimensions we need to have a DC with the desired font.
   619      * I'm assuming a dialog box in SDL is rare enough we can to the create.
   620      */
   621     FontDC = CreateCompatibleDC(0);
   622 
   623     {
   624         /* Create a duplicate of the font used in system message boxes. */
   625         LOGFONT lf;
   626         NONCLIENTMETRICS NCM;
   627         NCM.cbSize = sizeof(NCM);
   628         SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &NCM, 0);
   629         lf = NCM.lfMessageFont;
   630         DialogFont = CreateFontIndirect(&lf);
   631     }
   632 
   633     /* Select the font in to our DC */
   634     SelectObject(FontDC, DialogFont);
   635 
   636     {
   637         /* Get the metrics to try and figure our DLU conversion. */
   638         GetTextMetrics(FontDC, &TM);
   639 
   640         /* Calculation from the following documentation:
   641          * https://support.microsoft.com/en-gb/help/125681/how-to-calculate-dialog-base-units-with-non-system-based-font
   642          * This fixes bug 2137, dialog box calculation with a fixed-width system font
   643          */
   644         {
   645             SIZE extent;
   646             GetTextExtentPoint32A(FontDC, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 52, &extent);
   647             s_BaseUnitsX = (extent.cx / 26 + 1) / 2;
   648         }
   649         /*s_BaseUnitsX = TM.tmAveCharWidth + 1;*/
   650         s_BaseUnitsY = TM.tmHeight;
   651     }
   652 
   653     /* Measure the *pixel* size of the string. */
   654     wmessage = WIN_UTF8ToString(messageboxdata->message);
   655     SDL_zero(TextSize);
   656     DrawText(FontDC, wmessage, -1, &TextSize, DT_CALCRECT | DT_LEFT | DT_NOPREFIX | DT_EDITCONTROL);
   657 
   658     /* Add margins and some padding for hangs, etc. */
   659     TextSize.left += TextMargin;
   660     TextSize.right += TextMargin + 2;
   661     TextSize.top += TextMargin;
   662     TextSize.bottom += TextMargin + 2;
   663 
   664     /* Done with the DC, and the string */
   665     DeleteDC(FontDC);
   666     SDL_free(wmessage);
   667 
   668     /* Increase the size of the dialog by some border spacing around the text. */
   669     Size.cx = TextSize.right - TextSize.left;
   670     Size.cy = TextSize.bottom - TextSize.top;
   671     Size.cx += TextMargin * 2;
   672     Size.cy += TextMargin * 2;
   673 
   674     /* Make dialog wider and shift text over for the icon. */
   675     if (icon) {
   676         Size.cx += IconMargin + IconWidth;
   677         TextSize.left += IconMargin + IconWidth;
   678         TextSize.right += IconMargin + IconWidth;
   679     }
   680 
   681     /* Ensure the size is wide enough for all of the buttons. */
   682     if (Size.cx < messageboxdata->numbuttons * (ButtonWidth + ButtonMargin) + ButtonMargin)
   683         Size.cx = messageboxdata->numbuttons * (ButtonWidth + ButtonMargin) + ButtonMargin;
   684 
   685     /* Reset the height to the icon size if it is actually bigger than the text. */
   686     if (icon && Size.cy < IconMargin * 2 + IconHeight) {
   687         Size.cy = IconMargin * 2 + IconHeight;
   688     }
   689 
   690     /* Add vertical space for the buttons and border. */
   691     Size.cy += ButtonHeight + TextMargin;
   692 
   693     dialog = CreateDialogData(Size.cx, Size.cy, messageboxdata->title);
   694     if (!dialog) {
   695         return -1;
   696     }
   697 
   698     if (icon && ! AddDialogStaticIcon(dialog, IconMargin, IconMargin, IconWidth, IconHeight, icon)) {
   699         FreeDialogData(dialog);
   700         return -1;
   701     }
   702 
   703     if (!AddDialogStaticText(dialog, TextSize.left, TextSize.top, TextSize.right - TextSize.left, TextSize.bottom - TextSize.top, messageboxdata->message)) {
   704         FreeDialogData(dialog);
   705         return -1;
   706     }
   707 
   708     /* Align the buttons to the right/bottom. */
   709     x = Size.cx - (ButtonWidth + ButtonMargin) * messageboxdata->numbuttons;
   710     y = Size.cy - ButtonHeight - ButtonMargin;
   711     for (i = messageboxdata->numbuttons - 1; i >= 0; --i) {
   712         SDL_bool isdefault = SDL_FALSE;
   713         const char *buttontext;
   714 
   715         if (buttons[i].flags & SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT) {
   716             defbuttoncount++;
   717             if (defbuttoncount == 1) {
   718                 isdefault = SDL_TRUE;
   719             }
   720         }
   721 
   722         buttontext = EscapeAmpersands(&ampescape, &ampescapesize, buttons[i].text);
   723         if (buttontext == NULL || !AddDialogButton(dialog, x, y, ButtonWidth, ButtonHeight, buttontext, buttons[i].buttonid, isdefault)) {
   724             FreeDialogData(dialog);
   725             SDL_free(ampescape);
   726             return -1;
   727         }
   728         x += ButtonWidth + ButtonMargin;
   729     }
   730     SDL_free(ampescape);
   731 
   732     /* If we have a parent window, get the Instance and HWND for them
   733      * so that our little dialog gets exclusive focus at all times. */
   734     if (messageboxdata->window) {
   735         ParentWindow = ((SDL_WindowData*)messageboxdata->window->driverdata)->hwnd;
   736     }
   737 
   738     result = DialogBoxIndirectParam(NULL, (DLGTEMPLATE*)dialog->lpDialog, ParentWindow, (DLGPROC)MessageBoxDialogProc, (LPARAM)messageboxdata);
   739     if (result >= IDBUTTONINDEX0 && result - IDBUTTONINDEX0 < messageboxdata->numbuttons) {
   740         *buttonid = messageboxdata->buttons[(messageboxdata->numbuttons - 1) - (result - IDBUTTONINDEX0)].buttonid;
   741         retval = 0;
   742     } else if (result == IDCLOSED) {
   743         /* Dialog window closed by user or system. */
   744         /* This could use a special return code. */
   745         retval = 0;
   746         *buttonid = -1;
   747     } else {
   748         if (result == 0) {
   749             SDL_SetError("Invalid parent window handle");
   750         } else if (result == -1) {
   751             SDL_SetError("The message box encountered an error.");
   752         } else if (result == IDINVALPTRINIT || result == IDINVALPTRSETFOCUS || result == IDINVALPTRCOMMAND) {
   753             SDL_SetError("Invalid message box pointer in dialog procedure");
   754         } else if (result == IDINVALPTRDLGITEM) {
   755             SDL_SetError("Couldn't find dialog control of the default enter-key button");
   756         } else {
   757             SDL_SetError("An unknown error occured");
   758         }
   759         retval = -1;
   760     }
   761 
   762     FreeDialogData(dialog);
   763     return retval;
   764 }
   765 
   766 /* TaskDialogIndirect procedure
   767  * This is because SDL targets Windows XP (0x501), so this is not defined in the platform SDK.
   768  */
   769 typedef HRESULT(FAR WINAPI *TASKDIALOGINDIRECTPROC)(const TASKDIALOGCONFIG *pTaskConfig, int *pnButton, int *pnRadioButton, BOOL *pfVerificationFlagChecked);
   770 
   771 int
   772 WIN_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)
   773 {
   774     HWND ParentWindow = NULL;
   775     wchar_t *wmessage;
   776     wchar_t *wtitle;
   777     TASKDIALOGCONFIG TaskConfig;
   778     TASKDIALOG_BUTTON *pButtons;
   779     TASKDIALOG_BUTTON *pButton;
   780     HMODULE hComctl32;
   781     TASKDIALOGINDIRECTPROC pfnTaskDialogIndirect;
   782     HRESULT hr;
   783     char *ampescape = NULL;
   784     size_t ampescapesize = 0;
   785     int nButton;
   786     int nCancelButton;
   787     int i;
   788 
   789     if (SIZE_MAX / sizeof(TASKDIALOG_BUTTON) < messageboxdata->numbuttons) {
   790         return SDL_OutOfMemory();
   791     }
   792 
   793     /* If we cannot load comctl32.dll use the old messagebox! */
   794     hComctl32 = LoadLibrary(TEXT("Comctl32.dll"));
   795     if (hComctl32 == NULL) {
   796         return WIN_ShowOldMessageBox(messageboxdata, buttonid);
   797     }
   798 
   799     /* If TaskDialogIndirect doesn't exist use the old messagebox!
   800        This will fail prior to Windows Vista.
   801        The manifest file in the application may require targeting version 6 of comctl32.dll, even
   802        when we use LoadLibrary here!
   803        If you don't want to bother with manifests, put this #pragma in your app's source code somewhere:
   804        pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0'  processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
   805      */
   806     pfnTaskDialogIndirect = (TASKDIALOGINDIRECTPROC) GetProcAddress(hComctl32, "TaskDialogIndirect");
   807     if (pfnTaskDialogIndirect == NULL) {
   808         FreeLibrary(hComctl32);
   809         return WIN_ShowOldMessageBox(messageboxdata, buttonid);
   810     }
   811 
   812     /* If we have a parent window, get the Instance and HWND for them
   813        so that our little dialog gets exclusive focus at all times. */
   814     if (messageboxdata->window) {
   815         ParentWindow = ((SDL_WindowData *) messageboxdata->window->driverdata)->hwnd;
   816     }
   817 
   818     wmessage = WIN_UTF8ToString(messageboxdata->message);
   819     wtitle = WIN_UTF8ToString(messageboxdata->title);
   820 
   821     SDL_zero(TaskConfig);
   822     TaskConfig.cbSize = sizeof (TASKDIALOGCONFIG);
   823     TaskConfig.hwndParent = ParentWindow;
   824     TaskConfig.dwFlags = TDF_SIZE_TO_CONTENT;
   825     TaskConfig.pszWindowTitle = wtitle;
   826     if (messageboxdata->flags & SDL_MESSAGEBOX_ERROR) {
   827         TaskConfig.pszMainIcon = TD_ERROR_ICON;
   828     } else if (messageboxdata->flags & SDL_MESSAGEBOX_WARNING) {
   829         TaskConfig.pszMainIcon = TD_WARNING_ICON;
   830     } else if (messageboxdata->flags & SDL_MESSAGEBOX_INFORMATION) {
   831         TaskConfig.pszMainIcon = TD_INFORMATION_ICON;
   832     } else {
   833         TaskConfig.pszMainIcon = NULL;
   834     }
   835 
   836     TaskConfig.pszContent = wmessage;
   837     TaskConfig.cButtons = messageboxdata->numbuttons;
   838     pButtons = SDL_malloc(sizeof (TASKDIALOG_BUTTON) * messageboxdata->numbuttons);
   839     TaskConfig.nDefaultButton = 0;
   840     nCancelButton = 0;
   841     for (i = 0; i < messageboxdata->numbuttons; i++)
   842     {
   843         const char *buttontext;
   844         pButton = &pButtons[messageboxdata->numbuttons-1-i];
   845         if (messageboxdata->buttons[i].flags & SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT) {
   846             nCancelButton = messageboxdata->buttons[i].buttonid;
   847             pButton->nButtonID = 2;
   848         } else {
   849             pButton->nButtonID = messageboxdata->buttons[i].buttonid + 1;
   850             if (pButton->nButtonID >= 2) {
   851                 pButton->nButtonID++;
   852             }
   853         }
   854         buttontext = EscapeAmpersands(&ampescape, &ampescapesize, messageboxdata->buttons[i].text);
   855         if (buttontext == NULL) {
   856             int j;
   857             FreeLibrary(hComctl32);
   858             SDL_free(ampescape);
   859             SDL_free(wmessage);
   860             SDL_free(wtitle);
   861             for (j = 0; j < i; j++) {
   862                 SDL_free((wchar_t *) pButtons[j].pszButtonText);
   863             }
   864             SDL_free(pButtons);
   865             return -1;
   866         }
   867         pButton->pszButtonText = WIN_UTF8ToString(buttontext);
   868         if (messageboxdata->buttons[i].flags & SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT) {
   869             TaskConfig.nDefaultButton = pButton->nButtonID;
   870         }
   871     }
   872     TaskConfig.pButtons = pButtons;
   873 
   874     /* Show the Task Dialog */
   875     hr = pfnTaskDialogIndirect(&TaskConfig, &nButton, NULL, NULL);
   876 
   877     /* Free everything */
   878     FreeLibrary(hComctl32);
   879     SDL_free(ampescape);
   880     SDL_free(wmessage);
   881     SDL_free(wtitle);
   882     for (i = 0; i < messageboxdata->numbuttons; i++) {
   883         SDL_free((wchar_t *) pButtons[i].pszButtonText);
   884     }
   885     SDL_free(pButtons);
   886 
   887     /* Check the Task Dialog was successful and give the result */
   888     if (SUCCEEDED(hr)) {
   889         if (nButton == 2) {
   890             *buttonid = nCancelButton;
   891         } else if (nButton > 2) {
   892             *buttonid = nButton-1-1;
   893         } else {
   894             *buttonid = nButton-1;
   895         }
   896         return 0;
   897     }
   898 
   899     /* We failed showing the Task Dialog, use the old message box! */
   900     return WIN_ShowOldMessageBox(messageboxdata, buttonid);
   901 }
   902 
   903 #endif /* SDL_VIDEO_DRIVER_WINDOWS */
   904 
   905 /* vi: set ts=4 sw=4 expandtab: */