SDL_windowsmessagebox.c (12903B)
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2014 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 39typedef 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 53typedef 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 67typedef struct 68{ 69 DLGTEMPLATEEX* lpDialog; 70 Uint8 *data; 71 size_t size; 72 size_t used; 73} WIN_DialogData; 74 75 76static 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 90static 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 114static 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 127static 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 139static 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 167static int s_BaseUnitsX; 168static int s_BaseUnitsY; 169static 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 178static 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 219static 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 225static 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 236static void FreeDialogData(WIN_DialogData *dialog) 237{ 238 SDL_free(dialog->data); 239 SDL_free(dialog); 240} 241 242static 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 341int 342WIN_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: */