// 11 june 2015 #include "uipriv_unix.h" struct uiWindow { uiUnixControl c; GtkWidget *widget; GtkContainer *container; GtkWindow *window; GtkWidget *vboxWidget; GtkContainer *vboxContainer; GtkBox *vbox; GtkWidget *childHolderWidget; GtkContainer *childHolderContainer; GtkWidget *menubar; uiControl *child; int margined; int (*onClosing)(uiWindow *, void *); void *onClosingData; void (*onPositionChanged)(uiWindow *, void *); void *onPositionChangedData; gboolean changingPosition; void (*onContentSizeChanged)(uiWindow *, void *); void *onContentSizeChangedData; gboolean changingSize; gboolean fullscreen; }; static gboolean onClosing(GtkWidget *win, GdkEvent *e, gpointer data) { uiWindow *w = uiWindow(data); // manually destroy the window ourselves; don't let the delete-event handler do it if ((*(w->onClosing))(w, w->onClosingData)) uiControlDestroy(uiControl(w)); // don't continue to the default delete-event handler; we destroyed the window by now return TRUE; } static gboolean onConfigure(GtkWidget *win, GdkEvent *e, gpointer data) { uiWindow *w = uiWindow(data); // there doesn't seem to be a way to determine if only moving or only resizing is happening :/ if (w->changingPosition) w->changingPosition = FALSE; else (*(w->onPositionChanged))(w, w->onPositionChangedData); // always continue handling return FALSE; } static void onSizeAllocate(GtkWidget *widget, GdkRectangle *allocation, gpointer data) { uiWindow *w = uiWindow(data); if (w->changingSize) w->changingSize = FALSE; else (*(w->onContentSizeChanged))(w, w->onContentSizeChangedData); } static int defaultOnClosing(uiWindow *w, void *data) { return 0; } static void defaultOnPositionContentSizeChanged(uiWindow *w, void *data) { // do nothing } static void uiWindowDestroy(uiControl *c) { uiWindow *w = uiWindow(c); // first hide ourselves gtk_widget_hide(w->widget); // now destroy the child if (w->child != NULL) { uiControlSetParent(w->child, NULL); uiUnixControlSetContainer(uiUnixControl(w->child), w->childHolderContainer, TRUE); uiControlDestroy(w->child); } // now destroy the menus, if any if (w->menubar != NULL) freeMenubar(w->menubar); gtk_widget_destroy(w->childHolderWidget); gtk_widget_destroy(w->vboxWidget); // and finally free ourselves g_object_unref(w->widget); uiFreeControl(uiControl(w)); } uiUnixControlDefaultHandle(uiWindow) uiControl *uiWindowParent(uiControl *c) { return NULL; } void uiWindowSetParent(uiControl *c, uiControl *parent) { uiUserBugCannotSetParentOnToplevel("uiWindow"); } static int uiWindowToplevel(uiControl *c) { return 1; } uiUnixControlDefaultVisible(uiWindow) static void uiWindowShow(uiControl *c) { uiWindow *w = uiWindow(c); // don't use gtk_widget_show_all() as that will show all children, regardless of user settings // don't use gtk_widget_show(); that doesn't bring to front or give keyboard focus // (gtk_window_present() does call gtk_widget_show() though) gtk_window_present(w->window); } uiUnixControlDefaultHide(uiWindow) uiUnixControlDefaultEnabled(uiWindow) uiUnixControlDefaultEnable(uiWindow) uiUnixControlDefaultDisable(uiWindow) // TODO? uiUnixControlDefaultSetContainer(uiWindow) 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); } // TODO allow specifying either as NULL on all platforms void uiWindowPosition(uiWindow *w, int *x, int *y) { gint rx, ry; gtk_window_get_position(w->window, &rx, &ry); *x = rx; *y = ry; } void uiWindowSetPosition(uiWindow *w, int x, int y) { w->changingPosition = TRUE; gtk_window_move(w->window, x, y); // gtk_window_move() is asynchronous // we need to wait for a configure-event // thanks to hergertme in irc.gimp.net/#gtk+ while (w->changingPosition) if (!uiMainStep(1)) break; // stop early if uiQuit() called } void uiWindowCenter(uiWindow *w) { gint x, y; GtkAllocation winalloc; GdkWindow *gdkwin; GdkScreen *screen; GdkRectangle workarea; gtk_widget_get_allocation(w->widget, &winalloc); gdkwin = gtk_widget_get_window(w->widget); screen = gdk_window_get_screen(gdkwin); gdk_screen_get_monitor_workarea(screen, gdk_screen_get_monitor_at_window(screen, gdkwin), &workarea); x = (workarea.width - winalloc.width) / 2; y = (workarea.height - winalloc.height) / 2; // TODO move up slightly? see what Mutter or GNOME Shell or GNOME Terminal do(es)? uiWindowSetPosition(w, x, y); } // TODO this and size changed get set during uiWindowDestroy void uiWindowOnPositionChanged(uiWindow *w, void (*f)(uiWindow *, void *), void *data) { w->onPositionChanged = f; w->onPositionChangedData = data; } void uiWindowContentSize(uiWindow *w, int *width, int *height) { GtkAllocation allocation; gtk_widget_get_allocation(w->childHolderWidget, &allocation); *width = allocation.width; *height = allocation.height; } // TODO what happens if the size is already the current one? // TODO a spurious size-allocate gets sent after this function returns // TODO can't reduce the size this way void uiWindowSetContentSize(uiWindow *w, int width, int height) { w->changingSize = TRUE; gtk_widget_set_size_request(w->childHolderWidget, width, height); while (w->changingSize) if (!uiMainStep(1)) break; // stop early if uiQuit() called gtk_widget_set_size_request(w->childHolderWidget, -1, -1); } int uiWindowFullscreen(uiWindow *w) { return w->fullscreen; } // TODO use window-state-event to track // TODO does this send an extra size changed? void uiWindowSetFullscreen(uiWindow *w, int fullscreen) { w->fullscreen = fullscreen; if (w->fullscreen) gtk_window_fullscreen(w->window); else gtk_window_unfullscreen(w->window); } void uiWindowOnContentSizeChanged(uiWindow *w, void (*f)(uiWindow *, void *), void *data) { w->onContentSizeChanged = f; w->onContentSizeChangedData = data; } void uiWindowOnClosing(uiWindow *w, int (*f)(uiWindow *, void *), void *data) { w->onClosing = f; w->onClosingData = data; } int uiWindowBorderless(uiWindow *w) { return gtk_window_get_decorated(w->window) == FALSE; } void uiWindowSetBorderless(uiWindow *w, int borderless) { gtk_window_set_decorated(w->window, borderless == 0); } // TODO save and restore expands and aligns void uiWindowSetChild(uiWindow *w, uiControl *child) { if (w->child != NULL) { uiControlSetParent(w->child, NULL); uiUnixControlSetContainer(uiUnixControl(w->child), w->childHolderContainer, TRUE); } w->child = child; if (w->child != NULL) { uiControlSetParent(w->child, uiControl(w)); uiUnixControlSetContainer(uiUnixControl(w->child), w->childHolderContainer, FALSE); } } int uiWindowMargined(uiWindow *w) { return w->margined; } void uiWindowSetMargined(uiWindow *w, int margined) { w->margined = margined; setMargined(w->childHolderContainer, w->margined); } uiWindow *uiNewWindow(const char *title, int width, int height, int hasMenubar) { uiWindow *w; uiUnixNewControl(uiWindow, w); 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); w->vboxWidget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); w->vboxContainer = GTK_CONTAINER(w->vboxWidget); w->vbox = GTK_BOX(w->vboxWidget); // set the vbox as the GtkWindow child gtk_container_add(w->container, w->vboxWidget); if (hasMenubar) { w->menubar = makeMenubar(uiWindow(w)); gtk_container_add(w->vboxContainer, w->menubar); } w->childHolderWidget = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); w->childHolderContainer = GTK_CONTAINER(w->childHolderWidget); gtk_widget_set_hexpand(w->childHolderWidget, TRUE); gtk_widget_set_halign(w->childHolderWidget, GTK_ALIGN_FILL); gtk_widget_set_vexpand(w->childHolderWidget, TRUE); gtk_widget_set_valign(w->childHolderWidget, GTK_ALIGN_FILL); gtk_container_add(w->vboxContainer, w->childHolderWidget); // show everything in the vbox, but not the GtkWindow itself gtk_widget_show_all(w->vboxWidget); // and connect our events g_signal_connect(w->widget, "delete-event", G_CALLBACK(onClosing), w); g_signal_connect(w->widget, "configure-event", G_CALLBACK(onConfigure), w); g_signal_connect(w->childHolderWidget, "size-allocate", G_CALLBACK(onSizeAllocate), w); uiWindowOnClosing(w, defaultOnClosing, NULL); uiWindowOnPositionChanged(w, defaultOnPositionContentSizeChanged, NULL); uiWindowOnContentSizeChanged(w, defaultOnPositionContentSizeChanged, NULL); // normally it's SetParent() that does this, but we can't call SetParent() on a uiWindow // TODO we really need to clean this up g_object_ref(w->widget); return w; }