summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/reference/endless/Makefile.am4
-rw-r--r--endless/Makefile.am1
-rw-r--r--endless/eostopbar-private.h56
-rw-r--r--endless/eostopbar.c161
-rw-r--r--endless/eoswindow.c158
-rw-r--r--test/Makefile.am2
-rw-r--r--test/smoke-tests/app-window.js15
-rw-r--r--test/test-window.c27
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);
}