diff --git a/doc/darwin-controls.md b/doc/darwin-controls.md index 0e7e1c16..e20cc284 100644 --- a/doc/darwin-controls.md +++ b/doc/darwin-controls.md @@ -29,12 +29,12 @@ Each method is named for the `uiDarwinControl` function that it implements. As s ### `uiDarwinControlHandle()` ```objective-c -uiprivExtern NSView *uiDarwinControlHandle(uiControl *c); +uiprivExtern id uiDarwinControlHandle(uiControl *c); ``` `uiDarwinControlHandle()` returns the Objective-C object that underpins `c`, or `nil` if `c` does not have any underlying object associated with it when called. -The object returned by `uiDarwinControlHandle()` is owned by `c`; you do not receive a reference to it at all. The object is valid until either `c` is destroyed or until `c` decides to destroy the object; you can handle the latter by catching TODO. In general, you should not store the returned string pointer directly for later use, nor should you attempt to retain the returned object. Instead, use the returned handle immediately if you have to, or follow TODO if you need to adjust properties of the handle that should persist across handle destruction/creation. +The object returned by `uiDarwinControlHandle()` is owned by `c`; you do not receive a reference to it at all. The object is valid until either `c` is destroyed or until `c` decides to destroy the object; you can handle the latter by catching TODO. In general, you should not store the returned object pointer directly for later use, nor should you attempt to retain the returned object. Instead, use the returned handle immediately if you have to, or follow TODO if you need to adjust properties of the handle that should persist across handle destruction/creation. `uiWindow`s have a single handle of type `NSWindow` that is created when the `uiWindow` is created and destroyed when the `uiWindow` is destroyed. Despite this, you should still follow the best practices described above. diff --git a/doc/unix-controls.md b/doc/unix-controls.md index 87fb4cc3..f0014c9f 100644 --- a/doc/unix-controls.md +++ b/doc/unix-controls.md @@ -14,6 +14,7 @@ TODO typedef struct uiControlOSVtable uiControlOSVtable; struct uiControlOSVtable { size_t Size; + GtkWidget *(*Handle)(uiControl *c, void *implData); }; ``` @@ -23,4 +24,22 @@ You are responsible for allocating and initializing this struct. To do so, you s Each method takes at least two parameters. The first, `c`, is the `uiControl` itself. The second, `implData`, is the implementation data pointer; it is the same as the pointer returned by `uiControlImplData(c)`, and is provided here as a convenience. -Each method is named for the `uiUnixControl` function that it implements. As such, details on how to implement these methods are documented alongside those functions. For instance, instructions on implementing `TODO()` are given under the documentation for `TODO()`. +Each method is named for the `uiUnixControl` function that it implements. As such, details on how to implement these methods are documented alongside those functions. For instance, instructions on implementing `Handle()` are given under the documentation for `uiUnixControlHandle()`. + +### `uiUnixControlHandle()` + +```objective-c +uiprivExtern GtkWidget *uiUnixControlHandle(uiControl *c); +``` + +`uiUnixControlHandle()` returns the GtkWidget that underpins `c`, or `NULL` if `c` does not have any underlying widget associated with it when called. + +The widget returned by `uiUnixControlHandle()` is owned by `c`; you do not receive a reference to it at all. The widget is valid until either `c` is destroyed or until `c` decides to destroy the widget; you can handle the latter by catching TODO. In general, you should not store the returned widget pointer directly for later use, nor should you attempt to acquire a reference to the returned object. Instead, use the returned handle immediately if you have to, or follow TODO if you need to adjust properties of the handle that should persist across handle destruction/creation. + +`uiWindow`s have a single handle of type `GtkWindow` that is created when the `uiWindow` is created and destroyed when the `uiWindow` is destroyed. Despite this, you should still follow the best practices described above. + +For all other `uiControl`s defined by libui, the returned object is of the appropriate `GtkWidget` subclass: + +* TODO + +It is a programmer error to pass `NULL` for `c`. TODO a non-`uiControl`? diff --git a/test/controls_unix.c b/test/controls_unix.c index 29b48a7d..60a4b632 100644 --- a/test/controls_unix.c +++ b/test/controls_unix.c @@ -1,8 +1,14 @@ // 10 june 2019 #include "test_unix.h" +static GtkWidget *osVtableNopHandle(uiControl *c, void *implData) +{ + return NULL; +} + static const uiControlOSVtable osVtable = { .Size = sizeof (uiControlOSVtable), + .Handle = osVtableNopHandle, }; const uiControlOSVtable *testOSVtable(void) @@ -23,3 +29,26 @@ Test(WrongControlOSVtableSizeIsProgrammerError) uiRegisterControlType("name", &vtable, &osvt, 0); endCheckProgrammerError(ctx); } + +Test(ControlOSVtableWithMissingHandleMethodIsProgrammerError) +{ + uiControlVtable vtable; + uiControlOSVtable osvt; + void *ctx; + + testControlLoadNopVtable(&vtable); + ctx = beginCheckProgrammerError("uiRegisterControlType(): required uiControlOSVtable method Handle() missing for uiControl type name"); + osvt = osVtable; + osvt.Handle = NULL; + uiRegisterControlType("name", &vtable, &osvt, 0); + endCheckProgrammerError(ctx); +} + +Test(GettingUnixHandleOfNullControlIsProgrammerError) +{ + void *ctx; + + ctx = beginCheckProgrammerError("uiUnixControlHandle(): invalid null pointer for uiControl"); + uiUnixControlHandle(NULL); + endCheckProgrammerError(ctx); +} diff --git a/test/meson.build b/test/meson.build index 8f5c5ad0..c2932037 100644 --- a/test/meson.build +++ b/test/meson.build @@ -30,6 +30,7 @@ elif libui_OS == 'haiku' else libui_test_sources += files([ 'controls_unix.c', + 'window_unix.c', ]) endif @@ -77,6 +78,18 @@ elif libui_OS == 'darwin' required: true), ] endif +elif libui_OS == 'haiku' + # TODO +else + # static mode already gives us these dependencies + if libui_mode != 'static' + libui_test_deps += [ + dependency('gtk+-3.0', + version: '>=3.10.0', + method: 'pkg-config', + required: true), + ] + endif endif pymod = import('python') diff --git a/test/test_unix.h b/test/test_unix.h index e2876e35..daa521a8 100644 --- a/test/test_unix.h +++ b/test/test_unix.h @@ -1,3 +1,5 @@ // 10 june 2019 +// TODO proper macros and headers +#include #define libuiOSHeader "../ui_unix.h" #include "test.h" diff --git a/test/window_darwin.m b/test/window_darwin.m index 1e2b8057..2961b17c 100644 --- a/test/window_darwin.m +++ b/test/window_darwin.m @@ -36,7 +36,7 @@ static void testSetWindowTitleImplFull(const char *file, long line, const char * nsw = (NSWindow *) uiDarwinControlHandle(uiControl(w)); got = [[nsw title] UTF8String]; if (!utf8equal(got, want)) - utf8diffErrorFull(file, line, "uiWindowTitle() reported wrong title after uiWindowSetTitle()", got, want); + utf8diffErrorFull(file, line, "-[NSWindow title] reported wrong title after uiWindowSetTitle()", got, want); uiControlFree(uiControl(w)); } diff --git a/test/window_unix.c b/test/window_unix.c new file mode 100644 index 00000000..7e91657f --- /dev/null +++ b/test/window_unix.c @@ -0,0 +1,78 @@ +// 24 may 2020 +#include "test_unix.h" + +Test(WindowHasHandleFromStart) +{ + uiWindow *a; + + a = uiNewWindow(); + if (uiUnixControlHandle(uiControl(a)) == NULL) + TestErrorf("uiUnixControlHandle(brand new uiWindow) is NULL; should not be"); + uiControlFree(uiControl(a)); +} + +Test(InitialWindowTitleIsEmptyString_OSLevel) +{ + uiWindow *w; + GtkWindow *gw; + const char *title; + + w = uiNewWindow(); + gw = GTK_WINDOW(uiUnixControlHandle(uiControl(w))); + title = gtk_window_get_title(gw); + if (!utf8equal(title, testUTF8Empty)) + utf8diffError("brand new uiWindow has wrong title", title, testUTF8Empty); + uiControlFree(uiControl(w)); +} + +static void testSetWindowTitleImplFull(const char *file, long line, const char *title, const char *want) +{ + uiWindow *w; + GtkWindow *gw; + const char *got; + + w = uiNewWindow(); + uiWindowSetTitle(w, title); + gw = GTK_WINDOW(uiUnixControlHandle(uiControl(w))); + got = gtk_window_get_title(gw); + if (!utf8equal(got, want)) + utf8diffErrorFull(file, line, "gtk_window_get_title() reported wrong title after uiWindowSetTitle()", got, want); + uiControlFree(uiControl(w)); +} + +#define testSetWindowTitleImpl(title, want) testSetWindowTitleImplFull(__FILE__, __LINE__, title, want) + +Test(SetWindowTitle_OSLevel_Empty) +{ + testSetWindowTitleImpl(testUTF8Empty, testUTF8Empty); +} + +Test(SetWindowTitle_OSLevel_ASCIIOnly) +{ + testSetWindowTitleImpl(testUTF8ASCIIOnly, testUTF8ASCIIOnly); +} + +Test(SetWindowTitle_OSLevel_WithTwoByte) +{ + testSetWindowTitleImpl(testUTF8WithTwoByte, testUTF8WithTwoByte); +} + +Test(SetWindowTitle_OSLevel_WithThreeByte) +{ + testSetWindowTitleImpl(testUTF8WithThreeByte, testUTF8WithThreeByte); +} + +Test(SetWindowTitle_OSLevel_WithFourByte) +{ + testSetWindowTitleImpl(testUTF8WithFourByte, testUTF8WithFourByte); +} + +Test(SetWindowTitle_OSLevel_Combined) +{ + testSetWindowTitleImpl(testUTF8Combined, testUTF8Combined); +} + +Test(SetWindowTitle_OSLevel_Invalid) +{ + testSetWindowTitleImpl(testUTF8InvalidInput, testUTF8InvalidOutput); +} diff --git a/ui_unix.h b/ui_unix.h index b7f0ccdf..1026e020 100644 --- a/ui_unix.h +++ b/ui_unix.h @@ -13,8 +13,11 @@ extern "C" { struct uiControlOSVtable { size_t Size; + GtkWidget *(*Handle)(uiControl *c, void *implData); }; +uiprivExtern GtkWidget *uiUnixControlHandle(uiControl *c); + #ifdef __cplusplus } #endif diff --git a/unix/controls.c b/unix/controls.c index b0ff2c98..d204231f 100644 --- a/unix/controls.c +++ b/unix/controls.c @@ -1,12 +1,18 @@ // 8 june 2019 #include "uipriv_unix.h" -bool uiprivOSVtableValid(const uiControlOSVtable *osVtable, const char *func) +bool uiprivOSVtableValid(const char *name, const uiControlOSVtable *osVtable, const char *func) { if (osVtable->Size != sizeof (uiControlOSVtable)) { uiprivProgrammerErrorWrongStructSize(osVtable->Size, "uiControlOSVtable", func); return false; } +#define checkMethod(method) \ + if (osVtable->method == NULL) { \ + uiprivProgrammerErrorRequiredControlMethodMissing(name, "uiControlOSVtable", #method, func); \ + return 0; \ + } + checkMethod(Handle) return true; } @@ -18,3 +24,19 @@ uiControlOSVtable *uiprivCloneOSVtable(const uiControlOSVtable *osVtable) *v2 = *osVtable; return v2; } + +#define callVtable(method, ...) ((*(method))(__VA_ARGS__)) + +GtkWidget *uiUnixControlHandle(uiControl *c) +{ + uiControlOSVtable *osVtable; + + if (!uiprivCheckInitializedAndThread()) + return NULL; + if (c == NULL) { + uiprivProgrammerErrorNullPointer("uiControl", uiprivFunc); + return NULL; + } + osVtable = uiprivControlOSVtable(c); + return callVtable(osVtable->Handle, c, uiControlImplData(c)); +} diff --git a/unix/meson.build b/unix/meson.build index f8a35ded..84935e8e 100644 --- a/unix/meson.build +++ b/unix/meson.build @@ -3,6 +3,7 @@ libui_sources += [ 'unix/controls.c', 'unix/main.c', + 'unix/window.c', ] libui_deps += [ diff --git a/zOLD_unix/window.c b/unix/window.c similarity index 73% rename from zOLD_unix/window.c rename to unix/window.c index c5ba2038..14328f99 100644 --- a/zOLD_unix/window.c +++ b/unix/window.c @@ -1,13 +1,13 @@ // 11 june 2015 #include "uipriv_unix.h" -struct uiWindow { - uiUnixControl c; - +struct windowImplData { GtkWidget *widget; GtkContainer *container; GtkWindow *window; + char *title; +#if 0 GtkWidget *vboxWidget; GtkContainer *vboxContainer; GtkBox *vbox; @@ -25,8 +25,12 @@ struct uiWindow { void (*onContentSizeChanged)(uiWindow *, void *); void *onContentSizeChangedData; gboolean fullscreen; +#endif }; +#if 0 +// skip { + static gboolean onClosing(GtkWidget *win, GdkEvent *e, gpointer data) { uiWindow *w = uiWindow(data); @@ -120,11 +124,6 @@ char *uiWindowTitle(uiWindow *w) return uiUnixStrdupText(gtk_window_get_title(w->window)); } -void uiWindowSetTitle(uiWindow *w, const char *title) -{ - gtk_window_set_title(w->window, title); -} - void uiWindowContentSize(uiWindow *w, int *width, int *height) { GtkAllocation allocation; @@ -229,18 +228,20 @@ void uiWindowSetMargined(uiWindow *w, int margined) uiprivSetMargined(w->childHolderContainer, w->margined); } -uiWindow *uiNewWindow(const char *title, int width, int height, int hasMenubar) +// } skip +#endif + +static bool windowInit(uiControl *c, void *implData, void *initData) { - uiWindow *w; + struct windowImplData *wi = (struct windowImplData *) implData; - uiUnixNewControl(uiWindow, w); + wi->widget = gtk_window_new(GTK_WINDOW_TOPLEVEL); + wi->container = GTK_CONTAINER(wi->widget); + wi->window = GTK_WINDOW(wi->widget); - w->widget = gtk_window_new(GTK_WINDOW_TOPLEVEL); - w->container = GTK_CONTAINER(w->widget); - w->window = GTK_WINDOW(w->widget); - - gtk_window_set_title(w->window, title); - gtk_window_resize(w->window, width, height); + gtk_window_set_title(wi->window, ""); +#if 0 + gtk_window_resize(wi->window, width, height); w->vboxWidget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); w->vboxContainer = GTK_CONTAINER(w->vboxWidget); @@ -270,10 +271,87 @@ uiWindow *uiNewWindow(const char *title, int width, int height, int hasMenubar) g_signal_connect(w->childHolderWidget, "size-allocate", G_CALLBACK(onSizeAllocate), w); uiWindowOnClosing(w, defaultOnClosing, NULL); uiWindowOnContentSizeChanged(w, defaultOnPositionContentSizeChanged, NULL); +#endif - // normally it's SetParent() that does this, but we can't call SetParent() on a uiWindow - // TODO we really need to clean this up, especially since see uiWindowDestroy() above - g_object_ref(w->widget); + // TODO replace this with a function that does this and sets the visibility stuff + g_object_ref(wi->widget); - return w; + return true; +} + +static void windowFree(uiControl *c, void *implData) +{ + struct windowImplData *wi = (struct windowImplData *) implData; + + if (wi->title != NULL) { + uiprivFreeUTF8(wi->title); + wi->title = NULL; + } + // use gtk_widget_destroy() instead of g_object_unref() because GTK+ has internal references (see #165) + // TODO just do this in general? + gtk_widget_destroy(wi->widget); +} + +static void windowParentChanging(uiControl *c, void *implData, uiControl *oldParent) +{ + uiprivProgrammerErrorCannotHaveWindowsAsChildren(); +} + +static void windowParentChanged(uiControl *c, void *implData, uiControl *newParent) +{ + uiprivProgrammerErrorCannotHaveWindowsAsChildren(); +} + +static GtkWidget *windowHandle(uiControl *c, void *implData) +{ + struct windowImplData *wi = (struct windowImplData *) implData; + + return wi->widget; +} + +static const uiControlVtable windowVtable = { + .Size = sizeof (uiControlVtable), + .Init = windowInit, + .Free = windowFree, + .ParentChanging = windowParentChanging, + .ParentChanged = windowParentChanged, +}; + +static const uiControlOSVtable windowOSVtable = { + .Size = sizeof (uiControlOSVtable), + .Handle = windowHandle, +}; + +static uint32_t windowType = 0; + +uint32_t uiprivSysWindowType(void) +{ + if (windowType == 0) + windowType = uiRegisterControlType("uiWindow", &windowVtable, &windowOSVtable, sizeof (struct windowImplData)); + return windowType; +} + +uiWindow *uiprivSysNewWindow(void) +{ + return (uiWindow *) uiNewControl(uiWindowType(), NULL); +} + +const char *uiprivSysWindowTitle(uiWindow *w) +{ + struct windowImplData *wi = (struct windowImplData *) uiControlImplData(uiControl(w)); + + if (wi->title == NULL) + // TODO replace this with a dedicated UTF-8 empty string object + return ""; + return wi->title; +} + +void uiprivSysWindowSetTitle(uiWindow *w, const char *title) +{ + struct windowImplData *wi = (struct windowImplData *) uiControlImplData(uiControl(w)); + + if (wi->title != NULL) + uiprivFreeUTF8(wi->title); + wi->title = uiprivSanitizeUTF8(title); + gtk_window_set_title(wi->window, wi->title); }