1 /*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2020 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 #ifdef HAVE_LIMITS_H
26 #include <limits.h>
27 #else
28 #ifndef SIZE_MAX
29 #define SIZE_MAX ((size_t)-1)
30 #endif
31 #endif
32
33 #include "../../core/windows/SDL_windows.h"
34
35 #include "SDL_assert.h"
36 #include "SDL_windowsvideo.h"
37 #include "SDL_windowstaskdialog.h"
38
39 #ifndef SS_EDITCONTROL
40 #define SS_EDITCONTROL 0x2000
41 #endif
42
43 #ifndef IDOK
44 #define IDOK 1
45 #endif
46
47 #ifndef IDCANCEL
48 #define IDCANCEL 2
49 #endif
50
51 /* Custom dialog return codes */
52 #define IDCLOSED 20
53 #define IDINVALPTRINIT 50
54 #define IDINVALPTRCOMMAND 51
55 #define IDINVALPTRSETFOCUS 52
56 #define IDINVALPTRDLGITEM 53
57 /* First button ID */
58 #define IDBUTTONINDEX0 100
59
60 #define DLGITEMTYPEBUTTON 0x0080
61 #define DLGITEMTYPESTATIC 0x0082
62
63 /* Windows only sends the lower 16 bits of the control ID when a button
64 * gets clicked. There are also some predefined and custom IDs that lower
65 * the available number further. 2^16 - 101 buttons should be enough for
66 * everyone, no need to make the code more complex.
67 */
68 #define MAX_BUTTONS (0xffff - 100)
69
70
71 /* Display a Windows message box */
72
73 #pragma pack(push, 1)
74
75 typedef struct
76 {
77 WORD dlgVer;
78 WORD signature;
79 DWORD helpID;
80 DWORD exStyle;
81 DWORD style;
82 WORD cDlgItems;
83 short x;
84 short y;
85 short cx;
86 short cy;
87 } DLGTEMPLATEEX;
88
89 typedef struct
90 {
91 DWORD helpID;
92 DWORD exStyle;
93 DWORD style;
94 short x;
95 short y;
96 short cx;
97 short cy;
98 DWORD id;
99 } DLGITEMTEMPLATEEX;
100
101 #pragma pack(pop)
102
103 typedef struct
104 {
105 DLGTEMPLATEEX* lpDialog;
106 Uint8 *data;
107 size_t size;
108 size_t used;
109 WORD numbuttons;
110 } WIN_DialogData;
111
GetButtonIndex(const SDL_MessageBoxData * messageboxdata,Uint32 flags,size_t * i)112 static SDL_bool GetButtonIndex(const SDL_MessageBoxData *messageboxdata, Uint32 flags, size_t *i)
113 {
114 for (*i = 0; *i < (size_t)messageboxdata->numbuttons; ++*i) {
115 if (messageboxdata->buttons[*i].flags & flags) {
116 return SDL_TRUE;
117 }
118 }
119 return SDL_FALSE;
120 }
121
MessageBoxDialogProc(HWND hDlg,UINT iMessage,WPARAM wParam,LPARAM lParam)122 static INT_PTR MessageBoxDialogProc(HWND hDlg, UINT iMessage, WPARAM wParam, LPARAM lParam)
123 {
124 const SDL_MessageBoxData *messageboxdata;
125 size_t buttonindex;
126
127 switch ( iMessage ) {
128 case WM_INITDIALOG:
129 if (lParam == 0) {
130 EndDialog(hDlg, IDINVALPTRINIT);
131 return TRUE;
132 }
133 messageboxdata = (const SDL_MessageBoxData *)lParam;
134 SetWindowLongPtr(hDlg, GWLP_USERDATA, lParam);
135
136 if (GetButtonIndex(messageboxdata, SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, &buttonindex)) {
137 /* Focus on the first default return-key button */
138 HWND buttonctl = GetDlgItem(hDlg, (int)(IDBUTTONINDEX0 + buttonindex));
139 if (buttonctl == NULL) {
140 EndDialog(hDlg, IDINVALPTRDLGITEM);
141 }
142 PostMessage(hDlg, WM_NEXTDLGCTL, (WPARAM)buttonctl, TRUE);
143 } else {
144 /* Give the focus to the dialog window instead */
145 SetFocus(hDlg);
146 }
147 return FALSE;
148 case WM_SETFOCUS:
149 messageboxdata = (const SDL_MessageBoxData *)GetWindowLongPtr(hDlg, GWLP_USERDATA);
150 if (messageboxdata == NULL) {
151 EndDialog(hDlg, IDINVALPTRSETFOCUS);
152 return TRUE;
153 }
154
155 /* Let the default button be focused if there is one. Otherwise, prevent any initial focus. */
156 if (GetButtonIndex(messageboxdata, SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, &buttonindex)) {
157 return FALSE;
158 }
159 return TRUE;
160 case WM_COMMAND:
161 messageboxdata = (const SDL_MessageBoxData *)GetWindowLongPtr(hDlg, GWLP_USERDATA);
162 if (messageboxdata == NULL) {
163 EndDialog(hDlg, IDINVALPTRCOMMAND);
164 return TRUE;
165 }
166
167 /* Return the ID of the button that was pushed */
168 if (wParam == IDOK) {
169 if (GetButtonIndex(messageboxdata, SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, &buttonindex)) {
170 EndDialog(hDlg, IDBUTTONINDEX0 + buttonindex);
171 }
172 } else if (wParam == IDCANCEL) {
173 if (GetButtonIndex(messageboxdata, SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, &buttonindex)) {
174 EndDialog(hDlg, IDBUTTONINDEX0 + buttonindex);
175 } else {
176 /* Closing of window was requested by user or system. It would be rude not to comply. */
177 EndDialog(hDlg, IDCLOSED);
178 }
179 } else if (wParam >= IDBUTTONINDEX0 && (int)wParam - IDBUTTONINDEX0 < messageboxdata->numbuttons) {
180 EndDialog(hDlg, wParam);
181 }
182 return TRUE;
183
184 default:
185 break;
186 }
187 return FALSE;
188 }
189
ExpandDialogSpace(WIN_DialogData * dialog,size_t space)190 static SDL_bool ExpandDialogSpace(WIN_DialogData *dialog, size_t space)
191 {
192 /* Growing memory in 64 KiB steps. */
193 const size_t sizestep = 0x10000;
194 size_t size = dialog->size;
195
196 if (size == 0) {
197 /* Start with 4 KiB or a multiple of 64 KiB to fit the data. */
198 size = 0x1000;
199 if (SIZE_MAX - sizestep < space) {
200 size = space;
201 } else if (space > size) {
202 size = (space + sizestep) & ~(sizestep - 1);
203 }
204 } else if (SIZE_MAX - dialog->used < space) {
205 SDL_OutOfMemory();
206 return SDL_FALSE;
207 } else if (SIZE_MAX - (dialog->used + space) < sizestep) {
208 /* Close to the maximum. */
209 size = dialog->used + space;
210 } else if (size < dialog->used + space) {
211 /* Round up to the next 64 KiB block. */
212 size = dialog->used + space;
213 size += sizestep - size % sizestep;
214 }
215
216 if (size > dialog->size) {
217 void *data = SDL_realloc(dialog->data, size);
218 if (!data) {
219 SDL_OutOfMemory();
220 return SDL_FALSE;
221 }
222 dialog->data = data;
223 dialog->size = size;
224 dialog->lpDialog = (DLGTEMPLATEEX*)dialog->data;
225 }
226 return SDL_TRUE;
227 }
228
AlignDialogData(WIN_DialogData * dialog,size_t size)229 static SDL_bool AlignDialogData(WIN_DialogData *dialog, size_t size)
230 {
231 size_t padding = (dialog->used % size);
232
233 if (!ExpandDialogSpace(dialog, padding)) {
234 return SDL_FALSE;
235 }
236
237 dialog->used += padding;
238
239 return SDL_TRUE;
240 }
241
AddDialogData(WIN_DialogData * dialog,const void * data,size_t size)242 static SDL_bool AddDialogData(WIN_DialogData *dialog, const void *data, size_t size)
243 {
244 if (!ExpandDialogSpace(dialog, size)) {
245 return SDL_FALSE;
246 }
247
248 SDL_memcpy(dialog->data+dialog->used, data, size);
249 dialog->used += size;
250
251 return SDL_TRUE;
252 }
253
AddDialogString(WIN_DialogData * dialog,const char * string)254 static SDL_bool AddDialogString(WIN_DialogData *dialog, const char *string)
255 {
256 WCHAR *wstring;
257 WCHAR *p;
258 size_t count;
259 SDL_bool status;
260
261 if (!string) {
262 string = "";
263 }
264
265 wstring = WIN_UTF8ToString(string);
266 if (!wstring) {
267 return SDL_FALSE;
268 }
269
270 /* Find out how many characters we have, including null terminator */
271 count = 0;
272 for (p = wstring; *p; ++p) {
273 ++count;
274 }
275 ++count;
276
277 status = AddDialogData(dialog, wstring, count*sizeof(WCHAR));
278 SDL_free(wstring);
279 return status;
280 }
281
282 static int s_BaseUnitsX;
283 static int s_BaseUnitsY;
Vec2ToDLU(short * x,short * y)284 static void Vec2ToDLU(short *x, short *y)
285 {
286 SDL_assert(s_BaseUnitsX != 0); /* we init in WIN_ShowMessageBox(), which is the only public function... */
287
288 *x = MulDiv(*x, 4, s_BaseUnitsX);
289 *y = MulDiv(*y, 8, s_BaseUnitsY);
290 }
291
292
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)293 static SDL_bool AddDialogControl(WIN_DialogData *dialog, WORD type, DWORD style, DWORD exStyle, int x, int y, int w, int h, int id, const char *caption, WORD ordinal)
294 {
295 DLGITEMTEMPLATEEX item;
296 WORD marker = 0xFFFF;
297 WORD extraData = 0;
298
299 SDL_zero(item);
300 item.style = style;
301 item.exStyle = exStyle;
302 item.x = x;
303 item.y = y;
304 item.cx = w;
305 item.cy = h;
306 item.id = id;
307
308 Vec2ToDLU(&item.x, &item.y);
309 Vec2ToDLU(&item.cx, &item.cy);
310
311 if (!AlignDialogData(dialog, sizeof(DWORD))) {
312 return SDL_FALSE;
313 }
314 if (!AddDialogData(dialog, &item, sizeof(item))) {
315 return SDL_FALSE;
316 }
317 if (!AddDialogData(dialog, &marker, sizeof(marker))) {
318 return SDL_FALSE;
319 }
320 if (!AddDialogData(dialog, &type, sizeof(type))) {
321 return SDL_FALSE;
322 }
323 if (type == DLGITEMTYPEBUTTON || (type == DLGITEMTYPESTATIC && caption != NULL)) {
324 if (!AddDialogString(dialog, caption)) {
325 return SDL_FALSE;
326 }
327 } else {
328 if (!AddDialogData(dialog, &marker, sizeof(marker))) {
329 return SDL_FALSE;
330 }
331 if (!AddDialogData(dialog, &ordinal, sizeof(ordinal))) {
332 return SDL_FALSE;
333 }
334 }
335 if (!AddDialogData(dialog, &extraData, sizeof(extraData))) {
336 return SDL_FALSE;
337 }
338 if (type == DLGITEMTYPEBUTTON) {
339 dialog->numbuttons++;
340 }
341 ++dialog->lpDialog->cDlgItems;
342
343 return SDL_TRUE;
344 }
345
AddDialogStaticText(WIN_DialogData * dialog,int x,int y,int w,int h,const char * text)346 static SDL_bool AddDialogStaticText(WIN_DialogData *dialog, int x, int y, int w, int h, const char *text)
347 {
348 DWORD style = WS_VISIBLE | WS_CHILD | SS_LEFT | SS_NOPREFIX | SS_EDITCONTROL | WS_GROUP;
349 return AddDialogControl(dialog, DLGITEMTYPESTATIC, style, 0, x, y, w, h, -1, text, 0);
350 }
351
AddDialogStaticIcon(WIN_DialogData * dialog,int x,int y,int w,int h,Uint16 ordinal)352 static SDL_bool AddDialogStaticIcon(WIN_DialogData *dialog, int x, int y, int w, int h, Uint16 ordinal)
353 {
354 DWORD style = WS_VISIBLE | WS_CHILD | SS_ICON | WS_GROUP;
355 return AddDialogControl(dialog, DLGITEMTYPESTATIC, style, 0, x, y, w, h, -2, NULL, ordinal);
356 }
357
AddDialogButton(WIN_DialogData * dialog,int x,int y,int w,int h,const char * text,int id,SDL_bool isDefault)358 static SDL_bool AddDialogButton(WIN_DialogData *dialog, int x, int y, int w, int h, const char *text, int id, SDL_bool isDefault)
359 {
360 DWORD style = WS_VISIBLE | WS_CHILD | WS_TABSTOP;
361 if (isDefault) {
362 style |= BS_DEFPUSHBUTTON;
363 } else {
364 style |= BS_PUSHBUTTON;
365 }
366 /* The first button marks the start of the group. */
367 if (dialog->numbuttons == 0) {
368 style |= WS_GROUP;
369 }
370 return AddDialogControl(dialog, DLGITEMTYPEBUTTON, style, 0, x, y, w, h, id, text, 0);
371 }
372
FreeDialogData(WIN_DialogData * dialog)373 static void FreeDialogData(WIN_DialogData *dialog)
374 {
375 SDL_free(dialog->data);
376 SDL_free(dialog);
377 }
378
CreateDialogData(int w,int h,const char * caption)379 static WIN_DialogData *CreateDialogData(int w, int h, const char *caption)
380 {
381 WIN_DialogData *dialog;
382 DLGTEMPLATEEX dialogTemplate;
383 WORD WordToPass;
384
385 SDL_zero(dialogTemplate);
386 dialogTemplate.dlgVer = 1;
387 dialogTemplate.signature = 0xffff;
388 dialogTemplate.style = (WS_CAPTION | DS_CENTER | DS_SHELLFONT);
389 dialogTemplate.x = 0;
390 dialogTemplate.y = 0;
391 dialogTemplate.cx = w;
392 dialogTemplate.cy = h;
393 Vec2ToDLU(&dialogTemplate.cx, &dialogTemplate.cy);
394
395 dialog = (WIN_DialogData *)SDL_calloc(1, sizeof(*dialog));
396 if (!dialog) {
397 return NULL;
398 }
399
400 if (!AddDialogData(dialog, &dialogTemplate, sizeof(dialogTemplate))) {
401 FreeDialogData(dialog);
402 return NULL;
403 }
404
405 /* No menu */
406 WordToPass = 0;
407 if (!AddDialogData(dialog, &WordToPass, 2)) {
408 FreeDialogData(dialog);
409 return NULL;
410 }
411
412 /* No custom class */
413 if (!AddDialogData(dialog, &WordToPass, 2)) {
414 FreeDialogData(dialog);
415 return NULL;
416 }
417
418 /* title */
419 if (!AddDialogString(dialog, caption)) {
420 FreeDialogData(dialog);
421 return NULL;
422 }
423
424 /* Font stuff */
425 {
426 /*
427 * We want to use the system messagebox font.
428 */
429 BYTE ToPass;
430
431 NONCLIENTMETRICSA NCM;
432 NCM.cbSize = sizeof(NCM);
433 SystemParametersInfoA(SPI_GETNONCLIENTMETRICS, 0, &NCM, 0);
434
435 /* Font size - convert to logical font size for dialog parameter. */
436 {
437 HDC ScreenDC = GetDC(NULL);
438 int LogicalPixelsY = GetDeviceCaps(ScreenDC, LOGPIXELSY);
439 if (!LogicalPixelsY) /* This can happen if the application runs out of GDI handles */
440 LogicalPixelsY = 72;
441 WordToPass = (WORD)(-72 * NCM.lfMessageFont.lfHeight / LogicalPixelsY);
442 ReleaseDC(NULL, ScreenDC);
443 }
444
445 if (!AddDialogData(dialog, &WordToPass, 2)) {
446 FreeDialogData(dialog);
447 return NULL;
448 }
449
450 /* Font weight */
451 WordToPass = (WORD)NCM.lfMessageFont.lfWeight;
452 if (!AddDialogData(dialog, &WordToPass, 2)) {
453 FreeDialogData(dialog);
454 return NULL;
455 }
456
457 /* italic? */
458 ToPass = NCM.lfMessageFont.lfItalic;
459 if (!AddDialogData(dialog, &ToPass, 1)) {
460 FreeDialogData(dialog);
461 return NULL;
462 }
463
464 /* charset? */
465 ToPass = NCM.lfMessageFont.lfCharSet;
466 if (!AddDialogData(dialog, &ToPass, 1)) {
467 FreeDialogData(dialog);
468 return NULL;
469 }
470
471 /* font typeface. */
472 if (!AddDialogString(dialog, NCM.lfMessageFont.lfFaceName)) {
473 FreeDialogData(dialog);
474 return NULL;
475 }
476 }
477
478 return dialog;
479 }
480
481 /* Escaping ampersands is necessary to disable mnemonics in dialog controls.
482 * The caller provides a char** for dst and a size_t* for dstlen where the
483 * address of the work buffer and its size will be stored. Their values must be
484 * NULL and 0 on the first call. src is the string to be escaped. On error, the
485 * function returns NULL and, on success, returns a pointer to the escaped
486 * sequence as a read-only string that is valid until the next call or until the
487 * work buffer is freed. Once all strings have been processed, it's the caller's
488 * responsibilty to free the work buffer with SDL_free, even on errors.
489 */
EscapeAmpersands(char ** dst,size_t * dstlen,const char * src)490 static const char *EscapeAmpersands(char **dst, size_t *dstlen, const char *src)
491 {
492 char *newdst;
493 size_t ampcount = 0;
494 size_t srclen = 0;
495
496 if (src == NULL) {
497 return NULL;
498 }
499
500 while (src[srclen]) {
501 if (src[srclen] == '&') {
502 ampcount++;
503 }
504 srclen++;
505 }
506 srclen++;
507
508 if (ampcount == 0) {
509 /* Nothing to do. */
510 return src;
511 }
512 if (SIZE_MAX - srclen < ampcount) {
513 return NULL;
514 }
515 if (*dst == NULL || *dstlen < srclen + ampcount) {
516 /* Allocating extra space in case the next strings are a bit longer. */
517 size_t extraspace = SIZE_MAX - (srclen + ampcount);
518 if (extraspace > 512) {
519 extraspace = 512;
520 }
521 *dstlen = srclen + ampcount + extraspace;
522 SDL_free(*dst);
523 *dst = NULL;
524 newdst = SDL_malloc(*dstlen);
525 if (newdst == NULL) {
526 return NULL;
527 }
528 *dst = newdst;
529 } else {
530 newdst = *dst;
531 }
532
533 /* The escape character is the ampersand itself. */
534 while (srclen--) {
535 if (*src == '&') {
536 *newdst++ = '&';
537 }
538 *newdst++ = *src++;
539 }
540
541 return *dst;
542 }
543
544 /* This function is called if a Task Dialog is unsupported. */
545 static int
WIN_ShowOldMessageBox(const SDL_MessageBoxData * messageboxdata,int * buttonid)546 WIN_ShowOldMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)
547 {
548 WIN_DialogData *dialog;
549 int i, x, y, retval;
550 HFONT DialogFont;
551 SIZE Size;
552 RECT TextSize;
553 wchar_t* wmessage;
554 TEXTMETRIC TM;
555 HDC FontDC;
556 INT_PTR result;
557 char *ampescape = NULL;
558 size_t ampescapesize = 0;
559 Uint16 defbuttoncount = 0;
560 Uint16 icon = 0;
561
562 HWND ParentWindow = NULL;
563
564 const int ButtonWidth = 88;
565 const int ButtonHeight = 26;
566 const int TextMargin = 16;
567 const int ButtonMargin = 12;
568 const int IconWidth = GetSystemMetrics(SM_CXICON);
569 const int IconHeight = GetSystemMetrics(SM_CYICON);
570 const int IconMargin = 20;
571
572 if (messageboxdata->numbuttons > MAX_BUTTONS) {
573 return SDL_SetError("Number of butons exceeds limit of %d", MAX_BUTTONS);
574 }
575
576 switch (messageboxdata->flags) {
577 case SDL_MESSAGEBOX_ERROR:
578 icon = (Uint16)(size_t)IDI_ERROR;
579 break;
580 case SDL_MESSAGEBOX_WARNING:
581 icon = (Uint16)(size_t)IDI_WARNING;
582 break;
583 case SDL_MESSAGEBOX_INFORMATION:
584 icon = (Uint16)(size_t)IDI_INFORMATION;
585 break;
586 }
587
588 /* Jan 25th, 2013 - dant@fleetsa.com
589 *
590 *
591 * I've tried to make this more reasonable, but I've run in to a lot
592 * of nonsense.
593 *
594 * The original issue is the code was written in pixels and not
595 * dialog units (DLUs). All DialogBox functions use DLUs, which
596 * vary based on the selected font (yay).
597 *
598 * According to MSDN, the most reliable way to convert is via
599 * MapDialogUnits, which requires an HWND, which we don't have
600 * at time of template creation.
601 *
602 * We do however have:
603 * The system font (DLU width 8 for me)
604 * The font we select for the dialog (DLU width 6 for me)
605 *
606 * Based on experimentation, *neither* of these return the value
607 * actually used. Stepping in to MapDialogUnits(), the conversion
608 * is fairly clear, and uses 7 for me.
609 *
610 * As a result, some of this is hacky to ensure the sizing is
611 * somewhat correct.
612 *
613 * Honestly, a long term solution is to use CreateWindow, not CreateDialog.
614 *
615
616 *
617 * In order to get text dimensions we need to have a DC with the desired font.
618 * I'm assuming a dialog box in SDL is rare enough we can to the create.
619 */
620 FontDC = CreateCompatibleDC(0);
621
622 {
623 /* Create a duplicate of the font used in system message boxes. */
624 LOGFONT lf;
625 NONCLIENTMETRICS NCM;
626 NCM.cbSize = sizeof(NCM);
627 SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &NCM, 0);
628 lf = NCM.lfMessageFont;
629 DialogFont = CreateFontIndirect(&lf);
630 }
631
632 /* Select the font in to our DC */
633 SelectObject(FontDC, DialogFont);
634
635 {
636 /* Get the metrics to try and figure our DLU conversion. */
637 GetTextMetrics(FontDC, &TM);
638
639 /* Calculation from the following documentation:
640 * https://support.microsoft.com/en-gb/help/125681/how-to-calculate-dialog-base-units-with-non-system-based-font
641 * This fixes bug 2137, dialog box calculation with a fixed-width system font
642 */
643 {
644 SIZE extent;
645 GetTextExtentPoint32A(FontDC, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 52, &extent);
646 s_BaseUnitsX = (extent.cx / 26 + 1) / 2;
647 }
648 /*s_BaseUnitsX = TM.tmAveCharWidth + 1;*/
649 s_BaseUnitsY = TM.tmHeight;
650 }
651
652 /* Measure the *pixel* size of the string. */
653 wmessage = WIN_UTF8ToString(messageboxdata->message);
654 SDL_zero(TextSize);
655 DrawText(FontDC, wmessage, -1, &TextSize, DT_CALCRECT | DT_LEFT | DT_NOPREFIX | DT_EDITCONTROL);
656
657 /* Add margins and some padding for hangs, etc. */
658 TextSize.left += TextMargin;
659 TextSize.right += TextMargin + 2;
660 TextSize.top += TextMargin;
661 TextSize.bottom += TextMargin + 2;
662
663 /* Done with the DC, and the string */
664 DeleteDC(FontDC);
665 SDL_free(wmessage);
666
667 /* Increase the size of the dialog by some border spacing around the text. */
668 Size.cx = TextSize.right - TextSize.left;
669 Size.cy = TextSize.bottom - TextSize.top;
670 Size.cx += TextMargin * 2;
671 Size.cy += TextMargin * 2;
672
673 /* Make dialog wider and shift text over for the icon. */
674 if (icon) {
675 Size.cx += IconMargin + IconWidth;
676 TextSize.left += IconMargin + IconWidth;
677 TextSize.right += IconMargin + IconWidth;
678 }
679
680 /* Ensure the size is wide enough for all of the buttons. */
681 if (Size.cx < messageboxdata->numbuttons * (ButtonWidth + ButtonMargin) + ButtonMargin)
682 Size.cx = messageboxdata->numbuttons * (ButtonWidth + ButtonMargin) + ButtonMargin;
683
684 /* Reset the height to the icon size if it is actually bigger than the text. */
685 if (icon && Size.cy < IconMargin * 2 + IconHeight) {
686 Size.cy = IconMargin * 2 + IconHeight;
687 }
688
689 /* Add vertical space for the buttons and border. */
690 Size.cy += ButtonHeight + TextMargin;
691
692 dialog = CreateDialogData(Size.cx, Size.cy, messageboxdata->title);
693 if (!dialog) {
694 return -1;
695 }
696
697 if (icon && ! AddDialogStaticIcon(dialog, IconMargin, IconMargin, IconWidth, IconHeight, icon)) {
698 FreeDialogData(dialog);
699 return -1;
700 }
701
702 if (!AddDialogStaticText(dialog, TextSize.left, TextSize.top, TextSize.right - TextSize.left, TextSize.bottom - TextSize.top, messageboxdata->message)) {
703 FreeDialogData(dialog);
704 return -1;
705 }
706
707 /* Align the buttons to the right/bottom. */
708 x = Size.cx - (ButtonWidth + ButtonMargin) * messageboxdata->numbuttons;
709 y = Size.cy - ButtonHeight - ButtonMargin;
710 for (i = 0; i < messageboxdata->numbuttons; i++) {
711 SDL_bool isdefault = SDL_FALSE;
712 const char *buttontext;
713 const SDL_MessageBoxButtonData *sdlButton;
714
715 /* We always have to create the dialog buttons from left to right
716 * so that the tab order is correct. Select the info to use
717 * depending on which order was requested. */
718 if (messageboxdata->flags & SDL_MESSAGEBOX_BUTTONS_LEFT_TO_RIGHT) {
719 sdlButton = &messageboxdata->buttons[i];
720 } else {
721 sdlButton = &messageboxdata->buttons[messageboxdata->numbuttons - 1 - i];
722 }
723
724 if (sdlButton->flags & SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT) {
725 defbuttoncount++;
726 if (defbuttoncount == 1) {
727 isdefault = SDL_TRUE;
728 }
729 }
730
731 buttontext = EscapeAmpersands(&escape, &escapesize, sdlButton->text);
732 /* Make sure to provide the correct ID to keep buttons indexed in the
733 * same order as how they are in messageboxdata. */
734 if (buttontext == NULL || !AddDialogButton(dialog, x, y, ButtonWidth, ButtonHeight, buttontext, IDBUTTONINDEX0 + (int)(sdlButton - messageboxdata->buttons), isdefault)) {
735 FreeDialogData(dialog);
736 SDL_free(ampescape);
737 return -1;
738 }
739
740 x += ButtonWidth + ButtonMargin;
741 }
742 SDL_free(ampescape);
743
744 /* If we have a parent window, get the Instance and HWND for them
745 * so that our little dialog gets exclusive focus at all times. */
746 if (messageboxdata->window) {
747 ParentWindow = ((SDL_WindowData*)messageboxdata->window->driverdata)->hwnd;
748 }
749
750 result = DialogBoxIndirectParam(NULL, (DLGTEMPLATE*)dialog->lpDialog, ParentWindow, (DLGPROC)MessageBoxDialogProc, (LPARAM)messageboxdata);
751 if (result >= IDBUTTONINDEX0 && result - IDBUTTONINDEX0 < messageboxdata->numbuttons) {
752 *buttonid = messageboxdata->buttons[result - IDBUTTONINDEX0].buttonid;
753 retval = 0;
754 } else if (result == IDCLOSED) {
755 /* Dialog window closed by user or system. */
756 /* This could use a special return code. */
757 retval = 0;
758 *buttonid = -1;
759 } else {
760 if (result == 0) {
761 SDL_SetError("Invalid parent window handle");
762 } else if (result == -1) {
763 SDL_SetError("The message box encountered an error.");
764 } else if (result == IDINVALPTRINIT || result == IDINVALPTRSETFOCUS || result == IDINVALPTRCOMMAND) {
765 SDL_SetError("Invalid message box pointer in dialog procedure");
766 } else if (result == IDINVALPTRDLGITEM) {
767 SDL_SetError("Couldn't find dialog control of the default enter-key button");
768 } else {
769 SDL_SetError("An unknown error occured");
770 }
771 retval = -1;
772 }
773
774 FreeDialogData(dialog);
775 return retval;
776 }
777
778 /* TaskDialogIndirect procedure
779 * This is because SDL targets Windows XP (0x501), so this is not defined in the platform SDK.
780 */
781 typedef HRESULT(FAR WINAPI *TASKDIALOGINDIRECTPROC)(const TASKDIALOGCONFIG *pTaskConfig, int *pnButton, int *pnRadioButton, BOOL *pfVerificationFlagChecked);
782
783 int
WIN_ShowMessageBox(const SDL_MessageBoxData * messageboxdata,int * buttonid)784 WIN_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)
785 {
786 HWND ParentWindow = NULL;
787 wchar_t *wmessage;
788 wchar_t *wtitle;
789 TASKDIALOGCONFIG TaskConfig;
790 TASKDIALOG_BUTTON *pButtons;
791 TASKDIALOG_BUTTON *pButton;
792 HMODULE hComctl32;
793 TASKDIALOGINDIRECTPROC pfnTaskDialogIndirect;
794 HRESULT hr;
795 char *ampescape = NULL;
796 size_t ampescapesize = 0;
797 int nButton;
798 int nCancelButton;
799 int i;
800
801 if (SIZE_MAX / sizeof(TASKDIALOG_BUTTON) < messageboxdata->numbuttons) {
802 return SDL_OutOfMemory();
803 }
804
805 /* If we cannot load comctl32.dll use the old messagebox! */
806 hComctl32 = LoadLibrary(TEXT("Comctl32.dll"));
807 if (hComctl32 == NULL) {
808 return WIN_ShowOldMessageBox(messageboxdata, buttonid);
809 }
810
811 /* If TaskDialogIndirect doesn't exist use the old messagebox!
812 This will fail prior to Windows Vista.
813 The manifest file in the application may require targeting version 6 of comctl32.dll, even
814 when we use LoadLibrary here!
815 If you don't want to bother with manifests, put this #pragma in your app's source code somewhere:
816 pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
817 */
818 pfnTaskDialogIndirect = (TASKDIALOGINDIRECTPROC) GetProcAddress(hComctl32, "TaskDialogIndirect");
819 if (pfnTaskDialogIndirect == NULL) {
820 FreeLibrary(hComctl32);
821 return WIN_ShowOldMessageBox(messageboxdata, buttonid);
822 }
823
824 /* If we have a parent window, get the Instance and HWND for them
825 so that our little dialog gets exclusive focus at all times. */
826 if (messageboxdata->window) {
827 ParentWindow = ((SDL_WindowData *) messageboxdata->window->driverdata)->hwnd;
828 }
829
830 wmessage = WIN_UTF8ToString(messageboxdata->message);
831 wtitle = WIN_UTF8ToString(messageboxdata->title);
832
833 SDL_zero(TaskConfig);
834 TaskConfig.cbSize = sizeof (TASKDIALOGCONFIG);
835 TaskConfig.hwndParent = ParentWindow;
836 TaskConfig.dwFlags = TDF_SIZE_TO_CONTENT;
837 TaskConfig.pszWindowTitle = wtitle;
838 if (messageboxdata->flags & SDL_MESSAGEBOX_ERROR) {
839 TaskConfig.pszMainIcon = TD_ERROR_ICON;
840 } else if (messageboxdata->flags & SDL_MESSAGEBOX_WARNING) {
841 TaskConfig.pszMainIcon = TD_WARNING_ICON;
842 } else if (messageboxdata->flags & SDL_MESSAGEBOX_INFORMATION) {
843 TaskConfig.pszMainIcon = TD_INFORMATION_ICON;
844 } else {
845 TaskConfig.pszMainIcon = NULL;
846 }
847
848 TaskConfig.pszContent = wmessage;
849 TaskConfig.cButtons = messageboxdata->numbuttons;
850 pButtons = SDL_malloc(sizeof (TASKDIALOG_BUTTON) * messageboxdata->numbuttons);
851 TaskConfig.nDefaultButton = 0;
852 nCancelButton = 0;
853 for (i = 0; i < messageboxdata->numbuttons; i++)
854 {
855 const char *buttontext;
856 if (messageboxdata->flags & SDL_MESSAGEBOX_BUTTONS_LEFT_TO_RIGHT) {
857 pButton = &pButtons[i];
858 } else {
859 pButton = &pButtons[messageboxdata->numbuttons - 1 - i];
860 }
861 if (messageboxdata->buttons[i].flags & SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT) {
862 nCancelButton = messageboxdata->buttons[i].buttonid;
863 pButton->nButtonID = IDCANCEL;
864 } else {
865 pButton->nButtonID = IDBUTTONINDEX0 + i;
866 }
867 buttontext = EscapeAmpersands(&escape, &escapesize, messageboxdata->buttons[i].text);
868 if (buttontext == NULL) {
869 int j;
870 FreeLibrary(hComctl32);
871 SDL_free(ampescape);
872 SDL_free(wmessage);
873 SDL_free(wtitle);
874 for (j = 0; j < i; j++) {
875 SDL_free((wchar_t *) pButtons[j].pszButtonText);
876 }
877 SDL_free(pButtons);
878 return -1;
879 }
880 pButton->pszButtonText = WIN_UTF8ToString(buttontext);
881 if (messageboxdata->buttons[i].flags & SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT) {
882 TaskConfig.nDefaultButton = pButton->nButtonID;
883 }
884 }
885 TaskConfig.pButtons = pButtons;
886
887 /* Show the Task Dialog */
888 hr = pfnTaskDialogIndirect(&TaskConfig, &nButton, NULL, NULL);
889
890 /* Free everything */
891 FreeLibrary(hComctl32);
892 SDL_free(ampescape);
893 SDL_free(wmessage);
894 SDL_free(wtitle);
895 for (i = 0; i < messageboxdata->numbuttons; i++) {
896 SDL_free((wchar_t *) pButtons[i].pszButtonText);
897 }
898 SDL_free(pButtons);
899
900 /* Check the Task Dialog was successful and give the result */
901 if (SUCCEEDED(hr)) {
902 if (nButton == IDCANCEL) {
903 *buttonid = nCancelButton;
904 } else if (nButton >= IDBUTTONINDEX0 && nButton < IDBUTTONINDEX0 + messageboxdata->numbuttons) {
905 *buttonid = messageboxdata->buttons[nButton - IDBUTTONINDEX0].buttonid;
906 } else {
907 *buttonid = -1;
908 }
909 return 0;
910 }
911
912 /* We failed showing the Task Dialog, use the old message box! */
913 return WIN_ShowOldMessageBox(messageboxdata, buttonid);
914 }
915
916 #endif /* SDL_VIDEO_DRIVER_WINDOWS */
917
918 /* vi: set ts=4 sw=4 expandtab: */
919