SDL message box improvements from Dan Thompson
authorSam Lantinga <slouken@libsdl.org>
Tue, 26 Mar 2013 04:57:29 -0700
changeset 702855f2cc0a09d5
parent 7027 b1c25fd3907f
child 7029 377cc88f3dc8
SDL message box improvements from Dan Thompson
src/video/windows/SDL_windowsmessagebox.c
     1.1 --- a/src/video/windows/SDL_windowsmessagebox.c	Mon Mar 25 12:04:16 2013 -0700
     1.2 +++ b/src/video/windows/SDL_windowsmessagebox.c	Tue Mar 26 04:57:29 2013 -0700
     1.3 @@ -28,9 +28,39 @@
     1.4  
     1.5  /* Display a Windows message box */
     1.6  
     1.7 +#pragma pack(push, 1)
     1.8 +
     1.9  typedef struct
    1.10  {
    1.11 -    LPDLGTEMPLATE lpDialog;
    1.12 +	WORD dlgVer;
    1.13 +	WORD signature;
    1.14 +	DWORD helpID;
    1.15 +	DWORD exStyle;
    1.16 +	DWORD style;
    1.17 +	WORD cDlgItems;
    1.18 +	short x;
    1.19 +	short y;
    1.20 +	short cx;
    1.21 +	short cy;
    1.22 +} DLGTEMPLATEEX;
    1.23 +
    1.24 +typedef struct
    1.25 +{
    1.26 +	DWORD helpID;
    1.27 +	DWORD exStyle;
    1.28 +	DWORD style;
    1.29 +	short x;
    1.30 +	short y;
    1.31 +	short cx;
    1.32 +	short cy;
    1.33 +	DWORD id;
    1.34 +} DLGITEMTEMPLATEEX;
    1.35 +
    1.36 +#pragma pack(pop)
    1.37 +
    1.38 +typedef struct
    1.39 +{
    1.40 +    DLGTEMPLATEEX* lpDialog;
    1.41      Uint8 *data;
    1.42      size_t size;
    1.43      size_t used;
    1.44 @@ -70,7 +100,7 @@
    1.45          }
    1.46          dialog->data = data;
    1.47          dialog->size = size;
    1.48 -        dialog->lpDialog = (LPDLGTEMPLATE)dialog->data;
    1.49 +        dialog->lpDialog = (DLGTEMPLATEEX*)dialog->data;
    1.50      }
    1.51      return SDL_TRUE;
    1.52  }
    1.53 @@ -128,21 +158,35 @@
    1.54      return status;
    1.55  }
    1.56  
    1.57 +static int s_BaseUnitsX;
    1.58 +static int s_BaseUnitsY;
    1.59 +static void Vec2ToDLU(WORD* x, WORD* y)
    1.60 +{
    1.61 +    SDL_assert(s_BaseUnitsX != 0); // we init in WIN_ShowMessageBox(), which is the only public function...    
    1.62 +
    1.63 +    *x = MulDiv(*x, 4, s_BaseUnitsX);
    1.64 +    *y = MulDiv(*y, 8, s_BaseUnitsY);
    1.65 +}
    1.66 +
    1.67 +
    1.68  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)
    1.69  {
    1.70 -    DLGITEMTEMPLATE item;
    1.71 +    DLGITEMTEMPLATEEX item;
    1.72      WORD marker = 0xFFFF;
    1.73      WORD extraData = 0;
    1.74  
    1.75      SDL_zero(item);
    1.76      item.style = style;
    1.77 -    item.dwExtendedStyle = exStyle;
    1.78 +    item.exStyle = exStyle;
    1.79      item.x = x;
    1.80      item.y = y;
    1.81      item.cx = w;
    1.82      item.cy = h;
    1.83      item.id = id;
    1.84  
    1.85 +    Vec2ToDLU(&item.x, &item.y);
    1.86 +    Vec2ToDLU(&item.cx, &item.cy);
    1.87 +
    1.88      if (!AlignDialogData(dialog, sizeof(DWORD))) {
    1.89          return SDL_FALSE;
    1.90      }
    1.91 @@ -161,14 +205,14 @@
    1.92      if (!AddDialogData(dialog, &extraData, sizeof(extraData))) {
    1.93          return SDL_FALSE;
    1.94      }
    1.95 -    ++dialog->lpDialog->cdit;
    1.96 +    ++dialog->lpDialog->cDlgItems;
    1.97  
    1.98      return SDL_TRUE;
    1.99  }
   1.100  
   1.101  static SDL_bool AddDialogStatic(WIN_DialogData *dialog, int x, int y, int w, int h, const char *text)
   1.102  {
   1.103 -    DWORD style = WS_VISIBLE | WS_CHILD | SS_LEFT | SS_NOPREFIX;
   1.104 +    DWORD style = WS_VISIBLE | WS_CHILD | SS_LEFT | SS_NOPREFIX | SS_EDITCONTROL;
   1.105      return AddDialogControl(dialog, 0x0082, style, 0, x, y, w, h, -1, text);
   1.106  }
   1.107  
   1.108 @@ -194,14 +238,18 @@
   1.109  static WIN_DialogData *CreateDialogData(int w, int h, const char *caption)
   1.110  {
   1.111      WIN_DialogData *dialog;
   1.112 -    DLGTEMPLATE dialogTemplate;
   1.113 +    DLGTEMPLATEEX dialogTemplate;
   1.114 +    WORD WordToPass;
   1.115  
   1.116      SDL_zero(dialogTemplate);
   1.117 -    dialogTemplate.style = (WS_CAPTION | DS_CENTER);
   1.118 +    dialogTemplate.dlgVer = 1;
   1.119 +    dialogTemplate.signature = 0xffff;
   1.120 +    dialogTemplate.style = (WS_CAPTION | DS_CENTER | DS_SHELLFONT);
   1.121      dialogTemplate.x = 0;
   1.122      dialogTemplate.y = 0;
   1.123      dialogTemplate.cx = w;
   1.124      dialogTemplate.cy = h;
   1.125 +    Vec2ToDLU(&dialogTemplate.cx, &dialogTemplate.cy);
   1.126  
   1.127      dialog = (WIN_DialogData *)SDL_calloc(1, sizeof(*dialog));
   1.128      if (!dialog) {
   1.129 @@ -213,17 +261,76 @@
   1.130          return NULL;
   1.131      }
   1.132  
   1.133 -    /* There is no menu or special class */
   1.134 -    if (!AddDialogString(dialog, "") || !AddDialogString(dialog, "")) {
   1.135 +    // No menu
   1.136 +    WordToPass = 0;
   1.137 +    if (!AddDialogData(dialog, &WordToPass, 2)) {
   1.138          FreeDialogData(dialog);
   1.139          return NULL;
   1.140      }
   1.141  
   1.142 +    // No custom class
   1.143 +    if (!AddDialogData(dialog, &WordToPass, 2)) {
   1.144 +        FreeDialogData(dialog);
   1.145 +        return NULL;
   1.146 +    }
   1.147 +
   1.148 +    // title
   1.149      if (!AddDialogString(dialog, caption)) {
   1.150          FreeDialogData(dialog);
   1.151          return NULL;
   1.152      }
   1.153  
   1.154 +    // Font stuff
   1.155 +    {
   1.156 +        //
   1.157 +        // We want to use the system messagebox font.
   1.158 +        //
   1.159 +        BYTE ToPass;
   1.160 +        
   1.161 +        NONCLIENTMETRICSA NCM;
   1.162 +        NCM.cbSize = sizeof(NCM);
   1.163 +        SystemParametersInfoA(SPI_GETNONCLIENTMETRICS, 0, &NCM, 0);
   1.164 +        
   1.165 +        // Font size - convert to logical font size for dialog parameter.
   1.166 +        {
   1.167 +            HDC ScreenDC = GetDC(0);
   1.168 +            WordToPass = (WORD)(-72 * NCM.lfMessageFont.lfHeight / GetDeviceCaps(ScreenDC, LOGPIXELSY));
   1.169 +            ReleaseDC(0, ScreenDC);
   1.170 +        }
   1.171 +
   1.172 +        if (!AddDialogData(dialog, &WordToPass, 2)) {
   1.173 +            FreeDialogData(dialog);
   1.174 +            return NULL;
   1.175 +        }
   1.176 +
   1.177 +        // Font weight
   1.178 +        WordToPass = (WORD)NCM.lfMessageFont.lfWeight;
   1.179 +        if (!AddDialogData(dialog, &WordToPass, 2)) {
   1.180 +            FreeDialogData(dialog);
   1.181 +            return NULL;
   1.182 +        }
   1.183 +
   1.184 +        // italic?
   1.185 +        ToPass = NCM.lfMessageFont.lfItalic;
   1.186 +        if (!AddDialogData(dialog, &ToPass, 1)) {
   1.187 +            FreeDialogData(dialog);
   1.188 +            return NULL;
   1.189 +        }
   1.190 +
   1.191 +        // charset?
   1.192 +        ToPass = NCM.lfMessageFont.lfCharSet;
   1.193 +        if (!AddDialogData(dialog, &ToPass, 1)) {
   1.194 +            FreeDialogData(dialog);
   1.195 +            return NULL;
   1.196 +        }
   1.197 +
   1.198 +        // font typeface.
   1.199 +        if (!AddDialogString(dialog, NCM.lfMessageFont.lfFaceName)) {
   1.200 +            FreeDialogData(dialog);
   1.201 +            return NULL;
   1.202 +        }
   1.203 +    }
   1.204 +
   1.205      return dialog;
   1.206  }
   1.207  
   1.208 @@ -231,30 +338,114 @@
   1.209  WIN_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)
   1.210  {
   1.211      WIN_DialogData *dialog;
   1.212 -    int i, x, y, w, h, gap;
   1.213 -    INT_PTR which;
   1.214 +    int i, x, y, which;
   1.215      const SDL_MessageBoxButtonData *buttons = messageboxdata->buttons;
   1.216 +    HFONT DialogFont;
   1.217 +    SIZE Size;
   1.218 +    RECT TextSize;
   1.219 +    wchar_t* wmessage;
   1.220 +    TEXTMETRIC TM;
   1.221 +
   1.222 +
   1.223 +    const int ButtonWidth = 88;
   1.224 +    const int ButtonHeight = 26;
   1.225 +    const int TextMargin = 16;
   1.226 +    const int ButtonMargin = 12;
   1.227   
   1.228 -    /* FIXME: Need a better algorithm for laying out the message box */
   1.229  
   1.230 -    dialog = CreateDialogData(570, 260, messageboxdata->title);
   1.231 +    // Jan 25th, 2013 - dant@fleetsa.com
   1.232 +    //
   1.233 +    //
   1.234 +    // I've tried to make this more reasonable, but I've run in to a lot 
   1.235 +    // of nonsense.
   1.236 +    //
   1.237 +    // The original issue is the code was written in pixels and not 
   1.238 +    // dialog units (DLUs). All DialogBox functions use DLUs, which
   1.239 +    // vary based on the selected font (yay).
   1.240 +    //
   1.241 +    // According to MSDN, the most reliable way to convert is via
   1.242 +    // MapDialogUnits, which requires an HWND, which we don't have
   1.243 +    // at time of template creation.
   1.244 +    //
   1.245 +    // We do however have:
   1.246 +    //  The system font (DLU width 8 for me)
   1.247 +    //  The font we select for the dialog (DLU width 6 for me)
   1.248 +    //
   1.249 +    // Based on experimentation, *neither* of these return the value
   1.250 +    // actually used. Stepping in to MapDialogUnits(), the conversion
   1.251 +    // is fairly clear, and uses 7 for me.
   1.252 +    //
   1.253 +    // As a result, some of this is hacky to ensure the sizing is 
   1.254 +    // somewhat correct.
   1.255 +    //
   1.256 +    // Honestly, a long term solution is to use CreateWindow, not CreateDialog.
   1.257 +    //
   1.258 +
   1.259 +    //
   1.260 +    // In order to get text dimensions we need to have a DC with the desired font.
   1.261 +    // I'm assuming a dialog box in SDL is rare enough we can to the create.
   1.262 +    //
   1.263 +    HDC FontDC = CreateCompatibleDC(0);
   1.264 +    
   1.265 +    {
   1.266 +        // Create a duplicate of the font used in system message boxes.
   1.267 +        LOGFONT lf;
   1.268 +        NONCLIENTMETRICS NCM;
   1.269 +        NCM.cbSize = sizeof(NCM);
   1.270 +        SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &NCM, 0);
   1.271 +        lf = NCM.lfMessageFont;
   1.272 +        DialogFont = CreateFontIndirect(&lf);
   1.273 +    }
   1.274 +
   1.275 +    // Select the font in to our DC
   1.276 +    SelectObject(FontDC, DialogFont);
   1.277 +
   1.278 +    {
   1.279 +        // Get the metrics to try and figure our DLU conversion.
   1.280 +        GetTextMetrics(FontDC, &TM);
   1.281 +        s_BaseUnitsX = TM.tmAveCharWidth + 1;
   1.282 +        s_BaseUnitsY = TM.tmHeight;
   1.283 +    }
   1.284 +    
   1.285 +    // Measure the *pixel* size of the string.
   1.286 +    wmessage = WIN_UTF8ToString(messageboxdata->message);
   1.287 +    SDL_zero(TextSize);
   1.288 +    Size.cx = DrawText(FontDC, wmessage, -1, &TextSize, DT_CALCRECT);
   1.289 +
   1.290 +    // Add some padding for hangs, etc.
   1.291 +    TextSize.right += 2;
   1.292 +    TextSize.bottom += 2;
   1.293 +
   1.294 +    // Done with the DC, and the string
   1.295 +    DeleteDC(FontDC);
   1.296 +    SDL_free(wmessage);
   1.297 +
   1.298 +    // Increase the size of the dialog by some border spacing around the text.
   1.299 +    Size.cx = TextSize.right - TextSize.left;
   1.300 +    Size.cy = TextSize.bottom - TextSize.top;
   1.301 +    Size.cx += TextMargin * 2;
   1.302 +    Size.cy += TextMargin * 2;
   1.303 +
   1.304 +    // Ensure the size is wide enough for all of the buttons.
   1.305 +    if (Size.cx < messageboxdata->numbuttons * (ButtonWidth + ButtonMargin) + ButtonMargin)
   1.306 +        Size.cx = messageboxdata->numbuttons * (ButtonWidth + ButtonMargin) + ButtonMargin;
   1.307 +
   1.308 +    // Add vertical space for the buttons and border.
   1.309 +    Size.cy += ButtonHeight + TextMargin;
   1.310 +
   1.311 +    dialog = CreateDialogData(Size.cx, Size.cy, messageboxdata->title);
   1.312      if (!dialog) {
   1.313          return -1;
   1.314      }
   1.315  
   1.316 -    w = 100;
   1.317 -    h = 25;
   1.318 -    gap = 10;
   1.319 -    x = gap;
   1.320 -    y = 50;
   1.321 -
   1.322 -    if (!AddDialogStatic(dialog, x, y, 550, 100, messageboxdata->message)) {
   1.323 +    if (!AddDialogStatic(dialog, TextMargin, TextMargin, TextSize.right - TextSize.left, TextSize.bottom - TextSize.top, messageboxdata->message)) {
   1.324          FreeDialogData(dialog);
   1.325          return -1;
   1.326      }
   1.327  
   1.328 -    y += 110;
   1.329 -
   1.330 +    // Align the buttons to the right/bottom.
   1.331 +    x = Size.cx - ButtonWidth - ButtonMargin;
   1.332 +    y = Size.cy - ButtonHeight - ButtonMargin;
   1.333      for (i = 0; i < messageboxdata->numbuttons; ++i) {
   1.334          SDL_bool isDefault;
   1.335  
   1.336 @@ -263,15 +454,15 @@
   1.337          } else {
   1.338              isDefault = SDL_FALSE;
   1.339          }
   1.340 -        if (!AddDialogButton(dialog, x, y, w, h, buttons[i].text, i, isDefault)) {
   1.341 +        if (!AddDialogButton(dialog, x, y, ButtonWidth, ButtonHeight, buttons[i].text, i, isDefault)) {
   1.342              FreeDialogData(dialog);
   1.343              return -1;
   1.344          }
   1.345 -        x += w + gap;
   1.346 +        x -= ButtonWidth + ButtonMargin;
   1.347      }
   1.348  
   1.349      /* FIXME: If we have a parent window, get the Instance and HWND for them */
   1.350 -    which = DialogBoxIndirect(NULL, dialog->lpDialog, NULL, (DLGPROC)MessageBoxDialogProc);
   1.351 +    which = DialogBoxIndirect(NULL, (DLGTEMPLATE*)dialog->lpDialog, NULL, (DLGPROC)MessageBoxDialogProc);
   1.352      *buttonid = buttons[which].buttonid;
   1.353  
   1.354      FreeDialogData(dialog);