diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | Makefile.mingw | 12 | ||||
| -rw-r--r-- | alf/alf.cpp | 850 | ||||
| -rw-r--r-- | alf/alf.h | 125 | ||||
| -rw-r--r-- | out/.gitignore | 1 | ||||
| -rw-r--r-- | widgetfactory.c | 103 |
6 files changed, 1092 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3159f7f --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.kdev4 diff --git a/Makefile.mingw b/Makefile.mingw new file mode 100644 index 0000000..9a6e50a --- /dev/null +++ b/Makefile.mingw @@ -0,0 +1,12 @@ +CXX = i686-w64-mingw32-c++ +CFLAGS = -std=c++98 -Wall -Wextra -mwindows -municode -DUNICODE -D_UNICODE -fno-exceptions -fno-rtti -gstabs +LDFLAGS = -luser32 -lcomctl32 -lshell32 -lversion -static + +out/widgetfactory.exe: out/widgetfactory.o out/alf.o + $(CXX) $(CFLAGS) -o $@ $^ $(LDFLAGS) + +out/alf.o: alf/alf.cpp alf/alf.h + $(CXX) $(CFLAGS) -c -o $@ $< + +out/widgetfactory.o: widgetfactory.c alf/alf.h + $(CXX) $(CFLAGS) -c -o $@ $< diff --git a/alf/alf.cpp b/alf/alf.cpp new file mode 100644 index 0000000..13839e4 --- /dev/null +++ b/alf/alf.cpp @@ -0,0 +1,850 @@ +#include "alf.h" + +#include <windows.h> +#include <commctrl.h> +#include <windowsx.h> +#include <stdio.h> +#include <stddef.h> + +typedef struct ALFListHeader { + struct ALFListHeader *prev; + struct ALFListHeader *next; +} ALFListHeader; + +#define ALF_LIST_CONTAINER(ContainerType, containermember, listp) \ + ((ContainerType *)((char *)(listp) - offsetof(ContainerType, containermember))) + +#define ALF_FOR_LIST(ContainerType, containermember, listp, iteratorvar) \ + for (ALFListHeader *alf_list_##iteratorvar##_curr = (listp)->next, \ + *alf_list_##iteratorvar##_next = alf_list_##iteratorvar##_curr->next; \ + alf_list_##iteratorvar##_curr != (listp); \ + alf_list_##iteratorvar##_curr = alf_list_##iteratorvar##_next, \ + alf_list_##iteratorvar##_next = alf_list_##iteratorvar##_next->next) \ + for (ContainerType *iteratorvar = (ContainerType *)((char *)alf_list_##iteratorvar##_curr - offsetof(ContainerType, containermember)); \ + iteratorvar; iteratorvar = NULL) \ + + +static inline BOOL +ALF_ListIsEmpty(ALFListHeader *list) +{ + return list->next == list; +} + +static inline void +ALF_ListInsert(ALFListHeader *list, ALFListHeader *newel) +{ + newel->prev = list; + newel->next = list->next; + newel->next->prev = newel; + list->next = newel; +} + +static inline void +ALF_ListRemove(ALFListHeader *member) +{ + member->prev->next = member->next; + member->next->prev = member->prev; + member->next = NULL; + member->prev = NULL; +} + +static inline void +ALF_ListInit(ALFListHeader *list) +{ + list->next = list->prev = list; +} + +typedef struct { + ALFListHeader list; + HWND hwnd; + UINT x; + UINT y; + UINT cptWidth; + UINT cptHeight; + UINT cptMarginTop; + UINT cptMarginRight; + UINT cptMarginBottom; + UINT cptMarginLeft; + DWORD flags; +} ALFWidgetPriv; + +typedef struct { + int minWidth; + int allocatedWidth; + int allocatedPosition; + int expand : 1; +} ALFRowOrColumn; + +typedef struct { + ALFRowOrColumn *columns; + ALFRowOrColumn *rows; + int nColumns; + int nRows; + int totalMinWidth; + int totalMinHeight; + int occupiedColumnCount; + int occupiedRowCount; +} ALFLayout; + +typedef struct { + ALFWindowVTable *vtbl; + void *closure; + ALFWindowFonts fonts; + ALFListHeader widgets; + int modalResult; + ALFLayout layout; +} ALFWindowPriv; + +static void +ALF_InitializeWindowPriv(HWND hwnd, ALFWindowPriv *priv, ALFWindowInstanceParams *params) +{ + priv->vtbl = (ALFWindowVTable*)GetClassLongPtr(hwnd, 0); + priv->closure = params->closure; + ALF_ListInit(&priv->widgets); +} + +static void +ALF_DestroyWindowPriv(ALFWindowPriv *priv) +{ + if (priv->vtbl->postdestroy) + priv->vtbl->postdestroy(priv->closure); + + ALF_FOR_LIST(ALFWidgetPriv, list, &priv->widgets, w) { + HeapFree(GetProcessHeap(), 0, w); + } + ALF_ListInit(&priv->widgets); + + HeapFree(GetProcessHeap(), 0, priv->layout.columns); + HeapFree(GetProcessHeap(), 0, priv->layout.rows); + HeapFree(GetProcessHeap(), 0, priv); +} + +int +ALF_CentipointsToPxPriv(ALFWindowPriv *priv, int cptValue) +{ + return MulDiv(cptValue, priv->fonts.dpi, 7200); +} + +static void +ALF_UpdateFontForWidget(ALFWindowPriv *priv, ALFWidgetPriv *widget) +{ + if (widget->hwnd && (widget->flags & ALF_MESSAGEFONT) == ALF_MESSAGEFONT) { + SendMessage(widget->hwnd, WM_SETFONT, (WPARAM)priv->fonts.hMessageFont, (LPARAM)NULL); + } +} + +void +ALF_UpdateFonts(HWND win) +{ + // TODO per-monitor DPI aware: GetDpiForWindow, SystemParametersInfoForDpi etc. + ALFWindowPriv *priv = (ALFWindowPriv*)GetWindowLongPtr(win, 0); + + priv->fonts.dpi = 0; + HDC hdcScreen = GetDC(NULL); + if (hdcScreen) { + priv->fonts.dpi = GetDeviceCaps(hdcScreen, LOGPIXELSY); + ReleaseDC(NULL, hdcScreen); + } + + if (!priv->fonts.dpi) { + priv->fonts.dpi = 96; // FIXME! fallback to default DPI + } + + NONCLIENTMETRICS ncm; + ZeroMemory(&ncm, sizeof(ncm)); + ncm.cbSize = sizeof(ncm); + + if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0)) { + priv->fonts.lfMessageFont = ncm.lfMessageFont; + } else { + // FIXME! fallback to default font, 8pt MS Shell Dlg + ZeroMemory(&priv->fonts.lfMessageFont, sizeof(priv->fonts.lfMessageFont)); + + priv->fonts.lfMessageFont.lfHeight = -MulDiv(8, priv->fonts.dpi, 72); + lstrcpyW(priv->fonts.lfMessageFont.lfFaceName, L"MS Shell Dlg"); + } + + if (priv->fonts.hMessageFont) { + DeleteObject(priv->fonts.hMessageFont); + } + priv->fonts.hMessageFont = CreateFontIndirect(&priv->fonts.lfMessageFont); + + ALF_FOR_LIST(ALFWidgetPriv, list, &priv->widgets, i) { + ALF_UpdateFontForWidget(priv, i); + } + + if (priv->vtbl->updatefonts) { + priv->vtbl->updatefonts(priv->closure, win, &priv->fonts); + } +} + +void +ALF_RecalculateLayout(HWND hwnd) +{ + SIZE dummy; + SendMessage(hwnd, ALF_WM_QUERYSIZE, 0, (LPARAM)&dummy); + SendMessage(hwnd, ALF_WM_APPLYLAYOUT, 0, 0); +} + +static void +ALF_CalculateSizes(ALFWindowPriv *win) +{ + for (int i = 0; i < win->layout.nColumns; ++i) { + ZeroMemory(&win->layout.columns[i], sizeof(win->layout.columns[i])); + } + for (int i = 0; i < win->layout.nRows; ++i) { + ZeroMemory(&win->layout.rows[i], sizeof(win->layout.rows[i])); + } + + ALF_FOR_LIST(ALFWidgetPriv, list, &win->widgets, c) { + while ((int)c->x >= win->layout.nColumns) { + // FIXME! overflow, use reallocarray(2) equivalent + if (win->layout.nColumns == 0) { + win->layout.nColumns = 1; + win->layout.columns = (ALFRowOrColumn*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY | HEAP_GENERATE_EXCEPTIONS, win->layout.nColumns*sizeof(win->layout.columns[0])); + } else { + win->layout.nColumns *= 2; + win->layout.columns = (ALFRowOrColumn*)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY | HEAP_GENERATE_EXCEPTIONS, win->layout.columns, win->layout.nColumns*sizeof(win->layout.columns[0])); + } + } + while ((int)c->y >= win->layout.nRows) { + // FIXME! overflow, use reallocarray(2) equivalent + if (win->layout.nRows == 0) { + win->layout.nRows = 1; + win->layout.rows = (ALFRowOrColumn*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY | HEAP_GENERATE_EXCEPTIONS, win->layout.nRows*sizeof(win->layout.rows[0])); + } else { + win->layout.nRows *= 2; + win->layout.rows = (ALFRowOrColumn*)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY | HEAP_GENERATE_EXCEPTIONS, win->layout.rows, win->layout.nRows*sizeof(win->layout.rows[0])); + } + } + + // TODO: ignore spanning cell + + SIZE qs = { ALF_CentipointsToPxPriv(win, c->cptWidth), + ALF_CentipointsToPxPriv(win, c->cptHeight) }; + if ((c->flags & ALF_QUERYSIZE) == ALF_QUERYSIZE) { + SendMessage(c->hwnd, ALF_WM_QUERYSIZE, 0, (LPARAM)&qs); + } + + qs.cx += ALF_CentipointsToPxPriv(win, c->cptMarginLeft); + qs.cx += ALF_CentipointsToPxPriv(win, c->cptMarginRight); + qs.cy += ALF_CentipointsToPxPriv(win, c->cptMarginTop); + qs.cy += ALF_CentipointsToPxPriv(win, c->cptMarginBottom); + + if (qs.cx > win->layout.columns[c->x].minWidth) + win->layout.columns[c->x].minWidth = qs.cx; + if (qs.cy > win->layout.rows[c->y].minWidth) + win->layout.rows[c->y].minWidth = qs.cy; + + // TODO: expand flag + } + + // TODO: second pass for spanning cells + + // update min width bookkeeping + win->layout.totalMinWidth = 0; + win->layout.occupiedColumnCount = 0; + for (int i = 0; i < win->layout.nColumns; ++i) { + if (win->layout.columns[i].minWidth > 0) { + win->layout.totalMinWidth += win->layout.columns[i].minWidth; + win->layout.occupiedColumnCount++; + // TODO: expand flag + } + } + win->layout.totalMinHeight = 0; + win->layout.occupiedRowCount = 0; + for (int i = 0; i < win->layout.nRows; ++i) { + if (win->layout.rows[i].minWidth > 0) { + win->layout.totalMinHeight += win->layout.rows[i].minWidth; + win->layout.occupiedRowCount++; + // TODO: expand flag + } + } + + // TODO: split here into allocation function +} + +static void +ALF_ApplyLayout(HWND hwnd, ALFWindowPriv *win) +{ + // allocate cell positions + // distribute free space + int extraWidth = 0; + int extraHeight = 0; + RECT client; + if (GetClientRect(hwnd, &client)) { + if (client.right - client.left > win->layout.totalMinWidth) + extraWidth = client.right - client.left - win->layout.totalMinWidth; + if (client.bottom - client.top > win->layout.totalMinHeight) + extraHeight = client.bottom - client.top - win->layout.totalMinHeight; + } + + int extraWidthPart = 0; + int extraWidthError = 0; + if (win->layout.occupiedColumnCount) { + extraWidthPart = extraWidth / win->layout.occupiedColumnCount; + extraWidthError = extraWidth - extraWidthPart * win->layout.occupiedColumnCount; + } + + for (int i = 0; i < win->layout.nColumns; ++i) { + win->layout.columns[i].allocatedWidth = win->layout.columns[i].minWidth; + + if (win->layout.columns[i].minWidth > 0) { + win->layout.columns[i].allocatedWidth += extraWidthPart; + if (extraWidthError) { + win->layout.columns[i].allocatedWidth++; + extraWidthError--; + } + } + + if (i == 0) { + win->layout.columns[i].allocatedPosition = 0; + } else { + win->layout.columns[i].allocatedPosition = + win->layout.columns[i-1].allocatedPosition + win->layout.columns[i-1].allocatedWidth; + } + } + + + int extraHeightPart = 0; + int extraHeightError = 0; + if (win->layout.occupiedRowCount) { + extraHeightPart = extraHeight / win->layout.occupiedRowCount; + extraHeightError = extraHeight - extraWidthPart * win->layout.occupiedRowCount; + } + + for (int i = 0; i < win->layout.nRows; ++i) { + win->layout.rows[i].allocatedWidth = win->layout.rows[i].minWidth; + + if (win->layout.rows[i].minWidth > 0) { + win->layout.rows[i].allocatedWidth += extraHeightPart; + if (extraHeightError) { + win->layout.rows[i].allocatedWidth++; + extraHeightError--; + } + } + + if (i == 0) { + win->layout.rows[i].allocatedPosition = 0; + } else { + win->layout.rows[i].allocatedPosition = + win->layout.rows[i-1].allocatedPosition + win->layout.rows[i-1].allocatedWidth; + } + } + + HDWP hdwp = BeginDeferWindowPos(win->layout.occupiedColumnCount * win->layout.occupiedRowCount); + + ALF_FOR_LIST(ALFWidgetPriv, list, &win->widgets, c) { + int marginleft = ALF_CentipointsToPxPriv(win, c->cptMarginLeft); + int marginright = ALF_CentipointsToPxPriv(win, c->cptMarginRight); + int margintop = ALF_CentipointsToPxPriv(win, c->cptMarginTop); + int marginbottom = ALF_CentipointsToPxPriv(win, c->cptMarginBottom); + + hdwp = DeferWindowPos(hdwp, + c->hwnd, 0, + win->layout.columns[c->x].allocatedPosition + marginleft, + win->layout.rows[c->y].allocatedPosition + margintop, + win->layout.columns[c->x].allocatedWidth - marginleft - marginright, + win->layout.rows[c->y].allocatedWidth - margintop - marginbottom, + SWP_NOACTIVATE | SWP_NOZORDER); + } + + EndDeferWindowPos(hdwp); +} + +static void +ALF_InternalAddWidget(HWND hwnd, ALFWindowPriv *priv, ALFAddWidgetParams *params) +{ + ALFWidgetPriv *w = (ALFWidgetPriv*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY|HEAP_GENERATE_EXCEPTIONS, sizeof(ALFWidgetPriv)); + w->hwnd = params->hwnd; + w->x = params->x; + w->y = params->y; + w->cptWidth = params->width; + w->cptHeight = params->height; + w->flags = params->flags; + w->cptMarginTop = params->margins[0]; + w->cptMarginRight = params->margins[1]; + w->cptMarginBottom = params->margins[2]; + w->cptMarginLeft = params->margins[3]; + + if (GetParent(w->hwnd) != hwnd) + SetParent(w->hwnd, hwnd); + + // TODO: QUERYUISTATE on parent, then replicate in child. But wait, XP appears to do this automatically!?? + + ALF_ListInsert(priv->widgets.prev, &w->list); + ALF_UpdateFontForWidget(priv, w); +} + +LRESULT +ALF_DefWindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + ALFWindowPriv *priv = (ALFWindowPriv*)GetWindowLongPtr(hwnd, 0); + + if (msg == ALF_WM_QUERYSIZE) { + ALF_CalculateSizes(priv); + SIZE *ps = (SIZE*)lparam; + ps->cx = priv->layout.totalMinWidth; + ps->cy = priv->layout.totalMinHeight; + return 0; + } + + if (msg == ALF_WM_APPLYLAYOUT) { + ALF_ApplyLayout(hwnd, priv); + return 0; + } + + if (msg == ALF_WM_GETMODALRESULT) { + return (LRESULT)priv->modalResult; + } + if (msg == ALF_WM_SETMODALRESULT) { + priv->modalResult = (int)wparam; + return 0; + } + + if (msg == ALF_WM_CENTIPOINTTOPX) { + return (LRESULT)ALF_CentipointsToPxPriv(priv, (int)wparam); + } + + if (msg == ALF_WM_ADDWIDGET) { + ALF_InternalAddWidget(hwnd, priv, (ALFAddWidgetParams *)lparam); + } + + if (msg == WM_SIZE) { + ALF_ApplyLayout(hwnd, priv); + return 0; + } + + if (msg == WM_GETMINMAXINFO) { + RECT tmp; + ZeroMemory(&tmp, sizeof(tmp)); + tmp.right = priv->layout.totalMinWidth; + tmp.bottom = priv->layout.totalMinHeight; + + // TODO ..ForDpi + if (AdjustWindowRectEx(&tmp, + GetWindowLong(hwnd, GWL_STYLE), + GetMenu(hwnd) != NULL, + GetWindowLong(hwnd, GWL_EXSTYLE))) { + MINMAXINFO *i = (MINMAXINFO *)lparam; + i->ptMinTrackSize.x = tmp.right - tmp.left; + i->ptMinTrackSize.y = tmp.bottom - tmp.top; + return 0; + } + } + + if (msg == WM_CREATE) { + if (priv->vtbl->create) { + priv->vtbl->create(priv->closure, hwnd); + } + BOOL alwaysUnderline = FALSE; + SystemParametersInfo(SPI_GETKEYBOARDCUES, 0, &alwaysUnderline, 0); + if (!alwaysUnderline) { + SendMessage(hwnd, WM_UPDATEUISTATE, MAKEWPARAM(UIS_INITIALIZE, 0), 0); + } + ALF_UpdateFonts(hwnd); + ALF_RecalculateLayout(hwnd); + } + + if (msg == WM_DESTROY && priv->vtbl->destroy) { + priv->vtbl->destroy(priv->closure, hwnd); + } + + if (msg == WM_CLOSE) { + if (!priv->vtbl->close || !priv->vtbl->close(priv->closure, hwnd)) { + priv->modalResult = 2; + ShowWindow(hwnd, FALSE); + } + return TRUE; + } + + if (msg == WM_NCDESTROY) { + SetWindowLongPtr(hwnd, 0, 0); + ALF_DestroyWindowPriv(priv); + } + + return DefWindowProc(hwnd, msg, wparam, lparam); +} + +void +ALF_AddWidget(HWND win, UINT x, UINT y, HWND widget, UINT minWidth, UINT minHeight, DWORD flags) +{ + ALFAddWidgetParams params; + ZeroMemory(¶ms, sizeof(params)); + params.hwnd = widget; + params.x = x; + params.y = y; + params.width = minWidth; + params.height = minHeight; + params.flags = flags; + ALF_AddWidgetEx(win, ¶ms); +} + +void +ALF_AddWidgetEx(HWND win, const ALFAddWidgetParams *p) +{ + SendMessage(win, ALF_WM_ADDWIDGET, 0, (LPARAM)p); +} + +static LRESULT CALLBACK +ALF_WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + if (msg == WM_NCCREATE) { + CREATESTRUCT *cs = (CREATESTRUCT*)(void*)lparam; + ALFWindowInstanceParams *params = (ALFWindowInstanceParams*)cs->lpCreateParams; + ALFWindowPriv *priv = (ALFWindowPriv*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY|HEAP_GENERATE_EXCEPTIONS, sizeof(ALFWindowPriv)); + SetWindowLongPtr(hwnd, 0, (LONG_PTR)priv); + ALF_InitializeWindowPriv(hwnd, priv, params); + } + + ALFWindowPriv *priv = (ALFWindowPriv*)(void*)GetWindowLongPtr(hwnd, 0); + if (priv != NULL) { + if (priv->vtbl->message) { + return priv->vtbl->message(priv->closure, hwnd, msg, wparam, lparam); + } else { + return ALF_DefWindowProc(hwnd, msg, wparam, lparam); + } + } else { + return DefWindowProc(hwnd, msg, wparam, lparam); + } +} + +LPTSTR +ALF_RegisterWindowClass(HINSTANCE hInstance, const ALFWindowClassParams *params) +{ + WNDCLASS cls; + ZeroMemory(&cls, sizeof(cls)); + + // TODO: autogenerate class name + + cls.style = params->classStyle; + cls.hInstance = hInstance; + cls.hCursor = LoadCursor(NULL, (LPTSTR)IDC_ARROW); + cls.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1); + cls.lpszClassName = params->className; + cls.cbWndExtra = sizeof(void*); + cls.cbClsExtra = sizeof(void*); + cls.lpfnWndProc = DefWindowProc; + + ATOM classatom = RegisterClass(&cls); + if (!classatom) + return NULL; + + ALFWindowVTable *pvtbl = (ALFWindowVTable*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY|HEAP_GENERATE_EXCEPTIONS, sizeof(ALFWindowVTable)); + *pvtbl = params->vtbl; + + HWND tmp = CreateWindowEx(0, MAKEINTATOM(classatom), TEXT("dummy"), 0, 0, 0, 0, 0, HWND_MESSAGE, 0, hInstance, 0); + SetClassLongPtr(tmp, 0, (LONG_PTR)pvtbl); + SetClassLongPtr(tmp, GCLP_WNDPROC, (LONG_PTR)ALF_WindowProc); + DestroyWindow(tmp); + + return MAKEINTATOM(classatom); +} + +HWND +ALF_InstantiateWindow(HINSTANCE hInstance, LPCTSTR className, const ALFWindowInstanceParams *params) +{ + return CreateWindowEx(params->windowExStyle, + className, + L"Window", + params->windowStyle, + CW_USEDEFAULT, CW_USEDEFAULT, + 300, 100, //FIXME + params->hwndParent, + NULL, + hInstance, + (void*)params); +} + +int +ALF_ShowModal(HWND win) +{ + MSG msg; + + ALF_SetModalResult(win, 0); + + // TODO: disable parent window + ShowWindow(win, SW_SHOW); + + while (GetMessage(&msg, NULL, 0, 0) > 0) { + // TODO: call application message hooks + // TODO: call preprocess message hook + + if (!IsDialogMessage(win, &msg)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + int mr = ALF_GetModalResult(win); + if (mr) + return mr; + } + + return 0; +} + + +void +ALF_SetModalResult(HWND win, int result) +{ + SendMessage(win, ALF_WM_SETMODALRESULT, (WPARAM)result, 0); +} + +int +ALF_GetModalResult(HWND win) +{ + return (int)SendMessage(win, ALF_WM_GETMODALRESULT, 0, 0); +} + +int +ALF_CentipointsToPixels(HWND win, int cptValue) +{ + return (int)SendMessage(win, ALF_WM_CENTIPOINTTOPX, (WPARAM)cptValue, 0); +} + +void +ALF_ResizeWindow(HWND win, UINT cptWidth, UINT cptHeight) +{ + int pxwidth = ALF_CentipointsToPixels(win, cptWidth); + int pxheight = ALF_CentipointsToPixels(win, cptHeight); + + 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); +} + +/* LABEL */ + +static LRESULT CALLBACK +ALF__LabelSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) +{ + (void)uIdSubclass; + (void)dwRefData; + + if (uMsg == ALF_WM_QUERYSIZE) { + HDC hdcLabel = GetDC(hwnd); + HFONT font = (HFONT)SendMessage(hwnd, WM_GETFONT, 0, 0); + HFONT oldFont = 0; + if (font) + oldFont = SelectFont(hdcLabel, font); + + // calc drawtext style + DWORD style = GetWindowLong(hwnd, GWL_STYLE); + UINT format = DT_LEFT | DT_EXPANDTABS | DT_CALCRECT; + if (style & SS_NOPREFIX) + format |= DT_NOPREFIX; + if (style & SS_EDITCONTROL) + format |= DT_EDITCONTROL; + if (style & SS_ENDELLIPSIS || style & SS_PATHELLIPSIS || style & SS_WORDELLIPSIS) + format |= DT_SINGLELINE; + + RECT r = { 0, 0, 100, 100 }; + + int textlen = GetWindowTextLength(hwnd); + TCHAR *textbuf = (TCHAR*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY|HEAP_GENERATE_EXCEPTIONS, (textlen + 1)*sizeof(TCHAR)); + GetWindowText(hwnd, textbuf, textlen+1); + + DrawText(hdcLabel, textbuf, -1, &r, format); + + HeapFree(GetProcessHeap(), 0, textbuf); + + SIZE *pSize = (SIZE*)(void*)lParam; + if (pSize->cx == 0) + pSize->cx = r.right - r.left; + if (pSize->cy == 0) + pSize->cy = r.bottom - r.top; + + if (font) + SelectFont(hdcLabel, oldFont); + + ReleaseDC(hwnd, hdcLabel); + } + + return DefSubclassProc(hwnd, uMsg, wParam, lParam); +} + +HWND +ALF_AddLabel(HWND win, int id, UINT x, UINT y, const WCHAR *text) +{ + HWND hwndLabel = CreateWindow(L"STATIC", + text, + WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP, + 0, 0, 100, 100, + win, + (HMENU)id, + (HINSTANCE)GetWindowLongPtr(win, GWLP_HINSTANCE), + NULL); + + SetWindowSubclass(hwndLabel, ALF__LabelSubclassProc, 0, 0); + + ALFAddWidgetParams p; + ZeroMemory(&p, sizeof(p)); + p.hwnd = hwndLabel; + p.x = x; + p.y = y; + p.width = 0; + p.height = 0; + p.flags = ALF_QUERYSIZE | ALF_MESSAGEFONT; + p.margins[0] = 300; + p.margins[2] = 375; + + ALF_AddWidgetEx(win, &p); + + return hwndLabel; +} + +/* EDIT */ +static LRESULT CALLBACK +ALF__EditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) +{ + (void)uIdSubclass; + (void)dwRefData; + + if (uMsg == ALF_WM_QUERYSIZE) { + HFONT font = (HFONT)SendMessage(hwnd, WM_GETFONT, 0, 0); + if (font) { + LOGFONT lf; + ZeroMemory(&lf, sizeof(lf)); + if (GetObject(font, sizeof(lf), &lf)) { + SIZE *ps = (SIZE*)lParam; + if (!ps->cx) { + ps->cx = ALF_CentipointsToPixels(GetParent(hwnd), 12000); + } + + if (!ps->cy) { + ps->cy = ALF_CentipointsToPixels(GetParent(hwnd), 525) + + MulDiv(16, -lf.lfHeight, 11); + } + } + } + return 0; + } + + return DefSubclassProc(hwnd, uMsg, wParam, lParam); +} + +HWND +ALF_AddEdit(HWND win, int id, UINT x, UINT y, const WCHAR *text) +{ + HWND hwndEdit = CreateWindowEx(WS_EX_CLIENTEDGE, + L"EDIT", + text, + WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_BORDER, + 0, 0, 100, 100, + win, + (HMENU)id, + (HINSTANCE)GetWindowLongPtr(win, GWLP_HINSTANCE), + NULL); + + SetWindowSubclass(hwndEdit, ALF__EditSubclassProc, 0, 0); + + ALFAddWidgetParams p; + ZeroMemory(&p, sizeof(p)); + p.hwnd = hwndEdit; + p.x = x; + p.y = y; + p.width = 0; + p.height = 0; + p.flags = ALF_QUERYSIZE | ALF_MESSAGEFONT; + + ALF_AddWidgetEx(win, &p); + + return hwndEdit; +} + +/* BUTTON */ +static LRESULT CALLBACK +ALF__ButtonSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) +{ + (void)uIdSubclass; + (void)dwRefData; + + if (uMsg == ALF_WM_QUERYSIZE) { + HDC hdc = GetDC(hwnd); + HFONT font = (HFONT)SendMessage(hwnd, WM_GETFONT, 0, 0); + HFONT oldFont = 0; + if (font) + oldFont = SelectFont(hdc, font); + + // calc drawtext style + DWORD style = GetWindowLong(hwnd, GWL_STYLE); + UINT format = DT_LEFT | DT_EXPANDTABS | DT_CALCRECT; + if ((style & BS_MULTILINE) == 0) + format |= DT_SINGLELINE; + + RECT r = { 0, 0, 100, 100 }; + + int textlen = GetWindowTextLength(hwnd); + TCHAR *textbuf = (TCHAR*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY|HEAP_GENERATE_EXCEPTIONS, (textlen + 1)*sizeof(TCHAR)); + GetWindowText(hwnd, textbuf, textlen+1); + + DrawText(hdc, textbuf, -1, &r, format); + + HeapFree(GetProcessHeap(), 0, textbuf); + + int padding = ALF_CentipointsToPixels(GetParent(hwnd), 525); + int minheight = ALF_CentipointsToPixels(GetParent(hwnd), 1725); + + SIZE *pSize = (SIZE*)(void*)lParam; + if (pSize->cx < r.right - r.left + padding) { + pSize->cx = r.right - r.left + padding; + } + if (pSize->cx < minheight) { + pSize->cx = minheight; + } + if (pSize->cy < r.bottom - r.top + padding) { + pSize->cy = r.bottom - r.top + padding; + } + if (pSize->cy < minheight) { + pSize->cy = minheight; + } + + if (font) + SelectFont(hdc, oldFont); + + ReleaseDC(hwnd, hdc); + } + + return DefSubclassProc(hwnd, uMsg, wParam, lParam); +} + +HWND +ALF_AddButton(HWND win, int id, UINT x, UINT y, const WCHAR *text) +{ + HWND hwndButton = CreateWindowEx(0, + L"BUTTON", + text, + WS_CHILD | WS_TABSTOP | WS_VISIBLE | BS_PUSHBUTTON | BS_MULTILINE, + 0, 0, 100, 100, + win, + (HMENU)id, + (HINSTANCE)GetWindowLongPtr(win, GWLP_HINSTANCE), + NULL); + + SetWindowSubclass(hwndButton, ALF__ButtonSubclassProc, 0, 0); + + ALFAddWidgetParams p; + ZeroMemory(&p, sizeof(p)); + p.hwnd = hwndButton; + p.x = x; + p.y = y; + p.width = 5625; + p.height = 0; + p.flags = ALF_QUERYSIZE | ALF_MESSAGEFONT; + + ALF_AddWidgetEx(win, &p); + + return hwndButton; +} diff --git a/alf/alf.h b/alf/alf.h new file mode 100644 index 0000000..d50bd67 --- /dev/null +++ b/alf/alf.h @@ -0,0 +1,125 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + int dpi; + LOGFONT lfMessageFont; + HFONT hMessageFont; +} ALFWindowFonts; + +typedef struct { + void (*create)(void * /*closure*/, HWND /*window*/); + void (*destroy)(void * /*closure*/, HWND /*window*/); + BOOL (*close)(void * /*closure*/, HWND /*window*/); + void (*postdestroy)(void * /*closure*/); + void (*updatefonts)(void * /*closure*/, HWND /*window*/, const ALFWindowFonts *fonts); + LRESULT (*message)(void * /*closure*/, HWND, UINT, WPARAM, LPARAM); + LRESULT (*command)(void * /*closure*/, HWND /*window*/, UINT /*sourceid*/, UINT /*notificationcode*/, LPARAM); + LRESULT (*notify)(void * /*closure*/, HWND /*window*/, WPARAM /*sourceid*/, NMHDR *); +} ALFWindowVTable; + +// layout flags +#define ALF_QUERYSIZE 0x01 +#define ALF_HEXPAND 0x02 +#define ALF_VEXPAND 0x04 +#define ALF_MESSAGEFONT 0x08 + +// messages +#define ALF_WM__BASE 0x2800 +#define ALF_WM_QUERYSIZE (ALF_WM__BASE + 1) +#define ALF_WM_APPLYLAYOUT (ALF_WM__BASE + 2) +#define ALF_WM_UPDATEFONTS (ALF_WM__BASE + 3) +#define ALF_WM_ADDWIDGET (ALF_WM__BASE + 4) +#define ALF_WM_WIDGETBYID (ALF_WM__BASE + 5) +#define ALF_WM_REMOVEWIDGET (ALF_WM__BASE + 6) +#define ALF_WM_SETMODALRESULT (ALF_WM__BASE + 7) +#define ALF_WM_GETMODALRESULT (ALF_WM__BASE + 8) +#define ALF_WM_CENTIPOINTTOPX (ALF_WM__BASE + 9) + + +typedef struct { + const WCHAR *className; + UINT classStyle; + ALFWindowVTable vtbl; +} ALFWindowClassParams; + +typedef struct { + void *closure; + HWND hwndParent; + UINT windowStyle; + UINT windowExStyle; +} ALFWindowInstanceParams; + +typedef struct { + HWND hwnd; + UINT x; + UINT y; + UINT width; + UINT height; + DWORD flags; + UINT margins[4]; +} ALFAddWidgetParams; + +LPTSTR +ALF_RegisterWindowClass(HINSTANCE hInstance, const ALFWindowClassParams *params); + +void +ALF_UnregisterWindowClass(HINSTANCE hInstance, LPCTSTR className); + +HWND +ALF_InstantiateWindow(HINSTANCE hInstance, LPCTSTR className, const ALFWindowInstanceParams *params); + +int +ALF_CentipointsToPixels(HWND win, int cptValue); + +LRESULT +ALF_DefWindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + +HWND +ALF_AddLabel(HWND win, int id, UINT x, UINT y, const WCHAR *text); + +HWND +ALF_AddEdit(HWND win, int id, UINT x, UINT y, const WCHAR *text); + +HWND +ALF_AddButton(HWND win, int id, UINT x, UINT y, const WCHAR *text); + +void +ALF_DestroyWidget(HWND win, int id); + +void +ALF_AddWidget(HWND win, UINT x, UINT y, HWND widget, UINT cptWidth, UINT cptHeight, DWORD flags); + +void +ALF_AddWidgetEx(HWND win, const ALFAddWidgetParams *params); + +HWND +ALF_WidgetHwndById(HWND win, int id); + +void +ALF_RecalculateLayout(HWND win); + +void +ALF_UpdateFonts(HWND win); + +void +ALF_ResizeWindow(HWND win, UINT cptWidth, UINT cptHeight); + +int +ALF_ShowModal(HWND win); + +void +ALF_SetModalResult(HWND win, int result); + +int +ALF_GetModalResult(HWND win); + +#ifdef __cplusplus +} // extern C +#endif diff --git a/out/.gitignore b/out/.gitignore new file mode 100644 index 0000000..72e8ffc --- /dev/null +++ b/out/.gitignore @@ -0,0 +1 @@ +* diff --git a/widgetfactory.c b/widgetfactory.c new file mode 100644 index 0000000..b5b9142 --- /dev/null +++ b/widgetfactory.c @@ -0,0 +1,103 @@ +#include "alf/alf.h" + +enum { + ID_LBLHELLO, + ID_LBL2, + ID_LBL3, + ID_ED1, + ID_B1, + ID_B2, + ID__MAX +}; + +static HBRUSH red; +static HBRUSH green; +static HBRUSH blue; +static HBRUSH white; + +LRESULT +handleMessage(void *closure, HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + (void)closure; + + if (msg == WM_CTLCOLORSTATIC) { + DWORD id = GetDlgCtrlID((HWND)lparam); + HDC hdcStatic = (HDC)wparam; + if (id == ID_LBLHELLO) { + SetTextColor(hdcStatic, RGB(0,0,0)); + SetBkColor(hdcStatic, RGB(255,0,0)); + return (LRESULT)red; + } else if (id == ID_LBL2) { + SetTextColor(hdcStatic, RGB(0,0,0)); + SetBkColor(hdcStatic, RGB(0,255,0)); + return (LRESULT)green; + } else if (id == ID_LBL3) { + SetTextColor(hdcStatic, RGB(255,255,255)); + SetBkColor(hdcStatic, RGB(0,0,255)); + return (LRESULT)blue; + } else { + SetTextColor(hdcStatic, RGB(0,0,0)); + SetBkColor(hdcStatic, RGB(255,255,255)); + return (LRESULT)white; + } + } + + return ALF_DefWindowProc(hwnd, msg, wparam, lparam); +} + +int CALLBACK +wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow) +{ + (void)hPrevInstance; + (void)lpCmdLine; + (void)nCmdShow; + + red = CreateSolidBrush(RGB(255,0,0)); + green = CreateSolidBrush(RGB(0,255,0)); + blue = CreateSolidBrush(RGB(0,0,255)); + white = CreateSolidBrush(RGB(255,255,255)); + + ALFWindowClassParams cparams; + ZeroMemory(&cparams, sizeof(cparams)); + + cparams.className = TEXT("DummyClass"); + cparams.vtbl.message = handleMessage; + + ALF_RegisterWindowClass(hInstance, &cparams); + + ALFWindowInstanceParams params; + ZeroMemory(¶ms, sizeof(params)); + params.windowStyle = WS_OVERLAPPEDWINDOW; + + HWND win = ALF_InstantiateWindow(hInstance, TEXT("DummyClass"), ¶ms); + + ALF_AddLabel(win, ID_LBL2, 1, 0, L"Hello, 2!\nblub"); + + HWND hwndLabel = CreateWindow( + L"STATIC", + L"Hello World!", + WS_CHILD | WS_VISIBLE | SS_LEFT, + 0, 0, 50, 25, + win, + (HMENU)ID_LBLHELLO, + hInstance, + NULL); + ALF_AddWidget(win, 0, 0, hwndLabel, 5000, 1000, ALF_MESSAGEFONT); + + ALF_AddLabel(win, ID_LBL3, 0, 1, L"Good Morning my &Angel!"); + + ALF_AddEdit(win, ID_ED1, 1, 1, L"Happy Birthday!"); + + ALF_AddButton(win, ID_B1, 2, 1, L"&Go!"); + ALF_AddButton(win, ID_B2, 0, 2, L"Oh m&y god,\r\nwho the hell cares?"); + + ALF_RecalculateLayout(win); + + ALF_ResizeWindow(win, 1, 1); + + ALF_ShowModal(win); + + DestroyWindow(win); + + return 0; +} |
