summaryrefslogtreecommitdiff
path: root/new/windows
diff options
context:
space:
mode:
Diffstat (limited to 'new/windows')
-rw-r--r--new/windows/alloc.c49
-rw-r--r--new/windows/button.c108
-rw-r--r--new/windows/checkbox.c123
-rw-r--r--new/windows/comctl32.c105
-rw-r--r--new/windows/debug.c111
-rw-r--r--new/windows/entry.c67
-rw-r--r--new/windows/init.c112
-rw-r--r--new/windows/label.c71
-rw-r--r--new/windows/main.c56
-rw-r--r--new/windows/newcontrol.c241
-rw-r--r--new/windows/parent.c268
-rw-r--r--new/windows/tab.c189
-rw-r--r--new/windows/text.c55
-rw-r--r--new/windows/util.c73
-rw-r--r--new/windows/window.c186
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);
+}