Did all the uiWindow stuff on GTK+.

This commit is contained in:
Pietro Gagliardi 2020-05-31 13:37:56 -04:00
parent eb1862afe1
commit 218439c215
11 changed files with 271 additions and 26 deletions

View File

@ -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.

View File

@ -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`?

View File

@ -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);
}

View File

@ -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')

View File

@ -1,3 +1,5 @@
// 10 june 2019
// TODO proper macros and headers
#include <gtk/gtk.h>
#define libuiOSHeader "../ui_unix.h"
#include "test.h"

View File

@ -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));
}

78
test/window_unix.c Normal file
View File

@ -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);
}

View File

@ -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

View File

@ -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));
}

View File

@ -3,6 +3,7 @@
libui_sources += [
'unix/controls.c',
'unix/main.c',
'unix/window.c',
]
libui_deps += [

View File

@ -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);
}