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