Atlas - SDL_windowsmessagebox.c

Home / ext / SDL / src / video / windows Lines: 1 | Size: 36945 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 Sam Lantinga <[email protected]> 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 defined(SDL_VIDEO_DRIVER_WINDOWS) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) 24 25#ifdef HAVE_LIMITS_H 26#include <limits.h> 27#endif 28#ifndef SIZE_MAX 29#define SIZE_MAX ((size_t)-1) 30#endif 31 32#include "../../core/windows/SDL_windows.h" 33 34#include "SDL_windowsvideo.h" 35 36#ifndef SS_EDITCONTROL 37#define SS_EDITCONTROL 0x2000 38#endif 39 40#ifndef IDOK 41#define IDOK 1 42#endif 43 44#ifndef IDCANCEL 45#define IDCANCEL 2 46#endif 47 48// Custom dialog return codes 49#define IDCLOSED 20 50#define IDINVALPTRINIT 50 51#define IDINVALPTRCOMMAND 51 52#define IDINVALPTRSETFOCUS 52 53#define IDINVALPTRDLGITEM 53 54// First button ID 55#define IDBUTTONINDEX0 100 56 57#define DLGITEMTYPEBUTTON 0x0080 58#define DLGITEMTYPESTATIC 0x0082 59 60/* Windows only sends the lower 16 bits of the control ID when a button 61 * gets clicked. There are also some predefined and custom IDs that lower 62 * the available number further. 2^16 - 101 buttons should be enough for 63 * everyone, no need to make the code more complex. 64 */ 65#define MAX_BUTTONS (0xffff - 100) 66 67// Display a Windows message box 68 69typedef HRESULT(CALLBACK *PFTASKDIALOGCALLBACK)(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, LONG_PTR lpRefData); 70 71enum _TASKDIALOG_FLAGS 72{ 73 TDF_ENABLE_HYPERLINKS = 0x0001, 74 TDF_USE_HICON_MAIN = 0x0002, 75 TDF_USE_HICON_FOOTER = 0x0004, 76 TDF_ALLOW_DIALOG_CANCELLATION = 0x0008, 77 TDF_USE_COMMAND_LINKS = 0x0010, 78 TDF_USE_COMMAND_LINKS_NO_ICON = 0x0020, 79 TDF_EXPAND_FOOTER_AREA = 0x0040, 80 TDF_EXPANDED_BY_DEFAULT = 0x0080, 81 TDF_VERIFICATION_FLAG_CHECKED = 0x0100, 82 TDF_SHOW_PROGRESS_BAR = 0x0200, 83 TDF_SHOW_MARQUEE_PROGRESS_BAR = 0x0400, 84 TDF_CALLBACK_TIMER = 0x0800, 85 TDF_POSITION_RELATIVE_TO_WINDOW = 0x1000, 86 TDF_RTL_LAYOUT = 0x2000, 87 TDF_NO_DEFAULT_RADIO_BUTTON = 0x4000, 88 TDF_CAN_BE_MINIMIZED = 0x8000, 89 // #if (NTDDI_VERSION >= NTDDI_WIN8) 90 TDF_NO_SET_FOREGROUND = 0x00010000, // Don't call SetForegroundWindow() when activating the dialog 91 // #endif // (NTDDI_VERSION >= NTDDI_WIN8) 92 TDF_SIZE_TO_CONTENT = 0x01000000 // used by ShellMessageBox to emulate MessageBox sizing behavior 93}; 94typedef int TASKDIALOG_FLAGS; // Note: _TASKDIALOG_FLAGS is an int 95 96typedef enum _TASKDIALOG_MESSAGES 97{ 98 TDM_NAVIGATE_PAGE = WM_USER + 101, 99 TDM_CLICK_BUTTON = WM_USER + 102, // wParam = Button ID 100 TDM_SET_MARQUEE_PROGRESS_BAR = WM_USER + 103, // wParam = 0 (nonMarque) wParam != 0 (Marquee) 101 TDM_SET_PROGRESS_BAR_STATE = WM_USER + 104, // wParam = new progress state 102 TDM_SET_PROGRESS_BAR_RANGE = WM_USER + 105, // lParam = MAKELPARAM(nMinRange, nMaxRange) 103 TDM_SET_PROGRESS_BAR_POS = WM_USER + 106, // wParam = new position 104 TDM_SET_PROGRESS_BAR_MARQUEE = WM_USER + 107, // wParam = 0 (stop marquee), wParam != 0 (start marquee), lparam = speed (milliseconds between repaints) 105 TDM_SET_ELEMENT_TEXT = WM_USER + 108, // wParam = element (TASKDIALOG_ELEMENTS), lParam = new element text (LPCWSTR) 106 TDM_CLICK_RADIO_BUTTON = WM_USER + 110, // wParam = Radio Button ID 107 TDM_ENABLE_BUTTON = WM_USER + 111, // lParam = 0 (disable), lParam != 0 (enable), wParam = Button ID 108 TDM_ENABLE_RADIO_BUTTON = WM_USER + 112, // lParam = 0 (disable), lParam != 0 (enable), wParam = Radio Button ID 109 TDM_CLICK_VERIFICATION = WM_USER + 113, // wParam = 0 (unchecked), 1 (checked), lParam = 1 (set key focus) 110 TDM_UPDATE_ELEMENT_TEXT = WM_USER + 114, // wParam = element (TASKDIALOG_ELEMENTS), lParam = new element text (LPCWSTR) 111 TDM_SET_BUTTON_ELEVATION_REQUIRED_STATE = WM_USER + 115, // wParam = Button ID, lParam = 0 (elevation not required), lParam != 0 (elevation required) 112 TDM_UPDATE_ICON = WM_USER + 116 // wParam = icon element (TASKDIALOG_ICON_ELEMENTS), lParam = new icon (hIcon if TDF_USE_HICON_* was set, PCWSTR otherwise) 113} TASKDIALOG_MESSAGES; 114 115typedef enum _TASKDIALOG_NOTIFICATIONS 116{ 117 TDN_CREATED = 0, 118 TDN_NAVIGATED = 1, 119 TDN_BUTTON_CLICKED = 2, // wParam = Button ID 120 TDN_HYPERLINK_CLICKED = 3, // lParam = (LPCWSTR)pszHREF 121 TDN_TIMER = 4, // wParam = Milliseconds since dialog created or timer reset 122 TDN_DESTROYED = 5, 123 TDN_RADIO_BUTTON_CLICKED = 6, // wParam = Radio Button ID 124 TDN_DIALOG_CONSTRUCTED = 7, 125 TDN_VERIFICATION_CLICKED = 8, // wParam = 1 if checkbox checked, 0 if not, lParam is unused and always 0 126 TDN_HELP = 9, 127 TDN_EXPANDO_BUTTON_CLICKED = 10 // wParam = 0 (dialog is now collapsed), wParam != 0 (dialog is now expanded) 128} TASKDIALOG_NOTIFICATIONS; 129 130typedef enum _TASKDIALOG_ELEMENTS 131{ 132 TDE_CONTENT, 133 TDE_EXPANDED_INFORMATION, 134 TDE_FOOTER, 135 TDE_MAIN_INSTRUCTION 136} TASKDIALOG_ELEMENTS; 137 138typedef enum _TASKDIALOG_ICON_ELEMENTS 139{ 140 TDIE_ICON_MAIN, 141 TDIE_ICON_FOOTER 142} TASKDIALOG_ICON_ELEMENTS; 143 144#define TD_WARNING_ICON MAKEINTRESOURCEW(-1) 145#define TD_ERROR_ICON MAKEINTRESOURCEW(-2) 146#define TD_INFORMATION_ICON MAKEINTRESOURCEW(-3) 147#define TD_SHIELD_ICON MAKEINTRESOURCEW(-4) 148 149enum _TASKDIALOG_COMMON_BUTTON_FLAGS 150{ 151 TDCBF_OK_BUTTON = 0x0001, // selected control return value IDOK 152 TDCBF_YES_BUTTON = 0x0002, // selected control return value IDYES 153 TDCBF_NO_BUTTON = 0x0004, // selected control return value IDNO 154 TDCBF_CANCEL_BUTTON = 0x0008, // selected control return value IDCANCEL 155 TDCBF_RETRY_BUTTON = 0x0010, // selected control return value IDRETRY 156 TDCBF_CLOSE_BUTTON = 0x0020 // selected control return value IDCLOSE 157}; 158typedef int TASKDIALOG_COMMON_BUTTON_FLAGS; // Note: _TASKDIALOG_COMMON_BUTTON_FLAGS is an int 159 160#pragma pack(push, 1) 161 162typedef struct _TASKDIALOG_BUTTON 163{ 164 int nButtonID; 165 PCWSTR pszButtonText; 166} TASKDIALOG_BUTTON; 167 168typedef struct _TASKDIALOGCONFIG 169{ 170 UINT cbSize; 171 HWND hwndParent; // incorrectly named, this is the owner window, not a parent. 172 HINSTANCE hInstance; // used for MAKEINTRESOURCE() strings 173 TASKDIALOG_FLAGS dwFlags; // TASKDIALOG_FLAGS (TDF_XXX) flags 174 TASKDIALOG_COMMON_BUTTON_FLAGS dwCommonButtons; // TASKDIALOG_COMMON_BUTTON (TDCBF_XXX) flags 175 PCWSTR pszWindowTitle; // string or MAKEINTRESOURCE() 176 union 177 { 178 HICON hMainIcon; 179 PCWSTR pszMainIcon; 180 } /*DUMMYUNIONNAME*/; 181 PCWSTR pszMainInstruction; 182 PCWSTR pszContent; 183 UINT cButtons; 184 const TASKDIALOG_BUTTON *pButtons; 185 int nDefaultButton; 186 UINT cRadioButtons; 187 const TASKDIALOG_BUTTON *pRadioButtons; 188 int nDefaultRadioButton; 189 PCWSTR pszVerificationText; 190 PCWSTR pszExpandedInformation; 191 PCWSTR pszExpandedControlText; 192 PCWSTR pszCollapsedControlText; 193 union 194 { 195 HICON hFooterIcon; 196 PCWSTR pszFooterIcon; 197 } /*DUMMYUNIONNAME2*/; 198 PCWSTR pszFooter; 199 PFTASKDIALOGCALLBACK pfCallback; 200 LONG_PTR lpCallbackData; 201 UINT cxWidth; // width of the Task Dialog's client area in DLU's. If 0, Task Dialog will calculate the ideal width. 202} TASKDIALOGCONFIG; 203 204typedef struct 205{ 206 WORD dlgVer; 207 WORD signature; 208 DWORD helpID; 209 DWORD exStyle; 210 DWORD style; 211 WORD cDlgItems; 212 short x; 213 short y; 214 short cx; 215 short cy; 216} DLGTEMPLATEEX; 217 218typedef struct 219{ 220 DWORD helpID; 221 DWORD exStyle; 222 DWORD style; 223 short x; 224 short y; 225 short cx; 226 short cy; 227 DWORD id; 228} DLGITEMTEMPLATEEX; 229 230#pragma pack(pop) 231 232typedef struct 233{ 234 DLGTEMPLATEEX *lpDialog; 235 void *data; 236 size_t size; 237 size_t used; 238 WORD numbuttons; 239} WIN_DialogData; 240 241static bool GetButtonIndex(const SDL_MessageBoxData *messageboxdata, SDL_MessageBoxButtonFlags flags, size_t *i) 242{ 243 for (*i = 0; *i < (size_t)messageboxdata->numbuttons; ++*i) { 244 if (messageboxdata->buttons[*i].flags & flags) { 245 return true; 246 } 247 } 248 return false; 249} 250 251static INT_PTR CALLBACK MessageBoxDialogProc(HWND hDlg, UINT iMessage, WPARAM wParam, LPARAM lParam) 252{ 253 const SDL_MessageBoxData *messageboxdata; 254 size_t buttonindex; 255 256 switch (iMessage) { 257 case WM_INITDIALOG: 258 if (lParam == 0) { 259 EndDialog(hDlg, IDINVALPTRINIT); 260 return TRUE; 261 } 262 messageboxdata = (const SDL_MessageBoxData *)lParam; 263 SetWindowLongPtr(hDlg, GWLP_USERDATA, lParam); 264 265 if (GetButtonIndex(messageboxdata, SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, &buttonindex)) { 266 // Focus on the first default return-key button 267 HWND buttonctl = GetDlgItem(hDlg, (int)(IDBUTTONINDEX0 + buttonindex)); 268 if (!buttonctl) { 269 EndDialog(hDlg, IDINVALPTRDLGITEM); 270 } 271 PostMessage(hDlg, WM_NEXTDLGCTL, (WPARAM)buttonctl, TRUE); 272 } else { 273 // Give the focus to the dialog window instead 274 SetFocus(hDlg); 275 } 276 return FALSE; 277 case WM_SETFOCUS: 278 messageboxdata = (const SDL_MessageBoxData *)GetWindowLongPtr(hDlg, GWLP_USERDATA); 279 if (!messageboxdata) { 280 EndDialog(hDlg, IDINVALPTRSETFOCUS); 281 return TRUE; 282 } 283 284 // Let the default button be focused if there is one. Otherwise, prevent any initial focus. 285 if (GetButtonIndex(messageboxdata, SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, &buttonindex)) { 286 return FALSE; 287 } 288 return TRUE; 289 case WM_COMMAND: 290 messageboxdata = (const SDL_MessageBoxData *)GetWindowLongPtr(hDlg, GWLP_USERDATA); 291 if (!messageboxdata) { 292 EndDialog(hDlg, IDINVALPTRCOMMAND); 293 return TRUE; 294 } 295 296 // Return the ID of the button that was pushed 297 if (wParam == IDOK) { 298 if (GetButtonIndex(messageboxdata, SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, &buttonindex)) { 299 EndDialog(hDlg, IDBUTTONINDEX0 + buttonindex); 300 } 301 } else if (wParam == IDCANCEL) { 302 if (GetButtonIndex(messageboxdata, SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, &buttonindex)) { 303 EndDialog(hDlg, IDBUTTONINDEX0 + buttonindex); 304 } else { 305 // Closing of window was requested by user or system. It would be rude not to comply. 306 EndDialog(hDlg, IDCLOSED); 307 } 308 } else if (wParam >= IDBUTTONINDEX0 && (int)wParam - IDBUTTONINDEX0 < messageboxdata->numbuttons) { 309 EndDialog(hDlg, wParam); 310 } 311 return TRUE; 312 313 default: 314 break; 315 } 316 return FALSE; 317} 318 319static bool ExpandDialogSpace(WIN_DialogData *dialog, size_t space) 320{ 321 // Growing memory in 64 KiB steps. 322 const size_t sizestep = 0x10000; 323 size_t size = dialog->size; 324 325 if (size == 0) { 326 // Start with 4 KiB or a multiple of 64 KiB to fit the data. 327 size = 0x1000; 328 if (SIZE_MAX - sizestep < space) { 329 size = space; 330 } else if (space > size) { 331 size = (space + sizestep) & ~(sizestep - 1); 332 } 333 } else if (SIZE_MAX - dialog->used < space) { 334 SDL_OutOfMemory(); 335 return false; 336 } else if (SIZE_MAX - (dialog->used + space) < sizestep) { 337 // Close to the maximum. 338 size = dialog->used + space; 339 } else if (size < dialog->used + space) { 340 // Round up to the next 64 KiB block. 341 size = dialog->used + space; 342 size += sizestep - size % sizestep; 343 } 344 345 if (size > dialog->size) { 346 void *data = SDL_realloc(dialog->data, size); 347 if (!data) { 348 return false; 349 } 350 dialog->data = data; 351 dialog->size = size; 352 dialog->lpDialog = (DLGTEMPLATEEX *)dialog->data; 353 } 354 return true; 355} 356 357static bool AlignDialogData(WIN_DialogData *dialog, size_t size) 358{ 359 size_t padding = (dialog->used % size); 360 361 if (!ExpandDialogSpace(dialog, padding)) { 362 return false; 363 } 364 365 dialog->used += padding; 366 367 return true; 368} 369 370static bool AddDialogData(WIN_DialogData *dialog, const void *data, size_t size) 371{ 372 if (!ExpandDialogSpace(dialog, size)) { 373 return false; 374 } 375 376 SDL_memcpy((Uint8 *)dialog->data + dialog->used, data, size); 377 dialog->used += size; 378 379 return true; 380} 381 382static bool AddDialogString(WIN_DialogData *dialog, const char *string) 383{ 384 WCHAR *wstring; 385 WCHAR *p; 386 size_t count; 387 bool status; 388 389 if (!string) { 390 string = ""; 391 } 392 393 wstring = WIN_UTF8ToStringW(string); 394 if (!wstring) { 395 return false; 396 } 397 398 // Find out how many characters we have, including null terminator 399 count = 0; 400 for (p = wstring; *p; ++p) { 401 ++count; 402 } 403 ++count; 404 405 status = AddDialogData(dialog, wstring, count * sizeof(WCHAR)); 406 SDL_free(wstring); 407 return status; 408} 409 410static int s_BaseUnitsX; 411static int s_BaseUnitsY; 412static void Vec2ToDLU(short *x, short *y) 413{ 414 SDL_assert(s_BaseUnitsX != 0); // we init in WIN_ShowMessageBox(), which is the only public function... 415 416 *x = (short)MulDiv(*x, 4, s_BaseUnitsX); 417 *y = (short)MulDiv(*y, 8, s_BaseUnitsY); 418} 419 420static bool AddDialogControl(WIN_DialogData *dialog, WORD type, DWORD style, DWORD exStyle, int x, int y, int w, int h, int id, const char *caption, WORD ordinal) 421{ 422 DLGITEMTEMPLATEEX item; 423 WORD marker = 0xFFFF; 424 WORD extraData = 0; 425 426 SDL_zero(item); 427 item.style = style; 428 item.exStyle = exStyle; 429 item.x = (short)x; 430 item.y = (short)y; 431 item.cx = (short)w; 432 item.cy = (short)h; 433 item.id = id; 434 435 Vec2ToDLU(&item.x, &item.y); 436 Vec2ToDLU(&item.cx, &item.cy); 437 438 if (!AlignDialogData(dialog, sizeof(DWORD))) { 439 return false; 440 } 441 if (!AddDialogData(dialog, &item, sizeof(item))) { 442 return false; 443 } 444 if (!AddDialogData(dialog, &marker, sizeof(marker))) { 445 return false; 446 } 447 if (!AddDialogData(dialog, &type, sizeof(type))) { 448 return false; 449 } 450 if (type == DLGITEMTYPEBUTTON || (type == DLGITEMTYPESTATIC && caption)) { 451 if (!AddDialogString(dialog, caption)) { 452 return false; 453 } 454 } else { 455 if (!AddDialogData(dialog, &marker, sizeof(marker))) { 456 return false; 457 } 458 if (!AddDialogData(dialog, &ordinal, sizeof(ordinal))) { 459 return false; 460 } 461 } 462 if (!AddDialogData(dialog, &extraData, sizeof(extraData))) { 463 return false; 464 } 465 if (type == DLGITEMTYPEBUTTON) { 466 dialog->numbuttons++; 467 } 468 ++dialog->lpDialog->cDlgItems; 469 470 return true; 471} 472 473static bool AddDialogStaticText(WIN_DialogData *dialog, int x, int y, int w, int h, const char *text) 474{ 475 DWORD style = WS_VISIBLE | WS_CHILD | SS_LEFT | SS_NOPREFIX | SS_EDITCONTROL | WS_GROUP; 476 return AddDialogControl(dialog, DLGITEMTYPESTATIC, style, 0, x, y, w, h, -1, text, 0); 477} 478 479static bool AddDialogStaticIcon(WIN_DialogData *dialog, int x, int y, int w, int h, Uint16 ordinal) 480{ 481 DWORD style = WS_VISIBLE | WS_CHILD | SS_ICON | WS_GROUP; 482 return AddDialogControl(dialog, DLGITEMTYPESTATIC, style, 0, x, y, w, h, -2, NULL, ordinal); 483} 484 485static bool AddDialogButton(WIN_DialogData *dialog, int x, int y, int w, int h, const char *text, int id, bool isDefault) 486{ 487 DWORD style = WS_VISIBLE | WS_CHILD | WS_TABSTOP; 488 if (isDefault) { 489 style |= BS_DEFPUSHBUTTON; 490 } else { 491 style |= BS_PUSHBUTTON; 492 } 493 // The first button marks the start of the group. 494 if (dialog->numbuttons == 0) { 495 style |= WS_GROUP; 496 } 497 return AddDialogControl(dialog, DLGITEMTYPEBUTTON, style, 0, x, y, w, h, id, text, 0); 498} 499 500static void FreeDialogData(WIN_DialogData *dialog) 501{ 502 SDL_free(dialog->data); 503 SDL_free(dialog); 504} 505 506static WIN_DialogData *CreateDialogData(int w, int h, const char *caption) 507{ 508 WIN_DialogData *dialog; 509 DLGTEMPLATEEX dialogTemplate; 510 WORD WordToPass; 511 512 SDL_zero(dialogTemplate); 513 dialogTemplate.dlgVer = 1; 514 dialogTemplate.signature = 0xffff; 515 dialogTemplate.style = (WS_CAPTION | DS_CENTER | DS_SHELLFONT); 516 dialogTemplate.x = 0; 517 dialogTemplate.y = 0; 518 dialogTemplate.cx = (short)w; 519 dialogTemplate.cy = (short)h; 520 Vec2ToDLU(&dialogTemplate.cx, &dialogTemplate.cy); 521 522 dialog = (WIN_DialogData *)SDL_calloc(1, sizeof(*dialog)); 523 if (!dialog) { 524 return NULL; 525 } 526 527 if (!AddDialogData(dialog, &dialogTemplate, sizeof(dialogTemplate))) { 528 FreeDialogData(dialog); 529 return NULL; 530 } 531 532 // No menu 533 WordToPass = 0; 534 if (!AddDialogData(dialog, &WordToPass, 2)) { 535 FreeDialogData(dialog); 536 return NULL; 537 } 538 539 // No custom class 540 if (!AddDialogData(dialog, &WordToPass, 2)) { 541 FreeDialogData(dialog); 542 return NULL; 543 } 544 545 // title 546 if (!AddDialogString(dialog, caption)) { 547 FreeDialogData(dialog); 548 return NULL; 549 } 550 551 // Font stuff 552 { 553 /* 554 * We want to use the system messagebox font. 555 */ 556 BYTE ToPass; 557 558 NONCLIENTMETRICSA NCM; 559 NCM.cbSize = sizeof(NCM); 560 SystemParametersInfoA(SPI_GETNONCLIENTMETRICS, 0, &NCM, 0); 561 562 // Font size - convert to logical font size for dialog parameter. 563 { 564 HDC ScreenDC = GetDC(NULL); 565 int LogicalPixelsY = GetDeviceCaps(ScreenDC, LOGPIXELSY); 566 if (!LogicalPixelsY) { 567 LogicalPixelsY = 72; // This can happen if the application runs out of GDI handles 568 } 569 570 WordToPass = (WORD)(-72 * NCM.lfMessageFont.lfHeight / LogicalPixelsY); 571 ReleaseDC(NULL, ScreenDC); 572 } 573 574 if (!AddDialogData(dialog, &WordToPass, 2)) { 575 FreeDialogData(dialog); 576 return NULL; 577 } 578 579 // Font weight 580 WordToPass = (WORD)NCM.lfMessageFont.lfWeight; 581 if (!AddDialogData(dialog, &WordToPass, 2)) { 582 FreeDialogData(dialog); 583 return NULL; 584 } 585 586 // italic? 587 ToPass = NCM.lfMessageFont.lfItalic; 588 if (!AddDialogData(dialog, &ToPass, 1)) { 589 FreeDialogData(dialog); 590 return NULL; 591 } 592 593 // charset? 594 ToPass = NCM.lfMessageFont.lfCharSet; 595 if (!AddDialogData(dialog, &ToPass, 1)) { 596 FreeDialogData(dialog); 597 return NULL; 598 } 599 600 // font typeface. 601 if (!AddDialogString(dialog, NCM.lfMessageFont.lfFaceName)) { 602 FreeDialogData(dialog); 603 return NULL; 604 } 605 } 606 607 return dialog; 608} 609 610/* Escaping ampersands is necessary to disable mnemonics in dialog controls. 611 * The caller provides a char** for dst and a size_t* for dstlen where the 612 * address of the work buffer and its size will be stored. Their values must be 613 * NULL and 0 on the first call. src is the string to be escaped. On error, the 614 * function returns NULL and, on success, returns a pointer to the escaped 615 * sequence as a read-only string that is valid until the next call or until the 616 * work buffer is freed. Once all strings have been processed, it's the caller's 617 * responsibility to free the work buffer with SDL_free, even on errors. 618 */ 619static const char *EscapeAmpersands(char **dst, size_t *dstlen, const char *src) 620{ 621 char *newdst; 622 size_t ampcount = 0; 623 size_t srclen = 0; 624 625 if (!src) { 626 return NULL; 627 } 628 629 while (src[srclen]) { 630 if (src[srclen] == '&') { 631 ampcount++; 632 } 633 srclen++; 634 } 635 srclen++; 636 637 if (ampcount == 0) { 638 // Nothing to do. 639 return src; 640 } 641 if (SIZE_MAX - srclen < ampcount) { 642 return NULL; 643 } 644 if (!*dst || *dstlen < srclen + ampcount) { 645 // Allocating extra space in case the next strings are a bit longer. 646 size_t extraspace = SIZE_MAX - (srclen + ampcount); 647 if (extraspace > 512) { 648 extraspace = 512; 649 } 650 *dstlen = srclen + ampcount + extraspace; 651 SDL_free(*dst); 652 *dst = NULL; 653 newdst = (char *)SDL_malloc(*dstlen); 654 if (!newdst) { 655 return NULL; 656 } 657 *dst = newdst; 658 } else { 659 newdst = *dst; 660 } 661 662 // The escape character is the ampersand itself. 663 while (srclen--) { 664 if (*src == '&') { 665 *newdst++ = '&'; 666 } 667 *newdst++ = *src++; 668 } 669 670 return *dst; 671} 672 673static float WIN_GetContentScale(void) 674{ 675 int dpi = 0; 676 677#if 0 // We don't know what monitor the dialog will be shown on 678 UINT hdpi_uint, vdpi_uint; 679 if (GetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &hdpi_uint, &vdpi_uint) == S_OK) { 680 dpi = (int)hdpi_uint; 681 } 682#endif 683 if (dpi == 0) { 684 // Window 8.0 and below: same DPI for all monitors 685 HDC hdc = GetDC(NULL); 686 if (hdc) { 687 dpi = GetDeviceCaps(hdc, LOGPIXELSX); 688 ReleaseDC(NULL, hdc); 689 } 690 } 691 if (dpi == 0) { 692 // Safe default 693 dpi = USER_DEFAULT_SCREEN_DPI; 694 } 695 return dpi / (float)USER_DEFAULT_SCREEN_DPI; 696} 697 698// This function is called if a Task Dialog is unsupported. 699static bool WIN_ShowOldMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID) 700{ 701 WIN_DialogData *dialog; 702 int i, x, y; 703 HFONT DialogFont; 704 SIZE Size; 705 RECT TextSize; 706 wchar_t *wmessage; 707 TEXTMETRIC TM; 708 HDC FontDC; 709 INT_PTR rc; 710 char *ampescape = NULL; 711 size_t ampescapesize = 0; 712 Uint16 defbuttoncount = 0; 713 Uint16 icon = 0; 714 bool result; 715 716 HWND ParentWindow = NULL; 717 718 const float scale = WIN_GetContentScale(); 719 const int ButtonWidth = (int)SDL_roundf(88 * scale); 720 const int ButtonHeight = (int)SDL_roundf(26 * scale); 721 const int TextMargin = (int)SDL_roundf(16 * scale); 722 const int ButtonMargin = (int)SDL_roundf(12 * scale); 723 const int IconWidth = GetSystemMetrics(SM_CXICON); 724 const int IconHeight = GetSystemMetrics(SM_CYICON); 725 const int IconMargin = (int)SDL_roundf(20 * scale); 726 727 if (messageboxdata->numbuttons > MAX_BUTTONS) { 728 return SDL_SetError("Number of buttons exceeds limit of %d", MAX_BUTTONS); 729 } 730 731 switch (messageboxdata->flags & (SDL_MESSAGEBOX_ERROR | SDL_MESSAGEBOX_WARNING | SDL_MESSAGEBOX_INFORMATION)) { 732 case SDL_MESSAGEBOX_ERROR: 733 icon = (Uint16)(size_t)IDI_ERROR; 734 break; 735 case SDL_MESSAGEBOX_WARNING: 736 icon = (Uint16)(size_t)IDI_WARNING; 737 break; 738 case SDL_MESSAGEBOX_INFORMATION: 739 icon = (Uint16)(size_t)IDI_INFORMATION; 740 break; 741 } 742 743 /* Jan 25th, 2013 - [email protected] 744 * 745 * I've tried to make this more reasonable, but I've run in to a lot 746 * of nonsense. 747 * 748 * The original issue is the code was written in pixels and not 749 * dialog units (DLUs). All DialogBox functions use DLUs, which 750 * vary based on the selected font (yay). 751 * 752 * According to MSDN, the most reliable way to convert is via 753 * MapDialogUnits, which requires an HWND, which we don't have 754 * at time of template creation. 755 * 756 * We do however have: 757 * The system font (DLU width 8 for me) 758 * The font we select for the dialog (DLU width 6 for me) 759 * 760 * Based on experimentation, *neither* of these return the value 761 * actually used. Stepping in to MapDialogUnits(), the conversion 762 * is fairly clear, and uses 7 for me. 763 * 764 * As a result, some of this is hacky to ensure the sizing is 765 * somewhat correct. 766 * 767 * Honestly, a long term solution is to use CreateWindow, not CreateDialog. 768 * 769 * In order to get text dimensions we need to have a DC with the desired font. 770 * I'm assuming a dialog box in SDL is rare enough we can to the create. 771 */ 772 FontDC = CreateCompatibleDC(0); 773 774 { 775 // Create a duplicate of the font used in system message boxes. 776 LOGFONT lf; 777 NONCLIENTMETRICS NCM; 778 NCM.cbSize = sizeof(NCM); 779 SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &NCM, 0); 780 lf = NCM.lfMessageFont; 781 DialogFont = CreateFontIndirect(&lf); 782 } 783 784 // Select the font in to our DC 785 SelectObject(FontDC, DialogFont); 786 787 { 788 // Get the metrics to try and figure our DLU conversion. 789 GetTextMetrics(FontDC, &TM); 790 791 /* Calculation from the following documentation: 792 * https://support.microsoft.com/en-gb/help/125681/how-to-calculate-dialog-base-units-with-non-system-based-font 793 * This fixes bug 2137, dialog box calculation with a fixed-width system font 794 */ 795 { 796 SIZE extent; 797 GetTextExtentPoint32A(FontDC, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 52, &extent); 798 s_BaseUnitsX = (extent.cx / 26 + 1) / 2; 799 } 800 // s_BaseUnitsX = TM.tmAveCharWidth + 1; 801 s_BaseUnitsY = TM.tmHeight; 802 } 803 804 /* Measure the *pixel* size of the string. */ 805 wmessage = WIN_UTF8ToStringW(messageboxdata->message); 806 SDL_zero(TextSize); 807 DrawTextW(FontDC, wmessage, -1, &TextSize, DT_CALCRECT | DT_LEFT | DT_NOPREFIX | DT_EDITCONTROL); 808 809 // Add margins and some padding for hangs, etc. 810 TextSize.left += TextMargin; 811 TextSize.right += TextMargin + 2; 812 TextSize.top += TextMargin; 813 TextSize.bottom += TextMargin + 2; 814 815 // Done with the DC, and the string 816 DeleteDC(FontDC); 817 SDL_free(wmessage); 818 819 // Increase the size of the dialog by some border spacing around the text. 820 Size.cx = TextSize.right - TextSize.left; 821 Size.cy = TextSize.bottom - TextSize.top; 822 Size.cx += TextMargin * 2; 823 Size.cy += TextMargin * 2; 824 825 // Make dialog wider and shift text over for the icon. 826 if (icon) { 827 Size.cx += IconMargin + IconWidth; 828 TextSize.left += IconMargin + IconWidth; 829 TextSize.right += IconMargin + IconWidth; 830 } 831 832 // Ensure the size is wide enough for all of the buttons. 833 if (Size.cx < (LONG)messageboxdata->numbuttons * (ButtonWidth + ButtonMargin) + ButtonMargin) { 834 Size.cx = (LONG)messageboxdata->numbuttons * (ButtonWidth + ButtonMargin) + ButtonMargin; 835 } 836 837 // Reset the height to the icon size if it is actually bigger than the text. 838 if (icon && Size.cy < (LONG)IconMargin * 2 + IconHeight) { 839 Size.cy = (LONG)IconMargin * 2 + IconHeight; 840 } 841 842 // Add vertical space for the buttons and border. 843 Size.cy += ButtonHeight + TextMargin; 844 845 dialog = CreateDialogData(Size.cx, Size.cy, messageboxdata->title); 846 if (!dialog) { 847 return false; 848 } 849 850 if (icon && !AddDialogStaticIcon(dialog, IconMargin, IconMargin, IconWidth, IconHeight, icon)) { 851 FreeDialogData(dialog); 852 return false; 853 } 854 855 if (!AddDialogStaticText(dialog, TextSize.left, TextSize.top, TextSize.right - TextSize.left, TextSize.bottom - TextSize.top, messageboxdata->message)) { 856 FreeDialogData(dialog); 857 return false; 858 } 859 860 // Align the buttons to the right/bottom. 861 x = Size.cx - (ButtonWidth + ButtonMargin) * messageboxdata->numbuttons; 862 y = Size.cy - ButtonHeight - ButtonMargin; 863 for (i = 0; i < messageboxdata->numbuttons; i++) { 864 bool isdefault = false; 865 const char *buttontext; 866 const SDL_MessageBoxButtonData *sdlButton; 867 868 /* We always have to create the dialog buttons from left to right 869 * so that the tab order is correct. Select the info to use 870 * depending on which order was requested. */ 871 if (messageboxdata->flags & SDL_MESSAGEBOX_BUTTONS_LEFT_TO_RIGHT) { 872 sdlButton = &messageboxdata->buttons[i]; 873 } else { 874 sdlButton = &messageboxdata->buttons[messageboxdata->numbuttons - 1 - i]; 875 } 876 877 if (sdlButton->flags & SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT) { 878 defbuttoncount++; 879 if (defbuttoncount == 1) { 880 isdefault = true; 881 } 882 } 883 884 buttontext = EscapeAmpersands(&ampescape, &ampescapesize, sdlButton->text); 885 /* Make sure to provide the correct ID to keep buttons indexed in the 886 * same order as how they are in messageboxdata. */ 887 if (!buttontext || !AddDialogButton(dialog, x, y, ButtonWidth, ButtonHeight, buttontext, IDBUTTONINDEX0 + (int)(sdlButton - messageboxdata->buttons), isdefault)) { 888 FreeDialogData(dialog); 889 SDL_free(ampescape); 890 return false; 891 } 892 893 x += ButtonWidth + ButtonMargin; 894 } 895 SDL_free(ampescape); 896 897 /* If we have a parent window, get the Instance and HWND for them 898 * so that our little dialog gets exclusive focus at all times. */ 899 if (messageboxdata->window) { 900 ParentWindow = messageboxdata->window->internal->hwnd; 901 } 902 903 rc = DialogBoxIndirectParam(NULL, (DLGTEMPLATE *)dialog->lpDialog, ParentWindow, MessageBoxDialogProc, (LPARAM)messageboxdata); 904 if (rc >= IDBUTTONINDEX0 && rc - IDBUTTONINDEX0 < messageboxdata->numbuttons) { 905 *buttonID = messageboxdata->buttons[rc - IDBUTTONINDEX0].buttonID; 906 result = true; 907 } else if (rc == IDCLOSED) { 908 // Dialog window closed by user or system. 909 // This could use a special return code. 910 result = true; 911 *buttonID = -1; 912 } else { 913 if (rc == 0) { 914 SDL_SetError("Invalid parent window handle"); 915 } else if (rc == -1) { 916 SDL_SetError("The message box encountered an error."); 917 } else if (rc == IDINVALPTRINIT || rc == IDINVALPTRSETFOCUS || rc == IDINVALPTRCOMMAND) { 918 SDL_SetError("Invalid message box pointer in dialog procedure"); 919 } else if (rc == IDINVALPTRDLGITEM) { 920 SDL_SetError("Couldn't find dialog control of the default enter-key button"); 921 } else { 922 SDL_SetError("An unknown error occurred"); 923 } 924 result = false; 925 } 926 927 FreeDialogData(dialog); 928 return result; 929} 930 931/* TaskDialogIndirect procedure 932 * This is because SDL targets Windows XP (0x501), so this is not defined in the platform SDK. 933 */ 934/* *INDENT-OFF* */ // clang-format off 935typedef HRESULT (FAR WINAPI *TASKDIALOGINDIRECTPROC)(const TASKDIALOGCONFIG *pTaskConfig, int *pnButton, int *pnRadioButton, BOOL *pfVerificationFlagChecked); 936/* *INDENT-ON* */ // clang-format on 937 938bool WIN_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID) 939{ 940 HWND ParentWindow = NULL; 941 wchar_t *wmessage; 942 wchar_t *wtitle; 943 TASKDIALOGCONFIG TaskConfig; 944 TASKDIALOG_BUTTON *pButtons; 945 TASKDIALOG_BUTTON *pButton; 946 HMODULE hComctl32; 947 TASKDIALOGINDIRECTPROC pTaskDialogIndirect; 948 HRESULT hr; 949 char *ampescape = NULL; 950 size_t ampescapesize = 0; 951 int nButton; 952 int nCancelButton; 953 int i; 954 bool result = false; 955 956 if (SIZE_MAX / sizeof(TASKDIALOG_BUTTON) < messageboxdata->numbuttons) { 957 return SDL_OutOfMemory(); 958 } 959 960 HMODULE hUser32 = GetModuleHandle(TEXT("user32.dll")); 961 typedef DPI_AWARENESS_CONTEXT (WINAPI *pfnSetThreadDpiAwarenessContext)(DPI_AWARENESS_CONTEXT); 962 pfnSetThreadDpiAwarenessContext pSetThreadDpiAwarenessContext = (pfnSetThreadDpiAwarenessContext)GetProcAddress(hUser32, "SetThreadDpiAwarenessContext"); 963 DPI_AWARENESS_CONTEXT previous_context = DPI_AWARENESS_CONTEXT_UNAWARE; 964 if (pSetThreadDpiAwarenessContext) { 965 previous_context = pSetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); 966 } 967 968 // If we cannot load comctl32.dll use the old messagebox! 969 hComctl32 = LoadLibrary(TEXT("comctl32.dll")); 970 if (!hComctl32) { 971 result = WIN_ShowOldMessageBox(messageboxdata, buttonID); 972 goto done; 973 } 974 975 /* If TaskDialogIndirect doesn't exist use the old messagebox! 976 This will fail prior to Windows Vista. 977 The manifest file in the application may require targeting version 6 of comctl32.dll, even 978 when we use LoadLibrary here! 979 If you don't want to bother with manifests, put this #pragma in your app's source code somewhere: 980 #pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") 981 */ 982 pTaskDialogIndirect = (TASKDIALOGINDIRECTPROC)GetProcAddress(hComctl32, "TaskDialogIndirect"); 983 if (!pTaskDialogIndirect) { 984 FreeLibrary(hComctl32); 985 result = WIN_ShowOldMessageBox(messageboxdata, buttonID); 986 goto done; 987 } 988 989 /* If we have a parent window, get the Instance and HWND for them 990 so that our little dialog gets exclusive focus at all times. */ 991 if (messageboxdata->window) { 992 ParentWindow = messageboxdata->window->internal->hwnd; 993 } 994 995 wmessage = WIN_UTF8ToStringW(messageboxdata->message); 996 wtitle = WIN_UTF8ToStringW(messageboxdata->title); 997 998 SDL_zero(TaskConfig); 999 TaskConfig.cbSize = sizeof(TASKDIALOGCONFIG); 1000 TaskConfig.hwndParent = ParentWindow; 1001 TaskConfig.dwFlags = TDF_SIZE_TO_CONTENT; 1002 TaskConfig.pszWindowTitle = wtitle; 1003 if (messageboxdata->flags & SDL_MESSAGEBOX_ERROR) { 1004 TaskConfig.pszMainIcon = TD_ERROR_ICON; 1005 } else if (messageboxdata->flags & SDL_MESSAGEBOX_WARNING) { 1006 TaskConfig.pszMainIcon = TD_WARNING_ICON; 1007 } else if (messageboxdata->flags & SDL_MESSAGEBOX_INFORMATION) { 1008 TaskConfig.pszMainIcon = TD_INFORMATION_ICON; 1009 } else { 1010 TaskConfig.pszMainIcon = NULL; 1011 } 1012 1013 TaskConfig.pszContent = wmessage; 1014 TaskConfig.cButtons = messageboxdata->numbuttons; 1015 pButtons = (TASKDIALOG_BUTTON *)SDL_malloc(sizeof(TASKDIALOG_BUTTON) * messageboxdata->numbuttons); 1016 TaskConfig.nDefaultButton = 0; 1017 nCancelButton = 0; 1018 for (i = 0; i < messageboxdata->numbuttons; i++) { 1019 const char *buttontext; 1020 if (messageboxdata->flags & SDL_MESSAGEBOX_BUTTONS_LEFT_TO_RIGHT) { 1021 pButton = &pButtons[i]; 1022 } else { 1023 pButton = &pButtons[messageboxdata->numbuttons - 1 - i]; 1024 } 1025 if (messageboxdata->buttons[i].flags & SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT) { 1026 nCancelButton = messageboxdata->buttons[i].buttonID; 1027 pButton->nButtonID = IDCANCEL; 1028 } else { 1029 pButton->nButtonID = IDBUTTONINDEX0 + i; 1030 } 1031 buttontext = EscapeAmpersands(&ampescape, &ampescapesize, messageboxdata->buttons[i].text); 1032 if (!buttontext) { 1033 int j; 1034 FreeLibrary(hComctl32); 1035 SDL_free(ampescape); 1036 SDL_free(wmessage); 1037 SDL_free(wtitle); 1038 for (j = 0; j < i; j++) { 1039 SDL_free((wchar_t *)pButtons[j].pszButtonText); 1040 } 1041 SDL_free(pButtons); 1042 return false; 1043 } 1044 pButton->pszButtonText = WIN_UTF8ToStringW(buttontext); 1045 if (messageboxdata->buttons[i].flags & SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT) { 1046 TaskConfig.nDefaultButton = pButton->nButtonID; 1047 } 1048 } 1049 TaskConfig.pButtons = pButtons; 1050 1051 // Show the Task Dialog 1052 hr = pTaskDialogIndirect(&TaskConfig, &nButton, NULL, NULL); 1053 1054 // Free everything 1055 FreeLibrary(hComctl32); 1056 SDL_free(ampescape); 1057 SDL_free(wmessage); 1058 SDL_free(wtitle); 1059 for (i = 0; i < messageboxdata->numbuttons; i++) { 1060 SDL_free((wchar_t *)pButtons[i].pszButtonText); 1061 } 1062 SDL_free(pButtons); 1063 1064 // Check the Task Dialog was successful and give the result 1065 if (SUCCEEDED(hr)) { 1066 if (nButton == IDCANCEL) { 1067 *buttonID = nCancelButton; 1068 } else if (nButton >= IDBUTTONINDEX0 && nButton < IDBUTTONINDEX0 + messageboxdata->numbuttons) { 1069 *buttonID = messageboxdata->buttons[nButton - IDBUTTONINDEX0].buttonID; 1070 } else { 1071 *buttonID = -1; 1072 } 1073 result = true; 1074 } else { 1075 // We failed showing the Task Dialog, use the old message box! 1076 result = WIN_ShowOldMessageBox(messageboxdata, buttonID); 1077 } 1078 1079done: 1080 if (pSetThreadDpiAwarenessContext) { 1081 pSetThreadDpiAwarenessContext(previous_context); 1082 } 1083 return result; 1084} 1085 1086#endif // SDL_VIDEO_DRIVER_WINDOWS 1087
[FILE END]
(C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.