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