1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2016 Sam Lantinga <slouken@libsdl.org>
4 
5   This software is provided 'as-is', without any express or implied
6   warranty.  In no event will the authors be held liable for any damages
7   arising from the use of this software.
8 
9   Permission is granted to anyone to use this software for any purpose,
10   including commercial applications, and to alter it and redistribute it
11   freely, subject to the following restrictions:
12 
13   1. The origin of this software must not be misrepresented; you must not
14      claim that you wrote the original software. If you use this software
15      in a product, an acknowledgment in the product documentation would be
16      appreciated but is not required.
17   2. Altered source versions must be plainly marked as such, and must not be
18      misrepresented as being the original software.
19   3. This notice may not be removed or altered from any source distribution.
20 */
21 #include "../../SDL_internal.h"
22 
23 #if SDL_VIDEO_DRIVER_WINDOWS
24 
25 #include "../../core/windows/SDL_windows.h"
26 
27 #include "SDL_assert.h"
28 #include "SDL_windowsvideo.h"
29 
30 
31 #ifndef SS_EDITCONTROL
32 #define SS_EDITCONTROL  0x2000
33 #endif
34 
35 /* Display a Windows message box */
36 
37 #pragma pack(push, 1)
38 
39 typedef struct
40 {
41     WORD dlgVer;
42     WORD signature;
43     DWORD helpID;
44     DWORD exStyle;
45     DWORD style;
46     WORD cDlgItems;
47     short x;
48     short y;
49     short cx;
50     short cy;
51 } DLGTEMPLATEEX;
52 
53 typedef struct
54 {
55     DWORD helpID;
56     DWORD exStyle;
57     DWORD style;
58     short x;
59     short y;
60     short cx;
61     short cy;
62     DWORD id;
63 } DLGITEMTEMPLATEEX;
64 
65 #pragma pack(pop)
66 
67 typedef struct
68 {
69     DLGTEMPLATEEX* lpDialog;
70     Uint8 *data;
71     size_t size;
72     size_t used;
73 } WIN_DialogData;
74 
75 
MessageBoxDialogProc(HWND hDlg,UINT iMessage,WPARAM wParam,LPARAM lParam)76 static INT_PTR MessageBoxDialogProc(HWND hDlg, UINT iMessage, WPARAM wParam, LPARAM lParam)
77 {
78     switch ( iMessage ) {
79     case WM_COMMAND:
80         /* Return the ID of the button that was pushed */
81         EndDialog(hDlg, LOWORD(wParam));
82         return TRUE;
83 
84     default:
85         break;
86     }
87     return FALSE;
88 }
89 
ExpandDialogSpace(WIN_DialogData * dialog,size_t space)90 static SDL_bool ExpandDialogSpace(WIN_DialogData *dialog, size_t space)
91 {
92     size_t size = dialog->size;
93 
94     if (size == 0) {
95         size = space;
96     } else {
97         while ((dialog->used + space) > size) {
98             size *= 2;
99         }
100     }
101     if (size > dialog->size) {
102         void *data = SDL_realloc(dialog->data, size);
103         if (!data) {
104             SDL_OutOfMemory();
105             return SDL_FALSE;
106         }
107         dialog->data = data;
108         dialog->size = size;
109         dialog->lpDialog = (DLGTEMPLATEEX*)dialog->data;
110     }
111     return SDL_TRUE;
112 }
113 
AlignDialogData(WIN_DialogData * dialog,size_t size)114 static SDL_bool AlignDialogData(WIN_DialogData *dialog, size_t size)
115 {
116     size_t padding = (dialog->used % size);
117 
118     if (!ExpandDialogSpace(dialog, padding)) {
119         return SDL_FALSE;
120     }
121 
122     dialog->used += padding;
123 
124     return SDL_TRUE;
125 }
126 
AddDialogData(WIN_DialogData * dialog,const void * data,size_t size)127 static SDL_bool AddDialogData(WIN_DialogData *dialog, const void *data, size_t size)
128 {
129     if (!ExpandDialogSpace(dialog, size)) {
130         return SDL_FALSE;
131     }
132 
133     SDL_memcpy(dialog->data+dialog->used, data, size);
134     dialog->used += size;
135 
136     return SDL_TRUE;
137 }
138 
AddDialogString(WIN_DialogData * dialog,const char * string)139 static SDL_bool AddDialogString(WIN_DialogData *dialog, const char *string)
140 {
141     WCHAR *wstring;
142     WCHAR *p;
143     size_t count;
144     SDL_bool status;
145 
146     if (!string) {
147         string = "";
148     }
149 
150     wstring = WIN_UTF8ToString(string);
151     if (!wstring) {
152         return SDL_FALSE;
153     }
154 
155     /* Find out how many characters we have, including null terminator */
156     count = 0;
157     for (p = wstring; *p; ++p) {
158         ++count;
159     }
160     ++count;
161 
162     status = AddDialogData(dialog, wstring, count*sizeof(WCHAR));
163     SDL_free(wstring);
164     return status;
165 }
166 
167 static int s_BaseUnitsX;
168 static int s_BaseUnitsY;
Vec2ToDLU(short * x,short * y)169 static void Vec2ToDLU(short *x, short *y)
170 {
171     SDL_assert(s_BaseUnitsX != 0); /* we init in WIN_ShowMessageBox(), which is the only public function... */
172 
173     *x = MulDiv(*x, 4, s_BaseUnitsX);
174     *y = MulDiv(*y, 8, s_BaseUnitsY);
175 }
176 
177 
AddDialogControl(WIN_DialogData * dialog,WORD type,DWORD style,DWORD exStyle,int x,int y,int w,int h,int id,const char * caption)178 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)
179 {
180     DLGITEMTEMPLATEEX item;
181     WORD marker = 0xFFFF;
182     WORD extraData = 0;
183 
184     SDL_zero(item);
185     item.style = style;
186     item.exStyle = exStyle;
187     item.x = x;
188     item.y = y;
189     item.cx = w;
190     item.cy = h;
191     item.id = id;
192 
193     Vec2ToDLU(&item.x, &item.y);
194     Vec2ToDLU(&item.cx, &item.cy);
195 
196     if (!AlignDialogData(dialog, sizeof(DWORD))) {
197         return SDL_FALSE;
198     }
199     if (!AddDialogData(dialog, &item, sizeof(item))) {
200         return SDL_FALSE;
201     }
202     if (!AddDialogData(dialog, &marker, sizeof(marker))) {
203         return SDL_FALSE;
204     }
205     if (!AddDialogData(dialog, &type, sizeof(type))) {
206         return SDL_FALSE;
207     }
208     if (!AddDialogString(dialog, caption)) {
209         return SDL_FALSE;
210     }
211     if (!AddDialogData(dialog, &extraData, sizeof(extraData))) {
212         return SDL_FALSE;
213     }
214     ++dialog->lpDialog->cDlgItems;
215 
216     return SDL_TRUE;
217 }
218 
AddDialogStatic(WIN_DialogData * dialog,int x,int y,int w,int h,const char * text)219 static SDL_bool AddDialogStatic(WIN_DialogData *dialog, int x, int y, int w, int h, const char *text)
220 {
221     DWORD style = WS_VISIBLE | WS_CHILD | SS_LEFT | SS_NOPREFIX | SS_EDITCONTROL;
222     return AddDialogControl(dialog, 0x0082, style, 0, x, y, w, h, -1, text);
223 }
224 
AddDialogButton(WIN_DialogData * dialog,int x,int y,int w,int h,const char * text,int id,SDL_bool isDefault)225 static SDL_bool AddDialogButton(WIN_DialogData *dialog, int x, int y, int w, int h, const char *text, int id, SDL_bool isDefault)
226 {
227     DWORD style = WS_VISIBLE | WS_CHILD;
228     if (isDefault) {
229         style |= BS_DEFPUSHBUTTON;
230     } else {
231         style |= BS_PUSHBUTTON;
232     }
233     return AddDialogControl(dialog, 0x0080, style, 0, x, y, w, h, id, text);
234 }
235 
FreeDialogData(WIN_DialogData * dialog)236 static void FreeDialogData(WIN_DialogData *dialog)
237 {
238     SDL_free(dialog->data);
239     SDL_free(dialog);
240 }
241 
CreateDialogData(int w,int h,const char * caption)242 static WIN_DialogData *CreateDialogData(int w, int h, const char *caption)
243 {
244     WIN_DialogData *dialog;
245     DLGTEMPLATEEX dialogTemplate;
246     WORD WordToPass;
247 
248     SDL_zero(dialogTemplate);
249     dialogTemplate.dlgVer = 1;
250     dialogTemplate.signature = 0xffff;
251     dialogTemplate.style = (WS_CAPTION | DS_CENTER | DS_SHELLFONT);
252     dialogTemplate.x = 0;
253     dialogTemplate.y = 0;
254     dialogTemplate.cx = w;
255     dialogTemplate.cy = h;
256     Vec2ToDLU(&dialogTemplate.cx, &dialogTemplate.cy);
257 
258     dialog = (WIN_DialogData *)SDL_calloc(1, sizeof(*dialog));
259     if (!dialog) {
260         return NULL;
261     }
262 
263     if (!AddDialogData(dialog, &dialogTemplate, sizeof(dialogTemplate))) {
264         FreeDialogData(dialog);
265         return NULL;
266     }
267 
268     /* No menu */
269     WordToPass = 0;
270     if (!AddDialogData(dialog, &WordToPass, 2)) {
271         FreeDialogData(dialog);
272         return NULL;
273     }
274 
275     /* No custom class */
276     if (!AddDialogData(dialog, &WordToPass, 2)) {
277         FreeDialogData(dialog);
278         return NULL;
279     }
280 
281     /* title */
282     if (!AddDialogString(dialog, caption)) {
283         FreeDialogData(dialog);
284         return NULL;
285     }
286 
287     /* Font stuff */
288     {
289         /*
290          * We want to use the system messagebox font.
291          */
292         BYTE ToPass;
293 
294         NONCLIENTMETRICSA NCM;
295         NCM.cbSize = sizeof(NCM);
296         SystemParametersInfoA(SPI_GETNONCLIENTMETRICS, 0, &NCM, 0);
297 
298         /* Font size - convert to logical font size for dialog parameter. */
299         {
300             HDC ScreenDC = GetDC(NULL);
301             int LogicalPixelsY = GetDeviceCaps(ScreenDC, LOGPIXELSY);
302             if (!LogicalPixelsY) /* This can happen if the application runs out of GDI handles */
303                 LogicalPixelsY = 72;
304             WordToPass = (WORD)(-72 * NCM.lfMessageFont.lfHeight / LogicalPixelsY);
305             ReleaseDC(NULL, ScreenDC);
306         }
307 
308         if (!AddDialogData(dialog, &WordToPass, 2)) {
309             FreeDialogData(dialog);
310             return NULL;
311         }
312 
313         /* Font weight */
314         WordToPass = (WORD)NCM.lfMessageFont.lfWeight;
315         if (!AddDialogData(dialog, &WordToPass, 2)) {
316             FreeDialogData(dialog);
317             return NULL;
318         }
319 
320         /* italic? */
321         ToPass = NCM.lfMessageFont.lfItalic;
322         if (!AddDialogData(dialog, &ToPass, 1)) {
323             FreeDialogData(dialog);
324             return NULL;
325         }
326 
327         /* charset? */
328         ToPass = NCM.lfMessageFont.lfCharSet;
329         if (!AddDialogData(dialog, &ToPass, 1)) {
330             FreeDialogData(dialog);
331             return NULL;
332         }
333 
334         /* font typeface. */
335         if (!AddDialogString(dialog, NCM.lfMessageFont.lfFaceName)) {
336             FreeDialogData(dialog);
337             return NULL;
338         }
339     }
340 
341     return dialog;
342 }
343 
344 int
WIN_ShowMessageBox(const SDL_MessageBoxData * messageboxdata,int * buttonid)345 WIN_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)
346 {
347     WIN_DialogData *dialog;
348     int i, x, y;
349     UINT_PTR which;
350     const SDL_MessageBoxButtonData *buttons = messageboxdata->buttons;
351     HFONT DialogFont;
352     SIZE Size;
353     RECT TextSize;
354     wchar_t* wmessage;
355     TEXTMETRIC TM;
356 
357 
358     const int ButtonWidth = 88;
359     const int ButtonHeight = 26;
360     const int TextMargin = 16;
361     const int ButtonMargin = 12;
362 
363 
364     /* Jan 25th, 2013 - dant@fleetsa.com
365      *
366      *
367      * I've tried to make this more reasonable, but I've run in to a lot
368      * of nonsense.
369      *
370      * The original issue is the code was written in pixels and not
371      * dialog units (DLUs). All DialogBox functions use DLUs, which
372      * vary based on the selected font (yay).
373      *
374      * According to MSDN, the most reliable way to convert is via
375      * MapDialogUnits, which requires an HWND, which we don't have
376      * at time of template creation.
377      *
378      * We do however have:
379      *  The system font (DLU width 8 for me)
380      *  The font we select for the dialog (DLU width 6 for me)
381      *
382      * Based on experimentation, *neither* of these return the value
383      * actually used. Stepping in to MapDialogUnits(), the conversion
384      * is fairly clear, and uses 7 for me.
385      *
386      * As a result, some of this is hacky to ensure the sizing is
387      * somewhat correct.
388      *
389      * Honestly, a long term solution is to use CreateWindow, not CreateDialog.
390      *
391 
392      *
393      * In order to get text dimensions we need to have a DC with the desired font.
394      * I'm assuming a dialog box in SDL is rare enough we can to the create.
395      */
396     HDC FontDC = CreateCompatibleDC(0);
397 
398     {
399         /* Create a duplicate of the font used in system message boxes. */
400         LOGFONT lf;
401         NONCLIENTMETRICS NCM;
402         NCM.cbSize = sizeof(NCM);
403         SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &NCM, 0);
404         lf = NCM.lfMessageFont;
405         DialogFont = CreateFontIndirect(&lf);
406     }
407 
408     /* Select the font in to our DC */
409     SelectObject(FontDC, DialogFont);
410 
411     {
412         /* Get the metrics to try and figure our DLU conversion. */
413         GetTextMetrics(FontDC, &TM);
414         s_BaseUnitsX = TM.tmAveCharWidth + 1;
415         s_BaseUnitsY = TM.tmHeight;
416     }
417 
418     /* Measure the *pixel* size of the string. */
419     wmessage = WIN_UTF8ToString(messageboxdata->message);
420     SDL_zero(TextSize);
421     Size.cx = DrawText(FontDC, wmessage, -1, &TextSize, DT_CALCRECT);
422 
423     /* Add some padding for hangs, etc. */
424     TextSize.right += 2;
425     TextSize.bottom += 2;
426 
427     /* Done with the DC, and the string */
428     DeleteDC(FontDC);
429     SDL_free(wmessage);
430 
431     /* Increase the size of the dialog by some border spacing around the text. */
432     Size.cx = TextSize.right - TextSize.left;
433     Size.cy = TextSize.bottom - TextSize.top;
434     Size.cx += TextMargin * 2;
435     Size.cy += TextMargin * 2;
436 
437     /* Ensure the size is wide enough for all of the buttons. */
438     if (Size.cx < messageboxdata->numbuttons * (ButtonWidth + ButtonMargin) + ButtonMargin)
439         Size.cx = messageboxdata->numbuttons * (ButtonWidth + ButtonMargin) + ButtonMargin;
440 
441     /* Add vertical space for the buttons and border. */
442     Size.cy += ButtonHeight + TextMargin;
443 
444     dialog = CreateDialogData(Size.cx, Size.cy, messageboxdata->title);
445     if (!dialog) {
446         return -1;
447     }
448 
449     if (!AddDialogStatic(dialog, TextMargin, TextMargin, TextSize.right - TextSize.left, TextSize.bottom - TextSize.top, messageboxdata->message)) {
450         FreeDialogData(dialog);
451         return -1;
452     }
453 
454     /* Align the buttons to the right/bottom. */
455     x = Size.cx - (ButtonWidth + ButtonMargin) * messageboxdata->numbuttons;
456     y = Size.cy - ButtonHeight - ButtonMargin;
457     for (i = messageboxdata->numbuttons - 1; i >= 0; --i) {
458         SDL_bool isDefault;
459 
460         if (buttons[i].flags & SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT) {
461             isDefault = SDL_TRUE;
462         } else {
463             isDefault = SDL_FALSE;
464         }
465         if (!AddDialogButton(dialog, x, y, ButtonWidth, ButtonHeight, buttons[i].text, i, isDefault)) {
466             FreeDialogData(dialog);
467             return -1;
468         }
469         x += ButtonWidth + ButtonMargin;
470     }
471 
472     /* FIXME: If we have a parent window, get the Instance and HWND for them */
473     which = DialogBoxIndirect(NULL, (DLGTEMPLATE*)dialog->lpDialog, NULL, (DLGPROC)MessageBoxDialogProc);
474     *buttonid = buttons[which].buttonid;
475 
476     FreeDialogData(dialog);
477     return 0;
478 }
479 
480 #endif /* SDL_VIDEO_DRIVER_WINDOWS */
481 
482 /* vi: set ts=4 sw=4 expandtab: */
483