From 096c63f7ed002b99a995c35a407483a0bfa161c5 Mon Sep 17 00:00:00 2001 From: "P. F. Chimento" Date: Mon, 15 Apr 2013 18:04:27 +0200 Subject: Application class Add an EosApplication class. Functionality: - present main application window when activated - warn if more than one application window is added Also add a stub EosWindow class that overrides GtkWindow's "application" property to be a construct-only property. [#4] --- configure.ac | 4 +- docs/reference/endless/endless-docs.xml | 4 +- docs/reference/endless/endless-sections.txt | 34 ++++++ endless/Makefile.am | 8 +- endless/application.c | 165 ++++++++++++++++++++++++++++ endless/application.h | 70 ++++++++++++ endless/endless.h | 2 + endless/types.h | 2 + endless/window.c | 121 ++++++++++++++++++++ endless/window.h | 71 ++++++++++++ test/Makefile.am | 4 +- test/run-tests.c | 4 + test/run-tests.h | 6 +- test/smoke-tests/app-window.js | 21 ++++ test/test-application.c | 48 ++++++++ test/test-window.c | 24 ++++ 16 files changed, 580 insertions(+), 8 deletions(-) create mode 100644 endless/application.c create mode 100644 endless/application.h create mode 100644 endless/window.c create mode 100644 endless/window.h create mode 100644 test/smoke-tests/app-window.js create mode 100644 test/test-application.c create mode 100644 test/test-window.c diff --git a/configure.ac b/configure.ac index 08f32f1..42f4098 100644 --- a/configure.ac +++ b/configure.ac @@ -69,10 +69,10 @@ AC_SUBST(EOS_SDK_LT_VERSION_INFO) # Required versions of libraries # Update these whenever you use a function that requires a certain API version -GLIB_REQUIREMENT="glib-2.0 >= 2.20" +GLIB_REQUIREMENT="glib-2.0 >= 2.36" GOBJECT_REQUIREMENT="gobject-2.0" GIO_REQUIREMENT="gio-2.0" -GTK_REQUIREMENT="gtk+-3.0 >= 3.0" +GTK_REQUIREMENT="gtk+-3.0 >= 3.4" # These go into the pkg-config file as Requires: and Requires.private: # (Generally, use Requires.private: instead of Requires:) EOS_REQUIRED_MODULES= diff --git a/docs/reference/endless/endless-docs.xml b/docs/reference/endless/endless-docs.xml index d4004aa..13866ca 100644 --- a/docs/reference/endless/endless-docs.xml +++ b/docs/reference/endless/endless-docs.xml @@ -17,7 +17,9 @@ Open Endless SDK reference (C API) - + + + diff --git a/docs/reference/endless/endless-sections.txt b/docs/reference/endless/endless-sections.txt index 4782fbb..8978d4d 100644 --- a/docs/reference/endless/endless-sections.txt +++ b/docs/reference/endless/endless-sections.txt @@ -4,3 +4,37 @@ eos_hello_sample_function EOS_SDK_ALL_API_VERSIONS + +
+application +EosApplication +eos_application_new + +EosApplicationClass +EOS_APPLICATION +EOS_APPLICATION_CLASS +EOS_APPLICATION_GET_CLASS +EOS_IS_APPLICATION +EOS_IS_APPLICATION_CLASS +EOS_TYPE_APPLICATION +eos_application_get_type + +EosApplicationPrivate +
+ +
+window +EosWindow +eos_window_new + +EosWindowClass +EOS_IS_WINDOW +EOS_IS_WINDOW_CLASS +EOS_TYPE_WINDOW +EOS_WINDOW +EOS_WINDOW_CLASS +EOS_WINDOW_GET_CLASS +eos_window_get_type + +EosWindowPrivate +
diff --git a/endless/Makefile.am b/endless/Makefile.am index e653097..583ca74 100644 --- a/endless/Makefile.am +++ b/endless/Makefile.am @@ -4,13 +4,17 @@ endless_public_installed_headers = endless/endless.h endless_private_installed_headers = \ endless/apiversion.h \ + endless/application.h \ endless/enums.h \ endless/macros.h \ - endless/types.h + endless/types.h \ + endless/window.h endless_library_sources = \ + endless/application.c \ endless/hello.c \ - endless/init.c endless/init-private.h + endless/init.c endless/init-private.h \ + endless/window.c # Endless GUI library lib_LTLIBRARIES = libendless-@EOS_SDK_API_VERSION@.la diff --git a/endless/application.c b/endless/application.c new file mode 100644 index 0000000..ef93e13 --- /dev/null +++ b/endless/application.c @@ -0,0 +1,165 @@ +/* Copyright 2013 Endless Mobile, Inc. */ + +#include "config.h" +#include "application.h" + +#include + +#include "window.h" + +/** + * SECTION:application + * @short_description: Start here with your application + * @title: Applications + * + * The #EosApplication class is where you start when programming your + * application. + * You should create a class that extends #EosApplication. + * + * You also need to think up an application ID. + * This takes the form of a reverse domain name, and it should be unique. + * This ID is used to make sure that only one copy of your application is + * running at any time; if a user tries to start a second copy, then the first + * copy is brought to the front. + * + * To set up your application's data and window, override the + * #GApplication::startup function, like this example do-nothing application, + * Smoke Grinder: + * |[ + * const Lang = imports.lang; + * const Endless = imports.gi.Endless; + * + * const SmokeGrinder = new Lang.Class ({ + * Name: 'SmokeGrinder', + * Extends: Endless.Application, + * + * vfunc_startup: function() { + * this.parent(); + * this._window = new Endless.Window({application: this}); + * this._window.show_all(); + * }, + * }); + * + * let app = new SmokeGrinder({ application_id: "com.example.smokegrinder", + * flags: 0 }); + * app.run(ARGV); + * ]| + */ + +G_DEFINE_TYPE (EosApplication, eos_application, GTK_TYPE_APPLICATION) + +#define APPLICATION_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((o), EOS_TYPE_APPLICATION, EosApplicationPrivate)) + +struct _EosApplicationPrivate +{ + EosWindow *main_application_window; +}; + +static void +eos_application_activate (GApplication *application) +{ + EosApplication *self = EOS_APPLICATION (application); + + G_APPLICATION_CLASS (eos_application_parent_class)->activate (application); + + /* Raise the main application window if it is iconified. This behavior will + be default in GTK at some future point, in which case the following + paragraph can be removed. */ + if (self->priv->main_application_window) + { + gtk_window_present (GTK_WINDOW (self->priv->main_application_window)); + } + + /* TODO: Should it be required to override activate() as in GApplication? */ +} + +static void +eos_application_window_added (GtkApplication *application, + GtkWindow *window) +{ + EosApplication *self = EOS_APPLICATION (application); + + GTK_APPLICATION_CLASS (eos_application_parent_class)->window_added ( + application, window); + + /* If the new window is an EosWindow, then it is our main application window; + it should be raised when the application is activated */ + if (EOS_IS_WINDOW (window)) + { + if (self->priv->main_application_window != NULL) + { + g_error ("You should not add more than one application window."); + } + g_object_ref (window); + self->priv->main_application_window = EOS_WINDOW (window); + } +} + +static void +eos_application_window_removed (GtkApplication *application, + GtkWindow *window) +{ + EosApplication *self = EOS_APPLICATION (application); + + GTK_APPLICATION_CLASS (eos_application_parent_class)->window_removed ( + application, window); + + if (EOS_IS_WINDOW (window)) + { + if (self->priv->main_application_window == NULL) + { + g_warning ("EosWindow is being removed from EosApplication, although " + "none was added."); + return; + } + if (self->priv->main_application_window != EOS_WINDOW (window)) + g_warning ("A different EosWindow is being removed from EosApplication " + "than the one that was added."); + g_object_unref (window); + self->priv->main_application_window = NULL; + } +} + +static void +eos_application_class_init (EosApplicationClass *klass) +{ + GApplicationClass *g_application_class = G_APPLICATION_CLASS (klass); + GtkApplicationClass *gtk_application_class = GTK_APPLICATION_CLASS (klass); + + g_type_class_add_private (klass, sizeof (EosApplicationPrivate)); + + g_application_class->activate = eos_application_activate; + gtk_application_class->window_added = eos_application_window_added; + gtk_application_class->window_removed = eos_application_window_removed; +} + +static void +eos_application_init (EosApplication *self) +{ + self->priv = APPLICATION_PRIVATE (self); +} + +/* Public API */ + +/** + * eos_application_new: + * @application_id: a unique identifier for the application, for example a + * reverse domain name. + * @flags: flags to apply to the application; see #GApplicationFlags. + * + * Create a new application. For the application ID, use a reverse domain name, + * such as com.endlessm.weather. See g_application_id_is_valid() + * for the full rules for application IDs. + * + * Returns: a pointer to the application. + */ +EosApplication * +eos_application_new (const gchar *application_id, + GApplicationFlags flags) +{ + return g_object_new (EOS_TYPE_APPLICATION, + "application-id", application_id, + "flags", flags, + NULL); +} diff --git a/endless/application.h b/endless/application.h new file mode 100644 index 0000000..5089c12 --- /dev/null +++ b/endless/application.h @@ -0,0 +1,70 @@ +/* Copyright 2013 Endless Mobile, Inc. */ + +#if !(defined(_EOS_SDK_INSIDE_ENDLESS_H) || defined(COMPILING_EOS_SDK)) +#error "Please do not include this header file directly." +#endif + +#ifndef EOS_APPLICATION_H +#define EOS_APPLICATION_H + +#include "types.h" + +#include + +#define EOS_TYPE_APPLICATION eos_application_get_type() + +#define EOS_APPLICATION(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + EOS_TYPE_APPLICATION, EosApplication)) + +#define EOS_APPLICATION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + EOS_TYPE_APPLICATION, EosApplicationClass)) + +#define EOS_IS_APPLICATION(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ + EOS_TYPE_APPLICATION)) + +#define EOS_IS_APPLICATION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + EOS_TYPE_APPLICATION)) + +#define EOS_APPLICATION_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + EOS_TYPE_APPLICATION, EosApplicationClass)) + +typedef struct _EosApplication EosApplication; +typedef struct _EosApplicationClass EosApplicationClass; +typedef struct _EosApplicationPrivate EosApplicationPrivate; + +/** + * EosApplication: + * + * This class structure contains no public members. + */ +struct _EosApplication +{ + /*< private >*/ + GtkApplication parent; + + EosApplicationPrivate *priv; +}; + +struct _EosApplicationClass +{ + GtkApplicationClass parent_class; + + /* For further expansion */ + gpointer _padding[8]; +}; + +EOS_SDK_ALL_API_VERSIONS +GType eos_application_get_type (void) G_GNUC_CONST; + +EOS_SDK_ALL_API_VERSIONS +EosApplication *eos_application_new (const gchar *application_id, + GApplicationFlags flags); + +G_END_DECLS + +#endif /* EOS_APPLICATION_H */ diff --git a/endless/endless.h b/endless/endless.h index 84a33b0..b452247 100644 --- a/endless/endless.h +++ b/endless/endless.h @@ -12,6 +12,8 @@ G_BEGIN_DECLS /* Pull in other header files */ #include "types.h" +#include "application.h" +#include "window.h" #undef _EOS_SDK_INSIDE_ENDLESS_H diff --git a/endless/types.h b/endless/types.h index a572d7c..bfa5c1b 100644 --- a/endless/types.h +++ b/endless/types.h @@ -11,6 +11,8 @@ #include "macros.h" #include "apiversion.h" +#include + /* Shared typedefs for structures */ #endif /* EOS_TYPES_H */ diff --git a/endless/window.c b/endless/window.c new file mode 100644 index 0000000..86c95fa --- /dev/null +++ b/endless/window.c @@ -0,0 +1,121 @@ +/* Copyright 2013 Endless Mobile, Inc. */ + +#include "config.h" +#include "window.h" + +#include "application.h" + +#include + +/** + * SECTION:window + * @short_description: A window for your application + * @title: Window + * + * Stub + */ + +G_DEFINE_TYPE (EosWindow, eos_window, GTK_TYPE_APPLICATION_WINDOW) + +#define WINDOW_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((o), EOS_TYPE_WINDOW, EosWindowPrivate)) + +struct _EosWindowPrivate +{ + EosApplication *application; +}; + +enum +{ + PROP_0, + PROP_APPLICATION, + NPROPS +}; + +static GParamSpec *eos_window_props[NPROPS] = { NULL, }; + +static void +eos_window_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + EosWindow *self = EOS_WINDOW (object); + + switch (property_id) + { + case PROP_APPLICATION: + g_value_set_object (value, self->priv->application); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +eos_window_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + EosWindow *self = EOS_WINDOW (object); + + switch (property_id) + { + case PROP_APPLICATION: + self->priv->application = g_value_get_object (value); + gtk_window_set_application (GTK_WINDOW (self), + GTK_APPLICATION (self->priv->application)); + if (self->priv->application == NULL) + g_critical ("In order to create a window, you must have an application " + "for it to connect to."); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +eos_window_class_init (EosWindowClass *klass) +{ + GObjectClass *object_class = G_OBJECT_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; + + eos_window_props[PROP_APPLICATION] = + g_param_spec_object ("application", "Application", + "Application associated with this window", + EOS_TYPE_APPLICATION, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, NPROPS, eos_window_props); +} + +static void +eos_window_init (EosWindow *self) +{ + self->priv = WINDOW_PRIVATE (self); +} + +/* Public API */ + +/** + * eos_window_new: + * @application: the #EosApplication that the window belongs to. + * + * Create a window. It is invisible by default. + * + * Returns: a pointer to the window. + */ +GtkWidget * +eos_window_new (EosApplication *application) +{ + return GTK_WIDGET (g_object_new (EOS_TYPE_WINDOW, + "application", application, + NULL)); +} diff --git a/endless/window.h b/endless/window.h new file mode 100644 index 0000000..b92b4eb --- /dev/null +++ b/endless/window.h @@ -0,0 +1,71 @@ +/* Copyright 2013 Endless Mobile, Inc. */ + +#if !(defined(_EOS_SDK_INSIDE_ENDLESS_H) || defined(COMPILING_EOS_SDK)) +#error "Please do not include this header file directly." +#endif + +#ifndef EOS_WINDOW_H +#define EOS_WINDOW_H + +#include "types.h" + +#include "application.h" + +G_BEGIN_DECLS + +#define EOS_TYPE_WINDOW eos_window_get_type() + +#define EOS_WINDOW(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + EOS_TYPE_WINDOW, EosWindow)) + +#define EOS_WINDOW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + EOS_TYPE_WINDOW, EosWindowClass)) + +#define EOS_IS_WINDOW(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ + EOS_TYPE_WINDOW)) + +#define EOS_IS_WINDOW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + EOS_TYPE_WINDOW)) + +#define EOS_WINDOW_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + EOS_TYPE_WINDOW, EosWindowClass)) + +typedef struct _EosWindow EosWindow; +typedef struct _EosWindowClass EosWindowClass; +typedef struct _EosWindowPrivate EosWindowPrivate; + +/** + * EosWindow: + * + * This class structure contains no public members. + */ +struct _EosWindow +{ + /*< private >*/ + GtkApplicationWindow parent; + + EosWindowPrivate *priv; +}; + +struct _EosWindowClass +{ + GtkApplicationWindowClass parent_class; + + /* For further expansion */ + gpointer _padding[8]; +}; + +EOS_SDK_ALL_API_VERSIONS +GType eos_window_get_type (void) G_GNUC_CONST; + +EOS_SDK_ALL_API_VERSIONS +GtkWidget *eos_window_new (EosApplication *application); + +G_END_DECLS + +#endif /* EOS_WINDOW_H */ diff --git a/test/Makefile.am b/test/Makefile.am index 85c0fe7..0211c7e 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -10,7 +10,9 @@ TEST_LIBS = @EOS_SDK_LIBS@ $(top_builddir)/libendless-@EOS_SDK_API_VERSION@.la test_run_tests_SOURCES = \ test/run-tests.c \ test/test-init.c \ - test/test-hello.c + test/test-hello.c \ + test/test-application.c \ + test/test-window.c test_run_tests_CPPFLAGS = $(TEST_FLAGS) test_run_tests_LDADD = $(TEST_LIBS) diff --git a/test/run-tests.c b/test/run-tests.c index ba016bc..96cd4b8 100644 --- a/test/run-tests.c +++ b/test/run-tests.c @@ -2,6 +2,7 @@ #include #include +#include #include "run-tests.h" @@ -10,9 +11,12 @@ main (int argc, char **argv) { g_test_init (&argc, &argv, NULL); + gtk_init (&argc, &argv); add_init_tests (); add_hello_tests (); + add_application_tests (); + add_window_tests (); return g_test_run (); } diff --git a/test/run-tests.h b/test/run-tests.h index 3efac9e..1d4c402 100644 --- a/test/run-tests.h +++ b/test/run-tests.h @@ -3,7 +3,9 @@ #ifndef RUN_TESTS_H #define RUN_TESTS_H -void add_init_tests (void); -void add_hello_tests (void); +void add_init_tests (void); +void add_hello_tests (void); +void add_application_tests (void); +void add_window_tests (void); #endif /* RUN_TESTS_H */ diff --git a/test/smoke-tests/app-window.js b/test/smoke-tests/app-window.js new file mode 100644 index 0000000..ab89961 --- /dev/null +++ b/test/smoke-tests/app-window.js @@ -0,0 +1,21 @@ +// Copyright 2013 Endless Mobile, Inc. + +const Lang = imports.lang; +const Endless = imports.gi.Endless; + +const TEST_APPLICATION_ID = 'com.endlessm.example.test'; + +const TestApplication = new Lang.Class ({ + Name: 'TestApplication', + Extends: Endless.Application, + + vfunc_startup: function() { + this.parent(); + this._window = new Endless.Window({application: this}); + this._window.show_all(); + }, +}); + +let app = new TestApplication({ application_id: TEST_APPLICATION_ID, + flags: 0 }); +app.run(ARGV); diff --git a/test/test-application.c b/test/test-application.c new file mode 100644 index 0000000..2b2411c --- /dev/null +++ b/test/test-application.c @@ -0,0 +1,48 @@ +/* Copyright 2013 Endless Mobile, Inc. */ + +#include +#include +#include + +#include "run-tests.h" + +#define TEST_APPLICATION_ID "com.endlessm.example.test" +#define EXPECTED_TWO_WINDOW_ERRMSG "*You should not add more than one application window*" + +static void +_two_windows_on_startup (EosApplication *app, gpointer data) +{ + GtkWidget *win1, *win2; + win1 = eos_window_new (app); + win2 = eos_window_new (app); + + /* Destroy the windows so that the application exits */ + gtk_widget_destroy (win1); + gtk_widget_destroy (win2); +} + +static void +test_undefined_two_windows (void) +{ + EosApplication *app = eos_application_new(TEST_APPLICATION_ID, 0); + g_signal_connect (app, "startup", + G_CALLBACK (_two_windows_on_startup), NULL); + + /* Unix-only test */ + if (g_test_trap_fork(0 /* timeout */, G_TEST_TRAP_SILENCE_STDERR)) + { + g_application_run (G_APPLICATION (app), 0, NULL); + exit (0); + } + + g_test_trap_assert_failed (); + g_test_trap_assert_stderr (EXPECTED_TWO_WINDOW_ERRMSG); +} + +void +add_application_tests (void) +{ + /* Tests for undefined behavior, i.e. programming errors */ + if (g_test_undefined ()) + g_test_add_func ("/application/two-windows", test_undefined_two_windows); +} diff --git a/test/test-window.c b/test/test-window.c new file mode 100644 index 0000000..831d94c --- /dev/null +++ b/test/test-window.c @@ -0,0 +1,24 @@ +/* Copyright 2013 Endless Mobile, Inc. */ + +#include +#include +#include + +#include "run-tests.h" + +static void +test_assign_application (GApplication *app) +{ + GtkWidget *win = eos_window_new (EOS_APPLICATION (app)); + + g_assert(EOS_APPLICATION (app) + == EOS_APPLICATION (gtk_window_get_application (GTK_WINDOW (win)))); + + gtk_widget_destroy (win); +} + +void +add_window_tests (void) +{ + ADD_APP_WINDOW_TEST ("/window/assign-application", test_assign_application); +} -- cgit v1.2.3