#include "alfpriv.h" #define ALF_TOPLEVEL_FLAG_MODALEND ((DWORD)1) typedef struct { HWND hwnd; ALFToplevelVTable *vtbl; void *closure; ALFListHeader toplevelList; ALFApplication *app; DWORD flags; LPARAM modalResult; ALFLayout layout; HWND hwndFocus; HFONT hMessageFont; } ALFToplevelPriv; struct tagALFApplication { ALFListHeader toplevelList; ALFToplevelPriv *activeToplevel; BOOL (*pretranslatemessage)(void *,MSG *); void *pretranslatemessage_closure; }; struct ALFToplevel_CreateParams{ ALFToplevelVTable *vtbl; void *closure; ALFApplication *app; }; static TCHAR _alf_toplevelClass[28] = {0}; static void ALF_InitializeToplevelPriv(HWND hwnd, ALFToplevelPriv *priv) { priv->hwnd = hwnd; ALF_ListInit(&priv->toplevelList); ALF_Layout_Init(&priv->layout); priv->layout.bgcolor = ALF_COLOR_SYS(COLOR_BTNFACE); } static void ALF_DestroyToplevelPriv(ALFToplevelPriv *priv) { if (priv->vtbl && priv->vtbl->postdestroy) priv->vtbl->postdestroy(priv->closure); if (priv->app && priv->app->activeToplevel == priv) priv->app->activeToplevel = NULL; ALF_ListRemove(&priv->toplevelList); ALF_Layout_Clear(&priv->layout); if (priv->hMessageFont) DeleteObject(priv->hMessageFont); ALF_Free(priv); } static void ALF_UpdateFontsPriv(HWND win, ALFToplevelPriv *priv) { // XXX: SystemParametersInfoForDpi is Unicode-only and needs Vista+ NONCLIENTMETRICS, ALF_NONCLIENTMETRICSW_VISTA ncm; ZeroMemory(&ncm, sizeof(ncm)); ncm.cbSize = sizeof(ncm); LOGFONT lfMessageFont; ZeroMemory(&lfMessageFont, sizeof(lfMessageFont)); if (ALF_Compat_SystemParametersInfoForDpi(SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0, (UINT)priv->layout.dpi)) { #ifdef UNICODE lfMessageFont = ncm.lfMessageFont; #else ALF_Compat_LogFontWtoA(&ncm.lfMessageFont, &lfMessageFont); #endif } else { // FIXME! fallback to default font, 8pt MS Shell Dlg ZeroMemory(&lfMessageFont, sizeof(lfMessageFont)); lfMessageFont.lfHeight = -MulDiv(8, priv->layout.dpi, 72); lstrcpy(lfMessageFont.lfFaceName, TEXT("MS Shell Dlg")); } if (priv->hMessageFont) { DeleteObject(priv->hMessageFont); } priv->hMessageFont = CreateFontIndirect(&lfMessageFont); SendMessage(win, WM_SETFONT, (WPARAM)priv->hMessageFont, (LPARAM)1); } BOOL ALF_Toplevel_PreTranslateMessage(HWND toplevel, MSG *message) { ALFToplevelPriv *priv = (ALFToplevelPriv *)GetWindowLongPtr(toplevel, DLGWINDOWEXTRA); return (priv->vtbl && priv->vtbl->pretranslatemessage && priv->vtbl->pretranslatemessage(priv->closure, toplevel, message)) || IsDialogMessage(toplevel, message); } void ALF_Toplevel_EnsureBigEnough(HWND toplevel) { MINMAXINFO i; ZeroMemory(&i, sizeof(i)); SendMessage(toplevel, WM_GETMINMAXINFO, 0, (LPARAM)&i); WINDOWPLACEMENT wp; ZeroMemory(&wp, sizeof(wp)); wp.length = sizeof(wp); GetWindowPlacement(toplevel, &wp); if (wp.rcNormalPosition.right - wp.rcNormalPosition.left < i.ptMinTrackSize.x || wp.rcNormalPosition.bottom - wp.rcNormalPosition.top < i.ptMinTrackSize.y) { if (wp.rcNormalPosition.right - wp.rcNormalPosition.left < i.ptMinTrackSize.x) wp.rcNormalPosition.right = wp.rcNormalPosition.left + i.ptMinTrackSize.x; if (wp.rcNormalPosition.bottom - wp.rcNormalPosition.top < i.ptMinTrackSize.y) wp.rcNormalPosition.bottom = wp.rcNormalPosition.top + i.ptMinTrackSize.y; SetWindowPlacement(toplevel, &wp); } } static INT_PTR CALLBACK ALF_Toplevel_DlgProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { (void)hwnd; (void)msg; (void)wparam; (void)lparam; return FALSE; } static LRESULT CALLBACK ALF_Toplevel_WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { if (msg == WM_CREATE) { ALFToplevelPriv *priv = ALF_New(ALFToplevelPriv, 1); SetWindowLongPtr(hwnd, DLGWINDOWEXTRA, (LONG_PTR)priv); ALF_InitializeToplevelPriv(hwnd, priv); int dpi = (int)ALF_Compat_GetDpiForWindow(hwnd); SendMessage(hwnd, ALF_WM_DPICHANGE, 0, (LPARAM)dpi); // will also update fonts } ALFToplevelPriv *priv = (ALFToplevelPriv*)(void*)GetWindowLongPtr(hwnd, DLGWINDOWEXTRA); if (priv != NULL) { if (priv->vtbl && priv->vtbl->message) { return priv->vtbl->message(priv->closure, hwnd, msg, wparam, lparam); } else { return ALF_Toplevel_DefWindowProc(hwnd, msg, wparam, lparam); } } else { return DefDlgProc(hwnd, msg, wparam, lparam); } } static void ALF_Toplevel_Paint(ALFToplevelPriv *priv, HWND hwnd, HDC dc, RECT *r) { if (priv->vtbl && priv->vtbl->paint) { priv->vtbl->paint(priv->closure, hwnd, dc, r); } else { ALF_FillRect(dc, r, priv->layout.bgcolor); } } LRESULT ALF_Toplevel_DefWindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { ALFToplevelPriv *priv = (ALFToplevelPriv*)GetWindowLongPtr(hwnd, DLGWINDOWEXTRA); if (msg == WM_INITDIALOG) { struct ALFToplevel_CreateParams *params = (struct ALFToplevel_CreateParams *)lparam; priv->vtbl = params->vtbl; priv->closure = params->closure; priv->app = params->app; if (priv->app) { ALF_ListInsert(&priv->app->toplevelList, &priv->toplevelList); } BOOL retval = TRUE; if (priv->vtbl && priv->vtbl->initialize) { retval = priv->vtbl->initialize(priv->closure, hwnd); } ALF_Toplevel_EnsureBigEnough(hwnd); return retval; } if (msg == ALF_WM_GETMODALRESULT) { return (LRESULT)priv->modalResult; } if (msg == ALF_WM_SETMODALRESULT) { priv->modalResult = lparam; priv->flags |= ALF_TOPLEVEL_FLAG_MODALEND; return 0; } if (msg == ALF_WM_GETDPI) { return (LRESULT)priv->layout.dpi; } if (msg == ALF_WM_UPDATEFONTS) { ALF_UpdateFontsPriv(hwnd, priv); return 0; } if (msg == WM_ACTIVATE) { if (wparam) { if (priv->app) { priv->app->activeToplevel = priv; } } else { if (priv->app && priv->app->activeToplevel == priv) { priv->app->activeToplevel = NULL; } } } if (msg == WM_ERASEBKGND) { return TRUE; // handled in WM_PAINT } if (msg == WM_PRINTCLIENT) { RECT r = { 0, 0, 0, 0 }; GetClientRect(hwnd, &r); ALF_Toplevel_Paint(priv, hwnd, (HDC)wparam, &r); return TRUE; } if (msg == WM_PAINT) { PAINTSTRUCT ps; HDC dc = BeginPaint(hwnd, &ps); ALF_Toplevel_Paint(priv, hwnd, dc, &ps.rcPaint); EndPaint(hwnd, &ps); return 0; } if (msg == WM_COMMAND) { HWND source = (HWND)lparam; WORD code = HIWORD(wparam); WORD id = LOWORD(wparam); LRESULT ret = 0; if (source != 0) ret = SendMessage(source, 0x2000 + WM_COMMAND, wparam, lparam); if (ret == 0 && priv->vtbl && priv->vtbl->command) ret = priv->vtbl->command(priv->closure, hwnd, code, id, source); return ret; } if (msg == WM_NOTIFY) { NMHDR *nmhdr = (NMHDR *)lparam; LRESULT ret = 0; if (nmhdr->hwndFrom) ret = SendMessage(nmhdr->hwndFrom, 0x2000 + WM_NOTIFY, wparam, lparam); if (ret == 0 && priv->vtbl && priv->vtbl->notify) ret = priv->vtbl->notify(priv->closure, hwnd, wparam, nmhdr); return ret; } if (msg == WM_DRAWITEM) { LPDRAWITEMSTRUCT dis = (DRAWITEMSTRUCT *)lparam; LRESULT ret = 0; if (wparam && dis->hwndItem) { ret = SendMessage(dis->hwndItem, 0x2000 + WM_DRAWITEM, wparam, lparam); } if (ret) return ret; } if (msg == WM_CTLCOLORSTATIC) { LRESULT ret = SendMessage((HWND)lparam, 0x2000 + WM_CTLCOLORSTATIC, wparam, lparam); if (ret) { return ret; } else { SetBkColor((HDC)wparam, GetSysColor(COLOR_BTNFACE)); SetTextColor((HDC)wparam, GetSysColor(COLOR_BTNTEXT)); return (LRESULT)GetSysColorBrush(COLOR_BTNFACE); } } if (msg == WM_CTLCOLORBTN) { LRESULT ret = SendMessage((HWND)lparam, 0x2000 + WM_CTLCOLORBTN, wparam, lparam); if (ret) { return ret; } else { SetBkColor((HDC)wparam, GetSysColor(COLOR_BTNFACE)); SetTextColor((HDC)wparam, GetSysColor(COLOR_BTNTEXT)); return (LRESULT)GetSysColorBrush(COLOR_BTNFACE); } } if (msg == WM_SIZE) { ALF_Layout_Apply(&priv->layout, hwnd); } if (msg == WM_SHOWWINDOW) { if (wparam) ALF_Layout_Validate(&priv->layout, hwnd); } if (msg == WM_GETMINMAXINFO) { RECT tmp; ZeroMemory(&tmp, sizeof(tmp)); SIZE s; ZeroMemory(&s, sizeof(s)); ALF_Layout_GetMinSize(&priv->layout, hwnd, &s); tmp.right = s.cx; tmp.bottom = s.cy; if (ALF_Compat_AdjustWindowRectExForDpi( &tmp, (DWORD)GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) != NULL, (DWORD)GetWindowLong(hwnd, GWL_EXSTYLE), (UINT)priv->layout.dpi)) { MINMAXINFO *i = (MINMAXINFO *)lparam; i->ptMinTrackSize.x = tmp.right - tmp.left; i->ptMinTrackSize.y = tmp.bottom - tmp.top; return 0; } } if (msg == WM_DESTROY && priv->vtbl && priv->vtbl->destroy) { priv->vtbl->destroy(priv->closure, hwnd); } if (msg == WM_CLOSE) { if (priv->vtbl && priv->vtbl->close) priv->vtbl->close(priv->closure, hwnd); return 0; } if (msg == WM_NCDESTROY) { SetWindowLongPtr(hwnd, DLGWINDOWEXTRA, 0); ALF_DestroyToplevelPriv(priv); } if (msg == WM_DPICHANGED) { int dpi = LOWORD(wparam); SendMessage(hwnd, ALF_WM_DPICHANGE, 0, (LPARAM)dpi); // will also update fonts and invalidate layout RECT *r = (RECT*)lparam; SetWindowPos(hwnd, NULL, r->left, r->top, r->right-r->left, r->bottom-r->top, SWP_NOACTIVATE|SWP_NOZORDER); } if (msg == WM_THEMECHANGED || msg == WM_SETTINGCHANGE) { ALF_UpdateFontsPriv(hwnd, priv); ALF_Layout_Invalidate(&priv->layout, hwnd); ALF_InvalidateBackground(hwnd); InvalidateRect(hwnd, NULL, TRUE); } if (msg == WM_WINDOWPOSCHANGED) { if (priv->vtbl && priv->vtbl->windowposchanged) { priv->vtbl->windowposchanged(priv->closure, hwnd, (WINDOWPOS *)lparam); } } if (msg == ALF_WM_GETAPPLICATION) { return (LRESULT)priv->app; } LRESULT ret = 0; if (ALF_Layout_HandleMessage(&priv->layout, hwnd, msg, wparam, lparam, &ret)) { // if the layout was changed, our current size might be too small if (msg == ALF_WM_VALIDATELAYOUT) { ALF_Toplevel_EnsureBigEnough(hwnd); } if (msg == ALF_WM_DPICHANGE) { ALF_UpdateFontsPriv(hwnd, priv); } return ret; } return DefDlgProc(hwnd, msg, wparam, lparam); } void ALF_RegisterToplevelClass(void) { WNDCLASS cls; ZeroMemory(&cls, sizeof(cls)); wsprintf(_alf_toplevelClass, TEXT("ALFWindow.%p"), (ULONG_PTR)&_alf_toplevelClass[0]); cls.style = 0; cls.hInstance = ALF_HINSTANCE; cls.hCursor = LoadCursor(NULL, (LPTSTR)IDC_ARROW); cls.hbrBackground = NULL; cls.lpszClassName = _alf_toplevelClass; cls.cbWndExtra = DLGWINDOWEXTRA + sizeof(void*); cls.cbClsExtra = 0; cls.lpfnWndProc = ALF_Toplevel_WindowProc; ATOM classatom = RegisterClass(&cls); if (!classatom) { MessageBox(NULL, TEXT("FATAL: Couldn't register toplevel window class"), TEXT("DEBUG"), MB_ICONHAND|MB_OK); } } void ALF_UnregisterToplevelClass(void) { UnregisterClass(_alf_toplevelClass, ALF_HINSTANCE); } HWND ALF_CreateToplevelWindow(DWORD exstyle, DWORD style, HWND hwndOwner, ALFApplication *app, ALFToplevelVTable *vtbl, void *closure) { #pragma pack(push, 1) struct { DLGTEMPLATE t; WCHAR s[32]; } t; #pragma pack(pop) ZeroMemory(&t, sizeof(t)); t.t.style = style; t.t.dwExtendedStyle = exstyle; t.t.x = 0; t.t.y = 0; t.t.cx = 10; t.t.cy = 10; t.t.cdit = 0; // NOTE: this only works because we know the window class name is all ascii for (int i = 0; _alf_toplevelClass[i]; ++i) { t.s[i+1] = _alf_toplevelClass[i]; } if (hwndOwner == GetDesktopWindow()) hwndOwner = NULL; if (hwndOwner && GetWindowLong(hwndOwner, GWL_STYLE) & WS_CHILD) hwndOwner = GetParent(hwndOwner); struct ALFToplevel_CreateParams params; params.vtbl = vtbl; params.closure = closure; params.app = app; return CreateDialogIndirectParam(ALF_HINSTANCE, &t.t, hwndOwner, ALF_Toplevel_DlgProc, (LPARAM)¶ms); } void ALF_Toplevel_SetDefaultButton(HWND win, WORD id) { SendMessage(win, DM_SETDEFID, (WPARAM)id, 0); } LPARAM ALF_Toplevel_ShowModal(HWND toplevel) { ALFToplevelPriv *priv = (ALFToplevelPriv *)GetWindowLongPtr(toplevel, DLGWINDOWEXTRA); priv->flags &= ~ALF_TOPLEVEL_FLAG_MODALEND; priv->modalResult = 0; HWND owner = GetWindow(toplevel, GW_OWNER); BOOL ownerEnabled = FALSE; ShowWindow(toplevel, SW_SHOW); if (owner) ownerEnabled = !EnableWindow(owner, FALSE); while (!(priv->flags & ALF_TOPLEVEL_FLAG_MODALEND)) { MSG msg; if (GetMessage(&msg, NULL, 0, 0)) { if (!(priv->app && ALF_Application_PreTranslateMessage(priv->app, &msg, toplevel)) && !ALF_Toplevel_PreTranslateMessage(toplevel, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } else { PostQuitMessage((int)msg.wParam); break; } } if (owner) EnableWindow(owner, ownerEnabled); ShowWindow(toplevel, SW_HIDE); return priv->modalResult; } void ALF_Toplevel_SetModalResult(HWND win, LPARAM result) { SendMessage(win, ALF_WM_SETMODALRESULT, 0, result); } LPARAM ALF_Toplevel_GetModalResult(HWND win) { return (LPARAM)SendMessage(win, ALF_WM_GETMODALRESULT, 0, 0); } void ALF_Toplevel_Resize(HWND win, int cptWidth, int cptHeight) { int dpi = ALF_GetDpi(win); int pxwidth = ALF_CentipointsToPixels(cptWidth, dpi); int pxheight = ALF_CentipointsToPixels(cptHeight, dpi); ALF_Toplevel_ResizePx(win, pxwidth, pxheight); } void ALF_Toplevel_ResizePx(HWND win, int pxwidth, int pxheight) { MINMAXINFO tmp = { { 0, 0 }, { pxwidth, pxheight }, { 0, 0 }, { pxwidth, pxheight }, { pxwidth, pxheight } }; SendMessage(win, WM_GETMINMAXINFO, 0, (LPARAM)&tmp); if (tmp.ptMinTrackSize.x > pxwidth) pxwidth = tmp.ptMinTrackSize.x; if (tmp.ptMinTrackSize.y > pxheight) pxheight = tmp.ptMinTrackSize.y; SetWindowPos(win, NULL, 0, 0, pxwidth, pxheight, SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOZORDER); } ALFApplication * ALF_Toplevel_Application(HWND toplevel) { return (ALFApplication *)SendMessage(toplevel, ALF_WM_GETAPPLICATION, 0, 0); } ALFApplication * ALF_CreateApplication(void) { ALFApplication *app = ALF_New(ALFApplication, 1); ALF_ListInit(&app->toplevelList); return app; } void ALF_Application_ProcessMessages(ALFApplication *app) { MSG msg; while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) { PostQuitMessage((int)msg.wParam); return; } else { if (!ALF_Application_PreTranslateMessage(app, &msg, NULL)) { TranslateMessage(&msg); DispatchMessage(&msg); } } } } void ALF_Application_Run(ALFApplication *app) { while (!ALF_ListIsEmpty(&app->toplevelList)) { MSG msg; if (GetMessage(&msg, NULL, 0, 0)) { if (!ALF_Application_PreTranslateMessage(app, &msg, NULL)) { TranslateMessage(&msg); DispatchMessage(&msg); } } else { PostQuitMessage((int)msg.wParam); return; } } } BOOL ALF_Application_EnumToplevels(ALFApplication *app, WNDENUMPROC proc, LPARAM param) { ALF_FOR_LIST(ALFToplevelPriv, toplevelList, &app->toplevelList, t) { if (!proc(t->hwnd, param)) return FALSE; } return TRUE; } HWND ALF_Application_ActiveToplevel(ALFApplication *app) { return app->activeToplevel->hwnd; } void ALF_Application_SetPreTranslateMessageHandler(ALFApplication *app, BOOL(*handler)(void *,MSG *), void *closure) { app->pretranslatemessage = handler; app->pretranslatemessage_closure = closure; } BOOL ALF_Application_PreTranslateMessage(ALFApplication *app, MSG *msg, HWND modalToplevel) { return (app->pretranslatemessage && app->pretranslatemessage(app->pretranslatemessage_closure, msg)) || (app->activeToplevel && app->activeToplevel->hwnd != modalToplevel && ALF_Toplevel_PreTranslateMessage(app->activeToplevel->hwnd, msg)); } // TODO: implement modal stuff /*void ALF_Application_DisableWindowsForModal(ALFApplication *app, HWND skip); void ALF_Application_ReenableWindowsForModal(ALFApplication *app, HWND skip);*/ void ALF_DestroyApplication(ALFApplication *app) { // don't use ALF_FOR_LIST here, since ALF_FOR_LIST can only handle destroying // the current element. But the window might to anything in WM_DESTROY, // including destroying various other windows. while (!ALF_ListIsEmpty(&app->toplevelList)) { HWND w = ALF_LIST_CONTAINER(ALFToplevelPriv, toplevelList, app->toplevelList.next)->hwnd; DestroyWindow(w); } ALF_Free(app); }