SDL message box improvements from Dan Thompson
authorSam Lantinga <slouken@libsdl.org>
Tue, 26 Mar 2013 04:57:29 -0700
changeset 7028 55f2cc0a09d5
parent 7027 b1c25fd3907f
child 7029 377cc88f3dc8
SDL message box improvements from Dan Thompson
src/video/windows/SDL_windowsmessagebox.c
--- a/src/video/windows/SDL_windowsmessagebox.c	Mon Mar 25 12:04:16 2013 -0700
+++ b/src/video/windows/SDL_windowsmessagebox.c	Tue Mar 26 04:57:29 2013 -0700
@@ -28,9 +28,39 @@
 
 /* Display a Windows message box */
 
+#pragma pack(push, 1)
+
 typedef struct
 {
-    LPDLGTEMPLATE lpDialog;
+	WORD dlgVer;
+	WORD signature;
+	DWORD helpID;
+	DWORD exStyle;
+	DWORD style;
+	WORD cDlgItems;
+	short x;
+	short y;
+	short cx;
+	short cy;
+} DLGTEMPLATEEX;
+
+typedef struct
+{
+	DWORD helpID;
+	DWORD exStyle;
+	DWORD style;
+	short x;
+	short y;
+	short cx;
+	short cy;
+	DWORD id;
+} DLGITEMTEMPLATEEX;
+
+#pragma pack(pop)
+
+typedef struct
+{
+    DLGTEMPLATEEX* lpDialog;
     Uint8 *data;
     size_t size;
     size_t used;
@@ -70,7 +100,7 @@
         }
         dialog->data = data;
         dialog->size = size;
-        dialog->lpDialog = (LPDLGTEMPLATE)dialog->data;
+        dialog->lpDialog = (DLGTEMPLATEEX*)dialog->data;
     }
     return SDL_TRUE;
 }
@@ -128,21 +158,35 @@
     return status;
 }
 
+static int s_BaseUnitsX;
+static int s_BaseUnitsY;
+static void Vec2ToDLU(WORD* x, WORD* y)
+{
+    SDL_assert(s_BaseUnitsX != 0); // we init in WIN_ShowMessageBox(), which is the only public function...    
+
+    *x = MulDiv(*x, 4, s_BaseUnitsX);
+    *y = MulDiv(*y, 8, s_BaseUnitsY);
+}
+
+
 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)
 {
-    DLGITEMTEMPLATE item;
+    DLGITEMTEMPLATEEX item;
     WORD marker = 0xFFFF;
     WORD extraData = 0;
 
     SDL_zero(item);
     item.style = style;
-    item.dwExtendedStyle = exStyle;
+    item.exStyle = exStyle;
     item.x = x;
     item.y = y;
     item.cx = w;
     item.cy = h;
     item.id = id;
 
+    Vec2ToDLU(&item.x, &item.y);
+    Vec2ToDLU(&item.cx, &item.cy);
+
     if (!AlignDialogData(dialog, sizeof(DWORD))) {
         return SDL_FALSE;
     }
@@ -161,14 +205,14 @@
     if (!AddDialogData(dialog, &extraData, sizeof(extraData))) {
         return SDL_FALSE;
     }
-    ++dialog->lpDialog->cdit;
+    ++dialog->lpDialog->cDlgItems;
 
     return SDL_TRUE;
 }
 
 static SDL_bool AddDialogStatic(WIN_DialogData *dialog, int x, int y, int w, int h, const char *text)
 {
-    DWORD style = WS_VISIBLE | WS_CHILD | SS_LEFT | SS_NOPREFIX;
+    DWORD style = WS_VISIBLE | WS_CHILD | SS_LEFT | SS_NOPREFIX | SS_EDITCONTROL;
     return AddDialogControl(dialog, 0x0082, style, 0, x, y, w, h, -1, text);
 }
 
@@ -194,14 +238,18 @@
 static WIN_DialogData *CreateDialogData(int w, int h, const char *caption)
 {
     WIN_DialogData *dialog;
-    DLGTEMPLATE dialogTemplate;
+    DLGTEMPLATEEX dialogTemplate;
+    WORD WordToPass;
 
     SDL_zero(dialogTemplate);
-    dialogTemplate.style = (WS_CAPTION | DS_CENTER);
+    dialogTemplate.dlgVer = 1;
+    dialogTemplate.signature = 0xffff;
+    dialogTemplate.style = (WS_CAPTION | DS_CENTER | DS_SHELLFONT);
     dialogTemplate.x = 0;
     dialogTemplate.y = 0;
     dialogTemplate.cx = w;
     dialogTemplate.cy = h;
+    Vec2ToDLU(&dialogTemplate.cx, &dialogTemplate.cy);
 
     dialog = (WIN_DialogData *)SDL_calloc(1, sizeof(*dialog));
     if (!dialog) {
@@ -213,15 +261,74 @@
         return NULL;
     }
 
-    /* There is no menu or special class */
-    if (!AddDialogString(dialog, "") || !AddDialogString(dialog, "")) {
+    // No menu
+    WordToPass = 0;
+    if (!AddDialogData(dialog, &WordToPass, 2)) {
+        FreeDialogData(dialog);
+        return NULL;
+    }
+
+    // No custom class
+    if (!AddDialogData(dialog, &WordToPass, 2)) {
+        FreeDialogData(dialog);
+        return NULL;
+    }
+
+    // title
+    if (!AddDialogString(dialog, caption)) {
         FreeDialogData(dialog);
         return NULL;
     }
 
-    if (!AddDialogString(dialog, caption)) {
-        FreeDialogData(dialog);
-        return NULL;
+    // Font stuff
+    {
+        //
+        // We want to use the system messagebox font.
+        //
+        BYTE ToPass;
+        
+        NONCLIENTMETRICSA NCM;
+        NCM.cbSize = sizeof(NCM);
+        SystemParametersInfoA(SPI_GETNONCLIENTMETRICS, 0, &NCM, 0);
+        
+        // Font size - convert to logical font size for dialog parameter.
+        {
+            HDC ScreenDC = GetDC(0);
+            WordToPass = (WORD)(-72 * NCM.lfMessageFont.lfHeight / GetDeviceCaps(ScreenDC, LOGPIXELSY));
+            ReleaseDC(0, ScreenDC);
+        }
+
+        if (!AddDialogData(dialog, &WordToPass, 2)) {
+            FreeDialogData(dialog);
+            return NULL;
+        }
+
+        // Font weight
+        WordToPass = (WORD)NCM.lfMessageFont.lfWeight;
+        if (!AddDialogData(dialog, &WordToPass, 2)) {
+            FreeDialogData(dialog);
+            return NULL;
+        }
+
+        // italic?
+        ToPass = NCM.lfMessageFont.lfItalic;
+        if (!AddDialogData(dialog, &ToPass, 1)) {
+            FreeDialogData(dialog);
+            return NULL;
+        }
+
+        // charset?
+        ToPass = NCM.lfMessageFont.lfCharSet;
+        if (!AddDialogData(dialog, &ToPass, 1)) {
+            FreeDialogData(dialog);
+            return NULL;
+        }
+
+        // font typeface.
+        if (!AddDialogString(dialog, NCM.lfMessageFont.lfFaceName)) {
+            FreeDialogData(dialog);
+            return NULL;
+        }
     }
 
     return dialog;
@@ -231,30 +338,114 @@
 WIN_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)
 {
     WIN_DialogData *dialog;
-    int i, x, y, w, h, gap;
-    INT_PTR which;
+    int i, x, y, which;
     const SDL_MessageBoxButtonData *buttons = messageboxdata->buttons;
+    HFONT DialogFont;
+    SIZE Size;
+    RECT TextSize;
+    wchar_t* wmessage;
+    TEXTMETRIC TM;
+
+
+    const int ButtonWidth = 88;
+    const int ButtonHeight = 26;
+    const int TextMargin = 16;
+    const int ButtonMargin = 12;
  
-    /* FIXME: Need a better algorithm for laying out the message box */
+
+    // Jan 25th, 2013 - dant@fleetsa.com
+    //
+    //
+    // I've tried to make this more reasonable, but I've run in to a lot 
+    // of nonsense.
+    //
+    // The original issue is the code was written in pixels and not 
+    // dialog units (DLUs). All DialogBox functions use DLUs, which
+    // vary based on the selected font (yay).
+    //
+    // According to MSDN, the most reliable way to convert is via
+    // MapDialogUnits, which requires an HWND, which we don't have
+    // at time of template creation.
+    //
+    // We do however have:
+    //  The system font (DLU width 8 for me)
+    //  The font we select for the dialog (DLU width 6 for me)
+    //
+    // Based on experimentation, *neither* of these return the value
+    // actually used. Stepping in to MapDialogUnits(), the conversion
+    // is fairly clear, and uses 7 for me.
+    //
+    // As a result, some of this is hacky to ensure the sizing is 
+    // somewhat correct.
+    //
+    // Honestly, a long term solution is to use CreateWindow, not CreateDialog.
+    //
 
-    dialog = CreateDialogData(570, 260, messageboxdata->title);
+    //
+    // In order to get text dimensions we need to have a DC with the desired font.
+    // I'm assuming a dialog box in SDL is rare enough we can to the create.
+    //
+    HDC FontDC = CreateCompatibleDC(0);
+    
+    {
+        // Create a duplicate of the font used in system message boxes.
+        LOGFONT lf;
+        NONCLIENTMETRICS NCM;
+        NCM.cbSize = sizeof(NCM);
+        SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &NCM, 0);
+        lf = NCM.lfMessageFont;
+        DialogFont = CreateFontIndirect(&lf);
+    }
+
+    // Select the font in to our DC
+    SelectObject(FontDC, DialogFont);
+
+    {
+        // Get the metrics to try and figure our DLU conversion.
+        GetTextMetrics(FontDC, &TM);
+        s_BaseUnitsX = TM.tmAveCharWidth + 1;
+        s_BaseUnitsY = TM.tmHeight;
+    }
+    
+    // Measure the *pixel* size of the string.
+    wmessage = WIN_UTF8ToString(messageboxdata->message);
+    SDL_zero(TextSize);
+    Size.cx = DrawText(FontDC, wmessage, -1, &TextSize, DT_CALCRECT);
+
+    // Add some padding for hangs, etc.
+    TextSize.right += 2;
+    TextSize.bottom += 2;
+
+    // Done with the DC, and the string
+    DeleteDC(FontDC);
+    SDL_free(wmessage);
+
+    // Increase the size of the dialog by some border spacing around the text.
+    Size.cx = TextSize.right - TextSize.left;
+    Size.cy = TextSize.bottom - TextSize.top;
+    Size.cx += TextMargin * 2;
+    Size.cy += TextMargin * 2;
+
+    // Ensure the size is wide enough for all of the buttons.
+    if (Size.cx < messageboxdata->numbuttons * (ButtonWidth + ButtonMargin) + ButtonMargin)
+        Size.cx = messageboxdata->numbuttons * (ButtonWidth + ButtonMargin) + ButtonMargin;
+
+    // Add vertical space for the buttons and border.
+    Size.cy += ButtonHeight + TextMargin;
+
+    dialog = CreateDialogData(Size.cx, Size.cy, messageboxdata->title);
     if (!dialog) {
         return -1;
     }
 
-    w = 100;
-    h = 25;
-    gap = 10;
-    x = gap;
-    y = 50;
-
-    if (!AddDialogStatic(dialog, x, y, 550, 100, messageboxdata->message)) {
+    if (!AddDialogStatic(dialog, TextMargin, TextMargin, TextSize.right - TextSize.left, TextSize.bottom - TextSize.top, messageboxdata->message)) {
         FreeDialogData(dialog);
         return -1;
     }
 
-    y += 110;
-
+    // Align the buttons to the right/bottom.
+    x = Size.cx - ButtonWidth - ButtonMargin;
+    y = Size.cy - ButtonHeight - ButtonMargin;
     for (i = 0; i < messageboxdata->numbuttons; ++i) {
         SDL_bool isDefault;
 
@@ -263,15 +454,15 @@
         } else {
             isDefault = SDL_FALSE;
         }
-        if (!AddDialogButton(dialog, x, y, w, h, buttons[i].text, i, isDefault)) {
+        if (!AddDialogButton(dialog, x, y, ButtonWidth, ButtonHeight, buttons[i].text, i, isDefault)) {
             FreeDialogData(dialog);
             return -1;
         }
-        x += w + gap;
+        x -= ButtonWidth + ButtonMargin;
     }
 
     /* FIXME: If we have a parent window, get the Instance and HWND for them */
-    which = DialogBoxIndirect(NULL, dialog->lpDialog, NULL, (DLGPROC)MessageBoxDialogProc);
+    which = DialogBoxIndirect(NULL, (DLGTEMPLATE*)dialog->lpDialog, NULL, (DLGPROC)MessageBoxDialogProc);
     *buttonid = buttons[which].buttonid;
 
     FreeDialogData(dialog);