diff options
Diffstat (limited to 'new/windows')
| -rw-r--r-- | new/windows/alloc.c | 49 | ||||
| -rw-r--r-- | new/windows/button.c | 108 | ||||
| -rw-r--r-- | new/windows/checkbox.c | 123 | ||||
| -rw-r--r-- | new/windows/comctl32.c | 105 | ||||
| -rw-r--r-- | new/windows/debug.c | 111 | ||||
| -rw-r--r-- | new/windows/entry.c | 67 | ||||
| -rw-r--r-- | new/windows/init.c | 112 | ||||
| -rw-r--r-- | new/windows/label.c | 71 | ||||
| -rw-r--r-- | new/windows/main.c | 56 | ||||
| -rw-r--r-- | new/windows/newcontrol.c | 241 | ||||
| -rw-r--r-- | new/windows/parent.c | 268 | ||||
| -rw-r--r-- | new/windows/tab.c | 189 | ||||
| -rw-r--r-- | new/windows/text.c | 55 | ||||
| -rw-r--r-- | new/windows/util.c | 73 | ||||
| -rw-r--r-- | new/windows/window.c | 186 |
15 files changed, 1814 insertions, 0 deletions
diff --git a/new/windows/alloc.c b/new/windows/alloc.c new file mode 100644 index 0000000..147c90d --- /dev/null +++ b/new/windows/alloc.c @@ -0,0 +1,49 @@ +// 4 december 2014 +#include "uipriv_windows.h" + +// wrappers for allocator of choice +// panics on memory exhausted, undefined on heap corruption or other unreliably-detected malady (see http://stackoverflow.com/questions/28761680/is-there-a-windows-api-memory-allocator-deallocator-i-can-use-that-will-just-giv) +// new memory is set to zero +// passing NULL to tableRealloc() acts like tableAlloc() +// passing NULL to tableFree() is a no-op + +void *uiAlloc(size_t size, const char *type) +{ + void *out; + + out = malloc(size); + if (out == NULL) { + fprintf(stderr, "memory exhausted in uiAlloc() allocating %s\n", type); + abort(); + } + ZeroMemory(out, size); + if (options.debugLogAllocations) + fprintf(stderr, "%p alloc %s\n", out, type); + return out; +} + +void *uiRealloc(void *p, size_t size, const char *type) +{ + void *out; + + if (p == NULL) + return uiAlloc(size, type); + out = realloc(p, size); + if (out == NULL) { + fprintf(stderr, "memory exhausted in uiRealloc() reallocating %s\n", type); + abort(); + } + // TODO zero the extra memory + if (options.debugLogAllocations) + fprintf(stderr, "%p realloc %p\n", p, out); + return out; +} + +void uiFree(void *p) +{ + if (p == NULL) + return; + free(p); + if (options.debugLogAllocations) + fprintf(stderr, "%p free\n", p); +} diff --git a/new/windows/button.c b/new/windows/button.c new file mode 100644 index 0000000..f89a197 --- /dev/null +++ b/new/windows/button.c @@ -0,0 +1,108 @@ +// 7 april 2015 +#include "uipriv_windows.h" + +struct button { + void (*onClicked)(uiControl *, void *); + void *onClickedData; +}; + +static BOOL onWM_COMMAND(uiControl *c, WORD code, LRESULT *lResult) +{ + struct button *b = (struct button *) (c->data); + + if (code != BN_CLICKED) + return FALSE; + (*(b->onClicked))(c, b->onClickedData); + *lResult = 0; + return TRUE; +} + +static BOOL onWM_NOTIFY(uiControl *c, NMHDR *nm, LRESULT *lResult) +{ + return FALSE; +} + +static void onWM_DESTROY(uiControl *c) +{ + struct button *b = (struct button *) (c->data); + + uiFree(b); +} + +// from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing +#define buttonHeight 14 + +static void preferredSize(uiControl *c, uiSizing *d, intmax_t *width, intmax_t *height) +{ + HWND hwnd; + SIZE size; + + hwnd = uiControlHWND(c); + + // try the comctl32 version 6 way + size.cx = 0; // explicitly ask for ideal size + size.cy = 0; + if (SendMessageW(hwnd, BCM_GETIDEALSIZE, 0, (LPARAM) (&size)) != FALSE) { + *width = size.cx; + *height = size.cy; + return; + } + + // that didn't work; fall back to using Microsoft's metrics + // Microsoft says to use a fixed width for all buttons; this isn't good enough + // use the text width instead, with some edge padding + *width = uiWindowsWindowTextWidth(hwnd) + (2 * GetSystemMetrics(SM_CXEDGE)); + *height = uiDlgUnitsToY(buttonHeight, d->sys->baseY); +} + +static void defaultOnClicked(uiControl *c, void *data) +{ + // do nothing +} + +uiControl *uiNewButton(const char *text) +{ + uiControl *c; + struct button *b; + uiWindowsNewControlParams p; + WCHAR *wtext; + + p.dwExStyle = 0; + p.lpClassName = L"button"; + wtext = toUTF16(text); + p.lpWindowName = wtext; + p.dwStyle = BS_PUSHBUTTON | WS_TABSTOP; + p.hInstance = hInstance; + p.useStandardControlFont = TRUE; + p.onWM_COMMAND = onWM_COMMAND; + p.onWM_NOTIFY = onWM_NOTIFY; + p.onWM_DESTROY = onWM_DESTROY; + c = uiWindowsNewControl(&p); + uiFree(wtext); + + c->preferredSize = preferredSize; + + b = uiNew(struct button); + b->onClicked = defaultOnClicked; + c->data = b; + + return c; +} + +char *uiButtonText(uiControl *c) +{ + return uiWindowsControlText(c); +} + +void uiButtonSetText(uiControl *c, const char *text) +{ + uiWindowsControlSetText(c, text); +} + +void uiButtonOnClicked(uiControl *c, void (*f)(uiControl *, void *), void *data) +{ + struct button *b = (struct button *) (c->data); + + b->onClicked = f; + b->onClickedData = data; +} diff --git a/new/windows/checkbox.c b/new/windows/checkbox.c new file mode 100644 index 0000000..951eee9 --- /dev/null +++ b/new/windows/checkbox.c @@ -0,0 +1,123 @@ +// 7 april 2015 +#include "uipriv_windows.h" + +struct checkbox { + void (*onToggled)(uiControl *, void *); + void *onToggledData; +}; + +static BOOL onWM_COMMAND(uiControl *c, WORD code, LRESULT *lResult) +{ + struct checkbox *cc = (struct checkbox *) (c->data); + HWND hwnd; + WPARAM check; + + if (code != BN_CLICKED) + return FALSE; + + // we didn't use BS_AUTOCHECKBOX (see controls_windows.go) so we have to manage the check state ourselves + hwnd = uiControlHWND(c); + check = BST_CHECKED; + if (SendMessage(hwnd, BM_GETCHECK, 0, 0) == BST_CHECKED) + check = BST_UNCHECKED; + SendMessage(hwnd, BM_SETCHECK, check, 0); + + (*(cc->onToggled))(c, cc->onToggledData); + *lResult = 0; + return TRUE; +} + +static BOOL onWM_NOTIFY(uiControl *c, NMHDR *nm, LRESULT *lResult) +{ + return FALSE; +} + +static void onWM_DESTROY(uiControl *c) +{ + struct checkbox *cc = (struct checkbox *) (c->data); + + uiFree(cc); +} + +// from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing +#define checkboxHeight 10 +// from http://msdn.microsoft.com/en-us/library/windows/desktop/bb226818%28v=vs.85%29.aspx +#define checkboxXFromLeftOfBoxToLeftOfLabel 12 + +static void preferredSize(uiControl *c, uiSizing *d, intmax_t *width, intmax_t *height) +{ + *width = uiDlgUnitsToX(checkboxXFromLeftOfBoxToLeftOfLabel, d->sys->baseX) + uiWindowsWindowTextWidth(uiControlHWND(c)); + *height = uiDlgUnitsToY(checkboxHeight, d->sys->baseY); +} + +static void defaultOnToggled(uiControl *c, void *data) +{ + // do nothing +} + +uiControl *uiNewCheckbox(const char *text) +{ + uiControl *c; + struct checkbox *cc; + uiWindowsNewControlParams p; + WCHAR *wtext; + + p.dwExStyle = 0; + p.lpClassName = L"button"; + wtext = toUTF16(text); + p.lpWindowName = wtext; + p.dwStyle = BS_CHECKBOX | WS_TABSTOP; + p.hInstance = hInstance; + p.useStandardControlFont = TRUE; + p.onWM_COMMAND = onWM_COMMAND; + p.onWM_NOTIFY = onWM_NOTIFY; + p.onWM_DESTROY = onWM_DESTROY; + c = uiWindowsNewControl(&p); + uiFree(wtext); + + c->preferredSize = preferredSize; + + cc = uiNew(struct checkbox); + cc->onToggled = defaultOnToggled; + c->data = cc; + + return c; +} + +char *uiCheckboxText(uiControl *c) +{ + return uiWindowsControlText(c); +} + +void uiCheckboxSetText(uiControl *c, const char *text) +{ + uiWindowsControlSetText(c, text); +} + +void uiCheckboxOnToggled(uiControl *c, void (*f)(uiControl *, void *), void *data) +{ + struct checkbox *cc = (struct checkbox *) (c->data); + + cc->onToggled = f; + cc->onToggledData = data; +} + +int uiCheckboxChecked(uiControl *c) +{ + HWND hwnd; + + hwnd = uiControlHWND(c); + return SendMessage(hwnd, BM_GETCHECK, 0, 0) == BST_CHECKED; +} + +void uiCheckboxSetChecked(uiControl *c, int checked) +{ + HWND hwnd; + WPARAM check; + + hwnd = uiControlHWND(c); + check = BST_CHECKED; + if (!checked) + check = BST_UNCHECKED; + SendMessage(hwnd, BM_SETCHECK, check, 0); +} diff --git a/new/windows/comctl32.c b/new/windows/comctl32.c new file mode 100644 index 0000000..93b3a27 --- /dev/null +++ b/new/windows/comctl32.c @@ -0,0 +1,105 @@ +// 17 july 2014 +#include "uipriv_windows.h" + +static ULONG_PTR comctlManifestCookie; +static HMODULE comctl32; + +// these are listed as WINAPI in both Microsoft's and MinGW's headers, but not on MSDN for some reason +BOOL (*WINAPI fv_SetWindowSubclass)(HWND, SUBCLASSPROC, UINT_PTR, DWORD_PTR); +BOOL (*WINAPI fv_RemoveWindowSubclass)(HWND, SUBCLASSPROC, UINT_PTR); +LRESULT (*WINAPI fv_DefSubclassProc)(HWND, UINT, WPARAM, LPARAM); + +#define wantedICCClasses ( \ + ICC_STANDARD_CLASSES | /* user32.dll controls */ \ + ICC_PROGRESS_CLASS | /* progress bars */ \ + ICC_TAB_CLASSES | /* tabs */ \ + ICC_LISTVIEW_CLASSES | /* table headers */ \ + ICC_UPDOWN_CLASS | /* spinboxes */ \ + 0) + +// note that this is an 8-bit character string we're writing; see the encoding clause +static const char manifest[] = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersion=\"1.0\">\n<assemblyIdentity\n version=\"1.0.0.0\"\n processorArchitecture=\"*\"\n name=\"CompanyName.ProductName.YourApplication\"\n type=\"win32\"\n/>\n<description>Your application description here.</description>\n<dependency>\n <dependentAssembly>\n <assemblyIdentity\n type=\"win32\"\n name=\"Microsoft.Windows.Common-Controls\"\n version=\"6.0.0.0\"\n processorArchitecture=\"*\"\n publicKeyToken=\"6595b64144ccf1df\"\n language=\"*\"\n />\n </dependentAssembly>\n</dependency>\n</assembly>\n"; + +/* +Windows requires a manifest file to enable Common Controls version 6. +The only way to not require an external manifest is to synthesize the manifest ourselves. +We can use the activation context API to load it at runtime. +References: +- http://stackoverflow.com/questions/4308503/how-to-enable-visual-styles-without-a-manifest +- http://support.microsoft.com/kb/830033 +Because neither Go nor MinGW have ways to compile in resources like this (as far as I know), we have to do the work ourselves. +*/ +const char *initCommonControls(void) +{ + WCHAR temppath[MAX_PATH + 1]; + WCHAR filename[MAX_PATH + 1]; + HANDLE file; + DWORD nExpected, nGot; + ACTCTX actctx; + HANDLE ac; + INITCOMMONCONTROLSEX icc; + FARPROC f; + // this is listed as WINAPI in both Microsoft's and MinGW's headers, but not on MSDN for some reason + BOOL (*WINAPI ficc)(const LPINITCOMMONCONTROLSEX); + + if (GetTempPathW(MAX_PATH + 1, temppath) == 0) + return "getting temporary path for writing manifest file in initCommonControls()"; + if (GetTempFileNameW(temppath, L"manifest", 0, filename) == 0) + return "getting temporary filename for writing manifest file in initCommonControls()"; + file = CreateFileW(filename, GENERIC_WRITE, + 0, // don't share while writing + NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (file == NULL) + return "creating manifest file in initCommonControls()"; + nExpected = (sizeof manifest / sizeof manifest[0]) - 1; // - 1 to omit the terminating null character) + SetLastError(0); // catch errorless short writes + if (WriteFile(file, manifest, nExpected, &nGot, NULL) == 0) + return "writing manifest file in initCommonControls()"; + if (nGot != nExpected) { + DWORD lasterr; + + lasterr = GetLastError(); + if (lasterr == 0) + return "writing entire manifest file (short write) without error code in initCommonControls()"; + return "writing entire manifest file (short write) in initCommonControls()"; + } + if (CloseHandle(file) == 0) + return "closing manifest file (this IS an error here because not doing so will prevent Windows from being able to use the manifest file in an activation context) in initCommonControls()"; + + ZeroMemory(&actctx, sizeof (ACTCTX)); + actctx.cbSize = sizeof (ACTCTX); + actctx.dwFlags = ACTCTX_FLAG_SET_PROCESS_DEFAULT; + actctx.lpSource = filename; + ac = CreateActCtx(&actctx); + if (ac == INVALID_HANDLE_VALUE) + return "creating activation context for synthesized manifest file in initCommonControls()"; + if (ActivateActCtx(ac, &comctlManifestCookie) == FALSE) + return "activating activation context for synthesized manifest file in initCommonControls()"; + + ZeroMemory(&icc, sizeof (INITCOMMONCONTROLSEX)); + icc.dwSize = sizeof (INITCOMMONCONTROLSEX); + icc.dwICC = wantedICCClasses; + + comctl32 = LoadLibraryW(L"comctl32.dll"); + if (comctl32 == NULL) + return "loading comctl32.dll in initCommonControls()"; + + // GetProcAddress() only takes a multibyte string +#define LOAD(fn) f = GetProcAddress(comctl32, fn); \ + if (f == NULL) \ + return "loading " fn "() in initCommonControls()"; + + LOAD("InitCommonControlsEx"); + ficc = (BOOL (*WINAPI)(const LPINITCOMMONCONTROLSEX)) f; + LOAD("SetWindowSubclass"); + fv_SetWindowSubclass = (BOOL (*WINAPI)(HWND, SUBCLASSPROC, UINT_PTR, DWORD_PTR)) f; + LOAD("RemoveWindowSubclass"); + fv_RemoveWindowSubclass = (BOOL (*WINAPI)(HWND, SUBCLASSPROC, UINT_PTR)) f; + LOAD("DefSubclassProc"); + fv_DefSubclassProc = (LRESULT (*WINAPI)(HWND, UINT, WPARAM, LPARAM)) f; + + if ((*ficc)(&icc) == FALSE) + return "initializing Common Controls (comctl32.dll) in initCommonControls()"; + + return NULL; +} diff --git a/new/windows/debug.c b/new/windows/debug.c new file mode 100644 index 0000000..bc84b63 --- /dev/null +++ b/new/windows/debug.c @@ -0,0 +1,111 @@ +// 25 february 2015 +#include "uipriv_windows.h" + +// uncomment the following line to enable debug messages +#define tableDebug +// uncomment the following line to halt on a debug message +#define tableDebugStop + +#ifdef tableDebug + +#include <stdio.h> + +HRESULT logLastError(const char *context) +{ + DWORD le; + WCHAR *msg; + BOOL parenthesize = FALSE; + BOOL localFreeFailed = FALSE; + DWORD localFreeLastError; + + le = GetLastError(); + fprintf(stderr, "%s: ", context); + if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, le, 0, (LPWSTR) (&msg), 0, NULL) != 0) { + fprintf(stderr, "%S (", msg); + if (LocalFree(msg) != NULL) { + localFreeFailed = TRUE; + localFreeLastError = GetLastError(); + } + parenthesize = TRUE; + } + fprintf(stderr, "GetLastError() == %I32u", le); + if (parenthesize) + fprintf(stderr, ")"); + if (localFreeFailed) + fprintf(stderr, "; local free of system message failed with last error %I32u", localFreeLastError); + fprintf(stderr, "\n"); +#ifdef tableDebugStop + DebugBreak(); +#endif + SetLastError(le); + // a function does not have to set a last error + // if the last error we get is actually 0, then HRESULT_FROM_WIN32(0) will return S_OK (0 cast to an HRESULT, since 0 <= 0), which we don't want + // prevent this by returning E_FAIL, so the rest of the Table code doesn't barge onward + if (le == 0) + return E_FAIL; + return HRESULT_FROM_WIN32(le); +} + +HRESULT logHRESULT(const char *context, HRESULT hr) +{ + WCHAR *msg; + BOOL parenthesize = FALSE; + BOOL localFreeFailed = FALSE; + DWORD localFreeLastError; + + fprintf(stderr, "%s: ", context); + // this isn't technically documented, but everyone does it, including Microsoft (see the implementation of _com_error::ErrorMessage() in a copy of comdef.h that comes with the Windows DDK) + if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, (DWORD) hr, 0, (LPWSTR) (&msg), 0, NULL) != 0) { + fprintf(stderr, "%S (", msg); + if (LocalFree(msg) != NULL) { + localFreeFailed = TRUE; + localFreeLastError = GetLastError(); + } + parenthesize = TRUE; + } + fprintf(stderr, "HRESULT == 0x%I32X", hr); + if (parenthesize) + fprintf(stderr, ")"); + if (localFreeFailed) + fprintf(stderr, "; local free of system message failed with last error %I32u", localFreeLastError); + fprintf(stderr, "\n"); +#ifdef tableDebugStop + DebugBreak(); +#endif + return hr; +} + +HRESULT logMemoryExhausted(const char *reason) +{ + fprintf(stderr, "memory exhausted %s\n", reason); +#ifdef tableDebugStop + DebugBreak(); +#endif + return E_OUTOFMEMORY; +} + +#else + +HRESULT logLastError(const char *reason) +{ + DWORD le; + + le = GetLastError(); + // we shouldn't need to do this, but let's do this anyway just to be safe + SetLastError(le); + if (le == 0) + return E_FAIL; + return HRESULT_FROM_WIN32(le); +} + +HRESULT logHRESULT(const char *reason, HRESULT hr) +{ + return hr; +} + +HRESULT logMemoryExhausted(const char *reason) +{ + return E_OUTOFMEMORY; +} + +#endif diff --git a/new/windows/entry.c b/new/windows/entry.c new file mode 100644 index 0000000..2297c28 --- /dev/null +++ b/new/windows/entry.c @@ -0,0 +1,67 @@ +// 8 april 2015 +#include "uipriv_windows.h" + +struct entry { +}; + +static BOOL onWM_COMMAND(uiControl *c, WORD code, LRESULT *lResult) +{ + return FALSE; +} + +static BOOL onWM_NOTIFY(uiControl *c, NMHDR *nm, LRESULT *lResult) +{ + return FALSE; +} + +static void onWM_DESTROY(uiControl *c) +{ + struct entry *e = (struct entry *) (c->data); + + uiFree(e); +} + +// from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing +#define entryWidth 107 /* this is actually the shorter progress bar width, but Microsoft only indicates as wide as necessary */ +#define entryHeight 14 + +static void preferredSize(uiControl *c, uiSizing *d, intmax_t *width, intmax_t *height) +{ + *width = uiDlgUnitsToX(entryWidth, d->sys->baseX); + *height = uiDlgUnitsToY(entryHeight, d->sys->baseY); +} + +uiControl *uiNewEntry(void) +{ + uiControl *c; + struct entry *e; + uiWindowsNewControlParams p; + + p.dwExStyle = WS_EX_CLIENTEDGE; + p.lpClassName = L"edit"; + p.lpWindowName = L""; + p.dwStyle = ES_AUTOHSCROLL | ES_LEFT | ES_NOHIDESEL | WS_TABSTOP; + p.hInstance = hInstance; + p.useStandardControlFont = TRUE; + p.onWM_COMMAND = onWM_COMMAND; + p.onWM_NOTIFY = onWM_NOTIFY; + p.onWM_DESTROY = onWM_DESTROY; + c = uiWindowsNewControl(&p); + + c->preferredSize = preferredSize; + + e = uiNew(struct entry); + c->data = e; + + return c; +} + +char *uiEntryText(uiControl *c) +{ + return uiWindowsControlText(c); +} + +void uiEntrySetText(uiControl *c, const char *text) +{ + uiWindowsControlSetText(c, text); +} diff --git a/new/windows/init.c b/new/windows/init.c new file mode 100644 index 0000000..9903934 --- /dev/null +++ b/new/windows/init.c @@ -0,0 +1,112 @@ +// 6 april 2015 +#include "uipriv_windows.h" + +HINSTANCE hInstance; +int nCmdShow; + +HFONT hMessageFont; + +HBRUSH hollowBrush; + +struct uiInitError { + char *msg; + char failbuf[256]; +}; + +#define initErrorFormat L"error %s: %s%sGetLastError() == %I32u%s" +#define initErrorArgs wmessage, sysmsg, beforele, le, afterle + +static const char *loadLastError(const char *message) +{ + WCHAR *sysmsg; + BOOL hassysmsg; + WCHAR *beforele; + WCHAR *afterle; + int n; + WCHAR *wmessage; + WCHAR *wstr; + const char *str; + DWORD le; + + le = GetLastError(); + if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, le, 0, (LPWSTR) (&sysmsg), 0, NULL) != 0) { + hassysmsg = TRUE; + beforele = L" ("; + afterle = L")"; + } else { + hassysmsg = FALSE; + sysmsg = L""; + beforele = L""; + afterle = L""; + } + wmessage = toUTF16(message); + n = _scwprintf(initErrorFormat, initErrorArgs); + wstr = (WCHAR *) uiAlloc((n + 1) * sizeof (WCHAR), "WCHAR[]"); + snwprintf(wstr, n + 1, initErrorFormat, initErrorArgs); + str = toUTF8(wstr); + uiFree(wstr); + if (hassysmsg) + if (LocalFree(sysmsg) != NULL) + logLastError("error freeing system message in loadLastError()"); + uiFree(wmessage); + return str; +} + +uiInitOptions options; + +const char *uiInit(uiInitOptions *o) +{ + STARTUPINFOW si; + const char *ce; + HICON hDefaultIcon; + HCURSOR hDefaultCursor; + NONCLIENTMETRICSW ncm; + + options = *o; + + hInstance = GetModuleHandle(NULL); + if (hInstance == NULL) + return loadLastError("getting program HINSTANCE"); + + nCmdShow = SW_SHOWDEFAULT; + GetStartupInfoW(&si); + if ((si.dwFlags & STARTF_USESHOWWINDOW) != 0) + nCmdShow = si.wShowWindow; + + ce = initCommonControls(); + if (ce != NULL) + return loadLastError(ce); + + hDefaultIcon = LoadIconW(NULL, IDI_APPLICATION); + if (hDefaultIcon == NULL) + return loadLastError("loading default icon for window classes"); + hDefaultCursor = LoadCursorW(NULL, IDC_ARROW); + if (hDefaultCursor == NULL) + return loadLastError("loading default cursor for window classes"); + + if (registerWindowClass(hDefaultIcon, hDefaultCursor) == 0) + return loadLastError("registering uiWindow window class"); + + ZeroMemory(&ncm, sizeof (NONCLIENTMETRICSW)); + ncm.cbSize = sizeof (NONCLIENTMETRICSW); + if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof (NONCLIENTMETRICSW), &ncm, sizeof (NONCLIENTMETRICSW)) == 0) + return loadLastError("getting default fonts"); + hMessageFont = CreateFontIndirectW(&(ncm.lfMessageFont)); + if (hMessageFont == NULL) + return loadLastError("loading default messagebox font; this is the default UI font"); + + ce = initParent(hDefaultIcon, hDefaultCursor); + if (ce != NULL) + return loadLastError(ce); + + hollowBrush = (HBRUSH) GetStockObject(HOLLOW_BRUSH); + if (hollowBrush == NULL) + return loadLastError("getting hollow brush"); + + return NULL; +} + +void uiFreeInitError(const char *err) +{ + uiFree((void *) err); +} diff --git a/new/windows/label.c b/new/windows/label.c new file mode 100644 index 0000000..3d8a5cc --- /dev/null +++ b/new/windows/label.c @@ -0,0 +1,71 @@ +// 11 april 2015 +#include "uipriv_windows.h" + +struct label { +}; + +static BOOL onWM_COMMAND(uiControl *c, WORD code, LRESULT *lResult) +{ + return FALSE; +} + +static BOOL onWM_NOTIFY(uiControl *c, NMHDR *nm, LRESULT *lResult) +{ + return FALSE; +} + +static void onWM_DESTROY(uiControl *c) +{ + struct label *l = (struct label *) (c->data); + + uiFree(l); +} + +// via http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing +#define labelHeight 8 + +static void preferredSize(uiControl *c, uiSizing *d, intmax_t *width, intmax_t *height) +{ + *width = uiWindowsWindowTextWidth(uiControlHWND(c)); + *height = uiDlgUnitsToY(labelHeight, d->sys->baseY); +} + +uiControl *uiNewLabel(const char *text) +{ + uiControl *c; + struct label *l; + uiWindowsNewControlParams p; + WCHAR *wtext; + + p.dwExStyle = 0; + p.lpClassName = L"static"; + wtext = toUTF16(text); + p.lpWindowName = wtext; + // SS_LEFTNOWORDWRAP clips text past the end; SS_NOPREFIX avoids accelerator translation + // controls are vertically aligned to the top by default (thanks Xeek in irc.freenode.net/#winapi) + p.dwStyle = SS_LEFTNOWORDWRAP | SS_NOPREFIX; + p.hInstance = hInstance; + p.useStandardControlFont = TRUE; + p.onWM_COMMAND = onWM_COMMAND; + p.onWM_NOTIFY = onWM_NOTIFY; + p.onWM_DESTROY = onWM_DESTROY; + c = uiWindowsNewControl(&p); + uiFree(wtext); + + c->preferredSize = preferredSize; + + l = uiNew(struct label); + c->data = l; + + return c; +} + +char *uiLabelText(uiControl *c) +{ + return uiWindowsControlText(c); +} + +void uiLabelSetText(uiControl *c, const char *text) +{ + uiWindowsControlSetText(c, text); +} diff --git a/new/windows/main.c b/new/windows/main.c new file mode 100644 index 0000000..bd973b7 --- /dev/null +++ b/new/windows/main.c @@ -0,0 +1,56 @@ +// 6 april 2015 +#include "uipriv_windows.h" + +// #qo LDFLAGS: -luser32 -lkernel32 -lgdi32 -luxtheme -lmsimg32 -lcomdlg32 -lole32 -loleaut32 -loleacc -luuid + +static void uimsgloop_else(MSG *msg) +{ + TranslateMessage(msg); + DispatchMessage(msg); +} + +void uiMain(void) +{ + MSG msg; + int res; + HWND active, focus; + + for (;;) { + SetLastError(0); + res = GetMessageW(&msg, NULL, 0, 0); + if (res < 0) + logLastError("error calling GetMessage() in uiMain()"); + if (res == 0) // WM_QUIT + break; + active = GetActiveWindow(); + if (active == NULL) { + uimsgloop_else(&msg); + continue; + } + + // bit of logic involved here: + // we don't want dialog messages passed into Areas, so we don't call IsDialogMessageW() there + // as for Tabs, we can't have both WS_TABSTOP and WS_EX_CONTROLPARENT set at the same time, so we hotswap the two styles to get the behavior we want + focus = GetFocus(); + if (focus != NULL) { +/*TODO switch (windowClassOf(focus, areaWindowClass, WC_TABCONTROLW, NULL)) { + case 0: // areaWindowClass + uimsgloop_area(active, focus, &msg); + continue; + case 1: // WC_TABCONTROLW + uimsgloop_tab(active, focus, &msg); + continue; + } + // else fall through +*/ } + + if (IsDialogMessage(active, &msg) != 0) + continue; + uimsgloop_else(&msg); + } +} + +void uiQuit(void) +{ + PostQuitMessage(0); +} diff --git a/new/windows/newcontrol.c b/new/windows/newcontrol.c new file mode 100644 index 0000000..105b70c --- /dev/null +++ b/new/windows/newcontrol.c @@ -0,0 +1,241 @@ +// 6 april 2015 +#include "uipriv_windows.h" + +typedef struct singleHWND singleHWND; + +struct singleHWND { + HWND hwnd; + BOOL (*onWM_COMMAND)(uiControl *, WORD, LRESULT *); + BOOL (*onWM_NOTIFY)(uiControl *, NMHDR *, LRESULT *); + void (*onWM_DESTROY)(uiControl *); + uiParent *parent; + BOOL userHid; + BOOL containerHid; + BOOL userDisabled; + BOOL containerDisabled; +}; + +static void singleDestroy(uiControl *c) +{ + singleHWND *s = (singleHWND *) (c->internal); + + if (DestroyWindow(s->hwnd) == 0) + logLastError("error destroying control in singleDestroy()"); + // the data structures are destroyed in the subclass procedure +} + +static uintptr_t singleHandle(uiControl *c) +{ + singleHWND *s = (singleHWND *) (c->internal); + + return (uintptr_t) (s->hwnd); +} + +static void singleSetParent(uiControl *c, uiParent *parent) +{ + singleHWND *s = (singleHWND *) (c->internal); + uiParent *oldparent; + HWND newParentHWND; + + oldparent = s->parent; + s->parent = parent; + newParentHWND = initialParent; + if (s->parent != NULL) + newParentHWND = uiParentHWND(s->parent); + if (SetParent(s->hwnd, newParentHWND) == NULL) + logLastError("error setting control parent in singleSetParent()"); + if (oldparent != NULL) + uiParentUpdate(oldparent); + if (s->parent != NULL) + uiParentUpdate(s->parent); +} + +static void singleResize(uiControl *c, intmax_t x, intmax_t y, intmax_t width, intmax_t height, uiSizing *d) +{ + singleHWND *s = (singleHWND *) (c->internal); + + if (MoveWindow(s->hwnd, x, y, width, height, TRUE) == 0) + logLastError("error moving control in singleResize()"); +} + +static int singleVisible(uiControl *c) +{ + singleHWND *s = (singleHWND *) (c->internal); + + if (s->userHid) + return 0; + return 1; +} + +static void singleShow(uiControl *c) +{ + singleHWND *s = (singleHWND *) (c->internal); + + s->userHid = FALSE; + if (!s->containerHid) { + ShowWindow(s->hwnd, SW_SHOW); + if (s->parent != NULL) + uiParentUpdate(s->parent); + } +} + +static void singleHide(uiControl *c) +{ + singleHWND *s = (singleHWND *) (c->internal); + + s->userHid = TRUE; + ShowWindow(s->hwnd, SW_HIDE); + if (s->parent != NULL) + uiParentUpdate(s->parent); +} + +static void singleContainerShow(uiControl *c) +{ + singleHWND *s = (singleHWND *) (c->internal); + + s->containerHid = FALSE; + if (!s->userHid) { + ShowWindow(s->hwnd, SW_SHOW); + if (s->parent != NULL) + uiParentUpdate(s->parent); + } +} + +static void singleContainerHide(uiControl *c) +{ + singleHWND *s = (singleHWND *) (c->internal); + + s->containerHid = TRUE; + ShowWindow(s->hwnd, SW_HIDE); + if (s->parent != NULL) + uiParentUpdate(s->parent); +} + +static void singleEnable(uiControl *c) +{ + singleHWND *s = (singleHWND *) (c->internal); + + s->userDisabled = FALSE; + if (!s->containerDisabled) + EnableWindow(s->hwnd, TRUE); +} + +static void singleDisable(uiControl *c) +{ + singleHWND *s = (singleHWND *) (c->internal); + + s->userDisabled = TRUE; + EnableWindow(s->hwnd, FALSE); +} + +static void singleContainerEnable(uiControl *c) +{ + singleHWND *s = (singleHWND *) (c->internal); + + s->containerDisabled = FALSE; + if (!s->userDisabled) + EnableWindow(s->hwnd, TRUE); +} + +static void singleContainerDisable(uiControl *c) +{ + singleHWND *s = (singleHWND *) (c->internal); + + s->containerDisabled = TRUE; + EnableWindow(s->hwnd, FALSE); +} + +static LRESULT CALLBACK singleSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) +{ + uiControl *c = (uiControl *) dwRefData; + singleHWND *s = (singleHWND *) (c->internal); + LRESULT lResult; + + switch (uMsg) { + case msgCOMMAND: + if ((*(s->onWM_COMMAND))(c, HIWORD(wParam), &lResult) != FALSE) + return lResult; + break; + case msgNOTIFY: + if ((*(s->onWM_NOTIFY))(c, (NMHDR *) lParam, &lResult) != FALSE) + return lResult; + break; + case WM_DESTROY: + (*(s->onWM_DESTROY))(c); + uiFree(s); + uiFree(c); + break; + case WM_NCDESTROY: + if ((*fv_RemoveWindowSubclass)(hwnd, singleSubclassProc, uIdSubclass) == FALSE) + logLastError("error removing Windows control subclass in singleSubclassProc()"); + break; + } + return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam); +} + +uiControl *uiWindowsNewControl(uiWindowsNewControlParams *p) +{ + uiControl *c; + singleHWND *s; + + s = uiNew(singleHWND); + s->hwnd = CreateWindowExW(p->dwExStyle, + p->lpClassName, p->lpWindowName, + p->dwStyle | WS_CHILD | WS_VISIBLE, + 0, 0, + // use a nonzero initial size just in case some control breaks with a zero initial size + 100, 100, + initialParent, NULL, p->hInstance, NULL); + if (s->hwnd == NULL) + logLastError("error creating control in uiWindowsNewControl()"); + s->onWM_COMMAND = p->onWM_COMMAND; + s->onWM_NOTIFY = p->onWM_NOTIFY; + s->onWM_DESTROY = p->onWM_DESTROY; + + c = uiNew(uiControl); + c->destroy = singleDestroy; + c->handle = singleHandle; + c->setParent = singleSetParent; + c->resize = singleResize; + c->visible = singleVisible; + c->show = singleShow; + c->hide = singleHide; + c->containerShow = singleContainerShow; + c->containerHide = singleContainerHide; + c->enable = singleEnable; + c->disable = singleDisable; + c->containerEnable = singleContainerEnable; + c->containerDisable = singleContainerDisable; + + if (p->useStandardControlFont) + SendMessageW(s->hwnd, WM_SETFONT, (WPARAM) hMessageFont, (LPARAM) TRUE); + + if ((*fv_SetWindowSubclass)(s->hwnd, singleSubclassProc, 0, (DWORD_PTR) c) == FALSE) + logLastError("error subclassing Windows control in uiWindowsNewControl()"); + + c->internal = s; + return c; +} + +char *uiWindowsControlText(uiControl *c) +{ + singleHWND *s = (singleHWND *) (c->internal); + WCHAR *wtext; + char *text; + + wtext = windowText(s->hwnd); + text = toUTF8(wtext); + uiFree(wtext); + return text; +} + +void uiWindowsControlSetText(uiControl *c, const char *text) +{ + singleHWND *s = (singleHWND *) (c->internal); + WCHAR *wtext; + + wtext = toUTF16(text); + if (SetWindowTextW(s->hwnd, wtext) == 0) + logLastError("error setting control text in uiWindowsControlSetText()"); + uiFree(wtext); +} diff --git a/new/windows/parent.c b/new/windows/parent.c new file mode 100644 index 0000000..b3606d1 --- /dev/null +++ b/new/windows/parent.c @@ -0,0 +1,268 @@ +// 10 april 2015 +#include "uipriv_windows.h" + +// All controls in package ui are children of a window of this class. +// This keeps everything together, makes hiding controls en masse (tab page switching, for instance) easy, and makes the overall design cleaner. +// In addition, controls that are first created or don't have a parent are considered children of the "initial parent", which is also of this class. +// This parent is invisible, disabled, and should not be interacted with. + +// TODOs +// - wiith CTLCOLOR handler: [12:24] <ZeroOne> There's flickering between tabs +// - with CTLCOLOR handler: [12:24] <ZeroOne> And setting the button text blanked out the entire GUI until I ran my mouse over the elements / [12:25] <ZeroOne> https://dl.dropboxusercontent.com/u/15144168/GUI%20stuff.png / [12:41] <ZeroOne> https://dl.dropboxusercontent.com/u/15144168/stack.png here have another screenshot +// - I get this too + +#define uiParentClass L"uiParentClass" + +HWND initialParent; + +static void paintControlBackground(HWND hwnd, HDC dc) +{ + HWND parent; + RECT r; + POINT pOrig; + DWORD le; + + parent = hwnd; + for (;;) { + parent = GetParent(parent); + if (parent == NULL) + logLastError("error getting parent control of control in paintControlBackground()"); + // wine sends these messages early, yay... + if (parent == initialParent) + return; + // skip groupboxes; they're (supposed to be) transparent + if (windowClassOf(parent, L"button", NULL) != 0) + break; + } + if (GetWindowRect(hwnd, &r) == 0) + logLastError("error getting control's window rect in paintControlBackground()"); + // the above is a window rect in screen coordinates; convert to parent coordinates + SetLastError(0); + if (MapWindowRect(NULL, parent, &r) == 0) { + le = GetLastError(); + SetLastError(le); // just to be safe + if (le != 0) + logLastError("error getting client origin of control in paintControlBackground()"); + } + if (SetWindowOrgEx(dc, r.left, r.top, &pOrig) == 0) + logLastError("error moving window origin in paintControlBackground()"); + SendMessageW(parent, WM_PRINTCLIENT, (WPARAM) dc, PRF_CLIENT); + if (SetWindowOrgEx(dc, pOrig.x, pOrig.y, NULL) == 0) + logLastError("error resetting window origin in paintControlBackground()"); +} + +// from https://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing and https://msdn.microsoft.com/en-us/library/windows/desktop/bb226818%28v=vs.85%29.aspx +// this X value is really only for buttons but I don't see a better one :/ +#define winXPadding 4 +#define winYPadding 4 + +static void resize(uiControl *control, HWND parent, RECT r, RECT margin) +{ + uiSizing d; + uiSizingSys sys; + HDC dc; + HFONT prevfont; + TEXTMETRICW tm; + SIZE size; + + size.cx = 0; + size.cy = 0; + ZeroMemory(&tm, sizeof (TEXTMETRICW)); + dc = GetDC(parent); + if (dc == NULL) + logLastError("error getting DC in resize()"); + prevfont = (HFONT) SelectObject(dc, hMessageFont); + if (prevfont == NULL) + logLastError("error loading control font into device context in resize()"); + if (GetTextMetricsW(dc, &tm) == 0) + logLastError("error getting text metrics in resize()"); + if (GetTextExtentPoint32W(dc, L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 52, &size) == 0) + logLastError("error getting text extent point in resize()"); + sys.baseX = (int) ((size.cx / 26 + 1) / 2); + sys.baseY = (int) tm.tmHeight; + sys.internalLeading = tm.tmInternalLeading; + if (SelectObject(dc, prevfont) != hMessageFont) + logLastError("error restoring previous font into device context in resize()"); + if (ReleaseDC(parent, dc) == 0) + logLastError("error releasing DC in resize()"); + r.left += uiDlgUnitsToX(margin.left, sys.baseX); + r.top += uiDlgUnitsToY(margin.top, sys.baseY); + r.right -= uiDlgUnitsToX(margin.right, sys.baseX); + r.bottom -= uiDlgUnitsToY(margin.bottom, sys.baseY); + d.xPadding = uiDlgUnitsToX(winXPadding, sys.baseX); + d.yPadding = uiDlgUnitsToY(winYPadding, sys.baseY); + d.sys = &sys; + uiControlResize(control, r.left, r.top, r.right - r.left, r.bottom - r.top, &d); +} + +struct parent { + HWND hwnd; + uiControl *child; + intmax_t marginLeft; + intmax_t marginTop; + intmax_t marginRight; + intmax_t marginBottom; +}; + +static LRESULT CALLBACK parentWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + uiParent *p; + struct parent *pp; + CREATESTRUCTW *cs = (CREATESTRUCTW *) lParam; + HWND control; + NMHDR *nm = (NMHDR *) lParam; + WINDOWPOS *wp = (WINDOWPOS *) lParam; + RECT r, margin; + + // these must always be executed, even on the initial parent + // why? http://blogs.msdn.com/b/oldnewthing/archive/2010/03/16/9979112.aspx + switch (uMsg) { + case WM_COMMAND: + // bounce back to the control in question + // except if to the initial parent, in which case act as if the message was ignored + control = (HWND) lParam; + if (control != NULL && IsChild(initialParent, control) == 0) + return SendMessageW(control, msgCOMMAND, wParam, lParam); + return DefWindowProcW(hwnd, uMsg, wParam, lParam); + case WM_NOTIFY: + // same as WM_COMMAND + control = nm->hwndFrom; + if (control != NULL && IsChild(initialParent, control) == 0) + return SendMessageW(control, msgNOTIFY, wParam, lParam); + return DefWindowProcW(hwnd, uMsg, wParam, lParam); + case WM_CTLCOLORSTATIC: + case WM_CTLCOLORBTN: +/*TODO // read-only TextFields and Textboxes are exempt + // this is because read-only edit controls count under WM_CTLCOLORSTATIC + if (windowClassOf((HWND) lParam, L"edit", NULL) == 0) + if (textfieldReadOnly((HWND) lParam)) + return DefWindowProcW(hwnd, uMsg, wParam, lParam); +*/ if (SetBkMode((HDC) wParam, TRANSPARENT) == 0) + logLastError("error setting transparent background mode to controls in parentWndProc()"); + paintControlBackground((HWND) lParam, (HDC) wParam); + return (LRESULT) hollowBrush; + } + + // these are only executed on actual parents + p = (uiParent *) GetWindowLongPtrW(hwnd, GWLP_USERDATA); + if (p == NULL) { + if (uMsg == WM_NCCREATE) { + p = (uiParent *) (cs->lpCreateParams); + // this will be NULL for the initial parent; that's what we want + SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR) p); + // fall through to DefWindowProcW() + } + // this is the return the initial parent will always use + return DefWindowProcW(hwnd, uMsg, wParam, lParam); + } + pp = (struct parent *) (p->Internal); + switch (uMsg) { + case WM_NCDESTROY: + // no need to explicitly destroy children; they're already gone by this point (and so are their data structures; they clean up after themselves) + uiFree(p->Internal); + uiFree(p); + break; // fall through to DefWindowPocW() + case WM_WINDOWPOSCHANGED: + if ((wp->flags & SWP_NOSIZE) != 0) + break; + // fall through + case msgUpdateChild: + if (pp->child == NULL) + break; + if (GetClientRect(pp->hwnd, &r) == 0) + logLastError("error getting client rect for resize in parentWndProc()"); + margin.left = pp->marginLeft; + margin.top = pp->marginTop; + margin.right = pp->marginRight; + margin.bottom = pp->marginBottom; + resize(pp->child, pp->hwnd, r, margin); + return 0; + } + + return DefWindowProcW(hwnd, uMsg, wParam, lParam); +} + +const char *initParent(HICON hDefaultIcon, HCURSOR hDefaultCursor) +{ + WNDCLASSW wc; + + ZeroMemory(&wc, sizeof (WNDCLASSW)); + wc.lpszClassName = uiParentClass; + wc.lpfnWndProc = parentWndProc; + wc.hInstance = hInstance; + wc.hIcon = hDefaultIcon; + wc.hCursor = hDefaultCursor; + wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1); + if (RegisterClassW(&wc) == 0) + return "registering parent window class"; + + initialParent = CreateWindowExW(0, + uiParentClass, L"", + WS_OVERLAPPEDWINDOW, + 0, 0, + 100, 100, + NULL, NULL, hInstance, NULL); + if (initialParent == NULL) + return "creating initial parent window"; + + // just to be safe, disable the initial parent so it can't be interacted with accidentally + // if this causes issues for our controls, we can remove it + EnableWindow(initialParent, FALSE); + return NULL; +} + +static uintptr_t parentHandle(uiParent *p) +{ + struct parent *pp = (struct parent *) (p->Internal); + + return (uintptr_t) (pp->hwnd); +} + +static void parentSetChild(uiParent *p, uiControl *child) +{ + struct parent *pp = (struct parent *) (p->Internal); + + pp->child = child; + if (pp->child != NULL) + uiControlSetParent(child, p); +} + +static void parentSetMargins(uiParent *p, intmax_t left, intmax_t top, intmax_t right, intmax_t bottom) +{ + struct parent *pp = (struct parent *) (p->Internal); + + pp->marginLeft = left; + pp->marginTop = top; + pp->marginRight = right; + pp->marginBottom = bottom; +} + +static void parentUpdate(uiParent *p) +{ + struct parent *pp = (struct parent *) (p->Internal); + + SendMessageW(pp->hwnd, msgUpdateChild, 0, 0); +} + +uiParent *uiNewParent(uintptr_t osParent) +{ + uiParent *p; + struct parent *pp; + + p = uiNew(uiParent); + p->Internal = uiNew(struct parent); // set now in case the parent class window procedure uses it + pp = (struct parent *) (p->Internal); + pp->hwnd = CreateWindowExW(0, + uiParentClass, L"", + WS_CHILD | WS_VISIBLE, + 0, 0, + 0, 0, + (HWND) osParent, NULL, hInstance, p); + if (pp->hwnd == NULL) + logLastError("error creating uiParent window in uiNewParent()"); + p->Handle = parentHandle; + p->SetChild = parentSetChild; + p->SetMargins = parentSetMargins; + p->Update = parentUpdate; + return p; +} diff --git a/new/windows/tab.c b/new/windows/tab.c new file mode 100644 index 0000000..00fccb0 --- /dev/null +++ b/new/windows/tab.c @@ -0,0 +1,189 @@ +// 12 april 2015 +#include "uipriv_windows.h" + +// TODO +// - tab change notifications aren't being sent on wine (anymore...? TODO) +// - tell wine developers that tab controls do respond to parent changes on real windows (at least comctl6 tab controls do) + +struct tab { + uiParent **pages; + uintmax_t len; + uintmax_t cap; +}; + +static BOOL onWM_COMMAND(uiControl *c, WORD code, LRESULT *lResult) +{ + return FALSE; +} + +// we have to handle hiding and showing of tab pages ourselves +static BOOL onWM_NOTIFY(uiControl *c, NMHDR *nm, LRESULT *lResult) +{ + struct tab *t = (struct tab *) (c->data); + LRESULT n; + + switch (nm->code) { + case TCN_SELCHANGING: + n = SendMessageW(uiControlHWND(c), TCM_GETCURSEL, 0, 0); + if (n != (LRESULT) (-1)) // if we're changing to a real tab + ShowWindow(uiParentHWND(t->pages[n]), SW_HIDE); + *lResult = FALSE; // and allow the change + return TRUE; + case TCN_SELCHANGE: + n = SendMessageW(uiControlHWND(c), TCM_GETCURSEL, 0, 0); + if (n != (LRESULT) (-1)) { // if we're changing to a real tab + ShowWindow(uiParentHWND(t->pages[n]), SW_SHOW); + // because we only resize the current child on resize, we'll need to trigger an update here + // don't call uiParentUpdate(); doing that won't size the content area (so we'll still have a 0x0 content area, for instance) + SendMessageW(uiControlHWND(c), msgUpdateChild, 0, 0); + } + *lResult = 0; + return TRUE; + } + return FALSE; +} + +static void onWM_DESTROY(uiControl *c) +{ + struct tab *t = (struct tab *) (c->data); + + // no need to worry about freeing the pages themselves; they'll destroy themselves after we return + uiFree(t->pages); + uiFree(t); +} + +static void preferredSize(uiControl *c, uiSizing *d, intmax_t *width, intmax_t *height) +{ + // TODO +} + +// common code for resizes +static void resizeTab(uiControl *c, LONG width, LONG height) +{ + struct tab *t = (struct tab *) (c->data); + HWND hwnd; + LRESULT n; + RECT r; + + hwnd = uiControlHWND(c); + + n = SendMessageW(hwnd, TCM_GETCURSEL, 0, 0); + if (n == (LRESULT) (-1)) // no child selected; do nothing + return; + + // make a rect at (0, 0) of the given window size + // this should give us the correct client coordinates + r.left = 0; + r.top = 0; + r.right = width; + r.bottom = height; + // convert to the display rectangle + SendMessageW(hwnd, TCM_ADJUSTRECT, FALSE, (LPARAM) (&r)); + + if (MoveWindow(uiParentHWND(t->pages[n]), r.left, r.top, r.right - r.left, r.bottom - r.top, TRUE) == 0) + logLastError("error resizing current tab page in resizeTab()"); +} + +// and finally, because we have to resize parents, we have to handle resizes and updates +static LRESULT CALLBACK tabSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) +{ + uiControl *c = (uiControl *) dwRefData; + WINDOWPOS *wp = (WINDOWPOS *) lParam; + LRESULT lResult; + RECT r; + + switch (uMsg) { + case WM_WINDOWPOSCHANGED: + if ((wp->flags & SWP_NOSIZE) != 0) + break; + // first, let the tab control handle it + lResult = (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam); + // we have the window rect width as part of the WINDOWPOS; resize + resizeTab(c, wp->cx, wp->cy); + return lResult; + case msgUpdateChild: + if (GetWindowRect(uiControlHWND(c), &r) == 0) + logLastError("error getting Tab window rect for synthesized resize message in tabSubProc()"); + // these are in screen coordinates, which match what WM_WINDOWPOSCHANGED gave us (thanks TODOTODOTODOTODOTODOTODOTODO) + resizeTab(c, r.right - r.left, r.bottom - r.top); + return 0; + case WM_NCDESTROY: + if ((*fv_RemoveWindowSubclass)(hwnd, tabSubProc, uIdSubclass) == FALSE) + logLastError("error removing Tab resize handling subclass in tabSubProc()"); + break; + } + return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam); +} + +uiControl *uiNewTab(void) +{ + uiControl *c; + struct tab *t; + uiWindowsNewControlParams p; + HWND hwnd; + + p.dwExStyle = 0; // don't set WS_EX_CONTROLPARENT yet; we do that dynamically in the message loop (see main_windows.c) + p.lpClassName = WC_TABCONTROLW; + p.lpWindowName = L""; + p.dwStyle = TCS_TOOLTIPS | WS_TABSTOP; + p.hInstance = hInstance; + p.useStandardControlFont = TRUE; + p.onWM_COMMAND = onWM_COMMAND; + p.onWM_NOTIFY = onWM_NOTIFY; + p.onWM_DESTROY = onWM_DESTROY; + c = uiWindowsNewControl(&p); + + c->preferredSize = preferredSize; + + t = uiNew(struct tab); + c->data = t; + + hwnd = uiControlHWND(c); + if ((*fv_SetWindowSubclass)(hwnd, tabSubProc, 0, (DWORD_PTR) c) == FALSE) + logLastError("error subclassing Tab to give it its own resize handler in uiNewTab()"); + + return c; +} + +#define tabCapGrow 32 + +void uiTabAddPage(uiControl *c, const char *name, uiControl *child) +{ + struct tab *t = (struct tab *) (c->data); + HWND hwnd; + TCITEMW item; + LRESULT n; + uiParent *parent; + WCHAR *wname; + + if (t->len >= t->cap) { + t->cap += tabCapGrow; + t->pages = (uiParent **) uiRealloc(t->pages, t->cap * sizeof (uiParent *), "uiParent *[]"); + } + + hwnd = uiControlHWND(c); + n = SendMessageW(hwnd, TCM_GETITEMCOUNT, 0, 0); + + parent = uiNewParent((uintptr_t) hwnd); + uiParentSetChild(parent, child); + uiParentUpdate(parent); + if (n != 0) // if this isn't the first page, we have to hide the other controls + ShowWindow(uiParentHWND(parent), SW_HIDE); + t->pages[t->len] = parent; + t->len++; + + ZeroMemory(&item, sizeof (TCITEMW)); + item.mask = TCIF_TEXT; + wname = toUTF16(name); + item.pszText = wname; + // MSDN's example code uses the first invalid index directly for this + if (SendMessageW(hwnd, TCM_INSERTITEM, (WPARAM) n, (LPARAM) (&item)) == (LRESULT) -1) + logLastError("error adding tab to Tab in uiTabAddPage()"); + uiFree(wname); + + // if this is the first tab, Windows will automatically show it /without/ sending a TCN_SELCHANGE notification + // (TODO verify that) + // so we need to manually resize the tab ourselves + // don't use uiUpdateParent() for the same reason as in the TCN_SELCHANGE handler + SendMessageW(uiControlHWND(c), msgUpdateChild, 0, 0); +} diff --git a/new/windows/text.c b/new/windows/text.c new file mode 100644 index 0000000..8d6d0b0 --- /dev/null +++ b/new/windows/text.c @@ -0,0 +1,55 @@ +// 9 april 2015 +#include "uipriv_windows.h" + +// see http://stackoverflow.com/a/29556509/3408572 + +#define MBTWC(str, wstr, bufsiz) MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, bufsiz) + +WCHAR *toUTF16(const char *str) +{ + WCHAR *wstr; + int n; + + n = MBTWC(str, NULL, 0); + if (n == 0) + logLastError("error figuring out number of characters to convert to in toUTF16()"); + wstr = (WCHAR *) uiAlloc(n * sizeof (WCHAR), "WCHAR[]"); + if (MBTWC(str, wstr, n) != n) + logLastError("error converting from UTF-8 to UTF-16 in toUTF16()"); + return wstr; +} + +#define WCTMB(wstr, str, bufsiz) WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, bufsiz, NULL, NULL) + +char *toUTF8(const WCHAR *wstr) +{ + char *str; + int n; + + n = WCTMB(wstr, NULL, 0); + if (n == 0) + logLastError("error figuring out number of characters to convert to in toUTF8()"); + str = (char *) uiAlloc(n * sizeof (char), "char[]"); + if (WCTMB(wstr, str, n) != n) + logLastError("error converting from UTF-16 to UTF-8 in toUTFF8()"); + return str; +} + +WCHAR *windowText(HWND hwnd) +{ + LRESULT n; + WCHAR *text; + + n = SendMessageW(hwnd, WM_GETTEXTLENGTH, 0, 0); + // WM_GETTEXTLENGTH does not include the null terminator + text = (WCHAR *) uiAlloc((n + 1) * sizeof (WCHAR), "WCHAR[]"); + // note the comparison: the size includes the null terminator, but the return does not + if (GetWindowTextW(hwnd, text, n + 1) != n) + logLastError("error getting window text in windowText()"); + return text; +} + +void uiFreeText(char *text) +{ + uiFree(text); +} diff --git a/new/windows/util.c b/new/windows/util.c new file mode 100644 index 0000000..93b32d8 --- /dev/null +++ b/new/windows/util.c @@ -0,0 +1,73 @@ +// 6 april 2015 +#include "uipriv_windows.h" + +intmax_t uiWindowsWindowTextWidth(HWND hwnd) +{ + LRESULT len; + WCHAR *text; + HDC dc; + HFONT prevfont; + SIZE size; + + size.cx = 0; + size.cy = 0; + + // first we need the window text + len = SendMessageW(hwnd, WM_GETTEXTLENGTH, 0, 0); + if (len == 0) // no text; nothing to do + return 0; + text = (WCHAR *) uiAlloc((len + 1) * sizeof (WCHAR), "WCHAR[]"); + // note the comparison: the size includes the null terminator, but the return does not + if (GetWindowText(hwnd, text, len + 1) != len) + logLastError("error getting window text in uiWindowsWindowTextWidth()"); + + // now we can do the calculations + dc = GetDC(hwnd); + if (dc == NULL) + logLastError("error getting DC in uiWindowsWindowTextWidth()"); + prevfont = (HFONT) SelectObject(dc, hMessageFont); + if (prevfont == NULL) + logLastError("error loading control font into device context in uiWindowsWindowTextWidth()"); + if (GetTextExtentPoint32W(dc, text, len, &size) == 0) + logLastError("error getting text extent point in uiWindowsWindowTextWidth()"); + if (SelectObject(dc, prevfont) != hMessageFont) + logLastError("error restoring previous font into device context in uiWindowsWindowTextWidth()"); + if (ReleaseDC(hwnd, dc) == 0) + logLastError("error releasing DC in uiWindowsWindowTextWidth()"); + uiFree(text); + + return size.cx; +} + +// this is a helper function that takes the logic of determining window classes and puts it all in one place +// there are a number of places where we need to know what window class an arbitrary handle has +// theoretically we could use the class atom to avoid a _wcsicmp() +// however, raymond chen advises against this - http://blogs.msdn.com/b/oldnewthing/archive/2004/10/11/240744.aspx (and we're not in control of the Tab class, before you say anything) +// usage: windowClassOf(hwnd, L"class 1", L"class 2", ..., NULL) +int windowClassOf(HWND hwnd, ...) +{ +// MSDN says 256 is the maximum length of a class name; add a few characters just to be safe (because it doesn't say whether this includes the terminating null character) +#define maxClassName 260 + WCHAR classname[maxClassName + 1]; + va_list ap; + WCHAR *curname; + int i; + + if (GetClassNameW(hwnd, classname, maxClassName) == 0) + logLastError("error getting name of window class in windowClassOf()"); + va_start(ap, hwnd); + i = 0; + for (;;) { + curname = va_arg(ap, WCHAR *); + if (curname == NULL) + break; + if (_wcsicmp(classname, curname) == 0) { + va_end(ap); + return i; + } + i++; + } + // no match + va_end(ap); + return -1; +} diff --git a/new/windows/window.c b/new/windows/window.c new file mode 100644 index 0000000..fb92c0d --- /dev/null +++ b/new/windows/window.c @@ -0,0 +1,186 @@ +// 6 april 2015 +#include "uipriv_windows.h" + +struct uiWindow { + HWND hwnd; + uiParent *content; + BOOL shownOnce; + int (*onClosing)(uiWindow *, void *); + void *onClosingData; + int margined; +}; + +#define uiWindowClass L"uiWindowClass" + +static LRESULT CALLBACK uiWindowWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + uiWindow *w; + CREATESTRUCTW *cs = (CREATESTRUCTW *) lParam; + WINDOWPOS *wp = (WINDOWPOS *) lParam; + RECT r; + HWND contenthwnd; + + w = (uiWindow *) GetWindowLongPtrW(hwnd, GWLP_USERDATA); + if (w == NULL) { + if (uMsg == WM_CREATE) + SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR) (cs->lpCreateParams)); + // fall through to DefWindowProc() anyway + return DefWindowProcW(hwnd, uMsg, wParam, lParam); + } + switch (uMsg) { + case WM_WINDOWPOSCHANGED: + if ((wp->flags & SWP_NOSIZE) != 0) + break; + // fall through + case msgUpdateChild: + if (GetClientRect(w->hwnd, &r) == 0) + logLastError("error getting window client rect for resize in uiWindowWndProc()"); + contenthwnd = uiParentHWND(w->content); + if (MoveWindow(contenthwnd, r.left, r.top, r.right - r.left, r.bottom - r.top, TRUE) == 0) + logLastError("error resizing window content parent in uiWindowWndProc()"); + return 0; + case WM_CLOSE: + if (!(*(w->onClosing))(w, w->onClosingData)) + return 0; + break; // fall through to DefWindowProcW() + case WM_DESTROY: + // no need to free the child ourselves; it'll destroy itself after we leave this handler + uiFree(w); + break; // fall through to DefWindowProcW() + } + return DefWindowProcW(hwnd, uMsg, wParam, lParam); +} + +ATOM registerWindowClass(HICON hDefaultIcon, HCURSOR hDefaultCursor) +{ + WNDCLASSW wc; + + ZeroMemory(&wc, sizeof (WNDCLASSW)); + wc.lpszClassName = uiWindowClass; + wc.lpfnWndProc = uiWindowWndProc; + wc.hInstance = hInstance; + wc.hIcon = hDefaultIcon; + wc.hCursor = hDefaultCursor; + wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1); + return RegisterClassW(&wc); +} + +#define exstyle 0 +#define style WS_OVERLAPPEDWINDOW + +static int defaultOnClosing(uiWindow *w, void *data) +{ + return 1; +} + +uiWindow *uiNewWindow(char *title, int width, int height) +{ + uiWindow *w; + RECT adjust; + WCHAR *wtitle; + + w = uiNew(uiWindow); + w->onClosing = defaultOnClosing; + + adjust.left = 0; + adjust.top = 0; + adjust.right = width; + adjust.bottom = height; + if (AdjustWindowRectEx(&adjust, style, FALSE, exstyle) == 0) + logLastError("error getting real window coordinates in uiWindow()"); + + wtitle = toUTF16(title); + w->hwnd = CreateWindowExW(exstyle, + uiWindowClass, wtitle, + style, + CW_USEDEFAULT, CW_USEDEFAULT, + adjust.right - adjust.left, adjust.bottom - adjust.top, + NULL, NULL, hInstance, w); + if (w->hwnd == NULL) + logLastError("error creating window in uiWindow()"); + uiFree(wtitle); + + w->content = uiNewParent((uintptr_t) (w->hwnd)); + + return w; +} + +void uiWindowDestroy(uiWindow *w) +{ + DestroyWindow(w->hwnd); +} + +uintptr_t uiWindowHandle(uiWindow *w) +{ + return (uintptr_t) (w->hwnd); +} + +char *uiWindowTitle(uiWindow *w) +{ + WCHAR *wtext; + char *text; + + wtext = windowText(w->hwnd); + text = toUTF8(wtext); + uiFree(wtext); + return text; +} + +void uiWindowSetTitle(uiWindow *w, const char *text) +{ + WCHAR *wtext; + + wtext = toUTF16(text); + if (SetWindowTextW(w->hwnd, wtext) == 0) + logLastError("error setting window title in uiWindowSetTitle()"); + uiFree(wtext); +} + +void uiWindowShow(uiWindow *w) +{ + if (w->shownOnce) { + ShowWindow(w->hwnd, SW_SHOW); + return; + } + w->shownOnce = TRUE; + ShowWindow(w->hwnd, nCmdShow); + if (UpdateWindow(w->hwnd) == 0) + logLastError("error calling UpdateWindow() after showing uiWindow for the first time"); +} + +void uiWindowHide(uiWindow *w) +{ + ShowWindow(w->hwnd, SW_HIDE); +} + +void uiWindowOnClosing(uiWindow *w, int (*f)(uiWindow *, void *), void *data) +{ + w->onClosing = f; + w->onClosingData = data; +} + +void uiWindowSetChild(uiWindow *w, uiControl *c) +{ + uiParentSetChild(w->content, c); + // don't call uiParentUpdate(); instead, synthesize a resize + // otherwise, we'll have a 0x0 content area at first + SendMessageW(w->hwnd, msgUpdateChild, 0, 0); +} + +int uiWindowMargined(uiWindow *w) +{ + return w->margined; +} + +// from https://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing +#define windowMargin 7 + +void uiWindowSetMargined(uiWindow *w, int margined) +{ + w->margined = margined; + if (w->margined) + uiParentSetMargins(w->content, windowMargin, windowMargin, windowMargin, windowMargin); + else + uiParentSetMargins(w->content, 0, 0, 0, 0); + uiParentUpdate(w->content); +} |
