Files
libsst/libsst-wm/Win32/SST_WMDialogBox_Win32.c
2026-04-03 00:22:39 -05:00

320 lines
8.5 KiB
C

/*
SST_WMDialogBox_Win32.c
Author: Patrick Baggett <ptbaggett@762studios.com>
Created: 1/5/2013
Purpose:
Model
License:
This program is free software. It comes without any warranty, to
the extent permitted by applicable law. You can redistribute it
and/or modify it under the terms of the Do What The Fuck You Want
To Public License, Version 2, as published by Sam Hocevar. See
http://sam.zoy.org/wtfpl/COPYING for more details.
*/
#include "Win32Private.h"
#define BUTTON_HSPACE 16 /* Space on either side of a button */
#define BUTTON_VSPACE 16 /* Space between top of button and dialog text */
#define TEXT_HSPACE 16
#define TEXT_VSPACE 16
typedef struct DialogBoxData
{
const char* message;
int lenMessage;
int buttonId;
int exitTime;
} DialogBoxData;
/*************************************************************************/
static LONG dlgRegCount = 0;
/*************************************************************************/
static LRESULT WINAPI libsstDlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
static HWND createButton(HWND owner, int id, const char* label, int x, int y, int w, int h);
/*************************************************************************/
int Win32_ShowDialogBox(SST_DisplayTarget target, SST_Window parent, const char* caption, const char* message, const char** buttons, int nrButtons)
{
SST_Window_Win32* win = (SST_Window_Win32*)parent;
SST_DisplayTarget_Win32* displayTarget = (SST_DisplayTarget_Win32*)target;
HWND hWnd;
HWND hParentWnd = NULL;
MSG msg;
HDC hDC;
HFONT hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
/* Compute size of a button. This is absolutely arcane, but the 50x14 units and the /4 /8 are documented. Somewhere... */
uint32_t units = (uint32_t)GetDialogBaseUnits();
int horiz = (int)MulDiv((units & 0xFF), 50, 4);
int vert = (int)MulDiv((units >> 16), 14, 8);
int w, h;
int returnCode;
/* Window rectangle computation */
DWORD style = WS_CAPTION | WS_SYSMENU | WS_CLIPCHILDREN;
DWORD styleEx = 0;
RECT r;
RECT textRect;
RECT centerOn;
DialogBoxData* dlgData;
/* Allocate dialog box data */
dlgData = (DialogBoxData*)HeapAlloc(GetProcessHeap(), 0, sizeof(DialogBoxData));
if(dlgData == NULL)
return -1;
dlgData->message = message;
dlgData->lenMessage = (int)strlen(message);
dlgData->buttonId = -1;
dlgData->exitTime = 0;
/*
First, figure out a good area to center the dialog box on. It's highly unintuitive to have it
it mapped in the top-left corner. We center it on the parent window (if possible) or the monitor
otherwise.
*/
/* Have a parent window? */
if(win != NULL)
{
/* Then center on it */
hParentWnd = win->hWnd;
GetWindowRect(hParentWnd, &centerOn);
}
else /* Otherwise center on default display */
{
FindMonitorInfo fmi;
/* Attempt to find the monitor associated with a display device */
findMonitor(&displayTarget->devs[0], &fmi);
/* Didn't find it? */
if(!fmi.foundIt)
return -1;
/* Center on the monitor */
centerOn.top = fmi.top;
centerOn.left = fmi.left;
centerOn.bottom = fmi.bottom;
centerOn.right = fmi.right;
}
/* Start the window as if it was at (0,0) */
r.top = 0;
r.left = 0;
r.bottom = 2*BUTTON_VSPACE+vert; /* Enough space for a button and vertical padding above and below it */
r.right = (LONG)nrButtons*(horiz + BUTTON_HSPACE)+BUTTON_HSPACE;
/* Now we need to compute the area requried to display the text. This will be summed with the area required for the buttons on the Y axis, but
the X axis will be the maximum of the two. */
/* 1) Create a fake DC to compute the size of the text's rect */
hDC = CreateDC("DISPLAY", NULL, NULL, NULL);
SelectObject(hDC, (HGDIOBJ)hFont);
/* 2) Actually compute the rect using DrawTextEx() and DT_CALCRECT. */
memset(&textRect, 0, sizeof(textRect));
DrawTextExA(hDC, (LPSTR)dlgData->message, dlgData->lenMessage, &textRect, DT_CALCRECT | DT_TOP | DT_LEFT, NULL);
DeleteDC(hDC);
/* 3) Add a border around the entire thing */
textRect.bottom += 2*TEXT_VSPACE;
textRect.right += 2*TEXT_HSPACE;
/* For the X-axis, we want the maximum of the amount of space it takes to display buttons and text */
if(r.right < textRect.right)
r.right = textRect.right;
/* For the Y-aaxis, just append space to the top of dialog box for the text rect */
r.bottom += textRect.bottom;
/* Move the window to the center of the screen: */
/* 1) Compute width and height of window */
w = r.right - r.left;
h = r.bottom - r.top;
AdjustWindowRectEx(&r, style, FALSE, styleEx);
/* After we adjusted the size of the window so that the client area is constant, we need
to move it back to (0,0). */
if(r.top != 0)
{
LONG d = -r.top;
r.top = 0;
r.bottom += d;
}
if(r.left != 0)
{
LONG d = -r.left;
r.left = 0;
r.right += d;
}
/* 2) Adjust so it fits on the screen by doing: `adjust = (center - size) /2` */
r.left += ((centerOn.right - centerOn.left) - w) / 2;
r.right += ((centerOn.right - centerOn.left) - w) / 2;
r.top += ((centerOn.bottom - centerOn.top) - h) / 2;
r.bottom += ((centerOn.bottom - centerOn.top) - h) / 2;
/* You can't create a window without registering the class, so let's do it now. Since
this function has "Concurrent" access, use an atomic operation to decide if it is necessary. */
if(InterlockedIncrement(&dlgRegCount) == 1)
{
WNDCLASSEXA wc;
memset(&wc, 0, sizeof(wc));
wc.cbSize = sizeof(wc);
wc.lpszClassName = SST_DLGCLASS;
wc.hInstance = GetModuleHandleA(NULL);
wc.lpfnWndProc = libsstDlgProc;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.style = CS_OWNDC;
wc.hbrBackground = (HBRUSH)(uintptr_t)(COLOR_WINDOW+1);
RegisterClassExA(&wc);
} /* TODO: I suppose it is possible that someone could turn this into a race condition. Priority = lowest */
/* FINALLY, create the dialog window */
hWnd = CreateWindowExA(styleEx,
SST_DLGCLASS,
caption,
style,
r.left, r.top, /* XY position */
r.right-r.left, r.bottom-r.top, /* Size */
hParentWnd,
(NULL),
GetModuleHandleA(NULL),
NULL);
/* Made the window successfully? */
if(hWnd != NULL)
{
int i;
POINT bottomRight;
RECT clientRect;
SetWindowLongPtrA(hWnd, GWLP_USERDATA, (LONG_PTR)dlgData);
ShowWindow(hWnd, SW_SHOW);
/* Get the coordinates of the bottom right pixel */
GetClientRect(hWnd, &clientRect);
bottomRight.x = clientRect.right;
bottomRight.y = clientRect.bottom;
/* Create the dialog buttons in reverse order starting at the right end of the dialog and moving left */
for(i=0; i<nrButtons; i++)
{
HWND hButton = createButton(hWnd, nrButtons-1-i, buttons[nrButtons-1-i], bottomRight.x-((i+1)*(BUTTON_HSPACE+horiz)), bottomRight.y-(BUTTON_VSPACE+vert), horiz, vert);
/* Set the font on them too */
SendMessage(hButton, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
}
do
{
while(PeekMessageA(&msg, hWnd, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessageA(&msg);
}
} while(!dlgData->exitTime);
}
returnCode = dlgData->buttonId;
HeapFree(GetProcessHeap(), 0,dlgData);
return returnCode;
}
/*************************************************************************/
static LRESULT WINAPI libsstDlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
DialogBoxData* dlgData = (DialogBoxData*)GetWindowLongPtrA(hWnd, GWLP_USERDATA);
switch(msg)
{
case WM_PAINT:
{
PAINTSTRUCT ps;
RECT r;
HDC hDC;
if(dlgData)
{
hDC = BeginPaint(hWnd, &ps);
SelectObject(hDC, GetStockObject(DEFAULT_GUI_FONT));
GetClientRect(hWnd, &r);
r.left += TEXT_HSPACE;
r.right -= TEXT_HSPACE;
r.top += TEXT_VSPACE;
r.bottom -= TEXT_VSPACE;
DrawTextExA(hDC, (LPSTR)dlgData->message, dlgData->lenMessage, &r, DT_TOP | DT_LEFT, NULL);
EndPaint(hWnd, &ps);
}
return 0;
break;
}
/* Aborting the dialog */
case WM_CLOSE:
dlgData->buttonId = -1;
dlgData->exitTime = 1;
DestroyWindow(hWnd);
return 0;
break;
case WM_COMMAND:
{
if(HIWORD(wParam) == BN_CLICKED)
{
dlgData->buttonId = (int)LOWORD(wParam);
dlgData->exitTime = 1;
DestroyWindow(hWnd);
}
return 0;
break;
}
}
return DefWindowProc(hWnd, msg, wParam, lParam);
}
/*************************************************************************/
static HWND createButton(HWND owner, int id, const char* label, int x, int y, int w, int h)
{
HWND hWnd = CreateWindowExA(0,
"BUTTON",
label,
WS_TABSTOP|WS_VISIBLE|
WS_CHILD|BS_DEFPUSHBUTTON,
x, y,
w, h,
owner,
(HMENU)(uintptr_t)id,
GetModuleHandleA(NULL),
NULL);
return hWnd;
}