From c26da66c5b1042ad94fd2a21dea006d920723565 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Tue, 26 Mar 2013 04:57:29 -0700 Subject: [PATCH] SDL message box improvements from Dan Thompson --- src/video/windows/SDL_windowsmessagebox.c | 243 +++++++++++++++++++--- 1 file changed, 217 insertions(+), 26 deletions(-) diff --git a/src/video/windows/SDL_windowsmessagebox.c b/src/video/windows/SDL_windowsmessagebox.c index 9865d7778..0ed50aff6 100644 --- a/src/video/windows/SDL_windowsmessagebox.c +++ b/src/video/windows/SDL_windowsmessagebox.c @@ -28,9 +28,39 @@ /* Display a Windows message box */ +#pragma pack(push, 1) + +typedef struct +{ + WORD dlgVer; + WORD signature; + DWORD helpID; + DWORD exStyle; + DWORD style; + WORD cDlgItems; + short x; + short y; + short cx; + short cy; +} DLGTEMPLATEEX; + typedef struct { - LPDLGTEMPLATE lpDialog; + 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 @@ static SDL_bool ExpandDialogSpace(WIN_DialogData *dialog, size_t space) } dialog->data = data; dialog->size = size; - dialog->lpDialog = (LPDLGTEMPLATE)dialog->data; + dialog->lpDialog = (DLGTEMPLATEEX*)dialog->data; } return SDL_TRUE; } @@ -128,21 +158,35 @@ static SDL_bool AddDialogString(WIN_DialogData *dialog, const char *string) 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 @@ static SDL_bool AddDialogControl(WIN_DialogData *dialog, WORD type, DWORD style, 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 void FreeDialogData(WIN_DialogData *dialog) 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,17 +261,76 @@ static WIN_DialogData *CreateDialogData(int w, int h, const char *caption) 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; } + // 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 @@ int 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 */ - dialog = CreateDialogData(570, 260, messageboxdata->title); + // 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. + // + + // + // 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 @@ WIN_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid) } 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);