diff options
-rw-r--r-- | docs/reference/endless/Makefile.am | 4 | ||||
-rw-r--r-- | endless/Makefile.am | 1 | ||||
-rw-r--r-- | endless/eostopbar-private.h | 56 | ||||
-rw-r--r-- | endless/eostopbar.c | 161 | ||||
-rw-r--r-- | endless/eoswindow.c | 158 | ||||
-rw-r--r-- | test/Makefile.am | 2 | ||||
-rw-r--r-- | test/smoke-tests/app-window.js | 15 | ||||
-rw-r--r-- | test/test-window.c | 27 |
8 files changed, 421 insertions, 3 deletions
diff --git a/docs/reference/endless/Makefile.am b/docs/reference/endless/Makefile.am index 5ac1019..7c493bd 100644 --- a/docs/reference/endless/Makefile.am +++ b/docs/reference/endless/Makefile.am @@ -48,7 +48,9 @@ EXTRA_HFILES= # Header files or dirs to ignore when scanning. Use base file/dir names # e.g. IGNORE_HFILES=gtkdebug.h gtkintl.h private_code -IGNORE_HFILES=$(top_srcdir)/endless/eosinit-private.h +IGNORE_HFILES= \ + $(top_srcdir)/endless/eosinit-private.h \ + $(top_srcdir)/endless/eostopbar-private.h # Images to copy into HTML directory. # e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png diff --git a/endless/Makefile.am b/endless/Makefile.am index b8d1a59..4ccd55d 100644 --- a/endless/Makefile.am +++ b/endless/Makefile.am @@ -14,6 +14,7 @@ endless_library_sources = \ endless/eosapplication.c \ endless/eoshello.c \ endless/eosinit.c endless/eosinit-private.h \ + endless/eostopbar.c endless/eostopbar-private.h \ endless/eoswindow.c # Endless GUI library diff --git a/endless/eostopbar-private.h b/endless/eostopbar-private.h new file mode 100644 index 0000000..800c8de --- /dev/null +++ b/endless/eostopbar-private.h @@ -0,0 +1,56 @@ +/* Copyright 2013 Endless Mobile, Inc. */ + +#ifndef EOS_TOP_BAR_H +#define EOS_TOP_BAR_H + +#include "eostypes.h" + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define EOS_TYPE_TOP_BAR eos_top_bar_get_type() + +#define EOS_TOP_BAR(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + EOS_TYPE_TOP_BAR, EosTopBar)) + +#define EOS_TOP_BAR_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + EOS_TYPE_TOP_BAR, EosTopBarClass)) + +#define EOS_IS_TOP_BAR(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ + EOS_TYPE_TOP_BAR)) + +#define EOS_IS_TOP_BAR_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + EOS_TYPE_TOP_BAR)) + +#define EOS_TOP_BAR_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + EOS_TYPE_TOP_BAR, EosTopBarClass)) + +typedef struct _EosTopBar EosTopBar; +typedef struct _EosTopBarClass EosTopBarClass; +typedef struct _EosTopBarPrivate EosTopBarPrivate; + +struct _EosTopBar +{ + GtkEventBox parent; + + EosTopBarPrivate *priv; +}; + +struct _EosTopBarClass +{ + GtkEventBoxClass parent_class; +}; + +GType eos_top_bar_get_type (void) G_GNUC_CONST; + +GtkWidget *eos_top_bar_new (void); + +G_END_DECLS + +#endif /* EOS_TOP_BAR_H */ diff --git a/endless/eostopbar.c b/endless/eostopbar.c new file mode 100644 index 0000000..74205d9 --- /dev/null +++ b/endless/eostopbar.c @@ -0,0 +1,161 @@ +/* Copyright 2013 Endless Mobile, Inc. */ + +#include "config.h" +#include "eostopbar-private.h" + +#include <glib-object.h> +#include <gtk/gtk.h> + +#define _EOS_STYLE_CLASS_TOP_BAR "top-bar" +#define _EOS_TOP_BAR_HEIGHT_PX 32 + +G_DEFINE_TYPE (EosTopBar, eos_top_bar, GTK_TYPE_EVENT_BOX) + +#define TOP_BAR_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((o), EOS_TYPE_TOP_BAR, EosTopBarPrivate)) + +struct _EosTopBarPrivate +{ + GtkWidget *inner_grid; + + GtkWidget *actions_hbox; + + GtkWidget *minimize_button; + GtkWidget *minimize_icon; + GtkWidget *close_button; + GtkWidget *close_icon; +}; + +enum { + CLOSE_CLICKED, + MINIMIZE_CLICKED, + LAST_SIGNAL +}; + +static guint top_bar_signals[LAST_SIGNAL] = { 0 }; + +static void +eos_top_bar_get_preferred_height (GtkWidget *widget, + int *minimum, + int *natural) +{ + if (minimum != NULL) + *minimum = _EOS_TOP_BAR_HEIGHT_PX; + if (natural != NULL) + *natural = _EOS_TOP_BAR_HEIGHT_PX; +} + +static void +eos_top_bar_class_init (EosTopBarClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + g_type_class_add_private (klass, sizeof (EosTopBarPrivate)); + + widget_class->get_preferred_height = eos_top_bar_get_preferred_height; + + /* + * Emitted when the minimize button has been activated. + */ + top_bar_signals[MINIMIZE_CLICKED] = + g_signal_new ("minimize-clicked", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); + + /* + * Emitted when the close button has been activated. + */ + top_bar_signals[CLOSE_CLICKED] = + g_signal_new ("close-clicked", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); +} + +static void +on_minimize_clicked_cb (GtkButton *button, + gpointer user_data) +{ + EosTopBar *self = EOS_TOP_BAR (user_data); + g_signal_emit (self, top_bar_signals[MINIMIZE_CLICKED], 0); +} + +static void +on_close_clicked_cb (GtkButton *button, + gpointer user_data) +{ + EosTopBar *self = EOS_TOP_BAR (user_data); + g_signal_emit (self, top_bar_signals[CLOSE_CLICKED], 0); +} + +static void +eos_top_bar_init (EosTopBar *self) +{ + GtkStyleContext *context; + + self->priv = TOP_BAR_PRIVATE (self); + + context = gtk_widget_get_style_context (GTK_WIDGET (self)); + gtk_style_context_add_class (context, _EOS_STYLE_CLASS_TOP_BAR); + + self->priv->actions_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_widget_set_hexpand (self->priv->actions_hbox, TRUE); + gtk_widget_set_halign (self->priv->actions_hbox, GTK_ALIGN_START); + + /* TODO implement adding actions and widgets to the actions_hbox */ + + self->priv->minimize_button = gtk_button_new (); + gtk_widget_set_hexpand (self->priv->minimize_button, FALSE); + gtk_widget_set_halign (self->priv->minimize_button, GTK_ALIGN_END); + self->priv->minimize_icon = + gtk_image_new_from_icon_name ("list-remove-symbolic", + GTK_ICON_SIZE_SMALL_TOOLBAR); + gtk_button_set_image (GTK_BUTTON (self->priv->minimize_button), + self->priv->minimize_icon); + + self->priv->close_button = gtk_button_new (); + gtk_widget_set_hexpand (self->priv->close_button, FALSE); + gtk_widget_set_halign (self->priv->close_button, GTK_ALIGN_END); + self->priv->close_icon = + gtk_image_new_from_icon_name ("window-close-symbolic", + GTK_ICON_SIZE_SMALL_TOOLBAR); + gtk_button_set_image (GTK_BUTTON (self->priv->close_button), + self->priv->close_icon); + + self->priv->inner_grid = gtk_grid_new (); + gtk_widget_set_hexpand (self->priv->inner_grid, TRUE); + gtk_widget_set_halign (self->priv->inner_grid, GTK_ALIGN_FILL); + + gtk_grid_attach(GTK_GRID (self->priv->inner_grid), + self->priv->actions_hbox, + 0, 0, 1, 1); + gtk_grid_attach_next_to (GTK_GRID (self->priv->inner_grid), + self->priv->close_button, NULL, + GTK_POS_RIGHT, 1, 1); + gtk_grid_attach_next_to (GTK_GRID (self->priv->inner_grid), + self->priv->minimize_button, + self->priv->close_button, + GTK_POS_LEFT, 1, 1); + + gtk_container_add (GTK_CONTAINER (self), self->priv->inner_grid); + + gtk_widget_set_hexpand (GTK_WIDGET (self), TRUE); + gtk_widget_set_halign (GTK_WIDGET (self), GTK_ALIGN_FILL); + + g_signal_connect (self->priv->minimize_button, "clicked", + G_CALLBACK (on_minimize_clicked_cb), self); + g_signal_connect (self->priv->close_button, "clicked", + G_CALLBACK (on_close_clicked_cb), self); +} + +GtkWidget * +eos_top_bar_new (void) +{ + return GTK_WIDGET (g_object_new (EOS_TYPE_TOP_BAR, NULL)); +} diff --git a/endless/eoswindow.c b/endless/eoswindow.c index f74911d..939d6e1 100644 --- a/endless/eoswindow.c +++ b/endless/eoswindow.c @@ -4,6 +4,7 @@ #include "eoswindow.h" #include "eosapplication.h" +#include "eostopbar-private.h" #include <gtk/gtk.h> @@ -38,6 +39,8 @@ G_DEFINE_TYPE (EosWindow, eos_window, GTK_TYPE_APPLICATION_WINDOW) struct _EosWindowPrivate { EosApplication *application; + + GtkWidget *top_bar; }; enum @@ -92,15 +95,142 @@ eos_window_set_property (GObject *object, } } +/* Piggy-back on the parent class's get_preferred_height(), but add the +height of our top bar. Do not assume any borders on the top bar. */ +static void +eos_window_get_preferred_height (GtkWidget *widget, + int *minimum_height, + int *natural_height) +{ + EosWindow *self = EOS_WINDOW (widget); + int top_bar_minimum, top_bar_natural; + + GTK_WIDGET_CLASS (eos_window_parent_class)->get_preferred_height (widget, + minimum_height, natural_height); + gtk_widget_get_preferred_height (self->priv->top_bar, + &top_bar_minimum, &top_bar_natural); + if (minimum_height != NULL) + *minimum_height += top_bar_minimum; + if (natural_height != NULL) + *natural_height += top_bar_natural; +} + +/* Remove space for our top bar from the allocation before doing a normal +size_allocate(). Do not assume any borders on the top bar. */ +static void +eos_window_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + EosWindow *self = EOS_WINDOW (widget); + GtkWidget *child; + GtkAllocation child_allocation = *allocation; + unsigned border_width; + + gtk_widget_set_allocation (widget, allocation); + + if (self->priv->top_bar != NULL) + { + int top_bar_natural; + GtkAllocation top_bar_allocation = *allocation; + + gtk_widget_get_preferred_height (self->priv->top_bar, + NULL, &top_bar_natural); + top_bar_allocation.height = MIN(top_bar_natural, allocation->height); + child_allocation.y += top_bar_allocation.height; + child_allocation.height -= top_bar_allocation.height; + + gtk_widget_size_allocate (self->priv->top_bar, &top_bar_allocation); + } + + /* We can't chain up to GtkWindow's implementation of size_allocate() here, + because it always assumes that its child begins at (0, 0). */ + child = gtk_bin_get_child (GTK_BIN (self)); + if (child != NULL) + { + border_width = gtk_container_get_border_width (GTK_CONTAINER (self)); + child_allocation.x += border_width; + child_allocation.y += border_width; + child_allocation.width -= 2 * border_width; + child_allocation.height -= 2 * border_width; + child_allocation.width = MAX(1, child_allocation.width); + child_allocation.height = MAX(1, child_allocation.height); + gtk_widget_size_allocate (child, &child_allocation); + } +} + +static void +eos_window_map (GtkWidget *widget) +{ + EosWindow *self = EOS_WINDOW (widget); + + GTK_WIDGET_CLASS (eos_window_parent_class)->map (widget); + if (self->priv->top_bar != NULL + && gtk_widget_get_visible (self->priv->top_bar)) + { + gtk_widget_map (self->priv->top_bar); + } +} + +static void +eos_window_unmap (GtkWidget *widget) +{ + EosWindow *self = EOS_WINDOW (widget); + + GTK_WIDGET_CLASS (eos_window_parent_class)->unmap (widget); + if (self->priv->top_bar != NULL) + gtk_widget_unmap (self->priv->top_bar); +} + +static void +eos_window_show (GtkWidget *widget) +{ + EosWindow *self = EOS_WINDOW (widget); + + GTK_WIDGET_CLASS (eos_window_parent_class)->show (widget); + if (self->priv->top_bar != NULL) + gtk_widget_show_all (self->priv->top_bar); +} + +/* The top bar is an internal child, so include it in our list of internal +children. */ +static void +eos_window_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data) +{ + EosWindow *self = EOS_WINDOW (container); + + if (include_internals && self->priv->top_bar != NULL) + (*callback) (self->priv->top_bar, callback_data); + GTK_CONTAINER_CLASS (eos_window_parent_class)->forall (container, + include_internals, + callback, + callback_data); +} + + static void eos_window_class_init (EosWindowClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); g_type_class_add_private (klass, sizeof (EosWindowPrivate)); object_class->get_property = eos_window_get_property; object_class->set_property = eos_window_set_property; + /* Overriding the following six functions is because we treat the top bar as + an "internal" child. This will not be necessary any more if we use + gtk_window_set_titlebar(), available from GTK >= 3.10. But for now we are + targeting GTK 3.8. Issue: [endlessm/eos-sdk#28] */ + widget_class->get_preferred_height = eos_window_get_preferred_height; + widget_class->size_allocate = eos_window_size_allocate; + widget_class->map = eos_window_map; + widget_class->unmap = eos_window_unmap; + widget_class->show = eos_window_show; + container_class->forall = eos_window_forall; /** * EosWindow:application: @@ -119,12 +249,40 @@ eos_window_class_init (EosWindowClass *klass) } static void +on_minimize_clicked_cb (GtkWidget* top_bar, + gpointer user_data) +{ + if (user_data != NULL) + { + gtk_window_iconify (GTK_WINDOW (user_data)); + } +} + +static void +on_close_clicked_cb (GtkWidget* top_bar, + gpointer user_data) +{ + if (user_data != NULL) + { + gtk_widget_destroy (GTK_WIDGET (user_data)); + } +} + +static void eos_window_init (EosWindow *self) { self->priv = WINDOW_PRIVATE (self); + self->priv->top_bar = eos_top_bar_new (); + gtk_widget_set_parent (self->priv->top_bar, GTK_WIDGET (self)); + gtk_window_set_decorated (GTK_WINDOW (self), FALSE); gtk_window_maximize (GTK_WINDOW (self)); + + g_signal_connect (self->priv->top_bar, "minimize-clicked", + G_CALLBACK (on_minimize_clicked_cb), self); + g_signal_connect (self->priv->top_bar, "close-clicked", + G_CALLBACK (on_close_clicked_cb), self); } /* Public API */ diff --git a/test/Makefile.am b/test/Makefile.am index 0211c7e..3599c88 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -4,7 +4,7 @@ noinst_PROGRAMS = \ test/run-tests \ test/smoke-tests/hello -TEST_FLAGS = @EOS_SDK_CFLAGS@ -I$(top_srcdir) +TEST_FLAGS = @EOS_SDK_CFLAGS@ -I$(top_srcdir) -DCOMPILING_EOS_SDK TEST_LIBS = @EOS_SDK_LIBS@ $(top_builddir)/libendless-@EOS_SDK_API_VERSION@.la test_run_tests_SOURCES = \ diff --git a/test/smoke-tests/app-window.js b/test/smoke-tests/app-window.js index ab89961..8f989ea 100644 --- a/test/smoke-tests/app-window.js +++ b/test/smoke-tests/app-window.js @@ -2,6 +2,7 @@ const Lang = imports.lang; const Endless = imports.gi.Endless; +const Gtk = imports.gi.Gtk; const TEST_APPLICATION_ID = 'com.endlessm.example.test'; @@ -11,9 +12,21 @@ const TestApplication = new Lang.Class ({ vfunc_startup: function() { this.parent(); - this._window = new Endless.Window({application: this}); + + this._button = new Gtk.Button({label: 'Close me'}); + this._button.connect('clicked', Lang.bind(this, this._onButtonClicked)); + + this._window = new Endless.Window({ + application: this, + border_width: 16 + }); + this._window.add(this._button); this._window.show_all(); }, + + _onButtonClicked: function () { + this._window.destroy(); + }, }); let app = new TestApplication({ application_id: TEST_APPLICATION_ID, diff --git a/test/test-window.c b/test/test-window.c index c7641bd..e0618c1 100644 --- a/test/test-window.c +++ b/test/test-window.c @@ -3,9 +3,11 @@ #include <stdlib.h> #include <gtk/gtk.h> #include <endless/endless.h> +#include "endless/eostopbar-private.h" #include "run-tests.h" +#define EXPECTED_TOP_BAR_HEIGHT 32 #define EXPECTED_NULL_APPLICATION_ERRMSG \ "*In order to create a window, you must have an application for it to " \ "connect to.*" @@ -72,6 +74,30 @@ test_screen_size (GApplication *app) gtk_widget_destroy (win); } +/* Query all the children of win, including the internal children, to find the +top bar */ +static void +find_top_bar (GtkWidget *widget, + GtkWidget **top_bar_return_location) +{ + if (EOS_IS_TOP_BAR (widget)) + *top_bar_return_location = widget; +} + +static void +test_has_top_bar (GApplication *app) +{ + GtkWidget *win = eos_window_new (EOS_APPLICATION (app)); + GtkWidget *top_bar = NULL; + + gtk_container_forall (GTK_CONTAINER (win), (GtkCallback)find_top_bar, + &top_bar); + g_assert (top_bar != NULL); + g_assert (EOS_IS_TOP_BAR (top_bar)); + + gtk_widget_destroy (win); +} + void add_window_tests (void) { @@ -79,4 +105,5 @@ add_window_tests (void) ADD_APP_WINDOW_TEST ("/window/application-not-null", test_application_not_null); ADD_APP_WINDOW_TEST ("/window/screen-size", test_screen_size); + ADD_APP_WINDOW_TEST ("/window/has-top-bar", test_has_top_bar); } |