Fixed bug 3804 - Message box on Windows truncates button ID
authorSam Lantinga <slouken@libsdl.org>
Sat, 24 Mar 2018 10:26:40 -0700
changeset 11937f21a2cd69acf
parent 11935 331397505b6c
child 11938 fbfacc66c65c
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.
src/video/SDL_video.c
src/video/windows/SDL_windowsmessagebox.c
     1.1 --- a/src/video/SDL_video.c	Mon Mar 19 14:52:53 2018 -0700
     1.2 +++ b/src/video/SDL_video.c	Sat Mar 24 10:26:40 2018 -0700
     1.3 @@ -3774,6 +3774,8 @@
     1.4  
     1.5      if (!messageboxdata) {
     1.6          return SDL_InvalidParamError("messageboxdata");
     1.7 +    } else if (messageboxdata->numbuttons < 0) {
     1.8 +        return SDL_SetError("Invalid number of buttons");
     1.9      }
    1.10  
    1.11      current_window = SDL_GetKeyboardFocus();
     2.1 --- a/src/video/windows/SDL_windowsmessagebox.c	Mon Mar 19 14:52:53 2018 -0700
     2.2 +++ b/src/video/windows/SDL_windowsmessagebox.c	Sat Mar 24 10:26:40 2018 -0700
     2.3 @@ -22,6 +22,14 @@
     2.4  
     2.5  #if SDL_VIDEO_DRIVER_WINDOWS
     2.6  
     2.7 +#ifdef HAVE_LIMITS_H
     2.8 +#include <limits.h>
     2.9 +#else
    2.10 +#ifndef SIZE_MAX
    2.11 +#define SIZE_MAX ((size_t)-1)
    2.12 +#endif
    2.13 +#endif
    2.14 +
    2.15  #include "../../core/windows/SDL_windows.h"
    2.16  
    2.17  #include "SDL_assert.h"
    2.18 @@ -32,6 +40,34 @@
    2.19  #define SS_EDITCONTROL  0x2000
    2.20  #endif
    2.21  
    2.22 +#ifndef IDOK
    2.23 +#define IDOK 1
    2.24 +#endif
    2.25 +
    2.26 +#ifndef IDCANCEL
    2.27 +#define IDCANCEL 2
    2.28 +#endif
    2.29 +
    2.30 +/* Custom dialog return codes */
    2.31 +#define IDCLOSED 20
    2.32 +#define IDINVALPTRINIT 50
    2.33 +#define IDINVALPTRCOMMAND 51
    2.34 +#define IDINVALPTRSETFOCUS 52
    2.35 +#define IDINVALPTRDLGITEM 53
    2.36 +/* First button ID */
    2.37 +#define IDBUTTONINDEX0 100
    2.38 +
    2.39 +#define DLGITEMTYPEBUTTON 0x0080
    2.40 +#define DLGITEMTYPESTATIC 0x0082
    2.41 +
    2.42 +/* Windows only sends the lower 16 bits of the control ID when a button
    2.43 + * gets clicked. There are also some predefined and custom IDs that lower
    2.44 + * the available number further. 2^16 - 101 buttons should be enough for
    2.45 + * everyone, no need to make the code more complex.
    2.46 + */
    2.47 +#define MAX_BUTTONS (0xffff - 100)
    2.48 +
    2.49 +
    2.50  /* Display a Windows message box */
    2.51  
    2.52  #pragma pack(push, 1)
    2.53 @@ -70,15 +106,79 @@
    2.54      Uint8 *data;
    2.55      size_t size;
    2.56      size_t used;
    2.57 +    WORD numbuttons;
    2.58  } WIN_DialogData;
    2.59  
    2.60 +static SDL_bool GetButtonIndex(const SDL_MessageBoxData *messageboxdata, Uint32 flags, size_t *i)
    2.61 +{
    2.62 +    for (*i = 0; *i < (size_t)messageboxdata->numbuttons; ++*i) {
    2.63 +        if (messageboxdata->buttons[*i].flags & flags) {
    2.64 +            return SDL_TRUE;
    2.65 +        }
    2.66 +    }
    2.67 +    return SDL_FALSE;
    2.68 +}
    2.69  
    2.70  static INT_PTR MessageBoxDialogProc(HWND hDlg, UINT iMessage, WPARAM wParam, LPARAM lParam)
    2.71  {
    2.72 +    const SDL_MessageBoxData *messageboxdata;
    2.73 +    size_t buttonindex;
    2.74 +
    2.75      switch ( iMessage ) {
    2.76 +    case WM_INITDIALOG:
    2.77 +        if (lParam == 0) {
    2.78 +            EndDialog(hDlg, IDINVALPTRINIT);
    2.79 +            return TRUE;
    2.80 +        }
    2.81 +        messageboxdata = (const SDL_MessageBoxData *)lParam;
    2.82 +        SetWindowLongPtr(hDlg, GWLP_USERDATA, lParam);
    2.83 +
    2.84 +        if (GetButtonIndex(messageboxdata, SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, &buttonindex)) {
    2.85 +            /* Focus on the first default return-key button */
    2.86 +            HWND buttonctl = GetDlgItem(hDlg, (int)(IDBUTTONINDEX0 + buttonindex));
    2.87 +            if (buttonctl == NULL) {
    2.88 +                EndDialog(hDlg, IDINVALPTRDLGITEM);
    2.89 +            }
    2.90 +            PostMessage(hDlg, WM_NEXTDLGCTL, (WPARAM)buttonctl, TRUE);
    2.91 +        } else {
    2.92 +            /* Give the focus to the dialog window instead */
    2.93 +            SetFocus(hDlg);
    2.94 +        }
    2.95 +        return FALSE;
    2.96 +    case WM_SETFOCUS:
    2.97 +        messageboxdata = (const SDL_MessageBoxData *)GetWindowLongPtr(hDlg, GWLP_USERDATA);
    2.98 +        if (messageboxdata == NULL) {
    2.99 +            EndDialog(hDlg, IDINVALPTRSETFOCUS);
   2.100 +            return TRUE;
   2.101 +        }
   2.102 +
   2.103 +        /* Let the default button be focused if there is one. Otherwise, prevent any initial focus. */
   2.104 +        if (GetButtonIndex(messageboxdata, SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, &buttonindex)) {
   2.105 +            return FALSE;
   2.106 +        }
   2.107 +        return TRUE;
   2.108      case WM_COMMAND:
   2.109 +        messageboxdata = (const SDL_MessageBoxData *)GetWindowLongPtr(hDlg, GWLP_USERDATA);
   2.110 +        if (messageboxdata == NULL) {
   2.111 +            EndDialog(hDlg, IDINVALPTRCOMMAND);
   2.112 +            return TRUE;
   2.113 +        }
   2.114 +
   2.115          /* Return the ID of the button that was pushed */
   2.116 -        EndDialog(hDlg, LOWORD(wParam));
   2.117 +        if (wParam == IDOK) {
   2.118 +            if (GetButtonIndex(messageboxdata, SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, &buttonindex)) {
   2.119 +                EndDialog(hDlg, IDBUTTONINDEX0 + buttonindex);
   2.120 +            }
   2.121 +        } else if (wParam == IDCANCEL) {
   2.122 +            if (GetButtonIndex(messageboxdata, SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, &buttonindex)) {
   2.123 +                EndDialog(hDlg, IDBUTTONINDEX0 + buttonindex);
   2.124 +            } else {
   2.125 +                /* Closing of window was requested by user or system. It would be rude not to comply. */
   2.126 +                EndDialog(hDlg, IDCLOSED);
   2.127 +            }
   2.128 +        } else if (wParam >= IDBUTTONINDEX0 && (int)wParam - IDBUTTONINDEX0 < messageboxdata->numbuttons) {
   2.129 +            EndDialog(hDlg, wParam);
   2.130 +        }
   2.131          return TRUE;
   2.132  
   2.133      default:
   2.134 @@ -89,15 +189,30 @@
   2.135  
   2.136  static SDL_bool ExpandDialogSpace(WIN_DialogData *dialog, size_t space)
   2.137  {
   2.138 +    /* Growing memory in 64 KiB steps. */
   2.139 +    const size_t sizestep = 0x10000;
   2.140      size_t size = dialog->size;
   2.141  
   2.142      if (size == 0) {
   2.143 -        size = space;
   2.144 -    } else {
   2.145 -        while ((dialog->used + space) > size) {
   2.146 -            size *= 2;
   2.147 +        /* Start with 4 KiB or a multiple of 64 KiB to fit the data. */
   2.148 +        size = 0x1000;
   2.149 +        if (SIZE_MAX - sizestep < space) {
   2.150 +            size = space;
   2.151 +        } else if (space > size) {
   2.152 +            size = (space + sizestep) & ~(sizestep - 1);
   2.153          }
   2.154 +    } else if (SIZE_MAX - dialog->used < space) {
   2.155 +        SDL_OutOfMemory();
   2.156 +        return SDL_FALSE;
   2.157 +    } else if (SIZE_MAX - (dialog->used + space) < sizestep) {
   2.158 +        /* Close to the maximum. */
   2.159 +        size = dialog->used + space;
   2.160 +    } else if (size < dialog->used + space) {
   2.161 +        /* Round up to the next 64 KiB block. */
   2.162 +        size = dialog->used + space;
   2.163 +        size += sizestep - size % sizestep;
   2.164      }
   2.165 +
   2.166      if (size > dialog->size) {
   2.167          void *data = SDL_realloc(dialog->data, size);
   2.168          if (!data) {
   2.169 @@ -175,7 +290,7 @@
   2.170  }
   2.171  
   2.172  
   2.173 -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)
   2.174 +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)
   2.175  {
   2.176      DLGITEMTEMPLATEEX item;
   2.177      WORD marker = 0xFFFF;
   2.178 @@ -205,32 +320,54 @@
   2.179      if (!AddDialogData(dialog, &type, sizeof(type))) {
   2.180          return SDL_FALSE;
   2.181      }
   2.182 -    if (!AddDialogString(dialog, caption)) {
   2.183 -        return SDL_FALSE;
   2.184 +    if (type == DLGITEMTYPEBUTTON || (type == DLGITEMTYPESTATIC && caption != NULL)) {
   2.185 +        if (!AddDialogString(dialog, caption)) {
   2.186 +            return SDL_FALSE;
   2.187 +        }
   2.188 +    } else {
   2.189 +        if (!AddDialogData(dialog, &marker, sizeof(marker))) {
   2.190 +            return SDL_FALSE;
   2.191 +        }
   2.192 +        if (!AddDialogData(dialog, &ordinal, sizeof(ordinal))) {
   2.193 +            return SDL_FALSE;
   2.194 +        }
   2.195      }
   2.196      if (!AddDialogData(dialog, &extraData, sizeof(extraData))) {
   2.197          return SDL_FALSE;
   2.198      }
   2.199 +    if (type == DLGITEMTYPEBUTTON) {
   2.200 +        dialog->numbuttons++;
   2.201 +    }
   2.202      ++dialog->lpDialog->cDlgItems;
   2.203  
   2.204      return SDL_TRUE;
   2.205  }
   2.206  
   2.207 -static SDL_bool AddDialogStatic(WIN_DialogData *dialog, int x, int y, int w, int h, const char *text)
   2.208 +static SDL_bool AddDialogStaticText(WIN_DialogData *dialog, int x, int y, int w, int h, const char *text)
   2.209  {
   2.210 -    DWORD style = WS_VISIBLE | WS_CHILD | SS_LEFT | SS_NOPREFIX | SS_EDITCONTROL;
   2.211 -    return AddDialogControl(dialog, 0x0082, style, 0, x, y, w, h, -1, text);
   2.212 +    DWORD style = WS_VISIBLE | WS_CHILD | SS_LEFT | SS_NOPREFIX | SS_EDITCONTROL | WS_GROUP;
   2.213 +    return AddDialogControl(dialog, DLGITEMTYPESTATIC, style, 0, x, y, w, h, -1, text, 0);
   2.214 +}
   2.215 +
   2.216 +static SDL_bool AddDialogStaticIcon(WIN_DialogData *dialog, int x, int y, int w, int h, Uint16 ordinal)
   2.217 +{
   2.218 +    DWORD style = WS_VISIBLE | WS_CHILD | SS_ICON | WS_GROUP;
   2.219 +    return AddDialogControl(dialog, DLGITEMTYPESTATIC, style, 0, x, y, w, h, -2, NULL, ordinal);
   2.220  }
   2.221  
   2.222  static SDL_bool AddDialogButton(WIN_DialogData *dialog, int x, int y, int w, int h, const char *text, int id, SDL_bool isDefault)
   2.223  {
   2.224 -    DWORD style = WS_VISIBLE | WS_CHILD;
   2.225 +    DWORD style = WS_VISIBLE | WS_CHILD | WS_TABSTOP;
   2.226      if (isDefault) {
   2.227          style |= BS_DEFPUSHBUTTON;
   2.228      } else {
   2.229          style |= BS_PUSHBUTTON;
   2.230      }
   2.231 -    return AddDialogControl(dialog, 0x0080, style, 0, x, y, w, h, id, text);
   2.232 +    /* The first button marks the start of the group. */
   2.233 +    if (dialog->numbuttons == 0) {
   2.234 +        style |= WS_GROUP;
   2.235 +    }
   2.236 +    return AddDialogControl(dialog, DLGITEMTYPEBUTTON, style, 0, x, y, w, h, IDBUTTONINDEX0 + dialog->numbuttons, text, 0);
   2.237  }
   2.238  
   2.239  static void FreeDialogData(WIN_DialogData *dialog)
   2.240 @@ -341,18 +478,87 @@
   2.241      return dialog;
   2.242  }
   2.243  
   2.244 +/* Escaping ampersands is necessary to disable mnemonics in dialog controls.
   2.245 + * The caller provides a char** for dst and a size_t* for dstlen where the
   2.246 + * address of the work buffer and its size will be stored. Their values must be
   2.247 + * NULL and 0 on the first call. src is the string to be escaped. On error, the
   2.248 + * function returns NULL and, on success, returns a pointer to the escaped
   2.249 + * sequence as a read-only string that is valid until the next call or until the
   2.250 + * work buffer is freed. Once all strings have been processed, it's the caller's
   2.251 + * responsibilty to free the work buffer with SDL_free, even on errors.
   2.252 + */
   2.253 +static const char *EscapeAmpersands(char **dst, size_t *dstlen, const char *src)
   2.254 +{
   2.255 +    char *newdst;
   2.256 +    size_t ampcount = 0;
   2.257 +    size_t srclen = 0;
   2.258 +
   2.259 +    if (src == NULL) {
   2.260 +        return NULL;
   2.261 +    }
   2.262 +
   2.263 +    while (src[srclen]) {
   2.264 +        if (src[srclen] == '&') {
   2.265 +            ampcount++;
   2.266 +        }
   2.267 +        srclen++;
   2.268 +    }
   2.269 +    srclen++;
   2.270 +
   2.271 +    if (ampcount == 0) {
   2.272 +        /* Nothing to do. */
   2.273 +        return src;
   2.274 +    }
   2.275 +    if (SIZE_MAX - srclen < ampcount) {
   2.276 +        return NULL;
   2.277 +    }
   2.278 +    if (*dst == NULL || *dstlen < srclen + ampcount) {
   2.279 +        /* Allocating extra space in case the next strings are a bit longer. */
   2.280 +        size_t extraspace = SIZE_MAX - (srclen + ampcount);
   2.281 +        if (extraspace > 512) {
   2.282 +            extraspace = 512;
   2.283 +        }
   2.284 +        *dstlen = srclen + ampcount + extraspace;
   2.285 +        SDL_free(*dst);
   2.286 +        *dst = NULL;
   2.287 +        newdst = SDL_malloc(*dstlen);
   2.288 +        if (newdst == NULL) {
   2.289 +            return NULL;
   2.290 +        }
   2.291 +        *dst = newdst;
   2.292 +    } else {
   2.293 +        newdst = *dst;
   2.294 +    }
   2.295 +
   2.296 +    /* The escape character is the ampersand itself. */
   2.297 +    while (srclen--) {
   2.298 +        if (*src == '&') {
   2.299 +            *newdst++ = '&';
   2.300 +        }
   2.301 +        *newdst++ = *src++;
   2.302 +    }
   2.303 +
   2.304 +    return *dst;
   2.305 +}
   2.306 +
   2.307  /* This function is called if a Task Dialog is unsupported. */
   2.308  static int
   2.309  WIN_ShowOldMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)
   2.310  {
   2.311      WIN_DialogData *dialog;
   2.312 -    int i, x, y;
   2.313 +    int i, x, y, retval;
   2.314      const SDL_MessageBoxButtonData *buttons = messageboxdata->buttons;
   2.315      HFONT DialogFont;
   2.316      SIZE Size;
   2.317      RECT TextSize;
   2.318      wchar_t* wmessage;
   2.319      TEXTMETRIC TM;
   2.320 +    HDC FontDC;
   2.321 +    INT_PTR result;
   2.322 +    char *ampescape = NULL;
   2.323 +    size_t ampescapesize = 0;
   2.324 +    Uint16 defbuttoncount = 0;
   2.325 +    Uint16 icon = 0;
   2.326  
   2.327      HWND ParentWindow = NULL;
   2.328  
   2.329 @@ -360,7 +566,25 @@
   2.330      const int ButtonHeight = 26;
   2.331      const int TextMargin = 16;
   2.332      const int ButtonMargin = 12;
   2.333 +    const int IconWidth = GetSystemMetrics(SM_CXICON);
   2.334 +    const int IconHeight = GetSystemMetrics(SM_CYICON);
   2.335 +    const int IconMargin = 20;
   2.336  
   2.337 +    if (messageboxdata->numbuttons > MAX_BUTTONS) {
   2.338 +        return SDL_SetError("Number of butons exceeds limit of %d", MAX_BUTTONS);
   2.339 +    }
   2.340 +
   2.341 +    switch (messageboxdata->flags) {
   2.342 +    case SDL_MESSAGEBOX_ERROR:
   2.343 +        icon = (Uint16)IDI_ERROR;
   2.344 +        break;
   2.345 +    case SDL_MESSAGEBOX_WARNING:
   2.346 +        icon = (Uint16)IDI_WARNING;
   2.347 +        break;
   2.348 +    case SDL_MESSAGEBOX_INFORMATION:
   2.349 +        icon = (Uint16)IDI_INFORMATION;
   2.350 +        break;
   2.351 +    }
   2.352  
   2.353      /* Jan 25th, 2013 - dant@fleetsa.com
   2.354       *
   2.355 @@ -394,7 +618,7 @@
   2.356       * In order to get text dimensions we need to have a DC with the desired font.
   2.357       * I'm assuming a dialog box in SDL is rare enough we can to the create.
   2.358       */
   2.359 -    HDC FontDC = CreateCompatibleDC(0);
   2.360 +    FontDC = CreateCompatibleDC(0);
   2.361  
   2.362      {
   2.363          /* Create a duplicate of the font used in system message boxes. */
   2.364 @@ -429,11 +653,13 @@
   2.365      /* Measure the *pixel* size of the string. */
   2.366      wmessage = WIN_UTF8ToString(messageboxdata->message);
   2.367      SDL_zero(TextSize);
   2.368 -    DrawText(FontDC, wmessage, -1, &TextSize, DT_CALCRECT);
   2.369 +    DrawText(FontDC, wmessage, -1, &TextSize, DT_CALCRECT | DT_LEFT | DT_NOPREFIX | DT_EDITCONTROL);
   2.370  
   2.371 -    /* Add some padding for hangs, etc. */
   2.372 -    TextSize.right += 2;
   2.373 -    TextSize.bottom += 2;
   2.374 +    /* Add margins and some padding for hangs, etc. */
   2.375 +    TextSize.left += TextMargin;
   2.376 +    TextSize.right += TextMargin + 2;
   2.377 +    TextSize.top += TextMargin;
   2.378 +    TextSize.bottom += TextMargin + 2;
   2.379  
   2.380      /* Done with the DC, and the string */
   2.381      DeleteDC(FontDC);
   2.382 @@ -445,10 +671,22 @@
   2.383      Size.cx += TextMargin * 2;
   2.384      Size.cy += TextMargin * 2;
   2.385  
   2.386 +    /* Make dialog wider and shift text over for the icon. */
   2.387 +    if (icon) {
   2.388 +        Size.cx += IconMargin + IconWidth;
   2.389 +        TextSize.left += IconMargin + IconWidth;
   2.390 +        TextSize.right += IconMargin + IconWidth;
   2.391 +    }
   2.392 +
   2.393      /* Ensure the size is wide enough for all of the buttons. */
   2.394      if (Size.cx < messageboxdata->numbuttons * (ButtonWidth + ButtonMargin) + ButtonMargin)
   2.395          Size.cx = messageboxdata->numbuttons * (ButtonWidth + ButtonMargin) + ButtonMargin;
   2.396  
   2.397 +    /* Reset the height to the icon size if it is actually bigger than the text. */
   2.398 +    if (icon && Size.cy < IconMargin * 2 + IconHeight) {
   2.399 +        Size.cy = IconMargin * 2 + IconHeight;
   2.400 +    }
   2.401 +
   2.402      /* Add vertical space for the buttons and border. */
   2.403      Size.cy += ButtonHeight + TextMargin;
   2.404  
   2.405 @@ -457,7 +695,12 @@
   2.406          return -1;
   2.407      }
   2.408  
   2.409 -    if (!AddDialogStatic(dialog, TextMargin, TextMargin, TextSize.right - TextSize.left, TextSize.bottom - TextSize.top, messageboxdata->message)) {
   2.410 +    if (icon && ! AddDialogStaticIcon(dialog, IconMargin, IconMargin, IconWidth, IconHeight, icon)) {
   2.411 +        FreeDialogData(dialog);
   2.412 +        return -1;
   2.413 +    }
   2.414 +
   2.415 +    if (!AddDialogStaticText(dialog, TextSize.left, TextSize.top, TextSize.right - TextSize.left, TextSize.bottom - TextSize.top, messageboxdata->message)) {
   2.416          FreeDialogData(dialog);
   2.417          return -1;
   2.418      }
   2.419 @@ -466,19 +709,25 @@
   2.420      x = Size.cx - (ButtonWidth + ButtonMargin) * messageboxdata->numbuttons;
   2.421      y = Size.cy - ButtonHeight - ButtonMargin;
   2.422      for (i = messageboxdata->numbuttons - 1; i >= 0; --i) {
   2.423 -        SDL_bool isDefault;
   2.424 +        SDL_bool isdefault = SDL_FALSE;
   2.425 +        const char *buttontext;
   2.426  
   2.427          if (buttons[i].flags & SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT) {
   2.428 -            isDefault = SDL_TRUE;
   2.429 -        } else {
   2.430 -            isDefault = SDL_FALSE;
   2.431 +            defbuttoncount++;
   2.432 +            if (defbuttoncount == 1) {
   2.433 +                isdefault = SDL_TRUE;
   2.434 +            }
   2.435          }
   2.436 -        if (!AddDialogButton(dialog, x, y, ButtonWidth, ButtonHeight, buttons[i].text, buttons[i].buttonid, isDefault)) {
   2.437 +
   2.438 +        buttontext = EscapeAmpersands(&ampescape, &ampescapesize, buttons[i].text);
   2.439 +        if (buttontext == NULL || !AddDialogButton(dialog, x, y, ButtonWidth, ButtonHeight, buttontext, buttons[i].buttonid, isdefault)) {
   2.440              FreeDialogData(dialog);
   2.441 +            SDL_free(ampescape);
   2.442              return -1;
   2.443          }
   2.444          x += ButtonWidth + ButtonMargin;
   2.445      }
   2.446 +    SDL_free(ampescape);
   2.447  
   2.448      /* If we have a parent window, get the Instance and HWND for them
   2.449       * so that our little dialog gets exclusive focus at all times. */
   2.450 @@ -486,10 +735,32 @@
   2.451          ParentWindow = ((SDL_WindowData*)messageboxdata->window->driverdata)->hwnd;
   2.452      }
   2.453  
   2.454 -    *buttonid = (int)DialogBoxIndirect(NULL, (DLGTEMPLATE*)dialog->lpDialog, ParentWindow, (DLGPROC)MessageBoxDialogProc);
   2.455 +    result = DialogBoxIndirectParam(NULL, (DLGTEMPLATE*)dialog->lpDialog, ParentWindow, (DLGPROC)MessageBoxDialogProc, (LPARAM)messageboxdata);
   2.456 +    if (result >= IDBUTTONINDEX0 && result - IDBUTTONINDEX0 < messageboxdata->numbuttons) {
   2.457 +        *buttonid = messageboxdata->buttons[(messageboxdata->numbuttons - 1) - (result - IDBUTTONINDEX0)].buttonid;
   2.458 +        retval = 0;
   2.459 +    } else if (result == IDCLOSED) {
   2.460 +        /* Dialog window closed by user or system. */
   2.461 +        /* This could use a special return code. */
   2.462 +        retval = 0;
   2.463 +        *buttonid = -1;
   2.464 +    } else {
   2.465 +        if (result == 0) {
   2.466 +            SDL_SetError("Invalid parent window handle");
   2.467 +        } else if (result == -1) {
   2.468 +            SDL_SetError("The message box encountered an error.");
   2.469 +        } else if (result == IDINVALPTRINIT || result == IDINVALPTRSETFOCUS || result == IDINVALPTRCOMMAND) {
   2.470 +            SDL_SetError("Invalid message box pointer in dialog procedure");
   2.471 +        } else if (result == IDINVALPTRDLGITEM) {
   2.472 +            SDL_SetError("Couldn't find dialog control of the default enter-key button");
   2.473 +        } else {
   2.474 +            SDL_SetError("An unknown error occured");
   2.475 +        }
   2.476 +        retval = -1;
   2.477 +    }
   2.478  
   2.479      FreeDialogData(dialog);
   2.480 -    return 0;
   2.481 +    return retval;
   2.482  }
   2.483  
   2.484  /* TaskDialogIndirect procedure
   2.485 @@ -509,16 +780,22 @@
   2.486      HMODULE hComctl32;
   2.487      TASKDIALOGINDIRECTPROC pfnTaskDialogIndirect;
   2.488      HRESULT hr;
   2.489 +    char *ampescape = NULL;
   2.490 +    size_t ampescapesize = 0;
   2.491      int nButton;
   2.492      int nCancelButton;
   2.493      int i;
   2.494  
   2.495 +    if (SIZE_MAX / sizeof(TASKDIALOG_BUTTON) < messageboxdata->numbuttons) {
   2.496 +        return SDL_OutOfMemory();
   2.497 +    }
   2.498 +
   2.499      /* If we cannot load comctl32.dll use the old messagebox! */
   2.500      hComctl32 = LoadLibrary(TEXT("Comctl32.dll"));
   2.501      if (hComctl32 == NULL) {
   2.502 -        return WIN_ShowOldMessageBox(messageboxdata,buttonid);
   2.503 +        return WIN_ShowOldMessageBox(messageboxdata, buttonid);
   2.504      }
   2.505 -    
   2.506 +
   2.507      /* If TaskDialogIndirect doesn't exist use the old messagebox!
   2.508         This will fail prior to Windows Vista.
   2.509         The manifest file in the application may require targeting version 6 of comctl32.dll, even
   2.510 @@ -563,6 +840,7 @@
   2.511      nCancelButton = 0;
   2.512      for (i = 0; i < messageboxdata->numbuttons; i++)
   2.513      {
   2.514 +        const char *buttontext;
   2.515          pButton = &pButtons[messageboxdata->numbuttons-1-i];
   2.516          if (messageboxdata->buttons[i].flags & SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT) {
   2.517              nCancelButton = messageboxdata->buttons[i].buttonid;
   2.518 @@ -573,7 +851,20 @@
   2.519                  pButton->nButtonID++;
   2.520              }
   2.521          }
   2.522 -        pButton->pszButtonText = WIN_UTF8ToString(messageboxdata->buttons[i].text);
   2.523 +        buttontext = EscapeAmpersands(&ampescape, &ampescapesize, messageboxdata->buttons[i].text);
   2.524 +        if (buttontext == NULL) {
   2.525 +            int j;
   2.526 +            FreeLibrary(hComctl32);
   2.527 +            SDL_free(ampescape);
   2.528 +            SDL_free(wmessage);
   2.529 +            SDL_free(wtitle);
   2.530 +            for (j = 0; j < i; j++) {
   2.531 +                SDL_free((wchar_t *) pButtons[j].pszButtonText);
   2.532 +            }
   2.533 +            SDL_free(pButtons);
   2.534 +            return -1;
   2.535 +        }
   2.536 +        pButton->pszButtonText = WIN_UTF8ToString(buttontext);
   2.537          if (messageboxdata->buttons[i].flags & SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT) {
   2.538              TaskConfig.nDefaultButton = pButton->nButtonID;
   2.539          }
   2.540 @@ -585,6 +876,7 @@
   2.541  
   2.542      /* Free everything */
   2.543      FreeLibrary(hComctl32);
   2.544 +    SDL_free(ampescape);
   2.545      SDL_free(wmessage);
   2.546      SDL_free(wtitle);
   2.547      for (i = 0; i < messageboxdata->numbuttons; i++) {