src/video/windows/SDL_windowsmessagebox.c
author Sam Lantinga <slouken@libsdl.org>
Thu, 29 Aug 2013 08:29:21 -0700
changeset 7719 31b5f9ff36ca
parent 7191 75360622e65f
child 7828 1451063c8ecd
permissions -rw-r--r--
Christoph Mallon: Remove pointless if (x) before SDL_free(x)
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2013 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_config.h"
    22 
    23 #if SDL_VIDEO_DRIVER_WINDOWS
    24 
    25 #include "SDL.h"
    26 #include "SDL_windowsvideo.h"
    27 
    28 
    29 #ifndef SS_EDITCONTROL
    30 #define SS_EDITCONTROL  0x2000
    31 #endif
    32 
    33 /* Display a Windows message box */
    34 
    35 #pragma pack(push, 1)
    36 
    37 typedef struct
    38 {
    39     WORD dlgVer;
    40     WORD signature;
    41     DWORD helpID;
    42     DWORD exStyle;
    43     DWORD style;
    44     WORD cDlgItems;
    45     short x;
    46     short y;
    47     short cx;
    48     short cy;
    49 } DLGTEMPLATEEX;
    50 
    51 typedef struct
    52 {
    53     DWORD helpID;
    54     DWORD exStyle;
    55     DWORD style;
    56     short x;
    57     short y;
    58     short cx;
    59     short cy;
    60     DWORD id;
    61 } DLGITEMTEMPLATEEX;
    62 
    63 #pragma pack(pop)
    64 
    65 typedef struct
    66 {
    67     DLGTEMPLATEEX* lpDialog;
    68     Uint8 *data;
    69     size_t size;
    70     size_t used;
    71 } WIN_DialogData;
    72 
    73 
    74 static INT_PTR MessageBoxDialogProc(HWND hDlg, UINT iMessage, WPARAM wParam, LPARAM lParam)
    75 {
    76     switch ( iMessage ) {
    77     case WM_COMMAND:
    78         /* Return the ID of the button that was pushed */
    79         EndDialog(hDlg, LOWORD(wParam));
    80         return TRUE;
    81 
    82     default:
    83         break;
    84     }
    85     return FALSE;
    86 }
    87 
    88 static SDL_bool ExpandDialogSpace(WIN_DialogData *dialog, size_t space)
    89 {
    90     size_t size = dialog->size;
    91 
    92     if (size == 0) {
    93         size = space;
    94     } else {
    95         while ((dialog->used + space) > size) {
    96             size *= 2;
    97         }
    98     }
    99     if (size > dialog->size) {
   100         void *data = SDL_realloc(dialog->data, size);
   101         if (!data) {
   102             SDL_OutOfMemory();
   103             return SDL_FALSE;
   104         }
   105         dialog->data = data;
   106         dialog->size = size;
   107         dialog->lpDialog = (DLGTEMPLATEEX*)dialog->data;
   108     }
   109     return SDL_TRUE;
   110 }
   111 
   112 static SDL_bool AlignDialogData(WIN_DialogData *dialog, size_t size)
   113 {
   114     size_t padding = (dialog->used % size);
   115 
   116     if (!ExpandDialogSpace(dialog, padding)) {
   117         return SDL_FALSE;
   118     }
   119 
   120     dialog->used += padding;
   121 
   122     return SDL_TRUE;
   123 }
   124 
   125 static SDL_bool AddDialogData(WIN_DialogData *dialog, const void *data, size_t size)
   126 {
   127     if (!ExpandDialogSpace(dialog, size)) {
   128         return SDL_FALSE;
   129     }
   130 
   131     SDL_memcpy(dialog->data+dialog->used, data, size);
   132     dialog->used += size;
   133 
   134     return SDL_TRUE;
   135 }
   136 
   137 static SDL_bool AddDialogString(WIN_DialogData *dialog, const char *string)
   138 {
   139     WCHAR *wstring;
   140     WCHAR *p;
   141     size_t count;
   142     SDL_bool status;
   143 
   144     if (!string) {
   145         string = "";
   146     }
   147 
   148     wstring = WIN_UTF8ToString(string);
   149     if (!wstring) {
   150         return SDL_FALSE;
   151     }
   152 
   153     /* Find out how many characters we have, including null terminator */
   154     count = 0;
   155     for (p = wstring; *p; ++p) {
   156         ++count;
   157     }
   158     ++count;
   159 
   160     status = AddDialogData(dialog, wstring, count*sizeof(WCHAR));
   161     SDL_free(wstring);
   162     return status;
   163 }
   164 
   165 static int s_BaseUnitsX;
   166 static int s_BaseUnitsY;
   167 static void Vec2ToDLU(short *x, short *y)
   168 {
   169     SDL_assert(s_BaseUnitsX != 0); /* we init in WIN_ShowMessageBox(), which is the only public function... */
   170 
   171     *x = MulDiv(*x, 4, s_BaseUnitsX);
   172     *y = MulDiv(*y, 8, s_BaseUnitsY);
   173 }
   174 
   175 
   176 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)
   177 {
   178     DLGITEMTEMPLATEEX item;
   179     WORD marker = 0xFFFF;
   180     WORD extraData = 0;
   181 
   182     SDL_zero(item);
   183     item.style = style;
   184     item.exStyle = exStyle;
   185     item.x = x;
   186     item.y = y;
   187     item.cx = w;
   188     item.cy = h;
   189     item.id = id;
   190 
   191     Vec2ToDLU(&item.x, &item.y);
   192     Vec2ToDLU(&item.cx, &item.cy);
   193 
   194     if (!AlignDialogData(dialog, sizeof(DWORD))) {
   195         return SDL_FALSE;
   196     }
   197     if (!AddDialogData(dialog, &item, sizeof(item))) {
   198         return SDL_FALSE;
   199     }
   200     if (!AddDialogData(dialog, &marker, sizeof(marker))) {
   201         return SDL_FALSE;
   202     }
   203     if (!AddDialogData(dialog, &type, sizeof(type))) {
   204         return SDL_FALSE;
   205     }
   206     if (!AddDialogString(dialog, caption)) {
   207         return SDL_FALSE;
   208     }
   209     if (!AddDialogData(dialog, &extraData, sizeof(extraData))) {
   210         return SDL_FALSE;
   211     }
   212     ++dialog->lpDialog->cDlgItems;
   213 
   214     return SDL_TRUE;
   215 }
   216 
   217 static SDL_bool AddDialogStatic(WIN_DialogData *dialog, int x, int y, int w, int h, const char *text)
   218 {
   219     DWORD style = WS_VISIBLE | WS_CHILD | SS_LEFT | SS_NOPREFIX | SS_EDITCONTROL;
   220     return AddDialogControl(dialog, 0x0082, style, 0, x, y, w, h, -1, text);
   221 }
   222 
   223 static SDL_bool AddDialogButton(WIN_DialogData *dialog, int x, int y, int w, int h, const char *text, int id, SDL_bool isDefault)
   224 {
   225     DWORD style = WS_VISIBLE | WS_CHILD;
   226     if (isDefault) {
   227         style |= BS_DEFPUSHBUTTON;
   228     } else {
   229         style |= BS_PUSHBUTTON;
   230     }
   231     return AddDialogControl(dialog, 0x0080, style, 0, x, y, w, h, id, text);
   232 }
   233 
   234 static void FreeDialogData(WIN_DialogData *dialog)
   235 {
   236     SDL_free(dialog->data);
   237     SDL_free(dialog);
   238 }
   239 
   240 static WIN_DialogData *CreateDialogData(int w, int h, const char *caption)
   241 {
   242     WIN_DialogData *dialog;
   243     DLGTEMPLATEEX dialogTemplate;
   244     WORD WordToPass;
   245 
   246     SDL_zero(dialogTemplate);
   247     dialogTemplate.dlgVer = 1;
   248     dialogTemplate.signature = 0xffff;
   249     dialogTemplate.style = (WS_CAPTION | DS_CENTER | DS_SHELLFONT);
   250     dialogTemplate.x = 0;
   251     dialogTemplate.y = 0;
   252     dialogTemplate.cx = w;
   253     dialogTemplate.cy = h;
   254     Vec2ToDLU(&dialogTemplate.cx, &dialogTemplate.cy);
   255 
   256     dialog = (WIN_DialogData *)SDL_calloc(1, sizeof(*dialog));
   257     if (!dialog) {
   258         return NULL;
   259     }
   260 
   261     if (!AddDialogData(dialog, &dialogTemplate, sizeof(dialogTemplate))) {
   262         FreeDialogData(dialog);
   263         return NULL;
   264     }
   265 
   266     /* No menu */
   267     WordToPass = 0;
   268     if (!AddDialogData(dialog, &WordToPass, 2)) {
   269         FreeDialogData(dialog);
   270         return NULL;
   271     }
   272 
   273     /* No custom class */
   274     if (!AddDialogData(dialog, &WordToPass, 2)) {
   275         FreeDialogData(dialog);
   276         return NULL;
   277     }
   278 
   279     /* title */
   280     if (!AddDialogString(dialog, caption)) {
   281         FreeDialogData(dialog);
   282         return NULL;
   283     }
   284 
   285     /* Font stuff */
   286     {
   287         /*
   288          * We want to use the system messagebox font.
   289          */
   290         BYTE ToPass;
   291 
   292         NONCLIENTMETRICSA NCM;
   293         NCM.cbSize = sizeof(NCM);
   294         SystemParametersInfoA(SPI_GETNONCLIENTMETRICS, 0, &NCM, 0);
   295 
   296         /* Font size - convert to logical font size for dialog parameter. */
   297         {
   298             HDC ScreenDC = GetDC(0);
   299             WordToPass = (WORD)(-72 * NCM.lfMessageFont.lfHeight / GetDeviceCaps(ScreenDC, LOGPIXELSY));
   300             ReleaseDC(0, ScreenDC);
   301         }
   302 
   303         if (!AddDialogData(dialog, &WordToPass, 2)) {
   304             FreeDialogData(dialog);
   305             return NULL;
   306         }
   307 
   308         /* Font weight */
   309         WordToPass = (WORD)NCM.lfMessageFont.lfWeight;
   310         if (!AddDialogData(dialog, &WordToPass, 2)) {
   311             FreeDialogData(dialog);
   312             return NULL;
   313         }
   314 
   315         /* italic? */
   316         ToPass = NCM.lfMessageFont.lfItalic;
   317         if (!AddDialogData(dialog, &ToPass, 1)) {
   318             FreeDialogData(dialog);
   319             return NULL;
   320         }
   321 
   322         /* charset? */
   323         ToPass = NCM.lfMessageFont.lfCharSet;
   324         if (!AddDialogData(dialog, &ToPass, 1)) {
   325             FreeDialogData(dialog);
   326             return NULL;
   327         }
   328 
   329         /* font typeface. */
   330         if (!AddDialogString(dialog, NCM.lfMessageFont.lfFaceName)) {
   331             FreeDialogData(dialog);
   332             return NULL;
   333         }
   334     }
   335 
   336     return dialog;
   337 }
   338 
   339 int
   340 WIN_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)
   341 {
   342     WIN_DialogData *dialog;
   343     int i, x, y, which;
   344     const SDL_MessageBoxButtonData *buttons = messageboxdata->buttons;
   345     HFONT DialogFont;
   346     SIZE Size;
   347     RECT TextSize;
   348     wchar_t* wmessage;
   349     TEXTMETRIC TM;
   350 
   351 
   352     const int ButtonWidth = 88;
   353     const int ButtonHeight = 26;
   354     const int TextMargin = 16;
   355     const int ButtonMargin = 12;
   356 
   357 
   358     /* Jan 25th, 2013 - dant@fleetsa.com
   359      *
   360      *
   361      * I've tried to make this more reasonable, but I've run in to a lot
   362      * of nonsense.
   363      *
   364      * The original issue is the code was written in pixels and not
   365      * dialog units (DLUs). All DialogBox functions use DLUs, which
   366      * vary based on the selected font (yay).
   367      *
   368      * According to MSDN, the most reliable way to convert is via
   369      * MapDialogUnits, which requires an HWND, which we don't have
   370      * at time of template creation.
   371      *
   372      * We do however have:
   373      *  The system font (DLU width 8 for me)
   374      *  The font we select for the dialog (DLU width 6 for me)
   375      *
   376      * Based on experimentation, *neither* of these return the value
   377      * actually used. Stepping in to MapDialogUnits(), the conversion
   378      * is fairly clear, and uses 7 for me.
   379      *
   380      * As a result, some of this is hacky to ensure the sizing is
   381      * somewhat correct.
   382      *
   383      * Honestly, a long term solution is to use CreateWindow, not CreateDialog.
   384      *
   385 
   386      *
   387      * In order to get text dimensions we need to have a DC with the desired font.
   388      * I'm assuming a dialog box in SDL is rare enough we can to the create.
   389      */
   390     HDC FontDC = CreateCompatibleDC(0);
   391 
   392     {
   393         /* Create a duplicate of the font used in system message boxes. */
   394         LOGFONT lf;
   395         NONCLIENTMETRICS NCM;
   396         NCM.cbSize = sizeof(NCM);
   397         SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &NCM, 0);
   398         lf = NCM.lfMessageFont;
   399         DialogFont = CreateFontIndirect(&lf);
   400     }
   401 
   402     /* Select the font in to our DC */
   403     SelectObject(FontDC, DialogFont);
   404 
   405     {
   406         /* Get the metrics to try and figure our DLU conversion. */
   407         GetTextMetrics(FontDC, &TM);
   408         s_BaseUnitsX = TM.tmAveCharWidth + 1;
   409         s_BaseUnitsY = TM.tmHeight;
   410     }
   411 
   412     /* Measure the *pixel* size of the string. */
   413     wmessage = WIN_UTF8ToString(messageboxdata->message);
   414     SDL_zero(TextSize);
   415     Size.cx = DrawText(FontDC, wmessage, -1, &TextSize, DT_CALCRECT);
   416 
   417     /* Add some padding for hangs, etc. */
   418     TextSize.right += 2;
   419     TextSize.bottom += 2;
   420 
   421     /* Done with the DC, and the string */
   422     DeleteDC(FontDC);
   423     SDL_free(wmessage);
   424 
   425     /* Increase the size of the dialog by some border spacing around the text. */
   426     Size.cx = TextSize.right - TextSize.left;
   427     Size.cy = TextSize.bottom - TextSize.top;
   428     Size.cx += TextMargin * 2;
   429     Size.cy += TextMargin * 2;
   430 
   431     /* Ensure the size is wide enough for all of the buttons. */
   432     if (Size.cx < messageboxdata->numbuttons * (ButtonWidth + ButtonMargin) + ButtonMargin)
   433         Size.cx = messageboxdata->numbuttons * (ButtonWidth + ButtonMargin) + ButtonMargin;
   434 
   435     /* Add vertical space for the buttons and border. */
   436     Size.cy += ButtonHeight + TextMargin;
   437 
   438     dialog = CreateDialogData(Size.cx, Size.cy, messageboxdata->title);
   439     if (!dialog) {
   440         return -1;
   441     }
   442 
   443     if (!AddDialogStatic(dialog, TextMargin, TextMargin, TextSize.right - TextSize.left, TextSize.bottom - TextSize.top, messageboxdata->message)) {
   444         FreeDialogData(dialog);
   445         return -1;
   446     }
   447 
   448     /* Align the buttons to the right/bottom. */
   449     x = Size.cx - ButtonWidth - ButtonMargin;
   450     y = Size.cy - ButtonHeight - ButtonMargin;
   451     for (i = 0; i < messageboxdata->numbuttons; ++i) {
   452         SDL_bool isDefault;
   453 
   454         if (buttons[i].flags & SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT) {
   455             isDefault = SDL_TRUE;
   456         } else {
   457             isDefault = SDL_FALSE;
   458         }
   459         if (!AddDialogButton(dialog, x, y, ButtonWidth, ButtonHeight, buttons[i].text, i, isDefault)) {
   460             FreeDialogData(dialog);
   461             return -1;
   462         }
   463         x -= ButtonWidth + ButtonMargin;
   464     }
   465 
   466     /* FIXME: If we have a parent window, get the Instance and HWND for them */
   467     which = DialogBoxIndirect(NULL, (DLGTEMPLATE*)dialog->lpDialog, NULL, (DLGPROC)MessageBoxDialogProc);
   468     *buttonid = buttons[which].buttonid;
   469 
   470     FreeDialogData(dialog);
   471     return 0;
   472 }
   473 
   474 #endif /* SDL_VIDEO_DRIVER_WINDOWS */
   475 
   476 /* vi: set ts=4 sw=4 expandtab: */