From 518a5ddbf15d50a254c732a80d5907ef8878abe0 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Wed, 15 Apr 2015 18:49:45 -0400 Subject: Split all OS backends into their own folders. --- new/alloc_darwin.m | 44 -------- new/alloc_unix.c | 33 ------ new/alloc_windows.c | 49 --------- new/button_darwin.m | 79 -------------- new/button_unix.c | 67 ------------ new/button_windows.c | 108 ------------------- new/checkbox_darwin.m | 98 ----------------- new/checkbox_unix.c | 89 ---------------- new/checkbox_windows.c | 123 ---------------------- new/comctl32_windows.c | 105 ------------------- new/darwin/alloc.m | 44 ++++++++ new/darwin/button.m | 79 ++++++++++++++ new/darwin/checkbox.m | 98 +++++++++++++++++ new/darwin/entry.m | 67 ++++++++++++ new/darwin/init.m | 67 ++++++++++++ new/darwin/label.m | 53 ++++++++++ new/darwin/main.m | 27 +++++ new/darwin/newcontrol.m | 236 +++++++++++++++++++++++++++++++++++++++++ new/darwin/parent.m | 118 +++++++++++++++++++++ new/darwin/tab.m | 67 ++++++++++++ new/darwin/text.m | 19 ++++ new/darwin/util.m | 20 ++++ new/darwin/window.m | 145 +++++++++++++++++++++++++ new/debug_windows.c | 111 -------------------- new/entry_darwin.m | 67 ------------ new/entry_unix.c | 41 -------- new/entry_windows.c | 67 ------------ new/init_darwin.m | 67 ------------ new/init_unix.c | 23 ---- new/init_windows.c | 112 -------------------- new/label_darwin.m | 53 ---------- new/label_unix.c | 45 -------- new/label_windows.c | 71 ------------- new/main_darwin.m | 27 ----- new/main_unix.c | 23 ---- new/main_windows.c | 56 ---------- new/newcontrol_darwin.m | 236 ----------------------------------------- new/newcontrol_unix.c | 229 ---------------------------------------- new/newcontrol_windows.c | 241 ------------------------------------------ new/parent_darwin.m | 118 --------------------- new/parent_unix.c | 183 -------------------------------- new/parent_windows.c | 268 ----------------------------------------------- new/tab_darwin.m | 67 ------------ new/tab_unix.c | 58 ---------- new/tab_windows.c | 189 --------------------------------- new/text_darwin.m | 19 ---- new/text_windows.c | 55 ---------- new/unix/alloc.c | 33 ++++++ new/unix/button.c | 67 ++++++++++++ new/unix/checkbox.c | 89 ++++++++++++++++ new/unix/entry.c | 41 ++++++++ new/unix/init.c | 23 ++++ new/unix/label.c | 45 ++++++++ new/unix/main.c | 23 ++++ new/unix/newcontrol.c | 229 ++++++++++++++++++++++++++++++++++++++++ new/unix/parent.c | 183 ++++++++++++++++++++++++++++++++ new/unix/tab.c | 58 ++++++++++ new/unix/util.c | 7 ++ new/unix/window.c | 105 +++++++++++++++++++ new/util_darwin.m | 20 ---- new/util_unix.c | 7 -- new/util_windows.c | 73 ------------- new/window_darwin.m | 145 ------------------------- new/window_unix.c | 105 ------------------- new/window_windows.c | 186 -------------------------------- new/windows/alloc.c | 49 +++++++++ new/windows/button.c | 108 +++++++++++++++++++ new/windows/checkbox.c | 123 ++++++++++++++++++++++ new/windows/comctl32.c | 105 +++++++++++++++++++ new/windows/debug.c | 111 ++++++++++++++++++++ new/windows/entry.c | 67 ++++++++++++ new/windows/init.c | 112 ++++++++++++++++++++ new/windows/label.c | 71 +++++++++++++ new/windows/main.c | 56 ++++++++++ new/windows/newcontrol.c | 241 ++++++++++++++++++++++++++++++++++++++++++ new/windows/parent.c | 268 +++++++++++++++++++++++++++++++++++++++++++++++ new/windows/tab.c | 189 +++++++++++++++++++++++++++++++++ new/windows/text.c | 55 ++++++++++ new/windows/util.c | 73 +++++++++++++ new/windows/window.c | 186 ++++++++++++++++++++++++++++++++ 80 files changed, 3757 insertions(+), 3757 deletions(-) delete mode 100644 new/alloc_darwin.m delete mode 100644 new/alloc_unix.c delete mode 100644 new/alloc_windows.c delete mode 100644 new/button_darwin.m delete mode 100644 new/button_unix.c delete mode 100644 new/button_windows.c delete mode 100644 new/checkbox_darwin.m delete mode 100644 new/checkbox_unix.c delete mode 100644 new/checkbox_windows.c delete mode 100644 new/comctl32_windows.c create mode 100644 new/darwin/alloc.m create mode 100644 new/darwin/button.m create mode 100644 new/darwin/checkbox.m create mode 100644 new/darwin/entry.m create mode 100644 new/darwin/init.m create mode 100644 new/darwin/label.m create mode 100644 new/darwin/main.m create mode 100644 new/darwin/newcontrol.m create mode 100644 new/darwin/parent.m create mode 100644 new/darwin/tab.m create mode 100644 new/darwin/text.m create mode 100644 new/darwin/util.m create mode 100644 new/darwin/window.m delete mode 100644 new/debug_windows.c delete mode 100644 new/entry_darwin.m delete mode 100644 new/entry_unix.c delete mode 100644 new/entry_windows.c delete mode 100644 new/init_darwin.m delete mode 100644 new/init_unix.c delete mode 100644 new/init_windows.c delete mode 100644 new/label_darwin.m delete mode 100644 new/label_unix.c delete mode 100644 new/label_windows.c delete mode 100644 new/main_darwin.m delete mode 100644 new/main_unix.c delete mode 100644 new/main_windows.c delete mode 100644 new/newcontrol_darwin.m delete mode 100644 new/newcontrol_unix.c delete mode 100644 new/newcontrol_windows.c delete mode 100644 new/parent_darwin.m delete mode 100644 new/parent_unix.c delete mode 100644 new/parent_windows.c delete mode 100644 new/tab_darwin.m delete mode 100644 new/tab_unix.c delete mode 100644 new/tab_windows.c delete mode 100644 new/text_darwin.m delete mode 100644 new/text_windows.c create mode 100644 new/unix/alloc.c create mode 100644 new/unix/button.c create mode 100644 new/unix/checkbox.c create mode 100644 new/unix/entry.c create mode 100644 new/unix/init.c create mode 100644 new/unix/label.c create mode 100644 new/unix/main.c create mode 100644 new/unix/newcontrol.c create mode 100644 new/unix/parent.c create mode 100644 new/unix/tab.c create mode 100644 new/unix/util.c create mode 100644 new/unix/window.c delete mode 100644 new/util_darwin.m delete mode 100644 new/util_unix.c delete mode 100644 new/util_windows.c delete mode 100644 new/window_darwin.m delete mode 100644 new/window_unix.c delete mode 100644 new/window_windows.c create mode 100644 new/windows/alloc.c create mode 100644 new/windows/button.c create mode 100644 new/windows/checkbox.c create mode 100644 new/windows/comctl32.c create mode 100644 new/windows/debug.c create mode 100644 new/windows/entry.c create mode 100644 new/windows/init.c create mode 100644 new/windows/label.c create mode 100644 new/windows/main.c create mode 100644 new/windows/newcontrol.c create mode 100644 new/windows/parent.c create mode 100644 new/windows/tab.c create mode 100644 new/windows/text.c create mode 100644 new/windows/util.c create mode 100644 new/windows/window.c diff --git a/new/alloc_darwin.m b/new/alloc_darwin.m deleted file mode 100644 index 8f539af..0000000 --- a/new/alloc_darwin.m +++ /dev/null @@ -1,44 +0,0 @@ -// 4 december 2014 -#import -#import "uipriv_darwin.h" - -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(); - } - memset(out, 0, 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/alloc_unix.c b/new/alloc_unix.c deleted file mode 100644 index 33482b2..0000000 --- a/new/alloc_unix.c +++ /dev/null @@ -1,33 +0,0 @@ -// 7 april 2015 -#include -#include "uipriv_unix.h" - -void *uiAlloc(size_t size, const char *type) -{ - void *out; - - out = g_malloc0(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); - // TODO fill with 0s - out = g_realloc(p, size); - if (options.debugLogAllocations) - fprintf(stderr, "%p realloc %p\n", p, out); - return out; -} - -void uiFree(void *p) -{ - g_free(p); - if (options.debugLogAllocations) - fprintf(stderr, "%p free\n", p); -} diff --git a/new/alloc_windows.c b/new/alloc_windows.c deleted file mode 100644 index 147c90d..0000000 --- a/new/alloc_windows.c +++ /dev/null @@ -1,49 +0,0 @@ -// 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/button_darwin.m b/new/button_darwin.m deleted file mode 100644 index 92e2c76..0000000 --- a/new/button_darwin.m +++ /dev/null @@ -1,79 +0,0 @@ -// 7 april 2015 -#import "uipriv_darwin.h" - -@interface uiNSButton : NSButton -@property uiControl *uiC; -@property void (*uiOnClicked)(uiControl *, void *); -@property void *uiOnClickedData; -@end - -@implementation uiNSButton - -- (void)viewDidMoveToSuperview -{ - if (uiDarwinControlFreeWhenAppropriate(self.uiC, [self superview])) { - [self setTarget:nil]; - self.uiC = NULL; - } - [super viewDidMoveToSuperview]; -} - -- (IBAction)uiButtonClicked:(id)sender -{ - (*(self.uiOnClicked))(self.uiC, self.uiOnClickedData); -} - -@end - -static void defaultOnClicked(uiControl *c, void *data) -{ - // do nothing -} - -uiControl *uiNewButton(const char *text) -{ - uiControl *c; - uiNSButton *b; - - c = uiDarwinNewControl([uiNSButton class], NO, NO); - b = (uiNSButton *) uiControlHandle(c); - b.uiC = c; - - [b setTitle:toNSString(text)]; - [b setButtonType:NSMomentaryPushInButton]; - [b setBordered:YES]; - [b setBezelStyle:NSRoundedBezelStyle]; - setStandardControlFont((NSControl *) b); - - [b setTarget:b]; - [b setAction:@selector(uiButtonClicked:)]; - - b.uiOnClicked = defaultOnClicked; - - return b.uiC; -} - -char *uiButtonText(uiControl *c) -{ - uiNSButton *b; - - b = (uiNSButton *) uiControlHandle(c); - return uiDarwinNSStringToText([b title]); -} - -void uiButtonSetText(uiControl *c, const char *text) -{ - uiNSButton *b; - - b = (uiNSButton *) uiControlHandle(c); - [b setTitle:toNSString(text)]; -} - -void uiButtonOnClicked(uiControl *c, void (*f)(uiControl *, void *), void *data) -{ - uiNSButton *b; - - b = (uiNSButton *) uiControlHandle(c); - b.uiOnClicked = f; - b.uiOnClickedData = data; -} diff --git a/new/button_unix.c b/new/button_unix.c deleted file mode 100644 index da09082..0000000 --- a/new/button_unix.c +++ /dev/null @@ -1,67 +0,0 @@ -// 7 april 2015 -#include "uipriv_unix.h" - -struct button { - void (*onClicked)(uiControl *, void *); - void *onClickedData; -}; - -static void onClicked(GtkButton *button, gpointer data) -{ - uiControl *c = (uiControl *) data; - struct button *b = (struct button *) (c->data); - - (*(b->onClicked))(c, b->onClickedData); -} - -static void defaultOnClicked(uiControl *c, void *data) -{ - // do nothing -} - -static void onDestroy(GtkWidget *widget, gpointer data) -{ - struct button *b = (struct button *) data; - - uiFree(b); -} - -uiControl *uiNewButton(const char *text) -{ - uiControl *c; - struct button *b; - GtkWidget *widget; - - c = uiUnixNewControl(GTK_TYPE_BUTTON, - FALSE, FALSE, - "label", text, - NULL); - - widget = GTK_WIDGET(uiControlHandle(c)); - g_signal_connect(widget, "clicked", G_CALLBACK(onClicked), c); - - b = uiNew(struct button); - g_signal_connect(widget, "destroy", G_CALLBACK(onDestroy), b); - b->onClicked = defaultOnClicked; - c->data = b; - - return c; -} - -char *uiButtonText(uiControl *c) -{ - return g_strdup(gtk_button_get_label(GTK_BUTTON(uiControlHandle(c)))); -} - -void uiButtonSetText(uiControl *c, const char *text) -{ - gtk_button_set_label(GTK_BUTTON(uiControlHandle(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/button_windows.c b/new/button_windows.c deleted file mode 100644 index f89a197..0000000 --- a/new/button_windows.c +++ /dev/null @@ -1,108 +0,0 @@ -// 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/checkbox_darwin.m b/new/checkbox_darwin.m deleted file mode 100644 index ca52389..0000000 --- a/new/checkbox_darwin.m +++ /dev/null @@ -1,98 +0,0 @@ -// 7 april 2015 -#import "uipriv_darwin.h" - -@interface uiCheckboxNSButton : NSButton -@property uiControl *uiC; -@property void (*uiOnToggled)(uiControl *, void *); -@property void *uiOnToggledData; -@end - -@implementation uiCheckboxNSButton - -- (void)viewDidMoveToSuperview -{ - if (uiDarwinControlFreeWhenAppropriate(self.uiC, [self superview])) { - [self setTarget:nil]; - self.uiC = NULL; - } - [super viewDidMoveToSuperview]; -} - -- (IBAction)uiCheckboxToggled:(id)sender -{ - (*(self.uiOnToggled))(self.uiC, self.uiOnToggledData); -} - -@end - -static void defaultOnToggled(uiControl *c, void *data) -{ - // do nothing -} - -uiControl *uiNewCheckbox(const char *text) -{ - uiControl *c; - uiCheckboxNSButton *cc; - - c = uiDarwinNewControl([uiCheckboxNSButton class], NO, NO); - cc = (uiCheckboxNSButton *) uiControlHandle(c); - cc.uiC = c; - - [cc setTitle:toNSString(text)]; - [cc setButtonType:NSSwitchButton]; - [cc setBordered:NO]; - setStandardControlFont((NSControl *) cc); - - [cc setTarget:cc]; - [cc setAction:@selector(uiCheckboxToggled:)]; - - cc.uiOnToggled = defaultOnToggled; - - return cc.uiC; -} - -char *uiCheckboxText(uiControl *c) -{ - uiCheckboxNSButton *cc; - - cc = (uiCheckboxNSButton *) uiControlHandle(c); - return uiDarwinNSStringToText([cc title]); -} - -void uiCheckboxSetText(uiControl *c, const char *text) -{ - uiCheckboxNSButton *cc; - - cc = (uiCheckboxNSButton *) uiControlHandle(c); - [cc setTitle:toNSString(text)]; -} - -void uiCheckboxOnToggled(uiControl *c, void (*f)(uiControl *, void *), void *data) -{ - uiCheckboxNSButton *cc; - - cc = (uiCheckboxNSButton *) uiControlHandle(c); - cc.uiOnToggled = f; - cc.uiOnToggledData = data; -} - -int uiCheckboxChecked(uiControl *c) -{ - uiCheckboxNSButton *cc; - - cc = (uiCheckboxNSButton *) uiControlHandle(c); - return [cc state] == NSOnState; -} - -void uiCheckboxSetChecked(uiControl *c, int checked) -{ - uiCheckboxNSButton *cc; - NSInteger state; - - cc = (uiCheckboxNSButton *) uiControlHandle(c); - state = NSOnState; - if (!checked) - state = NSOffState; - [cc setState:state]; -} diff --git a/new/checkbox_unix.c b/new/checkbox_unix.c deleted file mode 100644 index ddf9eda..0000000 --- a/new/checkbox_unix.c +++ /dev/null @@ -1,89 +0,0 @@ -// 7 april 2015 -#include "uipriv_unix.h" - -struct checkbox { - void (*onToggled)(uiControl *, void *); - void *onToggledData; - gulong onToggledSignal; -}; - -static void onToggled(GtkToggleButton *b, gpointer data) -{ - uiControl *c = (uiControl *) data; - struct checkbox *cc = (struct checkbox *) (c->data); - - (*(cc->onToggled))(c, cc->onToggledData); -} - -static void defaultOnToggled(uiControl *c, void *data) -{ - // do nothing -} - -static void onDestroy(GtkWidget *widget, gpointer data) -{ - struct checkbox *cc = (struct checkbox *) data; - - uiFree(cc); -} - -uiControl *uiNewCheckbox(const char *text) -{ - uiControl *c; - struct checkbox *cc; - GtkWidget *widget; - - c = uiUnixNewControl(GTK_TYPE_CHECK_BUTTON, - FALSE, FALSE, - "label", text, - NULL); - - widget = GTK_WIDGET(uiControlHandle(c)); - - cc = uiNew(struct checkbox); - g_signal_connect(widget, "destroy", G_CALLBACK(onDestroy), cc); - cc->onToggledSignal = g_signal_connect(widget, "toggled", G_CALLBACK(onToggled), c); - cc->onToggled = defaultOnToggled; - c->data = cc; - - return c; -} - -char *uiCheckboxText(uiControl *c) -{ - return g_strdup(gtk_button_get_label(GTK_BUTTON(uiControlHandle(c)))); -} - -void uiCheckboxSetText(uiControl *c, const char *text) -{ - gtk_button_set_label(GTK_BUTTON(uiControlHandle(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) -{ - return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(uiControlHandle(c))) != FALSE; -} - -void uiCheckboxSetChecked(uiControl *c, int checked) -{ - struct checkbox *cc = (struct checkbox *) (c->data); - GtkToggleButton *button; - gboolean active; - - active = FALSE; - if (checked) - active = TRUE; - // we need to inhibit sending of ::toggled because this WILL send a ::toggled otherwise - button = GTK_TOGGLE_BUTTON(uiControlHandle(c)); - g_signal_handler_block(button, cc->onToggledSignal); - gtk_toggle_button_set_active(button, active); - g_signal_handler_unblock(button, cc->onToggledSignal); -} diff --git a/new/checkbox_windows.c b/new/checkbox_windows.c deleted file mode 100644 index 951eee9..0000000 --- a/new/checkbox_windows.c +++ /dev/null @@ -1,123 +0,0 @@ -// 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/comctl32_windows.c b/new/comctl32_windows.c deleted file mode 100644 index 93b3a27..0000000 --- a/new/comctl32_windows.c +++ /dev/null @@ -1,105 +0,0 @@ -// 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[] = "\n\n\nYour application description here.\n\n \n \n \n\n\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/darwin/alloc.m b/new/darwin/alloc.m new file mode 100644 index 0000000..8f539af --- /dev/null +++ b/new/darwin/alloc.m @@ -0,0 +1,44 @@ +// 4 december 2014 +#import +#import "uipriv_darwin.h" + +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(); + } + memset(out, 0, 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/darwin/button.m b/new/darwin/button.m new file mode 100644 index 0000000..92e2c76 --- /dev/null +++ b/new/darwin/button.m @@ -0,0 +1,79 @@ +// 7 april 2015 +#import "uipriv_darwin.h" + +@interface uiNSButton : NSButton +@property uiControl *uiC; +@property void (*uiOnClicked)(uiControl *, void *); +@property void *uiOnClickedData; +@end + +@implementation uiNSButton + +- (void)viewDidMoveToSuperview +{ + if (uiDarwinControlFreeWhenAppropriate(self.uiC, [self superview])) { + [self setTarget:nil]; + self.uiC = NULL; + } + [super viewDidMoveToSuperview]; +} + +- (IBAction)uiButtonClicked:(id)sender +{ + (*(self.uiOnClicked))(self.uiC, self.uiOnClickedData); +} + +@end + +static void defaultOnClicked(uiControl *c, void *data) +{ + // do nothing +} + +uiControl *uiNewButton(const char *text) +{ + uiControl *c; + uiNSButton *b; + + c = uiDarwinNewControl([uiNSButton class], NO, NO); + b = (uiNSButton *) uiControlHandle(c); + b.uiC = c; + + [b setTitle:toNSString(text)]; + [b setButtonType:NSMomentaryPushInButton]; + [b setBordered:YES]; + [b setBezelStyle:NSRoundedBezelStyle]; + setStandardControlFont((NSControl *) b); + + [b setTarget:b]; + [b setAction:@selector(uiButtonClicked:)]; + + b.uiOnClicked = defaultOnClicked; + + return b.uiC; +} + +char *uiButtonText(uiControl *c) +{ + uiNSButton *b; + + b = (uiNSButton *) uiControlHandle(c); + return uiDarwinNSStringToText([b title]); +} + +void uiButtonSetText(uiControl *c, const char *text) +{ + uiNSButton *b; + + b = (uiNSButton *) uiControlHandle(c); + [b setTitle:toNSString(text)]; +} + +void uiButtonOnClicked(uiControl *c, void (*f)(uiControl *, void *), void *data) +{ + uiNSButton *b; + + b = (uiNSButton *) uiControlHandle(c); + b.uiOnClicked = f; + b.uiOnClickedData = data; +} diff --git a/new/darwin/checkbox.m b/new/darwin/checkbox.m new file mode 100644 index 0000000..ca52389 --- /dev/null +++ b/new/darwin/checkbox.m @@ -0,0 +1,98 @@ +// 7 april 2015 +#import "uipriv_darwin.h" + +@interface uiCheckboxNSButton : NSButton +@property uiControl *uiC; +@property void (*uiOnToggled)(uiControl *, void *); +@property void *uiOnToggledData; +@end + +@implementation uiCheckboxNSButton + +- (void)viewDidMoveToSuperview +{ + if (uiDarwinControlFreeWhenAppropriate(self.uiC, [self superview])) { + [self setTarget:nil]; + self.uiC = NULL; + } + [super viewDidMoveToSuperview]; +} + +- (IBAction)uiCheckboxToggled:(id)sender +{ + (*(self.uiOnToggled))(self.uiC, self.uiOnToggledData); +} + +@end + +static void defaultOnToggled(uiControl *c, void *data) +{ + // do nothing +} + +uiControl *uiNewCheckbox(const char *text) +{ + uiControl *c; + uiCheckboxNSButton *cc; + + c = uiDarwinNewControl([uiCheckboxNSButton class], NO, NO); + cc = (uiCheckboxNSButton *) uiControlHandle(c); + cc.uiC = c; + + [cc setTitle:toNSString(text)]; + [cc setButtonType:NSSwitchButton]; + [cc setBordered:NO]; + setStandardControlFont((NSControl *) cc); + + [cc setTarget:cc]; + [cc setAction:@selector(uiCheckboxToggled:)]; + + cc.uiOnToggled = defaultOnToggled; + + return cc.uiC; +} + +char *uiCheckboxText(uiControl *c) +{ + uiCheckboxNSButton *cc; + + cc = (uiCheckboxNSButton *) uiControlHandle(c); + return uiDarwinNSStringToText([cc title]); +} + +void uiCheckboxSetText(uiControl *c, const char *text) +{ + uiCheckboxNSButton *cc; + + cc = (uiCheckboxNSButton *) uiControlHandle(c); + [cc setTitle:toNSString(text)]; +} + +void uiCheckboxOnToggled(uiControl *c, void (*f)(uiControl *, void *), void *data) +{ + uiCheckboxNSButton *cc; + + cc = (uiCheckboxNSButton *) uiControlHandle(c); + cc.uiOnToggled = f; + cc.uiOnToggledData = data; +} + +int uiCheckboxChecked(uiControl *c) +{ + uiCheckboxNSButton *cc; + + cc = (uiCheckboxNSButton *) uiControlHandle(c); + return [cc state] == NSOnState; +} + +void uiCheckboxSetChecked(uiControl *c, int checked) +{ + uiCheckboxNSButton *cc; + NSInteger state; + + cc = (uiCheckboxNSButton *) uiControlHandle(c); + state = NSOnState; + if (!checked) + state = NSOffState; + [cc setState:state]; +} diff --git a/new/darwin/entry.m b/new/darwin/entry.m new file mode 100644 index 0000000..f2b208b --- /dev/null +++ b/new/darwin/entry.m @@ -0,0 +1,67 @@ +// 9 april 2015 +#import "uipriv_darwin.h" + +@interface uiNSTextField : NSTextField +@property uiControl *uiC; +@end + +@implementation uiNSTextField + +- (void)viewDidMoveToSuperview +{ + if (uiDarwinControlFreeWhenAppropriate(self.uiC, [self superview])) { + [self setTarget:nil]; + self.uiC = NULL; + } + [super viewDidMoveToSuperview]; +} + +@end + +// TOOD move elsewhere +// these are based on interface builder defaults; my comments in the old code weren't very good so I don't really know what talked about what, sorry :/ +void finishNewTextField(NSTextField *t, BOOL isEntry) +{ + setStandardControlFont((id) t); + + // THE ORDER OF THESE CALLS IS IMPORTANT; CHANGE IT AND THE BORDERS WILL DISAPPEAR + [t setBordered:NO]; + [t setBezelStyle:NSTextFieldSquareBezel]; + [t setBezeled:isEntry]; + + // we don't need to worry about substitutions/autocorrect here; see window_darwin.m for details + + [[t cell] setLineBreakMode:NSLineBreakByClipping]; + [[t cell] setScrollable:YES]; +} + +uiControl *uiNewEntry(void) +{ + uiControl *c; + uiNSTextField *t; + + c = uiDarwinNewControl([uiNSTextField class], NO, NO); + t = (uiNSTextField *) uiControlHandle(c); + t.uiC = c; + + [t setSelectable:YES]; // otherwise the setting is masked by the editable default of YES + finishNewTextField((NSTextField *) t, YES); + + return t.uiC; +} + +char *uiEntryText(uiControl *c) +{ + uiNSTextField *t; + + t = (uiNSTextField *) uiControlHandle(c); + return uiDarwinNSStringToText([t stringValue]); +} + +void uiEntrySetText(uiControl *c, const char *text) +{ + uiNSTextField *t; + + t = (uiNSTextField *) uiControlHandle(c); + [t setStringValue:toNSString(text)]; +} diff --git a/new/darwin/init.m b/new/darwin/init.m new file mode 100644 index 0000000..4f14a0a --- /dev/null +++ b/new/darwin/init.m @@ -0,0 +1,67 @@ +// 6 april 2015 +#import "uipriv_darwin.h" + +@interface uiApplication : NSApplication +@end + +@implementation uiApplication + +// hey look! we're overriding terminate:! +// we're going to make sure we can go back to main() whether Cocoa likes it or not! +// and just how are we going to do that, hm? +// (note: this is called after applicationShouldTerminate:) +- (void)terminate:(id)sender +{ + // yes that's right folks: DO ABSOLUTELY NOTHING. + // the magic is [NSApp run] will just... stop. + + // for debugging + NSLog(@"in terminate:"); +} + +@end + +@interface uiAppDelegate : NSObject +@end + +@implementation uiAppDelegate + +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)app +{ + // for debugging + NSLog(@"in applicationShouldTerminate:"); + return NSTerminateNow; +} + +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)app +{ + return NO; +} + +@end + +// we are not in control of the actual lifetimes and refcounts of NSViews (see http://stackoverflow.com/a/29523141/3408572) +// when we're done with a view, it'll be added as a subview of this one, and this one will be released on application shutdown +// we need this separate view because it's possible for controls to have no parent but still be alive +NSView *destroyedControlsView; + +uiInitOptions options; + +const char *uiInit(uiInitOptions *o) +{ + options = *o; + [uiApplication sharedApplication]; + // don't check for a NO return; something (launch services?) causes running from application bundles to always return NO when asking to change activation policy, even if the change is to the same activation policy! + // see https://github.com/andlabs/ui/issues/6 + [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + [NSApp setDelegate:[uiAppDelegate new]]; + + // we can use a stock NSView for this + destroyedControlsView = [[NSView alloc] initWithFrame:NSZeroRect]; + + return NULL; +} + +void uiFreeInitError(const char *err) +{ +} diff --git a/new/darwin/label.m b/new/darwin/label.m new file mode 100644 index 0000000..e3fb351 --- /dev/null +++ b/new/darwin/label.m @@ -0,0 +1,53 @@ +// 9 april 2015 +#import "uipriv_darwin.h" + +@interface uiLabelNSTextField : NSTextField +@property uiControl *uiC; +@end + +@implementation uiLabelNSTextField + +- (void)viewDidMoveToSuperview +{ + if (uiDarwinControlFreeWhenAppropriate(self.uiC, [self superview])) { + [self setTarget:nil]; + self.uiC = NULL; + } + [super viewDidMoveToSuperview]; +} + +@end + +uiControl *uiNewLabel(const char *text) +{ + uiControl *c; + uiLabelNSTextField *l; + + c = uiDarwinNewControl([uiLabelNSTextField class], NO, NO); + l = (uiLabelNSTextField *) uiControlHandle(c); + l.uiC = c; + + [l setStringValue:toNSString(text)]; + [l setEditable:NO]; + [l setSelectable:NO]; + [l setDrawsBackground:NO]; + finishNewTextField((NSTextField *) l, NO); + + return l.uiC; +} + +char *uiLabelText(uiControl *c) +{ + uiLabelNSTextField *t; + + t = (uiLabelNSTextField *) uiControlHandle(c); + return uiDarwinNSStringToText([t stringValue]); +} + +void uiLabelSetText(uiControl *c, const char *text) +{ + uiLabelNSTextField *t; + + t = (uiLabelNSTextField *) uiControlHandle(c); + [t setStringValue:toNSString(text)]; +} diff --git a/new/darwin/main.m b/new/darwin/main.m new file mode 100644 index 0000000..8663b58 --- /dev/null +++ b/new/darwin/main.m @@ -0,0 +1,27 @@ +// 6 april 2015 +#import "uipriv_darwin.h" + +// #qo LDFLAGS: -lobjc -framework Foundation -framework AppKit + +void uiMain(void) +{ + [NSApp run]; +} + +void uiQuit(void) +{ + NSEvent *e; + + [NSApp stop:NSApp]; + // stop: won't register until another event has passed; let's synthesize one + e = [NSEvent otherEventWithType:NSApplicationDefined + location:NSZeroPoint + modifierFlags:0 + timestamp:[[NSProcessInfo processInfo] systemUptime] + windowNumber:0 + context:[NSGraphicsContext currentContext] + subtype:0 + data1:0 + data2:0]; + [NSApp postEvent:e atStart:NO]; // let pending events take priority (this is what PostQuitMessage() on Windows does so we have to do it here too for parity; thanks to mikeash in irc.freenode.net/#macdev for confirming that this parameter should indeed be NO) +} diff --git a/new/darwin/newcontrol.m b/new/darwin/newcontrol.m new file mode 100644 index 0000000..9b7c6c4 --- /dev/null +++ b/new/darwin/newcontrol.m @@ -0,0 +1,236 @@ +// 7 april 2015 +#include "uipriv_darwin.h" + +typedef struct singleView singleView; + +struct singleView { + NSView *view; + NSScrollView *scrollView; + NSView *immediate; // the control that is added to the parent container; either view or scrollView + uiParent *parent; + BOOL userHid; + BOOL containerHid; + BOOL userDisabled; + BOOL containerDisabled; +}; + +static void singleDestroy(uiControl *c) +{ + singleView *s = (singleView *) (c->internal); + + [destroyedControlsView addSubview:s->immediate]; +} + +static uintptr_t singleHandle(uiControl *c) +{ + singleView *s = (singleView *) (c->internal); + + return (uintptr_t) (s->view); +} + +static void singleSetParent(uiControl *c, uiParent *parent) +{ + singleView *s = (singleView *) (c->internal); + NSView *parentView; + uiParent *oldparent; + + oldparent = s->parent; + s->parent = parent; + if (oldparent != NULL) { + [s->immediate removeFromSuperview]; + uiParentUpdate(oldparent); + } + if (s->parent != NULL) { + // TODO uiControlView(), uiParentView() + parentView = (NSView *) uiParentHandle(s->parent); + [parentView addSubview:s->immediate]; + uiParentUpdate(s->parent); + } +} + +// also good for NSBox and NSProgressIndicator +static void singlePreferredSize(uiControl *c, uiSizing *d, intmax_t *width, intmax_t *height) +{ + singleView *s = (singleView *) (c->internal); + NSControl *control; + NSRect r; + + control = (NSControl *) (s->view); + [control sizeToFit]; + // use alignmentRect here instead of frame because we'll be resizing based on that + r = [control alignmentRectForFrame:[control frame]]; + *width = (intmax_t) r.size.width; + *height = (intmax_t) r.size.height; +} + +static void singleResize(uiControl *c, intmax_t x, intmax_t y, intmax_t width, intmax_t height, uiSizing *d) +{ + singleView *s = (singleView *) (c->internal); + NSRect frame; + + frame.origin.x = x; + // mac os x coordinate system has (0,0) in the lower-left + frame.origin.y = ([[s->immediate superview] bounds].size.height - height) - y; + frame.size.width = width; + frame.size.height = height; + frame = [s->immediate frameForAlignmentRect:frame]; + [s->immediate setFrame:frame]; +} + +static int singleVisible(uiControl *c) +{ + singleView *s = (singleView *) (c->internal); + + if (s->userHid) + return 0; + return 1; +} + +static void singleShow(uiControl *c) +{ + singleView *s = (singleView *) (c->internal); + + s->userHid = NO; + if (!s->containerHid) { + [s->immediate setHidden:NO]; + if (s->parent != NULL) + uiParentUpdate(s->parent); + } +} + +static void singleHide(uiControl *c) +{ + singleView *s = (singleView *) (c->internal); + + s->userHid = YES; + [s->immediate setHidden:YES]; + if (s->parent != NULL) + uiParentUpdate(s->parent); +} + +static void singleContainerShow(uiControl *c) +{ + singleView *s = (singleView *) (c->internal); + + s->containerHid = NO; + if (!s->userHid) { + [s->immediate setHidden:NO]; + if (s->parent != NULL) + uiParentUpdate(s->parent); + } +} + +static void singleContainerHide(uiControl *c) +{ + singleView *s = (singleView *) (c->internal); + + s->containerHid = YES; + [s->immediate setHidden:YES]; + if (s->parent != NULL) + uiParentUpdate(s->parent); +} + +static void enable(singleView *s) +{ + if ([s->view respondsToSelector:@selector(setEnabled:)]) + [((NSControl *) (s->view)) setEnabled:YES]; +} + +static void disable(singleView *s) +{ + if ([s->view respondsToSelector:@selector(setEnabled:)]) + [((NSControl *) (s->view)) setEnabled:NO]; +} + +static void singleEnable(uiControl *c) +{ + singleView *s = (singleView *) (c->internal); + + s->userDisabled = NO; + if (!s->containerDisabled) + enable(s); +} + +static void singleDisable(uiControl *c) +{ + singleView *s = (singleView *) (c->internal); + + s->userDisabled = YES; + disable(s); +} + +static void singleContainerEnable(uiControl *c) +{ + singleView *s = (singleView *) (c->internal); + + s->containerDisabled = NO; + if (!s->userDisabled) + enable(s); +} + +static void singleContainerDisable(uiControl *c) +{ + singleView *s = (singleView *) (c->internal); + + s->containerDisabled = YES; + disable(s); +} + +uiControl *uiDarwinNewControl(Class class, BOOL inScrollView, BOOL scrollViewHasBorder) +{ + uiControl *c; + singleView *s; + + s = uiNew(singleView); + // thanks to autoxr and arwyn in irc.freenode.net/#macdev + s->view = (NSView *) [[class alloc] initWithFrame:NSZeroRect]; + s->immediate = s->view; + + if (inScrollView) { + s->scrollView = [[NSScrollView alloc] initWithFrame:NSZeroRect]; + [s->scrollView setDocumentView:s->view]; + [s->scrollView setHasHorizontalScroller:YES]; + [s->scrollView setHasVerticalScroller:YES]; + [s->scrollView setAutohidesScrollers:YES]; + if (scrollViewHasBorder) + [s->scrollView setBorderType:NSBezelBorder]; + else + [s->scrollView setBorderType:NSNoBorder]; + s->immediate = (NSView *) (s->scrollView); + } + + // and keep a reference to s->immediate for when we remove the control from its parent + [s->immediate retain]; + + c = uiNew(uiControl); + c->internal = s; + c->destroy = singleDestroy; + c->handle = singleHandle; + c->setParent = singleSetParent; + c->preferredSize = singlePreferredSize; + 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; + + return c; +} + +BOOL uiDarwinControlFreeWhenAppropriate(uiControl *c, NSView *newSuperview) +{ + singleView *s = (singleView *) (c->internal); + + if (newSuperview == destroyedControlsView) { + [s->immediate release]; // we don't need the reference anymore + uiFree(s); + uiFree(c); + return YES; + } + return NO; +} diff --git a/new/darwin/parent.m b/new/darwin/parent.m new file mode 100644 index 0000000..abf0e59 --- /dev/null +++ b/new/darwin/parent.m @@ -0,0 +1,118 @@ +// 4 august 2014 +#import "uipriv_darwin.h" + +// calling -[className] on the content views of NSWindow, NSTabItem, and NSBox all return NSView, so I'm assuming I just need to override these +// fornunately: +// - NSWindow resizing calls -[setFrameSize:] (but not -[setFrame:]) +// - NSTabView resizing calls both -[setFrame:] and -[setFrameSIze:] on the current tab +// - NSTabView switching tabs calls both -[setFrame:] and -[setFrameSize:] on the new tab +// so we just override setFrameSize: +// thanks to mikeash and JtRip in irc.freenode.net/#macdev +@interface uipParent : NSView { +// TODO +@public + uiControl *child; + intmax_t marginLeft; + intmax_t marginTop; + intmax_t marginRight; + intmax_t marginBottom; +} +- (void)uiUpdateNow; +@end + +@implementation uipParent + +uiLogObjCClassAllocations + +- (void)viewDidMoveToSuperview +{ + // we can't just use nil because NSTabView will set page views to nil when they're tabbed away + // this means that we have to explicitly move them to the destroyed controls view when we're done with them, and likewise in NSWindow + if ([self superview] == destroyedControlsView) + if (self->child != NULL) { + uiControlDestroy(self->child); + self->child = NULL; + [self release]; + } + [super viewDidMoveToSuperview]; +} + +- (void)setFrameSize:(NSSize)s +{ + [super setFrameSize:s]; + [self uiUpdateNow]; +} + +// These are based on measurements from Interface Builder. +// These seem to be based on Auto Layout constants, but I don't see an API that exposes these... +// This one is 8 for most pairs of controls that I've tried; the only difference is between two pushbuttons, where it's 12... +#define macXPadding 8 +// Likewise, this one appears to be 12 for pairs of push buttons... +#define macYPadding 8 + +- (void)uiUpdateNow +{ + uiSizing d; + intmax_t x, y, width, height; + + if (self->child == NULL) + return; + x = [self bounds].origin.x + self->marginLeft; + y = [self bounds].origin.y + self->marginTop; + width = [self bounds].size.width - (self->marginLeft + self->marginRight); + height = [self bounds].size.height - (self->marginTop + self->marginBottom); + d.xPadding = macXPadding; + d.yPadding = macYPadding; + uiControlResize(self->child, x, y, width, height, &d); +} + +@end + +static uintptr_t parentHandle(uiParent *p) +{ + uipParent *pp = (uipParent *) (p->Internal); + + return (uintptr_t) pp; +} + +static void parentSetChild(uiParent *p, uiControl *child) +{ + uipParent *pp = (uipParent *) (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) +{ + uipParent *pp = (uipParent *) (p->Internal); + + pp->marginLeft = left; + pp->marginTop = top; + pp->marginRight = right; + pp->marginBottom = bottom; +} + +static void parentUpdate(uiParent *p) +{ + uipParent *pp = (uipParent *) (p->Internal); + + [pp uiUpdateNow]; +} + +uiParent *uiNewParent(uintptr_t osParent) +{ + uiParent *p; + + p = uiNew(uiParent); + p->Internal = [[uipParent alloc] initWithFrame:NSZeroRect]; + p->Handle = parentHandle; + p->SetChild = parentSetChild; + p->SetMargins = parentSetMargins; + p->Update = parentUpdate; + // don't use osParent; we'll need to call specific selectors to set the parent view + // and keep the view alive so we can release it properly later + [((uipParent *) (p->Internal)) retain]; + return p; +} \ No newline at end of file diff --git a/new/darwin/tab.m b/new/darwin/tab.m new file mode 100644 index 0000000..c36181e --- /dev/null +++ b/new/darwin/tab.m @@ -0,0 +1,67 @@ +// 12 april 2015 +#import "uipriv_darwin.h" + +// TODO +// - verify margins against extra space around the tab +// - free child containers properly + +@interface uiNSTabView : NSTabView +@property uiControl *uiC; +@end + +@implementation uiNSTabView + +- (void)viewDidMoveToSuperview +{ + // TODO free all tabs explicitly + if (uiDarwinControlFreeWhenAppropriate(self.uiC, [self superview])) + self.uiC = NULL; + [super viewDidMoveToSuperview]; +} + +@end + +// the default new control implementation uses -sizeToFit, which we don't have with NSTabView +// fortunately, we do have -minimumSize +static void preferredSize(uiControl *c, uiSizing *d, intmax_t *width, intmax_t *height) +{ + uiNSTabView *tv; + NSSize s; + + tv = (uiNSTabView *) uiControlHandle(c); + s = [tv minimumSize]; + *width = (intmax_t) (s.width); + *height = (intmax_t) (s.height); +} + +uiControl *uiNewTab(void) +{ + uiControl *c; + uiNSTabView *t; + + c = uiDarwinNewControl([uiNSTabView class], NO, NO); + c->preferredSize = preferredSize; + t = (uiNSTabView *) uiControlHandle(c); + t.uiC = c; + + // also good for NSTabView (same selector and everything) + setStandardControlFont((NSControl *) t); + + return c; +} + +void uiTabAddPage(uiControl *c, const char *name, uiControl *child) +{ + uiNSTabView *tv; + uiParent *content; + NSTabViewItem *i; + + content = uiNewParent(0); + uiParentSetChild(content, child); + + i = [[NSTabViewItem alloc] initWithIdentifier:nil]; + [i setLabel:toNSString(name)]; + [i setView:((NSView *) uiParentHandle(content))]; + tv = (uiNSTabView *) uiControlHandle(c); + [tv addTabViewItem:i]; +} diff --git a/new/darwin/text.m b/new/darwin/text.m new file mode 100644 index 0000000..f0d3dab --- /dev/null +++ b/new/darwin/text.m @@ -0,0 +1,19 @@ +// 10 april 2015 +#import "uipriv_darwin.h" + +char *uiDarwinNSStringToText(NSString *s) +{ + char *out; + + out = strdup([s UTF8String]); + if (out == NULL) { + fprintf(stderr, "memory exhausted in uiDarwinNSStringToText()\n"); + abort(); + } + return out; +} + +void uiFreeText(char *s) +{ + free(s); +} diff --git a/new/darwin/util.m b/new/darwin/util.m new file mode 100644 index 0000000..906a0ea --- /dev/null +++ b/new/darwin/util.m @@ -0,0 +1,20 @@ +// 7 april 2015 +#import "uipriv_darwin.h" + +// also fine for NSCells and NSTexts (NSTextViews) +void setStandardControlFont(NSControl *control) +{ + [control setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]]]; +} + +void disableAutocorrect(NSTextView *tv) +{ + [tv setEnabledTextCheckingTypes:0]; + [tv setAutomaticDashSubstitutionEnabled:NO]; + // don't worry about automatic data detection; it won't change stringValue (thanks pretty_function in irc.freenode.net/#macdev) + [tv setAutomaticSpellingCorrectionEnabled:NO]; + [tv setAutomaticTextReplacementEnabled:NO]; + [tv setAutomaticQuoteSubstitutionEnabled:NO]; + [tv setAutomaticLinkDetectionEnabled:NO]; + [tv setSmartInsertDeleteEnabled:NO]; +} diff --git a/new/darwin/window.m b/new/darwin/window.m new file mode 100644 index 0000000..b0cab11 --- /dev/null +++ b/new/darwin/window.m @@ -0,0 +1,145 @@ +// 6 april 2015 +#import "uipriv_darwin.h" + +// TODO +// - free chilld containers properly + +@interface uiWindowDelegate : NSObject +@property (assign) NSWindow *w; +@property uiParent *content; +@property int (*onClosing)(uiWindow *, void *); +@property void *onClosingData; +@property uiWindow *uiw; +@end + +@implementation uiWindowDelegate + +uiLogObjCClassAllocations + +- (BOOL)windowShouldClose:(id)win +{ + // return exact constants to be safe + if ((*(self.onClosing))(self.uiw, self.onClosingData)) + return YES; + return NO; +} + +// after this method returns we assume the window will be released (see below), so we can go too +- (void)windowWillClose:(NSNotification *)note +{ + [self.w setDelegate:nil]; // see http://stackoverflow.com/a/29523141/3408572 + + // when we reach this point, we need to ensure that all the window's children are destroyed (for OS parity) + // because we need to set the content view's superview to the destroyed controls view to trigger deletion, we need to do this manually + // first, replace the current content view... + [self.w setContentView:[[NSView alloc] initWithFrame:NSZeroRect]]; + // ...then, trigger the deletion + [destroyedControlsView addSubview:((NSView *) uiParentHandle(self.content))]; + + uiFree(self.uiw); + [self release]; +} + +@end + +struct uiWindow { + uiWindowDelegate *d; + int margined; +}; + +static int defaultOnClosing(uiWindow *w, void *data) +{ + return 1; +} + +uiWindow *uiNewWindow(char *title, int width, int height) +{ + uiWindowDelegate *d; + + d = [uiWindowDelegate new]; + + d.w = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, (CGFloat) width, (CGFloat) height) + styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask) + backing:NSBackingStoreBuffered + defer:YES]; + [d.w setTitle:toNSString(title)]; + + // we do not want substitutions + // text fields, labels, etc. take their smart quotes and other autocorrect settings from their parent window, which provides a shared "field editor" + // so we have to turn them off here + // thanks akempgen in irc.freenode.net/#macdev + // for some reason, this selector returns NSText but is documented to return NSTextView... + // NOTE: if you disagree with me about disabling substitutions, start a github issue with why and I'll be happy to consider it + disableAutocorrect((NSTextView *) [d.w fieldEditor:YES forObject:nil]); + + // this is what will destroy the window on close + [d.w setReleasedWhenClosed:YES]; + + d.content = uiNewParent(0); + [d.w setContentView:((NSView *) uiParentHandle(d.content))]; + + d.onClosing = defaultOnClosing; + [d.w setDelegate:d]; + + d.uiw = uiNew(uiWindow); + d.uiw->d = d; + return d.uiw; +} + +#define D w->d + +void uiWindowDestroy(uiWindow *w) +{ + [D.w close]; +} + +uintptr_t uiWindowHandle(uiWindow *w) +{ + return (uintptr_t) (D.w); +} + +char *uiWindowTitle(uiWindow *w) +{ + return uiDarwinNSStringToText([D.w title]); +} + +void uiWindowSetTitle(uiWindow *w, const char *title) +{ + [D.w setTitle:toNSString(title)]; +} + +void uiWindowShow(uiWindow *w) +{ + [D.w makeKeyAndOrderFront:D.w]; +} + +void uiWindowHide(uiWindow *w) +{ + [D.w orderOut:D.w]; +} + +void uiWindowOnClosing(uiWindow *w, int (*f)(uiWindow *, void *), void *data) +{ + D.onClosing = f; + D.onClosingData = data; +} + +void uiWindowSetChild(uiWindow *w, uiControl *c) +{ + uiParentSetChild(D.content, c); +} + +int uiWindowMargined(uiWindow *w) +{ + return w->margined; +} + +void uiWindowSetMargined(uiWindow *w, int margined) +{ + w->margined = margined; + if (w->margined) + uiParentSetMargins(D.content, macXMargin, macYMargin, macXMargin, macYMargin); + else + uiParentSetMargins(D.content, 0, 0, 0, 0); + uiParentUpdate(D.content); +} diff --git a/new/debug_windows.c b/new/debug_windows.c deleted file mode 100644 index bc84b63..0000000 --- a/new/debug_windows.c +++ /dev/null @@ -1,111 +0,0 @@ -// 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 - -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/entry_darwin.m b/new/entry_darwin.m deleted file mode 100644 index f2b208b..0000000 --- a/new/entry_darwin.m +++ /dev/null @@ -1,67 +0,0 @@ -// 9 april 2015 -#import "uipriv_darwin.h" - -@interface uiNSTextField : NSTextField -@property uiControl *uiC; -@end - -@implementation uiNSTextField - -- (void)viewDidMoveToSuperview -{ - if (uiDarwinControlFreeWhenAppropriate(self.uiC, [self superview])) { - [self setTarget:nil]; - self.uiC = NULL; - } - [super viewDidMoveToSuperview]; -} - -@end - -// TOOD move elsewhere -// these are based on interface builder defaults; my comments in the old code weren't very good so I don't really know what talked about what, sorry :/ -void finishNewTextField(NSTextField *t, BOOL isEntry) -{ - setStandardControlFont((id) t); - - // THE ORDER OF THESE CALLS IS IMPORTANT; CHANGE IT AND THE BORDERS WILL DISAPPEAR - [t setBordered:NO]; - [t setBezelStyle:NSTextFieldSquareBezel]; - [t setBezeled:isEntry]; - - // we don't need to worry about substitutions/autocorrect here; see window_darwin.m for details - - [[t cell] setLineBreakMode:NSLineBreakByClipping]; - [[t cell] setScrollable:YES]; -} - -uiControl *uiNewEntry(void) -{ - uiControl *c; - uiNSTextField *t; - - c = uiDarwinNewControl([uiNSTextField class], NO, NO); - t = (uiNSTextField *) uiControlHandle(c); - t.uiC = c; - - [t setSelectable:YES]; // otherwise the setting is masked by the editable default of YES - finishNewTextField((NSTextField *) t, YES); - - return t.uiC; -} - -char *uiEntryText(uiControl *c) -{ - uiNSTextField *t; - - t = (uiNSTextField *) uiControlHandle(c); - return uiDarwinNSStringToText([t stringValue]); -} - -void uiEntrySetText(uiControl *c, const char *text) -{ - uiNSTextField *t; - - t = (uiNSTextField *) uiControlHandle(c); - [t setStringValue:toNSString(text)]; -} diff --git a/new/entry_unix.c b/new/entry_unix.c deleted file mode 100644 index 43caea3..0000000 --- a/new/entry_unix.c +++ /dev/null @@ -1,41 +0,0 @@ -// 8 april 2015 -#include "uipriv_unix.h" - -struct entry { -}; - -static void onDestroy(GtkWidget *widget, gpointer data) -{ - struct entry *e = (struct entry *) data; - - uiFree(e); -} - -uiControl *uiNewEntry(void) -{ - uiControl *c; - struct entry *e; - GtkWidget *widget; - - c = uiUnixNewControl(GTK_TYPE_ENTRY, - FALSE, FALSE, - NULL); - - widget = GTK_WIDGET(uiControlHandle(c)); - - e = uiNew(struct entry); - g_signal_connect(widget, "destroy", G_CALLBACK(onDestroy), e); - c->data = e; - - return c; -} - -char *uiEntryText(uiControl *c) -{ - return g_strdup(gtk_entry_get_text(GTK_ENTRY(uiControlHandle(c)))); -} - -void uiEntrySetText(uiControl *c, const char *text) -{ - gtk_entry_set_text(GTK_ENTRY(uiControlHandle(c)), text); -} diff --git a/new/entry_windows.c b/new/entry_windows.c deleted file mode 100644 index 2297c28..0000000 --- a/new/entry_windows.c +++ /dev/null @@ -1,67 +0,0 @@ -// 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/init_darwin.m b/new/init_darwin.m deleted file mode 100644 index 4f14a0a..0000000 --- a/new/init_darwin.m +++ /dev/null @@ -1,67 +0,0 @@ -// 6 april 2015 -#import "uipriv_darwin.h" - -@interface uiApplication : NSApplication -@end - -@implementation uiApplication - -// hey look! we're overriding terminate:! -// we're going to make sure we can go back to main() whether Cocoa likes it or not! -// and just how are we going to do that, hm? -// (note: this is called after applicationShouldTerminate:) -- (void)terminate:(id)sender -{ - // yes that's right folks: DO ABSOLUTELY NOTHING. - // the magic is [NSApp run] will just... stop. - - // for debugging - NSLog(@"in terminate:"); -} - -@end - -@interface uiAppDelegate : NSObject -@end - -@implementation uiAppDelegate - -- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)app -{ - // for debugging - NSLog(@"in applicationShouldTerminate:"); - return NSTerminateNow; -} - -- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)app -{ - return NO; -} - -@end - -// we are not in control of the actual lifetimes and refcounts of NSViews (see http://stackoverflow.com/a/29523141/3408572) -// when we're done with a view, it'll be added as a subview of this one, and this one will be released on application shutdown -// we need this separate view because it's possible for controls to have no parent but still be alive -NSView *destroyedControlsView; - -uiInitOptions options; - -const char *uiInit(uiInitOptions *o) -{ - options = *o; - [uiApplication sharedApplication]; - // don't check for a NO return; something (launch services?) causes running from application bundles to always return NO when asking to change activation policy, even if the change is to the same activation policy! - // see https://github.com/andlabs/ui/issues/6 - [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; - [NSApp setDelegate:[uiAppDelegate new]]; - - // we can use a stock NSView for this - destroyedControlsView = [[NSView alloc] initWithFrame:NSZeroRect]; - - return NULL; -} - -void uiFreeInitError(const char *err) -{ -} diff --git a/new/init_unix.c b/new/init_unix.c deleted file mode 100644 index b10c05b..0000000 --- a/new/init_unix.c +++ /dev/null @@ -1,23 +0,0 @@ -// 6 april 2015 -#include "uipriv_unix.h" - -uiInitOptions options; - -const char *uiInit(uiInitOptions *o) -{ - GError *err = NULL; - const char *msg; - - options = *o; - if (gtk_init_with_args(NULL, NULL, NULL, NULL, NULL, &err) == FALSE) { - msg = g_strdup(err->message); - g_error_free(err); - return msg; - } - return NULL; -} - -void uiFreeInitError(const char *err) -{ - g_free((gpointer) err); -} diff --git a/new/init_windows.c b/new/init_windows.c deleted file mode 100644 index 9903934..0000000 --- a/new/init_windows.c +++ /dev/null @@ -1,112 +0,0 @@ -// 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/label_darwin.m b/new/label_darwin.m deleted file mode 100644 index e3fb351..0000000 --- a/new/label_darwin.m +++ /dev/null @@ -1,53 +0,0 @@ -// 9 april 2015 -#import "uipriv_darwin.h" - -@interface uiLabelNSTextField : NSTextField -@property uiControl *uiC; -@end - -@implementation uiLabelNSTextField - -- (void)viewDidMoveToSuperview -{ - if (uiDarwinControlFreeWhenAppropriate(self.uiC, [self superview])) { - [self setTarget:nil]; - self.uiC = NULL; - } - [super viewDidMoveToSuperview]; -} - -@end - -uiControl *uiNewLabel(const char *text) -{ - uiControl *c; - uiLabelNSTextField *l; - - c = uiDarwinNewControl([uiLabelNSTextField class], NO, NO); - l = (uiLabelNSTextField *) uiControlHandle(c); - l.uiC = c; - - [l setStringValue:toNSString(text)]; - [l setEditable:NO]; - [l setSelectable:NO]; - [l setDrawsBackground:NO]; - finishNewTextField((NSTextField *) l, NO); - - return l.uiC; -} - -char *uiLabelText(uiControl *c) -{ - uiLabelNSTextField *t; - - t = (uiLabelNSTextField *) uiControlHandle(c); - return uiDarwinNSStringToText([t stringValue]); -} - -void uiLabelSetText(uiControl *c, const char *text) -{ - uiLabelNSTextField *t; - - t = (uiLabelNSTextField *) uiControlHandle(c); - [t setStringValue:toNSString(text)]; -} diff --git a/new/label_unix.c b/new/label_unix.c deleted file mode 100644 index 1f950d3..0000000 --- a/new/label_unix.c +++ /dev/null @@ -1,45 +0,0 @@ -// 11 april 2015 -#include "uipriv_unix.h" - -struct label { -}; - -static void onDestroy(GtkWidget *widget, gpointer data) -{ - struct label *l = (struct label *) data; - - uiFree(l); -} - -uiControl *uiNewLabel(const char *text) -{ - uiControl *c; - struct label *l; - GtkWidget *widget; - - c = uiUnixNewControl(GTK_TYPE_LABEL, - FALSE, FALSE, - "label", text, - "xalign", 0.0, // note: must be a float constant, otherwise the ... will turn it into an int and we get segfaults on some platforms (thanks ebassi in irc.gimp.net/#gtk+) - // TODO yalign 0? - NULL); - - widget = GTK_WIDGET(uiControlHandle(c)); - - l = uiNew(struct label); - g_signal_connect(widget, "destroy", G_CALLBACK(onDestroy), l); - c->data = l; - - return c; -} - -char *uiLabelText(uiControl *c) -{ - // TODO change g_strdup() to a wrapper function for export in ui_unix.h - return g_strdup(gtk_label_get_text(GTK_LABEL(uiControlHandle(c)))); -} - -void uiLabelSetText(uiControl *c, const char *text) -{ - gtk_label_set_text(GTK_LABEL(uiControlHandle(c)), text); -} diff --git a/new/label_windows.c b/new/label_windows.c deleted file mode 100644 index 3d8a5cc..0000000 --- a/new/label_windows.c +++ /dev/null @@ -1,71 +0,0 @@ -// 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/main_darwin.m b/new/main_darwin.m deleted file mode 100644 index 8663b58..0000000 --- a/new/main_darwin.m +++ /dev/null @@ -1,27 +0,0 @@ -// 6 april 2015 -#import "uipriv_darwin.h" - -// #qo LDFLAGS: -lobjc -framework Foundation -framework AppKit - -void uiMain(void) -{ - [NSApp run]; -} - -void uiQuit(void) -{ - NSEvent *e; - - [NSApp stop:NSApp]; - // stop: won't register until another event has passed; let's synthesize one - e = [NSEvent otherEventWithType:NSApplicationDefined - location:NSZeroPoint - modifierFlags:0 - timestamp:[[NSProcessInfo processInfo] systemUptime] - windowNumber:0 - context:[NSGraphicsContext currentContext] - subtype:0 - data1:0 - data2:0]; - [NSApp postEvent:e atStart:NO]; // let pending events take priority (this is what PostQuitMessage() on Windows does so we have to do it here too for parity; thanks to mikeash in irc.freenode.net/#macdev for confirming that this parameter should indeed be NO) -} diff --git a/new/main_unix.c b/new/main_unix.c deleted file mode 100644 index 10af782..0000000 --- a/new/main_unix.c +++ /dev/null @@ -1,23 +0,0 @@ -// 6 april 2015 -#include "uipriv_unix.h" - -// #qo pkg-config: gtk+-3.0 - -void uiMain(void) -{ - gtk_main(); -} - -// gtk_main_quit() may run immediately, or it may wait for other pending events; "it depends" (thanks mclasen in irc.gimp.net/#gtk+) -// PostQuitMessage() on Windows always waits, so we must do so too -// we'll do it by using an idle callback -static gboolean quit(gpointer data) -{ - gtk_main_quit(); - return FALSE; -} - -void uiQuit(void) -{ - gdk_threads_add_idle(quit, NULL); -} diff --git a/new/main_windows.c b/new/main_windows.c deleted file mode 100644 index bd973b7..0000000 --- a/new/main_windows.c +++ /dev/null @@ -1,56 +0,0 @@ -// 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/newcontrol_darwin.m b/new/newcontrol_darwin.m deleted file mode 100644 index 9b7c6c4..0000000 --- a/new/newcontrol_darwin.m +++ /dev/null @@ -1,236 +0,0 @@ -// 7 april 2015 -#include "uipriv_darwin.h" - -typedef struct singleView singleView; - -struct singleView { - NSView *view; - NSScrollView *scrollView; - NSView *immediate; // the control that is added to the parent container; either view or scrollView - uiParent *parent; - BOOL userHid; - BOOL containerHid; - BOOL userDisabled; - BOOL containerDisabled; -}; - -static void singleDestroy(uiControl *c) -{ - singleView *s = (singleView *) (c->internal); - - [destroyedControlsView addSubview:s->immediate]; -} - -static uintptr_t singleHandle(uiControl *c) -{ - singleView *s = (singleView *) (c->internal); - - return (uintptr_t) (s->view); -} - -static void singleSetParent(uiControl *c, uiParent *parent) -{ - singleView *s = (singleView *) (c->internal); - NSView *parentView; - uiParent *oldparent; - - oldparent = s->parent; - s->parent = parent; - if (oldparent != NULL) { - [s->immediate removeFromSuperview]; - uiParentUpdate(oldparent); - } - if (s->parent != NULL) { - // TODO uiControlView(), uiParentView() - parentView = (NSView *) uiParentHandle(s->parent); - [parentView addSubview:s->immediate]; - uiParentUpdate(s->parent); - } -} - -// also good for NSBox and NSProgressIndicator -static void singlePreferredSize(uiControl *c, uiSizing *d, intmax_t *width, intmax_t *height) -{ - singleView *s = (singleView *) (c->internal); - NSControl *control; - NSRect r; - - control = (NSControl *) (s->view); - [control sizeToFit]; - // use alignmentRect here instead of frame because we'll be resizing based on that - r = [control alignmentRectForFrame:[control frame]]; - *width = (intmax_t) r.size.width; - *height = (intmax_t) r.size.height; -} - -static void singleResize(uiControl *c, intmax_t x, intmax_t y, intmax_t width, intmax_t height, uiSizing *d) -{ - singleView *s = (singleView *) (c->internal); - NSRect frame; - - frame.origin.x = x; - // mac os x coordinate system has (0,0) in the lower-left - frame.origin.y = ([[s->immediate superview] bounds].size.height - height) - y; - frame.size.width = width; - frame.size.height = height; - frame = [s->immediate frameForAlignmentRect:frame]; - [s->immediate setFrame:frame]; -} - -static int singleVisible(uiControl *c) -{ - singleView *s = (singleView *) (c->internal); - - if (s->userHid) - return 0; - return 1; -} - -static void singleShow(uiControl *c) -{ - singleView *s = (singleView *) (c->internal); - - s->userHid = NO; - if (!s->containerHid) { - [s->immediate setHidden:NO]; - if (s->parent != NULL) - uiParentUpdate(s->parent); - } -} - -static void singleHide(uiControl *c) -{ - singleView *s = (singleView *) (c->internal); - - s->userHid = YES; - [s->immediate setHidden:YES]; - if (s->parent != NULL) - uiParentUpdate(s->parent); -} - -static void singleContainerShow(uiControl *c) -{ - singleView *s = (singleView *) (c->internal); - - s->containerHid = NO; - if (!s->userHid) { - [s->immediate setHidden:NO]; - if (s->parent != NULL) - uiParentUpdate(s->parent); - } -} - -static void singleContainerHide(uiControl *c) -{ - singleView *s = (singleView *) (c->internal); - - s->containerHid = YES; - [s->immediate setHidden:YES]; - if (s->parent != NULL) - uiParentUpdate(s->parent); -} - -static void enable(singleView *s) -{ - if ([s->view respondsToSelector:@selector(setEnabled:)]) - [((NSControl *) (s->view)) setEnabled:YES]; -} - -static void disable(singleView *s) -{ - if ([s->view respondsToSelector:@selector(setEnabled:)]) - [((NSControl *) (s->view)) setEnabled:NO]; -} - -static void singleEnable(uiControl *c) -{ - singleView *s = (singleView *) (c->internal); - - s->userDisabled = NO; - if (!s->containerDisabled) - enable(s); -} - -static void singleDisable(uiControl *c) -{ - singleView *s = (singleView *) (c->internal); - - s->userDisabled = YES; - disable(s); -} - -static void singleContainerEnable(uiControl *c) -{ - singleView *s = (singleView *) (c->internal); - - s->containerDisabled = NO; - if (!s->userDisabled) - enable(s); -} - -static void singleContainerDisable(uiControl *c) -{ - singleView *s = (singleView *) (c->internal); - - s->containerDisabled = YES; - disable(s); -} - -uiControl *uiDarwinNewControl(Class class, BOOL inScrollView, BOOL scrollViewHasBorder) -{ - uiControl *c; - singleView *s; - - s = uiNew(singleView); - // thanks to autoxr and arwyn in irc.freenode.net/#macdev - s->view = (NSView *) [[class alloc] initWithFrame:NSZeroRect]; - s->immediate = s->view; - - if (inScrollView) { - s->scrollView = [[NSScrollView alloc] initWithFrame:NSZeroRect]; - [s->scrollView setDocumentView:s->view]; - [s->scrollView setHasHorizontalScroller:YES]; - [s->scrollView setHasVerticalScroller:YES]; - [s->scrollView setAutohidesScrollers:YES]; - if (scrollViewHasBorder) - [s->scrollView setBorderType:NSBezelBorder]; - else - [s->scrollView setBorderType:NSNoBorder]; - s->immediate = (NSView *) (s->scrollView); - } - - // and keep a reference to s->immediate for when we remove the control from its parent - [s->immediate retain]; - - c = uiNew(uiControl); - c->internal = s; - c->destroy = singleDestroy; - c->handle = singleHandle; - c->setParent = singleSetParent; - c->preferredSize = singlePreferredSize; - 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; - - return c; -} - -BOOL uiDarwinControlFreeWhenAppropriate(uiControl *c, NSView *newSuperview) -{ - singleView *s = (singleView *) (c->internal); - - if (newSuperview == destroyedControlsView) { - [s->immediate release]; // we don't need the reference anymore - uiFree(s); - uiFree(c); - return YES; - } - return NO; -} diff --git a/new/newcontrol_unix.c b/new/newcontrol_unix.c deleted file mode 100644 index 500798b..0000000 --- a/new/newcontrol_unix.c +++ /dev/null @@ -1,229 +0,0 @@ -// 7 april 2015 -#include "uipriv_unix.h" - -typedef struct singleWidget singleWidget; - -struct singleWidget { - GtkWidget *widget; - GtkWidget *scrolledWindow; - GtkWidget *immediate; // the widget that is added to the parent container; either widget or scrolledWindow - uiParent *parent; - gboolean userHid; - gboolean containerHid; - gboolean userDisabled; - gboolean containerDisabled; -}; - -static void singleDestroy(uiControl *c) -{ - singleWidget *s = (singleWidget *) (c->internal); - - gtk_widget_destroy(s->immediate); -} - -static uintptr_t singleHandle(uiControl *c) -{ - singleWidget *s = (singleWidget *) (c->internal); - - return (uintptr_t) (s->widget); -} - -static void singleSetParent(uiControl *c, uiParent *parent) -{ - singleWidget *s = (singleWidget *) (c->internal); - uiParent *oldparent; - - oldparent = s->parent; - s->parent = parent; - if (oldparent != NULL) { - gtk_container_remove(GTK_CONTAINER(uiParentHandle(oldparent)), s->immediate); - uiParentUpdate(oldparent); - } - if (s->parent != NULL) { - gtk_container_add(GTK_CONTAINER(uiParentHandle(s->parent)), s->immediate); - uiParentUpdate(s->parent); - } -} - -static void singlePreferredSize(uiControl *c, uiSizing *d, intmax_t *width, intmax_t *height) -{ - singleWidget *s = (singleWidget *) (c->internal); - GtkRequisition natural; - - // use the natural size as the minimum size is an *absolute* minimum - // for example, if a label has ellipsizing on, it can be the width of the ellipses, not the text - // there is a warning about height-for-width sizing, but in my tests this isn't an issue - gtk_widget_get_preferred_size(s->widget, NULL, &natural); - *width = natural.width; - *height = natural.height; -} - -static void singleResize(uiControl *c, intmax_t x, intmax_t y, intmax_t width, intmax_t height, uiSizing *d) -{ - singleWidget *s = (singleWidget *) (c->internal); - GtkAllocation a; - - a.x = x; - a.y = y; - a.width = width; - a.height = height; - gtk_widget_size_allocate(s->immediate, &a); -} - -static int singleVisible(uiControl *c) -{ - singleWidget *s = (singleWidget *) (c->internal); - - if (s->userHid) - return 0; - return 1; -} - -static void singleShow(uiControl *c) -{ - singleWidget *s = (singleWidget *) (c->internal); - - s->userHid = FALSE; - if (!s->containerHid) { - gtk_widget_show_all(s->immediate); - if (s->parent != NULL) - uiParentUpdate(s->parent); - } -} - -static void singleHide(uiControl *c) -{ - singleWidget *s = (singleWidget *) (c->internal); - - s->userHid = TRUE; - gtk_widget_hide(s->immediate); - if (s->parent != NULL) - uiParentUpdate(s->parent); -} - -static void singleContainerShow(uiControl *c) -{ - singleWidget *s = (singleWidget *) (c->internal); - - s->containerHid = FALSE; - if (!s->userHid) { - gtk_widget_show_all(s->immediate); - if (s->parent != NULL) - uiParentUpdate(s->parent); - } -} - -static void singleContainerHide(uiControl *c) -{ - singleWidget *s = (singleWidget *) (c->internal); - - s->containerHid = TRUE; - gtk_widget_hide(s->immediate); - if (s->parent != NULL) - uiParentUpdate(s->parent); -} - -static void singleEnable(uiControl *c) -{ - singleWidget *s = (singleWidget *) (c->internal); - - s->userDisabled = FALSE; - if (!s->containerDisabled) - gtk_widget_set_sensitive(s->immediate, TRUE); -} - -static void singleDisable(uiControl *c) -{ - singleWidget *s = (singleWidget *) (c->internal); - - s->userDisabled = TRUE; - gtk_widget_set_sensitive(s->immediate, FALSE); -} - -static void singleContainerEnable(uiControl *c) -{ - singleWidget *s = (singleWidget *) (c->internal); - - s->containerDisabled = FALSE; - if (!s->userDisabled) - gtk_widget_set_sensitive(s->immediate, TRUE); -} - -static void singleContainerDisable(uiControl *c) -{ - singleWidget *s = (singleWidget *) (c->internal); - - s->containerDisabled = TRUE; - gtk_widget_set_sensitive(s->immediate, FALSE); -} - -static void onDestroy(GtkWidget *widget, gpointer data) -{ - uiControl *c = (uiControl *) data; - singleWidget *s = (singleWidget *) (c->internal); - - uiFree(s); - uiFree(c); -} - -uiControl *uiUnixNewControl(GType type, gboolean inScrolledWindow, gboolean scrolledWindowHasBorder, const char *firstProperty, ...) -{ - uiControl *c; - singleWidget *s; - va_list ap; - - s = uiNew(singleWidget); - - va_start(ap, firstProperty); - s->widget = GTK_WIDGET(g_object_new_valist(type, firstProperty, ap)); - va_end(ap); - s->immediate = s->widget; - - if (inScrolledWindow) { - s->scrolledWindow = gtk_scrolled_window_new(NULL, NULL); - if (!GTK_IS_SCROLLABLE(s->widget)) - gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(s->scrolledWindow), s->widget); - else - gtk_container_add(GTK_CONTAINER(s->scrolledWindow), s->widget); - if (scrolledWindowHasBorder) - gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(s->scrolledWindow), GTK_SHADOW_IN); - s->immediate = s->scrolledWindow; - } - - // we need to keep an extra reference on the immediate widget - // this is so uiControlDestroy() can work regardless of when it is called and who calls it - // without this: - // - end user call works (only one ref) - // - call in uiContainer destructor fails (uiContainer ref freed) - // with this: - // - end user call works (shoudn't be in any container) - // - call in uiContainer works (both refs freed) - // this also ensures singleRemoveParent() works properly - g_object_ref_sink(s->immediate); - - c = uiNew(uiControl); - // assign s later; we still need it for one more thing - c->destroy = singleDestroy; - c->handle = singleHandle; - c->setParent = singleSetParent; - c->preferredSize = singlePreferredSize; - 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; - - // and let's free everything with the immediate widget - g_signal_connect(s->immediate, "destroy", G_CALLBACK(onDestroy), c); - - // finally, call gtk_widget_show_all() here to set the initial visibility of the widget - gtk_widget_show_all(s->immediate); - - c->internal = s; - return c; -} diff --git a/new/newcontrol_windows.c b/new/newcontrol_windows.c deleted file mode 100644 index 105b70c..0000000 --- a/new/newcontrol_windows.c +++ /dev/null @@ -1,241 +0,0 @@ -// 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/parent_darwin.m b/new/parent_darwin.m deleted file mode 100644 index abf0e59..0000000 --- a/new/parent_darwin.m +++ /dev/null @@ -1,118 +0,0 @@ -// 4 august 2014 -#import "uipriv_darwin.h" - -// calling -[className] on the content views of NSWindow, NSTabItem, and NSBox all return NSView, so I'm assuming I just need to override these -// fornunately: -// - NSWindow resizing calls -[setFrameSize:] (but not -[setFrame:]) -// - NSTabView resizing calls both -[setFrame:] and -[setFrameSIze:] on the current tab -// - NSTabView switching tabs calls both -[setFrame:] and -[setFrameSize:] on the new tab -// so we just override setFrameSize: -// thanks to mikeash and JtRip in irc.freenode.net/#macdev -@interface uipParent : NSView { -// TODO -@public - uiControl *child; - intmax_t marginLeft; - intmax_t marginTop; - intmax_t marginRight; - intmax_t marginBottom; -} -- (void)uiUpdateNow; -@end - -@implementation uipParent - -uiLogObjCClassAllocations - -- (void)viewDidMoveToSuperview -{ - // we can't just use nil because NSTabView will set page views to nil when they're tabbed away - // this means that we have to explicitly move them to the destroyed controls view when we're done with them, and likewise in NSWindow - if ([self superview] == destroyedControlsView) - if (self->child != NULL) { - uiControlDestroy(self->child); - self->child = NULL; - [self release]; - } - [super viewDidMoveToSuperview]; -} - -- (void)setFrameSize:(NSSize)s -{ - [super setFrameSize:s]; - [self uiUpdateNow]; -} - -// These are based on measurements from Interface Builder. -// These seem to be based on Auto Layout constants, but I don't see an API that exposes these... -// This one is 8 for most pairs of controls that I've tried; the only difference is between two pushbuttons, where it's 12... -#define macXPadding 8 -// Likewise, this one appears to be 12 for pairs of push buttons... -#define macYPadding 8 - -- (void)uiUpdateNow -{ - uiSizing d; - intmax_t x, y, width, height; - - if (self->child == NULL) - return; - x = [self bounds].origin.x + self->marginLeft; - y = [self bounds].origin.y + self->marginTop; - width = [self bounds].size.width - (self->marginLeft + self->marginRight); - height = [self bounds].size.height - (self->marginTop + self->marginBottom); - d.xPadding = macXPadding; - d.yPadding = macYPadding; - uiControlResize(self->child, x, y, width, height, &d); -} - -@end - -static uintptr_t parentHandle(uiParent *p) -{ - uipParent *pp = (uipParent *) (p->Internal); - - return (uintptr_t) pp; -} - -static void parentSetChild(uiParent *p, uiControl *child) -{ - uipParent *pp = (uipParent *) (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) -{ - uipParent *pp = (uipParent *) (p->Internal); - - pp->marginLeft = left; - pp->marginTop = top; - pp->marginRight = right; - pp->marginBottom = bottom; -} - -static void parentUpdate(uiParent *p) -{ - uipParent *pp = (uipParent *) (p->Internal); - - [pp uiUpdateNow]; -} - -uiParent *uiNewParent(uintptr_t osParent) -{ - uiParent *p; - - p = uiNew(uiParent); - p->Internal = [[uipParent alloc] initWithFrame:NSZeroRect]; - p->Handle = parentHandle; - p->SetChild = parentSetChild; - p->SetMargins = parentSetMargins; - p->Update = parentUpdate; - // don't use osParent; we'll need to call specific selectors to set the parent view - // and keep the view alive so we can release it properly later - [((uipParent *) (p->Internal)) retain]; - return p; -} \ No newline at end of file diff --git a/new/parent_unix.c b/new/parent_unix.c deleted file mode 100644 index 54d5d06..0000000 --- a/new/parent_unix.c +++ /dev/null @@ -1,183 +0,0 @@ -// 13 august 2014 -#include "uipriv_unix.h" - -#define uipParentType (uipParent_get_type()) -#define uipParent(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), uipParentType, uipParent)) -#define uipIsParent(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), uipParentType)) -#define uipParentClass(class) (G_TYPE_CHECK_CLASS_CAST((class), uipParentType, uipParentClass)) -#define uipIsParentClass(class) (G_TYPE_CHECK_CLASS_TYPE((class), uipParent)) -#define uipGetParentClass(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), uipParentType, uipParentClass)) - -typedef struct uipParent uipParent; -typedef struct uipParentClass uipParentClass; - -struct uipParent { - GtkContainer parent_instance; - // this is what triggers the resizing of all the children - uiControl *child; - // these are the actual children widgets of the container as far as GTK+ is concerned - GPtrArray *children; // for forall() - intmax_t marginLeft; - intmax_t marginTop; - intmax_t marginRight; - intmax_t marginBottom; -}; - -struct uipParentClass { - GtkContainerClass parent_class; -}; - -G_DEFINE_TYPE(uipParent, uipParent, GTK_TYPE_CONTAINER) - -static void uipParent_init(uipParent *p) -{ - if (options.debugLogAllocations) - fprintf(stderr, "%p alloc uipParent\n", p); - p->children = g_ptr_array_new(); - gtk_widget_set_has_window(GTK_WIDGET(p), FALSE); -} - -// instead of having GtkContainer itself unref all our controls, we'll run our own uiControlDestroy() functions for child, which will do that and more -// we still chain up because we need to, but by that point there will be no children for GtkContainer to free -static void uipParent_dispose(GObject *obj) -{ - uipParent *p = uipParent(obj); - - if (p->children != NULL) { - g_ptr_array_unref(p->children); - p->children = NULL; - } - if (p->child != NULL) { - uiControlDestroy(p->child); - p->child = NULL; - } - G_OBJECT_CLASS(uipParent_parent_class)->dispose(obj); -} - -static void uipParent_finalize(GObject *obj) -{ - G_OBJECT_CLASS(uipParent_parent_class)->finalize(obj); - if (options.debugLogAllocations) - fprintf(stderr, "%p free\n", obj); -} - -static void uipParent_add(GtkContainer *container, GtkWidget *widget) -{ - uipParent *p = uipParent(container); - - gtk_widget_set_parent(widget, GTK_WIDGET(p)); - if (p->children != NULL) - g_ptr_array_add(p->children, widget); -} - -static void uipParent_remove(GtkContainer *container, GtkWidget *widget) -{ - uipParent *p = uipParent(container); - - gtk_widget_unparent(widget); - if (p->children != NULL) - g_ptr_array_remove(p->children, widget); -} - -#define gtkXPadding 12 -#define gtkYPadding 6 - -static void uipParent_size_allocate(GtkWidget *widget, GtkAllocation *allocation) -{ - uipParent *p = uipParent(widget); - uiSizing d; - intmax_t x, y, width, height; - - gtk_widget_set_allocation(GTK_WIDGET(p), allocation); - if (p->child == NULL) - return; - x = allocation->x + p->marginLeft; - y = allocation->y + p->marginTop; - width = allocation->width - (p->marginLeft + p->marginRight); - height = allocation->height - (p->marginTop + p->marginBottom); - d.xPadding = gtkXPadding; - d.yPadding = gtkYPadding; - uiControlResize(p->child, x, y, width, height, &d); -} - -struct forall { - GtkCallback callback; - gpointer data; -}; - -static void doforall(gpointer obj, gpointer data) -{ - struct forall *s = (struct forall *) data; - - (*(s->callback))(GTK_WIDGET(obj), s->data); -} - -static void uipParent_forall(GtkContainer *container, gboolean includeInternals, GtkCallback callback, gpointer data) -{ - uipParent *p = uipParent(container); - struct forall s; - - s.callback = callback; - s.data = data; - if (p->children != NULL) - g_ptr_array_foreach(p->children, doforall, &s); -} - -static void uipParent_class_init(uipParentClass *class) -{ - G_OBJECT_CLASS(class)->dispose = uipParent_dispose; - G_OBJECT_CLASS(class)->finalize = uipParent_finalize; - GTK_WIDGET_CLASS(class)->size_allocate = uipParent_size_allocate; - GTK_CONTAINER_CLASS(class)->add = uipParent_add; - GTK_CONTAINER_CLASS(class)->remove = uipParent_remove; - GTK_CONTAINER_CLASS(class)->forall = uipParent_forall; -} - -static uintptr_t parentHandle(uiParent *p) -{ - uipParent *pp = uipParent(p->Internal); - - return (uintptr_t) pp; -} - -static void parentSetChild(uiParent *p, uiControl *child) -{ - uipParent *pp = uipParent(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) -{ - uipParent *pp = uipParent(p->Internal); - - pp->marginLeft = left; - pp->marginTop = top; - pp->marginRight = right; - pp->marginBottom = bottom; -} - -static void parentUpdate(uiParent *p) -{ - uipParent *pp = uipParent(p->Internal); - - gtk_widget_queue_resize(GTK_WIDGET(pp)); -} - -uiParent *uiNewParent(uintptr_t osParent) -{ - uiParent *p; - - p = uiNew(uiParent); - p->Internal = g_object_new(uipParentType, NULL); - p->Handle = parentHandle; - p->SetChild = parentSetChild; - p->SetMargins = parentSetMargins; - p->Update = parentUpdate; - gtk_container_add(GTK_CONTAINER(osParent), GTK_WIDGET(p->Internal)); - // and make it visible by default - gtk_widget_show_all(GTK_WIDGET(p->Internal)); - return p; -} diff --git a/new/parent_windows.c b/new/parent_windows.c deleted file mode 100644 index b3606d1..0000000 --- a/new/parent_windows.c +++ /dev/null @@ -1,268 +0,0 @@ -// 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] There's flickering between tabs -// - with CTLCOLOR handler: [12:24] And setting the button text blanked out the entire GUI until I ran my mouse over the elements / [12:25] https://dl.dropboxusercontent.com/u/15144168/GUI%20stuff.png / [12:41] 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/tab_darwin.m b/new/tab_darwin.m deleted file mode 100644 index c36181e..0000000 --- a/new/tab_darwin.m +++ /dev/null @@ -1,67 +0,0 @@ -// 12 april 2015 -#import "uipriv_darwin.h" - -// TODO -// - verify margins against extra space around the tab -// - free child containers properly - -@interface uiNSTabView : NSTabView -@property uiControl *uiC; -@end - -@implementation uiNSTabView - -- (void)viewDidMoveToSuperview -{ - // TODO free all tabs explicitly - if (uiDarwinControlFreeWhenAppropriate(self.uiC, [self superview])) - self.uiC = NULL; - [super viewDidMoveToSuperview]; -} - -@end - -// the default new control implementation uses -sizeToFit, which we don't have with NSTabView -// fortunately, we do have -minimumSize -static void preferredSize(uiControl *c, uiSizing *d, intmax_t *width, intmax_t *height) -{ - uiNSTabView *tv; - NSSize s; - - tv = (uiNSTabView *) uiControlHandle(c); - s = [tv minimumSize]; - *width = (intmax_t) (s.width); - *height = (intmax_t) (s.height); -} - -uiControl *uiNewTab(void) -{ - uiControl *c; - uiNSTabView *t; - - c = uiDarwinNewControl([uiNSTabView class], NO, NO); - c->preferredSize = preferredSize; - t = (uiNSTabView *) uiControlHandle(c); - t.uiC = c; - - // also good for NSTabView (same selector and everything) - setStandardControlFont((NSControl *) t); - - return c; -} - -void uiTabAddPage(uiControl *c, const char *name, uiControl *child) -{ - uiNSTabView *tv; - uiParent *content; - NSTabViewItem *i; - - content = uiNewParent(0); - uiParentSetChild(content, child); - - i = [[NSTabViewItem alloc] initWithIdentifier:nil]; - [i setLabel:toNSString(name)]; - [i setView:((NSView *) uiParentHandle(content))]; - tv = (uiNSTabView *) uiControlHandle(c); - [tv addTabViewItem:i]; -} diff --git a/new/tab_unix.c b/new/tab_unix.c deleted file mode 100644 index dd69e4c..0000000 --- a/new/tab_unix.c +++ /dev/null @@ -1,58 +0,0 @@ -// 12 april 2015 -#include "uipriv_unix.h" - -struct tab { - uiParent **pages; - uintmax_t len; - uintmax_t cap; -}; - -static void onDestroy(GtkWidget *widget, gpointer data) -{ - struct tab *t = (struct tab *) data; - - uiFree(t->pages); - uiFree(t); -} - -uiControl *uiNewTab(void) -{ - uiControl *c; - struct tab *t; - GtkWidget *widget; - - c = uiUnixNewControl(GTK_TYPE_NOTEBOOK, - FALSE, FALSE, - NULL); - - widget = GTK_WIDGET(uiControlHandle(c)); - - t = uiNew(struct tab); - g_signal_connect(widget, "destroy", G_CALLBACK(onDestroy), t); - c->data = t; - - return c; -} - -#define tabCapGrow 32 - -void uiTabAddPage(uiControl *c, const char *name, uiControl *child) -{ - struct tab *t = (struct tab *) (c->data); - GtkWidget *notebook; - uiParent *content; - - if (t->len >= t->cap) { - t->cap += tabCapGrow; - t->pages = (uiParent **) uiRealloc(t->pages, t->cap * sizeof (uiParent *), "uiParent *[]"); - } - - notebook = GTK_WIDGET(uiControlHandle(c)); - content = uiNewParent((uintptr_t) notebook); - uiParentSetChild(content, child); - uiParentUpdate(content); - gtk_notebook_set_tab_label_text(GTK_NOTEBOOK(notebook), GTK_WIDGET(uiParentHandle(content)), name); - - t->pages[t->len] = content; - t->len++; -} diff --git a/new/tab_windows.c b/new/tab_windows.c deleted file mode 100644 index 00fccb0..0000000 --- a/new/tab_windows.c +++ /dev/null @@ -1,189 +0,0 @@ -// 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/text_darwin.m b/new/text_darwin.m deleted file mode 100644 index f0d3dab..0000000 --- a/new/text_darwin.m +++ /dev/null @@ -1,19 +0,0 @@ -// 10 april 2015 -#import "uipriv_darwin.h" - -char *uiDarwinNSStringToText(NSString *s) -{ - char *out; - - out = strdup([s UTF8String]); - if (out == NULL) { - fprintf(stderr, "memory exhausted in uiDarwinNSStringToText()\n"); - abort(); - } - return out; -} - -void uiFreeText(char *s) -{ - free(s); -} diff --git a/new/text_windows.c b/new/text_windows.c deleted file mode 100644 index 8d6d0b0..0000000 --- a/new/text_windows.c +++ /dev/null @@ -1,55 +0,0 @@ -// 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/unix/alloc.c b/new/unix/alloc.c new file mode 100644 index 0000000..33482b2 --- /dev/null +++ b/new/unix/alloc.c @@ -0,0 +1,33 @@ +// 7 april 2015 +#include +#include "uipriv_unix.h" + +void *uiAlloc(size_t size, const char *type) +{ + void *out; + + out = g_malloc0(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); + // TODO fill with 0s + out = g_realloc(p, size); + if (options.debugLogAllocations) + fprintf(stderr, "%p realloc %p\n", p, out); + return out; +} + +void uiFree(void *p) +{ + g_free(p); + if (options.debugLogAllocations) + fprintf(stderr, "%p free\n", p); +} diff --git a/new/unix/button.c b/new/unix/button.c new file mode 100644 index 0000000..da09082 --- /dev/null +++ b/new/unix/button.c @@ -0,0 +1,67 @@ +// 7 april 2015 +#include "uipriv_unix.h" + +struct button { + void (*onClicked)(uiControl *, void *); + void *onClickedData; +}; + +static void onClicked(GtkButton *button, gpointer data) +{ + uiControl *c = (uiControl *) data; + struct button *b = (struct button *) (c->data); + + (*(b->onClicked))(c, b->onClickedData); +} + +static void defaultOnClicked(uiControl *c, void *data) +{ + // do nothing +} + +static void onDestroy(GtkWidget *widget, gpointer data) +{ + struct button *b = (struct button *) data; + + uiFree(b); +} + +uiControl *uiNewButton(const char *text) +{ + uiControl *c; + struct button *b; + GtkWidget *widget; + + c = uiUnixNewControl(GTK_TYPE_BUTTON, + FALSE, FALSE, + "label", text, + NULL); + + widget = GTK_WIDGET(uiControlHandle(c)); + g_signal_connect(widget, "clicked", G_CALLBACK(onClicked), c); + + b = uiNew(struct button); + g_signal_connect(widget, "destroy", G_CALLBACK(onDestroy), b); + b->onClicked = defaultOnClicked; + c->data = b; + + return c; +} + +char *uiButtonText(uiControl *c) +{ + return g_strdup(gtk_button_get_label(GTK_BUTTON(uiControlHandle(c)))); +} + +void uiButtonSetText(uiControl *c, const char *text) +{ + gtk_button_set_label(GTK_BUTTON(uiControlHandle(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/unix/checkbox.c b/new/unix/checkbox.c new file mode 100644 index 0000000..ddf9eda --- /dev/null +++ b/new/unix/checkbox.c @@ -0,0 +1,89 @@ +// 7 april 2015 +#include "uipriv_unix.h" + +struct checkbox { + void (*onToggled)(uiControl *, void *); + void *onToggledData; + gulong onToggledSignal; +}; + +static void onToggled(GtkToggleButton *b, gpointer data) +{ + uiControl *c = (uiControl *) data; + struct checkbox *cc = (struct checkbox *) (c->data); + + (*(cc->onToggled))(c, cc->onToggledData); +} + +static void defaultOnToggled(uiControl *c, void *data) +{ + // do nothing +} + +static void onDestroy(GtkWidget *widget, gpointer data) +{ + struct checkbox *cc = (struct checkbox *) data; + + uiFree(cc); +} + +uiControl *uiNewCheckbox(const char *text) +{ + uiControl *c; + struct checkbox *cc; + GtkWidget *widget; + + c = uiUnixNewControl(GTK_TYPE_CHECK_BUTTON, + FALSE, FALSE, + "label", text, + NULL); + + widget = GTK_WIDGET(uiControlHandle(c)); + + cc = uiNew(struct checkbox); + g_signal_connect(widget, "destroy", G_CALLBACK(onDestroy), cc); + cc->onToggledSignal = g_signal_connect(widget, "toggled", G_CALLBACK(onToggled), c); + cc->onToggled = defaultOnToggled; + c->data = cc; + + return c; +} + +char *uiCheckboxText(uiControl *c) +{ + return g_strdup(gtk_button_get_label(GTK_BUTTON(uiControlHandle(c)))); +} + +void uiCheckboxSetText(uiControl *c, const char *text) +{ + gtk_button_set_label(GTK_BUTTON(uiControlHandle(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) +{ + return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(uiControlHandle(c))) != FALSE; +} + +void uiCheckboxSetChecked(uiControl *c, int checked) +{ + struct checkbox *cc = (struct checkbox *) (c->data); + GtkToggleButton *button; + gboolean active; + + active = FALSE; + if (checked) + active = TRUE; + // we need to inhibit sending of ::toggled because this WILL send a ::toggled otherwise + button = GTK_TOGGLE_BUTTON(uiControlHandle(c)); + g_signal_handler_block(button, cc->onToggledSignal); + gtk_toggle_button_set_active(button, active); + g_signal_handler_unblock(button, cc->onToggledSignal); +} diff --git a/new/unix/entry.c b/new/unix/entry.c new file mode 100644 index 0000000..43caea3 --- /dev/null +++ b/new/unix/entry.c @@ -0,0 +1,41 @@ +// 8 april 2015 +#include "uipriv_unix.h" + +struct entry { +}; + +static void onDestroy(GtkWidget *widget, gpointer data) +{ + struct entry *e = (struct entry *) data; + + uiFree(e); +} + +uiControl *uiNewEntry(void) +{ + uiControl *c; + struct entry *e; + GtkWidget *widget; + + c = uiUnixNewControl(GTK_TYPE_ENTRY, + FALSE, FALSE, + NULL); + + widget = GTK_WIDGET(uiControlHandle(c)); + + e = uiNew(struct entry); + g_signal_connect(widget, "destroy", G_CALLBACK(onDestroy), e); + c->data = e; + + return c; +} + +char *uiEntryText(uiControl *c) +{ + return g_strdup(gtk_entry_get_text(GTK_ENTRY(uiControlHandle(c)))); +} + +void uiEntrySetText(uiControl *c, const char *text) +{ + gtk_entry_set_text(GTK_ENTRY(uiControlHandle(c)), text); +} diff --git a/new/unix/init.c b/new/unix/init.c new file mode 100644 index 0000000..b10c05b --- /dev/null +++ b/new/unix/init.c @@ -0,0 +1,23 @@ +// 6 april 2015 +#include "uipriv_unix.h" + +uiInitOptions options; + +const char *uiInit(uiInitOptions *o) +{ + GError *err = NULL; + const char *msg; + + options = *o; + if (gtk_init_with_args(NULL, NULL, NULL, NULL, NULL, &err) == FALSE) { + msg = g_strdup(err->message); + g_error_free(err); + return msg; + } + return NULL; +} + +void uiFreeInitError(const char *err) +{ + g_free((gpointer) err); +} diff --git a/new/unix/label.c b/new/unix/label.c new file mode 100644 index 0000000..1f950d3 --- /dev/null +++ b/new/unix/label.c @@ -0,0 +1,45 @@ +// 11 april 2015 +#include "uipriv_unix.h" + +struct label { +}; + +static void onDestroy(GtkWidget *widget, gpointer data) +{ + struct label *l = (struct label *) data; + + uiFree(l); +} + +uiControl *uiNewLabel(const char *text) +{ + uiControl *c; + struct label *l; + GtkWidget *widget; + + c = uiUnixNewControl(GTK_TYPE_LABEL, + FALSE, FALSE, + "label", text, + "xalign", 0.0, // note: must be a float constant, otherwise the ... will turn it into an int and we get segfaults on some platforms (thanks ebassi in irc.gimp.net/#gtk+) + // TODO yalign 0? + NULL); + + widget = GTK_WIDGET(uiControlHandle(c)); + + l = uiNew(struct label); + g_signal_connect(widget, "destroy", G_CALLBACK(onDestroy), l); + c->data = l; + + return c; +} + +char *uiLabelText(uiControl *c) +{ + // TODO change g_strdup() to a wrapper function for export in ui_unix.h + return g_strdup(gtk_label_get_text(GTK_LABEL(uiControlHandle(c)))); +} + +void uiLabelSetText(uiControl *c, const char *text) +{ + gtk_label_set_text(GTK_LABEL(uiControlHandle(c)), text); +} diff --git a/new/unix/main.c b/new/unix/main.c new file mode 100644 index 0000000..10af782 --- /dev/null +++ b/new/unix/main.c @@ -0,0 +1,23 @@ +// 6 april 2015 +#include "uipriv_unix.h" + +// #qo pkg-config: gtk+-3.0 + +void uiMain(void) +{ + gtk_main(); +} + +// gtk_main_quit() may run immediately, or it may wait for other pending events; "it depends" (thanks mclasen in irc.gimp.net/#gtk+) +// PostQuitMessage() on Windows always waits, so we must do so too +// we'll do it by using an idle callback +static gboolean quit(gpointer data) +{ + gtk_main_quit(); + return FALSE; +} + +void uiQuit(void) +{ + gdk_threads_add_idle(quit, NULL); +} diff --git a/new/unix/newcontrol.c b/new/unix/newcontrol.c new file mode 100644 index 0000000..500798b --- /dev/null +++ b/new/unix/newcontrol.c @@ -0,0 +1,229 @@ +// 7 april 2015 +#include "uipriv_unix.h" + +typedef struct singleWidget singleWidget; + +struct singleWidget { + GtkWidget *widget; + GtkWidget *scrolledWindow; + GtkWidget *immediate; // the widget that is added to the parent container; either widget or scrolledWindow + uiParent *parent; + gboolean userHid; + gboolean containerHid; + gboolean userDisabled; + gboolean containerDisabled; +}; + +static void singleDestroy(uiControl *c) +{ + singleWidget *s = (singleWidget *) (c->internal); + + gtk_widget_destroy(s->immediate); +} + +static uintptr_t singleHandle(uiControl *c) +{ + singleWidget *s = (singleWidget *) (c->internal); + + return (uintptr_t) (s->widget); +} + +static void singleSetParent(uiControl *c, uiParent *parent) +{ + singleWidget *s = (singleWidget *) (c->internal); + uiParent *oldparent; + + oldparent = s->parent; + s->parent = parent; + if (oldparent != NULL) { + gtk_container_remove(GTK_CONTAINER(uiParentHandle(oldparent)), s->immediate); + uiParentUpdate(oldparent); + } + if (s->parent != NULL) { + gtk_container_add(GTK_CONTAINER(uiParentHandle(s->parent)), s->immediate); + uiParentUpdate(s->parent); + } +} + +static void singlePreferredSize(uiControl *c, uiSizing *d, intmax_t *width, intmax_t *height) +{ + singleWidget *s = (singleWidget *) (c->internal); + GtkRequisition natural; + + // use the natural size as the minimum size is an *absolute* minimum + // for example, if a label has ellipsizing on, it can be the width of the ellipses, not the text + // there is a warning about height-for-width sizing, but in my tests this isn't an issue + gtk_widget_get_preferred_size(s->widget, NULL, &natural); + *width = natural.width; + *height = natural.height; +} + +static void singleResize(uiControl *c, intmax_t x, intmax_t y, intmax_t width, intmax_t height, uiSizing *d) +{ + singleWidget *s = (singleWidget *) (c->internal); + GtkAllocation a; + + a.x = x; + a.y = y; + a.width = width; + a.height = height; + gtk_widget_size_allocate(s->immediate, &a); +} + +static int singleVisible(uiControl *c) +{ + singleWidget *s = (singleWidget *) (c->internal); + + if (s->userHid) + return 0; + return 1; +} + +static void singleShow(uiControl *c) +{ + singleWidget *s = (singleWidget *) (c->internal); + + s->userHid = FALSE; + if (!s->containerHid) { + gtk_widget_show_all(s->immediate); + if (s->parent != NULL) + uiParentUpdate(s->parent); + } +} + +static void singleHide(uiControl *c) +{ + singleWidget *s = (singleWidget *) (c->internal); + + s->userHid = TRUE; + gtk_widget_hide(s->immediate); + if (s->parent != NULL) + uiParentUpdate(s->parent); +} + +static void singleContainerShow(uiControl *c) +{ + singleWidget *s = (singleWidget *) (c->internal); + + s->containerHid = FALSE; + if (!s->userHid) { + gtk_widget_show_all(s->immediate); + if (s->parent != NULL) + uiParentUpdate(s->parent); + } +} + +static void singleContainerHide(uiControl *c) +{ + singleWidget *s = (singleWidget *) (c->internal); + + s->containerHid = TRUE; + gtk_widget_hide(s->immediate); + if (s->parent != NULL) + uiParentUpdate(s->parent); +} + +static void singleEnable(uiControl *c) +{ + singleWidget *s = (singleWidget *) (c->internal); + + s->userDisabled = FALSE; + if (!s->containerDisabled) + gtk_widget_set_sensitive(s->immediate, TRUE); +} + +static void singleDisable(uiControl *c) +{ + singleWidget *s = (singleWidget *) (c->internal); + + s->userDisabled = TRUE; + gtk_widget_set_sensitive(s->immediate, FALSE); +} + +static void singleContainerEnable(uiControl *c) +{ + singleWidget *s = (singleWidget *) (c->internal); + + s->containerDisabled = FALSE; + if (!s->userDisabled) + gtk_widget_set_sensitive(s->immediate, TRUE); +} + +static void singleContainerDisable(uiControl *c) +{ + singleWidget *s = (singleWidget *) (c->internal); + + s->containerDisabled = TRUE; + gtk_widget_set_sensitive(s->immediate, FALSE); +} + +static void onDestroy(GtkWidget *widget, gpointer data) +{ + uiControl *c = (uiControl *) data; + singleWidget *s = (singleWidget *) (c->internal); + + uiFree(s); + uiFree(c); +} + +uiControl *uiUnixNewControl(GType type, gboolean inScrolledWindow, gboolean scrolledWindowHasBorder, const char *firstProperty, ...) +{ + uiControl *c; + singleWidget *s; + va_list ap; + + s = uiNew(singleWidget); + + va_start(ap, firstProperty); + s->widget = GTK_WIDGET(g_object_new_valist(type, firstProperty, ap)); + va_end(ap); + s->immediate = s->widget; + + if (inScrolledWindow) { + s->scrolledWindow = gtk_scrolled_window_new(NULL, NULL); + if (!GTK_IS_SCROLLABLE(s->widget)) + gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(s->scrolledWindow), s->widget); + else + gtk_container_add(GTK_CONTAINER(s->scrolledWindow), s->widget); + if (scrolledWindowHasBorder) + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(s->scrolledWindow), GTK_SHADOW_IN); + s->immediate = s->scrolledWindow; + } + + // we need to keep an extra reference on the immediate widget + // this is so uiControlDestroy() can work regardless of when it is called and who calls it + // without this: + // - end user call works (only one ref) + // - call in uiContainer destructor fails (uiContainer ref freed) + // with this: + // - end user call works (shoudn't be in any container) + // - call in uiContainer works (both refs freed) + // this also ensures singleRemoveParent() works properly + g_object_ref_sink(s->immediate); + + c = uiNew(uiControl); + // assign s later; we still need it for one more thing + c->destroy = singleDestroy; + c->handle = singleHandle; + c->setParent = singleSetParent; + c->preferredSize = singlePreferredSize; + 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; + + // and let's free everything with the immediate widget + g_signal_connect(s->immediate, "destroy", G_CALLBACK(onDestroy), c); + + // finally, call gtk_widget_show_all() here to set the initial visibility of the widget + gtk_widget_show_all(s->immediate); + + c->internal = s; + return c; +} diff --git a/new/unix/parent.c b/new/unix/parent.c new file mode 100644 index 0000000..54d5d06 --- /dev/null +++ b/new/unix/parent.c @@ -0,0 +1,183 @@ +// 13 august 2014 +#include "uipriv_unix.h" + +#define uipParentType (uipParent_get_type()) +#define uipParent(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), uipParentType, uipParent)) +#define uipIsParent(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), uipParentType)) +#define uipParentClass(class) (G_TYPE_CHECK_CLASS_CAST((class), uipParentType, uipParentClass)) +#define uipIsParentClass(class) (G_TYPE_CHECK_CLASS_TYPE((class), uipParent)) +#define uipGetParentClass(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), uipParentType, uipParentClass)) + +typedef struct uipParent uipParent; +typedef struct uipParentClass uipParentClass; + +struct uipParent { + GtkContainer parent_instance; + // this is what triggers the resizing of all the children + uiControl *child; + // these are the actual children widgets of the container as far as GTK+ is concerned + GPtrArray *children; // for forall() + intmax_t marginLeft; + intmax_t marginTop; + intmax_t marginRight; + intmax_t marginBottom; +}; + +struct uipParentClass { + GtkContainerClass parent_class; +}; + +G_DEFINE_TYPE(uipParent, uipParent, GTK_TYPE_CONTAINER) + +static void uipParent_init(uipParent *p) +{ + if (options.debugLogAllocations) + fprintf(stderr, "%p alloc uipParent\n", p); + p->children = g_ptr_array_new(); + gtk_widget_set_has_window(GTK_WIDGET(p), FALSE); +} + +// instead of having GtkContainer itself unref all our controls, we'll run our own uiControlDestroy() functions for child, which will do that and more +// we still chain up because we need to, but by that point there will be no children for GtkContainer to free +static void uipParent_dispose(GObject *obj) +{ + uipParent *p = uipParent(obj); + + if (p->children != NULL) { + g_ptr_array_unref(p->children); + p->children = NULL; + } + if (p->child != NULL) { + uiControlDestroy(p->child); + p->child = NULL; + } + G_OBJECT_CLASS(uipParent_parent_class)->dispose(obj); +} + +static void uipParent_finalize(GObject *obj) +{ + G_OBJECT_CLASS(uipParent_parent_class)->finalize(obj); + if (options.debugLogAllocations) + fprintf(stderr, "%p free\n", obj); +} + +static void uipParent_add(GtkContainer *container, GtkWidget *widget) +{ + uipParent *p = uipParent(container); + + gtk_widget_set_parent(widget, GTK_WIDGET(p)); + if (p->children != NULL) + g_ptr_array_add(p->children, widget); +} + +static void uipParent_remove(GtkContainer *container, GtkWidget *widget) +{ + uipParent *p = uipParent(container); + + gtk_widget_unparent(widget); + if (p->children != NULL) + g_ptr_array_remove(p->children, widget); +} + +#define gtkXPadding 12 +#define gtkYPadding 6 + +static void uipParent_size_allocate(GtkWidget *widget, GtkAllocation *allocation) +{ + uipParent *p = uipParent(widget); + uiSizing d; + intmax_t x, y, width, height; + + gtk_widget_set_allocation(GTK_WIDGET(p), allocation); + if (p->child == NULL) + return; + x = allocation->x + p->marginLeft; + y = allocation->y + p->marginTop; + width = allocation->width - (p->marginLeft + p->marginRight); + height = allocation->height - (p->marginTop + p->marginBottom); + d.xPadding = gtkXPadding; + d.yPadding = gtkYPadding; + uiControlResize(p->child, x, y, width, height, &d); +} + +struct forall { + GtkCallback callback; + gpointer data; +}; + +static void doforall(gpointer obj, gpointer data) +{ + struct forall *s = (struct forall *) data; + + (*(s->callback))(GTK_WIDGET(obj), s->data); +} + +static void uipParent_forall(GtkContainer *container, gboolean includeInternals, GtkCallback callback, gpointer data) +{ + uipParent *p = uipParent(container); + struct forall s; + + s.callback = callback; + s.data = data; + if (p->children != NULL) + g_ptr_array_foreach(p->children, doforall, &s); +} + +static void uipParent_class_init(uipParentClass *class) +{ + G_OBJECT_CLASS(class)->dispose = uipParent_dispose; + G_OBJECT_CLASS(class)->finalize = uipParent_finalize; + GTK_WIDGET_CLASS(class)->size_allocate = uipParent_size_allocate; + GTK_CONTAINER_CLASS(class)->add = uipParent_add; + GTK_CONTAINER_CLASS(class)->remove = uipParent_remove; + GTK_CONTAINER_CLASS(class)->forall = uipParent_forall; +} + +static uintptr_t parentHandle(uiParent *p) +{ + uipParent *pp = uipParent(p->Internal); + + return (uintptr_t) pp; +} + +static void parentSetChild(uiParent *p, uiControl *child) +{ + uipParent *pp = uipParent(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) +{ + uipParent *pp = uipParent(p->Internal); + + pp->marginLeft = left; + pp->marginTop = top; + pp->marginRight = right; + pp->marginBottom = bottom; +} + +static void parentUpdate(uiParent *p) +{ + uipParent *pp = uipParent(p->Internal); + + gtk_widget_queue_resize(GTK_WIDGET(pp)); +} + +uiParent *uiNewParent(uintptr_t osParent) +{ + uiParent *p; + + p = uiNew(uiParent); + p->Internal = g_object_new(uipParentType, NULL); + p->Handle = parentHandle; + p->SetChild = parentSetChild; + p->SetMargins = parentSetMargins; + p->Update = parentUpdate; + gtk_container_add(GTK_CONTAINER(osParent), GTK_WIDGET(p->Internal)); + // and make it visible by default + gtk_widget_show_all(GTK_WIDGET(p->Internal)); + return p; +} diff --git a/new/unix/tab.c b/new/unix/tab.c new file mode 100644 index 0000000..dd69e4c --- /dev/null +++ b/new/unix/tab.c @@ -0,0 +1,58 @@ +// 12 april 2015 +#include "uipriv_unix.h" + +struct tab { + uiParent **pages; + uintmax_t len; + uintmax_t cap; +}; + +static void onDestroy(GtkWidget *widget, gpointer data) +{ + struct tab *t = (struct tab *) data; + + uiFree(t->pages); + uiFree(t); +} + +uiControl *uiNewTab(void) +{ + uiControl *c; + struct tab *t; + GtkWidget *widget; + + c = uiUnixNewControl(GTK_TYPE_NOTEBOOK, + FALSE, FALSE, + NULL); + + widget = GTK_WIDGET(uiControlHandle(c)); + + t = uiNew(struct tab); + g_signal_connect(widget, "destroy", G_CALLBACK(onDestroy), t); + c->data = t; + + return c; +} + +#define tabCapGrow 32 + +void uiTabAddPage(uiControl *c, const char *name, uiControl *child) +{ + struct tab *t = (struct tab *) (c->data); + GtkWidget *notebook; + uiParent *content; + + if (t->len >= t->cap) { + t->cap += tabCapGrow; + t->pages = (uiParent **) uiRealloc(t->pages, t->cap * sizeof (uiParent *), "uiParent *[]"); + } + + notebook = GTK_WIDGET(uiControlHandle(c)); + content = uiNewParent((uintptr_t) notebook); + uiParentSetChild(content, child); + uiParentUpdate(content); + gtk_notebook_set_tab_label_text(GTK_NOTEBOOK(notebook), GTK_WIDGET(uiParentHandle(content)), name); + + t->pages[t->len] = content; + t->len++; +} diff --git a/new/unix/util.c b/new/unix/util.c new file mode 100644 index 0000000..72b71df --- /dev/null +++ b/new/unix/util.c @@ -0,0 +1,7 @@ +// 9 april 2015 +#include "uipriv_unix.h" + +void uiFreeText(char *t) +{ + g_free(t); +} diff --git a/new/unix/window.c b/new/unix/window.c new file mode 100644 index 0000000..e3fe6f3 --- /dev/null +++ b/new/unix/window.c @@ -0,0 +1,105 @@ +// 6 april 2015 +#include "uipriv_unix.h" + +struct uiWindow { + GtkWidget *widget; + uiParent *content; + int (*onClosing)(uiWindow *, void *); + void *onClosingData; + int margined; +}; + +static gboolean onClosing(GtkWidget *win, GdkEvent *e, gpointer data) +{ + uiWindow *w = (uiWindow *) data; + + // return exact values just in case + if ((*(w->onClosing))(w, w->onClosingData)) + return FALSE; + return TRUE; +} + +static int defaultOnClosing(uiWindow *w, void *data) +{ + return 1; +} + +static void onDestroy(GtkWidget *widget, gpointer data) +{ + uiWindow *w = (uiWindow *) data; + + uiFree(w); +} + +uiWindow *uiNewWindow(char *title, int width, int height) +{ + uiWindow *w; + + w = uiNew(uiWindow); + w->widget = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(w->widget), title); + gtk_window_resize(GTK_WINDOW(w->widget), width, height); + g_signal_connect(w->widget, "delete-event", G_CALLBACK(onClosing), w); + g_signal_connect(w->widget, "destroy", G_CALLBACK(onDestroy), w); + w->content = uiNewParent((uintptr_t) (w->widget)); + w->onClosing = defaultOnClosing; + return w; +} + +void uiWindowDestroy(uiWindow *w) +{ + gtk_widget_destroy(w->widget); +} + +uintptr_t uiWindowHandle(uiWindow *w) +{ + return (uintptr_t) (w->widget); +} + +char *uiWindowTitle(uiWindow *w) +{ + return g_strdup(gtk_window_get_title(GTK_WINDOW(w->widget))); +} + +void uiWindowSetTitle(uiWindow *w, const char *title) +{ + gtk_window_set_title(GTK_WINDOW(w->widget), title); +} + +void uiWindowShow(uiWindow *w) +{ + // don't use gtk_widget_show_all(); that will override user hidden settings + gtk_widget_show(w->widget); +} + +void uiWindowHide(uiWindow *w) +{ + gtk_widget_hide(w->widget); +} + +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); + uiParentUpdate(w->content); +} + +int uiWindowMargined(uiWindow *w) +{ + return w->margined; +} + +void uiWindowSetMargined(uiWindow *w, int margined) +{ + w->margined = margined; + if (w->margined) + uiParentSetMargins(w->content, gtkXMargin, gtkYMargin, gtkXMargin, gtkYMargin); + else + uiParentSetMargins(w->content, 0, 0, 0, 0); + uiParentUpdate(w->content); +} diff --git a/new/util_darwin.m b/new/util_darwin.m deleted file mode 100644 index 906a0ea..0000000 --- a/new/util_darwin.m +++ /dev/null @@ -1,20 +0,0 @@ -// 7 april 2015 -#import "uipriv_darwin.h" - -// also fine for NSCells and NSTexts (NSTextViews) -void setStandardControlFont(NSControl *control) -{ - [control setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]]]; -} - -void disableAutocorrect(NSTextView *tv) -{ - [tv setEnabledTextCheckingTypes:0]; - [tv setAutomaticDashSubstitutionEnabled:NO]; - // don't worry about automatic data detection; it won't change stringValue (thanks pretty_function in irc.freenode.net/#macdev) - [tv setAutomaticSpellingCorrectionEnabled:NO]; - [tv setAutomaticTextReplacementEnabled:NO]; - [tv setAutomaticQuoteSubstitutionEnabled:NO]; - [tv setAutomaticLinkDetectionEnabled:NO]; - [tv setSmartInsertDeleteEnabled:NO]; -} diff --git a/new/util_unix.c b/new/util_unix.c deleted file mode 100644 index 72b71df..0000000 --- a/new/util_unix.c +++ /dev/null @@ -1,7 +0,0 @@ -// 9 april 2015 -#include "uipriv_unix.h" - -void uiFreeText(char *t) -{ - g_free(t); -} diff --git a/new/util_windows.c b/new/util_windows.c deleted file mode 100644 index 93b32d8..0000000 --- a/new/util_windows.c +++ /dev/null @@ -1,73 +0,0 @@ -// 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/window_darwin.m b/new/window_darwin.m deleted file mode 100644 index b0cab11..0000000 --- a/new/window_darwin.m +++ /dev/null @@ -1,145 +0,0 @@ -// 6 april 2015 -#import "uipriv_darwin.h" - -// TODO -// - free chilld containers properly - -@interface uiWindowDelegate : NSObject -@property (assign) NSWindow *w; -@property uiParent *content; -@property int (*onClosing)(uiWindow *, void *); -@property void *onClosingData; -@property uiWindow *uiw; -@end - -@implementation uiWindowDelegate - -uiLogObjCClassAllocations - -- (BOOL)windowShouldClose:(id)win -{ - // return exact constants to be safe - if ((*(self.onClosing))(self.uiw, self.onClosingData)) - return YES; - return NO; -} - -// after this method returns we assume the window will be released (see below), so we can go too -- (void)windowWillClose:(NSNotification *)note -{ - [self.w setDelegate:nil]; // see http://stackoverflow.com/a/29523141/3408572 - - // when we reach this point, we need to ensure that all the window's children are destroyed (for OS parity) - // because we need to set the content view's superview to the destroyed controls view to trigger deletion, we need to do this manually - // first, replace the current content view... - [self.w setContentView:[[NSView alloc] initWithFrame:NSZeroRect]]; - // ...then, trigger the deletion - [destroyedControlsView addSubview:((NSView *) uiParentHandle(self.content))]; - - uiFree(self.uiw); - [self release]; -} - -@end - -struct uiWindow { - uiWindowDelegate *d; - int margined; -}; - -static int defaultOnClosing(uiWindow *w, void *data) -{ - return 1; -} - -uiWindow *uiNewWindow(char *title, int width, int height) -{ - uiWindowDelegate *d; - - d = [uiWindowDelegate new]; - - d.w = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, (CGFloat) width, (CGFloat) height) - styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask) - backing:NSBackingStoreBuffered - defer:YES]; - [d.w setTitle:toNSString(title)]; - - // we do not want substitutions - // text fields, labels, etc. take their smart quotes and other autocorrect settings from their parent window, which provides a shared "field editor" - // so we have to turn them off here - // thanks akempgen in irc.freenode.net/#macdev - // for some reason, this selector returns NSText but is documented to return NSTextView... - // NOTE: if you disagree with me about disabling substitutions, start a github issue with why and I'll be happy to consider it - disableAutocorrect((NSTextView *) [d.w fieldEditor:YES forObject:nil]); - - // this is what will destroy the window on close - [d.w setReleasedWhenClosed:YES]; - - d.content = uiNewParent(0); - [d.w setContentView:((NSView *) uiParentHandle(d.content))]; - - d.onClosing = defaultOnClosing; - [d.w setDelegate:d]; - - d.uiw = uiNew(uiWindow); - d.uiw->d = d; - return d.uiw; -} - -#define D w->d - -void uiWindowDestroy(uiWindow *w) -{ - [D.w close]; -} - -uintptr_t uiWindowHandle(uiWindow *w) -{ - return (uintptr_t) (D.w); -} - -char *uiWindowTitle(uiWindow *w) -{ - return uiDarwinNSStringToText([D.w title]); -} - -void uiWindowSetTitle(uiWindow *w, const char *title) -{ - [D.w setTitle:toNSString(title)]; -} - -void uiWindowShow(uiWindow *w) -{ - [D.w makeKeyAndOrderFront:D.w]; -} - -void uiWindowHide(uiWindow *w) -{ - [D.w orderOut:D.w]; -} - -void uiWindowOnClosing(uiWindow *w, int (*f)(uiWindow *, void *), void *data) -{ - D.onClosing = f; - D.onClosingData = data; -} - -void uiWindowSetChild(uiWindow *w, uiControl *c) -{ - uiParentSetChild(D.content, c); -} - -int uiWindowMargined(uiWindow *w) -{ - return w->margined; -} - -void uiWindowSetMargined(uiWindow *w, int margined) -{ - w->margined = margined; - if (w->margined) - uiParentSetMargins(D.content, macXMargin, macYMargin, macXMargin, macYMargin); - else - uiParentSetMargins(D.content, 0, 0, 0, 0); - uiParentUpdate(D.content); -} diff --git a/new/window_unix.c b/new/window_unix.c deleted file mode 100644 index e3fe6f3..0000000 --- a/new/window_unix.c +++ /dev/null @@ -1,105 +0,0 @@ -// 6 april 2015 -#include "uipriv_unix.h" - -struct uiWindow { - GtkWidget *widget; - uiParent *content; - int (*onClosing)(uiWindow *, void *); - void *onClosingData; - int margined; -}; - -static gboolean onClosing(GtkWidget *win, GdkEvent *e, gpointer data) -{ - uiWindow *w = (uiWindow *) data; - - // return exact values just in case - if ((*(w->onClosing))(w, w->onClosingData)) - return FALSE; - return TRUE; -} - -static int defaultOnClosing(uiWindow *w, void *data) -{ - return 1; -} - -static void onDestroy(GtkWidget *widget, gpointer data) -{ - uiWindow *w = (uiWindow *) data; - - uiFree(w); -} - -uiWindow *uiNewWindow(char *title, int width, int height) -{ - uiWindow *w; - - w = uiNew(uiWindow); - w->widget = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_title(GTK_WINDOW(w->widget), title); - gtk_window_resize(GTK_WINDOW(w->widget), width, height); - g_signal_connect(w->widget, "delete-event", G_CALLBACK(onClosing), w); - g_signal_connect(w->widget, "destroy", G_CALLBACK(onDestroy), w); - w->content = uiNewParent((uintptr_t) (w->widget)); - w->onClosing = defaultOnClosing; - return w; -} - -void uiWindowDestroy(uiWindow *w) -{ - gtk_widget_destroy(w->widget); -} - -uintptr_t uiWindowHandle(uiWindow *w) -{ - return (uintptr_t) (w->widget); -} - -char *uiWindowTitle(uiWindow *w) -{ - return g_strdup(gtk_window_get_title(GTK_WINDOW(w->widget))); -} - -void uiWindowSetTitle(uiWindow *w, const char *title) -{ - gtk_window_set_title(GTK_WINDOW(w->widget), title); -} - -void uiWindowShow(uiWindow *w) -{ - // don't use gtk_widget_show_all(); that will override user hidden settings - gtk_widget_show(w->widget); -} - -void uiWindowHide(uiWindow *w) -{ - gtk_widget_hide(w->widget); -} - -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); - uiParentUpdate(w->content); -} - -int uiWindowMargined(uiWindow *w) -{ - return w->margined; -} - -void uiWindowSetMargined(uiWindow *w, int margined) -{ - w->margined = margined; - if (w->margined) - uiParentSetMargins(w->content, gtkXMargin, gtkYMargin, gtkXMargin, gtkYMargin); - else - uiParentSetMargins(w->content, 0, 0, 0, 0); - uiParentUpdate(w->content); -} diff --git a/new/window_windows.c b/new/window_windows.c deleted file mode 100644 index fb92c0d..0000000 --- a/new/window_windows.c +++ /dev/null @@ -1,186 +0,0 @@ -// 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); -} 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[] = "\n\n\nYour application description here.\n\n \n \n \n\n\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 + +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] There's flickering between tabs +// - with CTLCOLOR handler: [12:24] And setting the button text blanked out the entire GUI until I ran my mouse over the elements / [12:25] https://dl.dropboxusercontent.com/u/15144168/GUI%20stuff.png / [12:41] 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); +} -- cgit v1.2.3