sdl

FORK: Simple Directmedia Layer
git clone https://git.neptards.moe/neptards/sdl.git
Log | Files | Refs

SDL_windowsmessagebox.c (29338B)


      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 #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 
    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 
    120 static __stdcall INT_PTR 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 
    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 
    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 
    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 
    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_UTF8ToString(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;
    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 
    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 
    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 
    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 
    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 
    371 static void FreeDialogData(WIN_DialogData *dialog)
    372 {
    373     SDL_free(dialog->data);
    374     SDL_free(dialog);
    375 }
    376 
    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  */
    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
    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_UTF8ToString(messageboxdata->message);
    649     SDL_zero(TextSize);
    650     DrawText(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, (DLGPROC)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
    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_UTF8ToString(messageboxdata->message);
    826     wtitle = WIN_UTF8ToString(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_UTF8ToString(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: */