diff options
| author | 3gg <3gg@shellblade.net> | 2025-12-27 12:03:39 -0800 |
|---|---|---|
| committer | 3gg <3gg@shellblade.net> | 2025-12-27 12:03:39 -0800 |
| commit | 5a079a2d114f96d4847d1ee305d5b7c16eeec50e (patch) | |
| tree | 8926ab44f168acf787d8e19608857b3af0f82758 /contrib/SDL-3.2.8/src/video/windows/SDL_windowsmessagebox.c | |
Initial commit
Diffstat (limited to 'contrib/SDL-3.2.8/src/video/windows/SDL_windowsmessagebox.c')
| -rw-r--r-- | contrib/SDL-3.2.8/src/video/windows/SDL_windowsmessagebox.c | 1086 |
1 files changed, 1086 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowsmessagebox.c b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsmessagebox.c new file mode 100644 index 0000000..c174e3d --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsmessagebox.c | |||
| @@ -0,0 +1,1086 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 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 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 | |||
| 69 | typedef HRESULT(CALLBACK *PFTASKDIALOGCALLBACK)(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, LONG_PTR lpRefData); | ||
| 70 | |||
| 71 | enum _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 | }; | ||
| 94 | typedef int TASKDIALOG_FLAGS; // Note: _TASKDIALOG_FLAGS is an int | ||
| 95 | |||
| 96 | typedef 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 | |||
| 115 | typedef 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 | |||
| 130 | typedef enum _TASKDIALOG_ELEMENTS | ||
| 131 | { | ||
| 132 | TDE_CONTENT, | ||
| 133 | TDE_EXPANDED_INFORMATION, | ||
| 134 | TDE_FOOTER, | ||
| 135 | TDE_MAIN_INSTRUCTION | ||
| 136 | } TASKDIALOG_ELEMENTS; | ||
| 137 | |||
| 138 | typedef 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 | |||
| 149 | enum _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 | }; | ||
| 158 | typedef int TASKDIALOG_COMMON_BUTTON_FLAGS; // Note: _TASKDIALOG_COMMON_BUTTON_FLAGS is an int | ||
| 159 | |||
| 160 | #pragma pack(push, 1) | ||
| 161 | |||
| 162 | typedef struct _TASKDIALOG_BUTTON | ||
| 163 | { | ||
| 164 | int nButtonID; | ||
| 165 | PCWSTR pszButtonText; | ||
| 166 | } TASKDIALOG_BUTTON; | ||
| 167 | |||
| 168 | typedef 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 | |||
| 204 | typedef 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 | |||
| 218 | typedef 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 | |||
| 232 | typedef struct | ||
| 233 | { | ||
| 234 | DLGTEMPLATEEX *lpDialog; | ||
| 235 | void *data; | ||
| 236 | size_t size; | ||
| 237 | size_t used; | ||
| 238 | WORD numbuttons; | ||
| 239 | } WIN_DialogData; | ||
| 240 | |||
| 241 | static 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 | |||
| 251 | static 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 | |||
| 319 | static 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 | |||
| 357 | static 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 | |||
| 370 | static 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 | |||
| 382 | static 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 | |||
| 410 | static int s_BaseUnitsX; | ||
| 411 | static int s_BaseUnitsY; | ||
| 412 | static 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 | |||
| 420 | static 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 | |||
| 473 | static 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 | |||
| 479 | static 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 | |||
| 485 | static 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 | |||
| 500 | static void FreeDialogData(WIN_DialogData *dialog) | ||
| 501 | { | ||
| 502 | SDL_free(dialog->data); | ||
| 503 | SDL_free(dialog); | ||
| 504 | } | ||
| 505 | |||
| 506 | static 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 | */ | ||
| 619 | static 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 | |||
| 673 | static 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. | ||
| 699 | static 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 - dant@fleetsa.com | ||
| 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(&escape, &escapesize, 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 | ||
| 935 | typedef HRESULT (FAR WINAPI *TASKDIALOGINDIRECTPROC)(const TASKDIALOGCONFIG *pTaskConfig, int *pnButton, int *pnRadioButton, BOOL *pfVerificationFlagChecked); | ||
| 936 | /* *INDENT-ON* */ // clang-format on | ||
| 937 | |||
| 938 | bool 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 pfnTaskDialogIndirect; | ||
| 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 * SetThreadDpiAwarenessContext_t)(DPI_AWARENESS_CONTEXT); | ||
| 962 | SetThreadDpiAwarenessContext_t SetThreadDpiAwarenessContextFunc = (SetThreadDpiAwarenessContext_t)GetProcAddress(hUser32, "SetThreadDpiAwarenessContext"); | ||
| 963 | DPI_AWARENESS_CONTEXT previous_context = DPI_AWARENESS_CONTEXT_UNAWARE; | ||
| 964 | if (SetThreadDpiAwarenessContextFunc) { | ||
| 965 | previous_context = SetThreadDpiAwarenessContextFunc(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 | pfnTaskDialogIndirect = (TASKDIALOGINDIRECTPROC)GetProcAddress(hComctl32, "TaskDialogIndirect"); | ||
| 983 | if (!pfnTaskDialogIndirect) { | ||
| 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(&escape, &escapesize, 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 = pfnTaskDialogIndirect(&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 | |||
| 1079 | done: | ||
| 1080 | if (SetThreadDpiAwarenessContextFunc) { | ||
| 1081 | SetThreadDpiAwarenessContextFunc(previous_context); | ||
| 1082 | } | ||
| 1083 | return result; | ||
| 1084 | } | ||
| 1085 | |||
| 1086 | #endif // SDL_VIDEO_DRIVER_WINDOWS | ||
