From 88ec007b9858132720cc602ac1a63396f66116bc Mon Sep 17 00:00:00 2001 From: Ryan Lortie Date: Thu, 5 Jan 2012 22:22:06 -0500 Subject: [PATCH] Add new GtkActionable interface This is the interface for GtkWidgets that can be associated with an action on a GtkAppicationWindow or associated GtkApplication. It essentially features 'action-name' and 'action-target' properties with some associated convenience API. This interface is implemented by GtkButton and GtkToolButton. https://bugzilla.gnome.org/show_bug.cgi?id=667394 --- gtk/Makefile.am | 4 + gtk/gsimpleactionobserver.c | 288 ++++++++++++++++++++++++++++++++++++ gtk/gsimpleactionobserver.h | 53 +++++++ gtk/gtk.h | 1 + gtk/gtkactionable.c | 117 +++++++++++++++ gtk/gtkactionable.h | 71 +++++++++ gtk/gtkapplicationprivate.h | 6 + gtk/gtkapplicationwindow.c | 24 ++- gtk/gtkbutton.c | 161 +++++++++++++++++++- gtk/gtkbuttonprivate.h | 5 + gtk/gtktoolbutton.c | 72 ++++++++- 11 files changed, 791 insertions(+), 11 deletions(-) create mode 100644 gtk/gsimpleactionobserver.c create mode 100644 gtk/gsimpleactionobserver.h create mode 100644 gtk/gtkactionable.c create mode 100644 gtk/gtkactionable.h diff --git a/gtk/Makefile.am b/gtk/Makefile.am index ab46644c24..97719ace6f 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -172,6 +172,7 @@ gtk_public_h_sources = \ gtkaccellabel.h \ gtkaccelmap.h \ gtkaccessible.h \ + gtkactionable.h \ gtkaction.h \ gtkactiongroup.h \ gtkactivatable.h \ @@ -392,6 +393,7 @@ gtk_private_type_h_sources = \ # GTK+ header files that don't get installed gtk_private_h_sources = \ gactionmuxer.h \ + gsimpleactionobserver.h \ gactionobserver.h \ gactionobservable.h \ gtkapplicationprivate.h \ @@ -512,8 +514,10 @@ deprecated_c_sources = \ gtk_base_c_sources = \ $(deprecated_c_sources) \ gactionmuxer.c \ + gsimpleactionobserver.c \ gactionobserver.c \ gactionobservable.c \ + gtkactionable.c \ gtkquery.c \ gtksearchengine.c \ gtksearchenginesimple.c \ diff --git a/gtk/gsimpleactionobserver.c b/gtk/gsimpleactionobserver.c new file mode 100644 index 0000000000..fdbe4bffd1 --- /dev/null +++ b/gtk/gsimpleactionobserver.c @@ -0,0 +1,288 @@ +/* + * Copyright © 2012 Canonical Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * licence or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + * + * Authors: Ryan Lortie + */ + +#include "config.h" + +#include "gsimpleactionobserver.h" +#include "gactionobservable.h" + +typedef GObjectClass GSimpleActionObserverClass; +struct _GSimpleActionObserver +{ + GObject parent_instance; + + GActionGroup *action_group; + gchar *action_name; + GVariant *target; + + gboolean can_activate; + gboolean active; + gboolean enabled; + + gint reporting; +}; + +static void g_simple_action_observer_init_iface (GActionObserverInterface *iface); +G_DEFINE_TYPE_WITH_CODE (GSimpleActionObserver, g_simple_action_observer, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_OBSERVER, g_simple_action_observer_init_iface)); + +enum +{ + PROP_0, + PROP_ACTIVE, + PROP_ENABLED, + N_PROPS +}; + +static GParamSpec *g_simple_action_observer_pspecs[N_PROPS]; + +static void +g_simple_action_observer_action_added (GActionObserver *g_observer, + GActionObservable *observable, + const gchar *action_name, + const GVariantType *parameter_type, + gboolean enabled, + GVariant *state) +{ + GSimpleActionObserver *observer = G_SIMPLE_ACTION_OBSERVER (g_observer); + gboolean active; + + /* we can only activate if we have the correct type of parameter */ + observer->can_activate = (observer->target == NULL && parameter_type == NULL) || + (observer->target != NULL && parameter_type != NULL && + g_variant_is_of_type (observer->target, parameter_type)); + + if (observer->can_activate) + { + if (observer->target != NULL && state != NULL) + active = g_variant_equal (state, observer->target); + + else if (state != NULL && g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN)) + active = g_variant_get_boolean (state); + + else + active = FALSE; + + if (active != observer->active) + { + observer->active = active; + observer->reporting++; + g_object_notify_by_pspec (G_OBJECT (observer), g_simple_action_observer_pspecs[PROP_ACTIVE]); + observer->reporting--; + } + + if (enabled != observer->enabled) + { + observer->enabled = enabled; + g_object_notify_by_pspec (G_OBJECT (observer), g_simple_action_observer_pspecs[PROP_ENABLED]); + } + } +} + +static void +g_simple_action_observer_action_enabled_changed (GActionObserver *g_observer, + GActionObservable *observable, + const gchar *action_name, + gboolean enabled) +{ + GSimpleActionObserver *observer = G_SIMPLE_ACTION_OBSERVER (g_observer); + + if (!observer->can_activate) + return; + + if (enabled != observer->enabled) + { + observer->enabled = enabled; + g_object_notify_by_pspec (G_OBJECT (observer), g_simple_action_observer_pspecs[PROP_ENABLED]); + } +} + +static void +g_simple_action_observer_action_state_changed (GActionObserver *g_observer, + GActionObservable *observable, + const gchar *action_name, + GVariant *state) +{ + GSimpleActionObserver *observer = G_SIMPLE_ACTION_OBSERVER (g_observer); + gboolean active = FALSE; + + if (!observer->can_activate) + return; + + if (observer->target) + active = g_variant_equal (state, observer->target); + + else if (g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN)) + active = g_variant_get_boolean (state); + + if (active != observer->active) + { + observer->active = active; + observer->reporting++; + g_object_notify_by_pspec (G_OBJECT (observer), g_simple_action_observer_pspecs[PROP_ACTIVE]); + observer->reporting--; + } +} + +static void +g_simple_action_observer_action_removed (GActionObserver *g_observer, + GActionObservable *observable, + const gchar *action_name) +{ + GSimpleActionObserver *observer = G_SIMPLE_ACTION_OBSERVER (g_observer); + + if (!observer->can_activate) + return; + + observer->can_activate = FALSE; + + if (observer->active) + { + observer->active = FALSE; + observer->reporting++; + g_object_notify_by_pspec (G_OBJECT (observer), g_simple_action_observer_pspecs[PROP_ACTIVE]); + observer->reporting--; + } + + if (observer->enabled) + { + observer->enabled = FALSE; + g_object_notify_by_pspec (G_OBJECT (observer), g_simple_action_observer_pspecs[PROP_ENABLED]); + } +} + +static void +g_simple_action_observer_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + GSimpleActionObserver *observer = G_SIMPLE_ACTION_OBSERVER (object); + + switch (prop_id) + { + case PROP_ACTIVE: + g_value_set_boolean (value, observer->active); + break; + + case PROP_ENABLED: + g_value_set_boolean (value, observer->enabled); + break; + + default: + g_assert_not_reached (); + } +} + +static void +g_simple_action_observer_finalize (GObject *object) +{ + GSimpleActionObserver *observer = G_SIMPLE_ACTION_OBSERVER (object); + + g_object_unref (observer->action_group); + g_free (observer->action_name); + + if (observer->target) + g_variant_unref (observer->target); + + G_OBJECT_CLASS (g_simple_action_observer_parent_class) + ->finalize (object); +} + +static void +g_simple_action_observer_init (GSimpleActionObserver *observer) +{ +} + +static void +g_simple_action_observer_init_iface (GActionObserverInterface *iface) +{ + iface->action_added = g_simple_action_observer_action_added; + iface->action_enabled_changed = g_simple_action_observer_action_enabled_changed; + iface->action_state_changed = g_simple_action_observer_action_state_changed; + iface->action_removed = g_simple_action_observer_action_removed; +} + +static void +g_simple_action_observer_class_init (GObjectClass *class) +{ + class->get_property = g_simple_action_observer_get_property; + class->finalize = g_simple_action_observer_finalize; + + g_simple_action_observer_pspecs[PROP_ACTIVE] = g_param_spec_boolean ("active", "active", "active", FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_simple_action_observer_pspecs[PROP_ENABLED] = g_param_spec_boolean ("enabled", "enabled", "enabled", FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (class, N_PROPS, g_simple_action_observer_pspecs); +} + +GSimpleActionObserver * +g_simple_action_observer_new (GActionObservable *observable, + const gchar *action_name, + GVariant *target) +{ + GSimpleActionObserver *observer; + const GVariantType *type; + gboolean enabled; + GVariant *state; + + observer = g_object_new (G_TYPE_SIMPLE_ACTION_OBSERVER, NULL); + observer->action_group = g_object_ref (observable); + observer->action_name = g_strdup (action_name); + if (target) + observer->target = g_variant_ref_sink (target); + + g_action_observable_register_observer (observable, action_name, G_ACTION_OBSERVER (observer)); + + if (g_action_group_query_action (observer->action_group, action_name, &enabled, &type, NULL, NULL, &state)) + { + g_simple_action_observer_action_added (G_ACTION_OBSERVER (observer), observable, + action_name, type, enabled, state); + if (state) + g_variant_unref (state); + } + + return observer; +} + +void +g_simple_action_observer_activate (GSimpleActionObserver *observer) +{ + g_return_if_fail (G_IS_SIMPLE_ACTION_OBSERVER (observer)); + + if (observer->can_activate && !observer->reporting) + g_action_group_activate_action (G_ACTION_GROUP (observer->action_group), + observer->action_name, observer->target); +} + +gboolean +g_simple_action_observer_get_active (GSimpleActionObserver *observer) +{ + g_return_val_if_fail (G_IS_SIMPLE_ACTION_OBSERVER (observer), FALSE); + + return observer->active; +} + +gboolean +g_simple_action_observer_get_enabled (GSimpleActionObserver *observer) +{ + g_return_val_if_fail (G_IS_SIMPLE_ACTION_OBSERVER (observer), FALSE); + + return observer->enabled; +} diff --git a/gtk/gsimpleactionobserver.h b/gtk/gsimpleactionobserver.h new file mode 100644 index 0000000000..c3340f71b2 --- /dev/null +++ b/gtk/gsimpleactionobserver.h @@ -0,0 +1,53 @@ +/* + * Copyright © 2012 Canonical Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * licence or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + * + * Authors: Ryan Lortie + */ + +#ifndef __G_SIMPLE_ACTION_OBSERVER_H__ +#define __G_SIMPLE_ACTION_OBSERVER_H__ + +#include "gactionobserver.h" + +G_BEGIN_DECLS + +#define G_TYPE_SIMPLE_ACTION_OBSERVER (g_simple_action_observer_get_type ()) +#define G_SIMPLE_ACTION_OBSERVER(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \ + G_TYPE_SIMPLE_ACTION_OBSERVER, \ + GSimpleActionObserver)) +#define G_IS_SIMPLE_ACTION_OBSERVER(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), \ + G_TYPE_SIMPLE_ACTION_OBSERVER)) + +typedef struct _GSimpleActionObserver GSimpleActionObserver; + +G_GNUC_INTERNAL +GType g_simple_action_observer_get_type (void); +G_GNUC_INTERNAL +GSimpleActionObserver * g_simple_action_observer_new (GActionObservable *observable, + const gchar *action_name, + GVariant *target); +G_GNUC_INTERNAL +void g_simple_action_observer_activate (GSimpleActionObserver *observer); +G_GNUC_INTERNAL +gboolean g_simple_action_observer_get_active (GSimpleActionObserver *observer); +G_GNUC_INTERNAL +gboolean g_simple_action_observer_get_enabled (GSimpleActionObserver *observer); + +G_END_DECLS + +#endif /* __G_SIMPLE_ACTION_OBSERVER_H__ */ diff --git a/gtk/gtk.h b/gtk/gtk.h index 0668636fc0..88823e2381 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtkactionable.c b/gtk/gtkactionable.c new file mode 100644 index 0000000000..f78474e8f0 --- /dev/null +++ b/gtk/gtkactionable.c @@ -0,0 +1,117 @@ +/* + * Copyright © 2012 Canonical Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * licence or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + * + * Authors: Ryan Lortie + */ + +#include "config.h" + +#include "gtkactionable.h" + +#include "gtkwidget.h" +#include "gtkintl.h" + +G_DEFINE_INTERFACE (GtkActionable, gtk_actionable, GTK_TYPE_WIDGET) + +static void +gtk_actionable_default_init (GtkActionableInterface *iface) +{ + g_object_interface_install_property (iface, + g_param_spec_string ("action-name", P_("action name"), + P_("The name of the associated action, like 'app.quit'"), + NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_interface_install_property (iface, + g_param_spec_variant ("action-target", P_("action target value"), + P_("The parameter for action invocations"), + G_VARIANT_TYPE_ANY, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +} + +const gchar * +gtk_actionable_get_action_name (GtkActionable *actionable) +{ + g_return_val_if_fail (GTK_IS_ACTIONABLE (actionable), NULL); + + return GTK_ACTIONABLE_GET_IFACE (actionable) + ->get_action_name (actionable); +} + +void +gtk_actionable_set_action_name (GtkActionable *actionable, + const gchar *action_name) +{ + g_return_if_fail (GTK_IS_ACTIONABLE (actionable)); + + GTK_ACTIONABLE_GET_IFACE (actionable) + ->set_action_name (actionable, action_name); +} + +GVariant * +gtk_actionable_get_action_target_value (GtkActionable *actionable) +{ + g_return_val_if_fail (GTK_IS_ACTIONABLE (actionable), NULL); + + return GTK_ACTIONABLE_GET_IFACE (actionable) + ->get_action_target_value (actionable); +} + +void +gtk_actionable_set_action_target_value (GtkActionable *actionable, + GVariant *target_value) +{ + g_return_if_fail (GTK_IS_ACTIONABLE (actionable)); + + GTK_ACTIONABLE_GET_IFACE (actionable) + ->set_action_target_value (actionable, target_value); +} + +void +gtk_actionable_set_action_target (GtkActionable *actionable, + const gchar *format_string, + ...) +{ + va_list ap; + + va_start (ap, format_string); + gtk_actionable_set_action_target_value (actionable, g_variant_new_va (format_string, NULL, &ap)); + va_end (ap); +} + +void +gtk_actionable_set_detailed_action_name (GtkActionable *actionable, + const gchar *detailed_action_name) +{ + gchar **parts; + + g_return_if_fail (GTK_IS_ACTIONABLE (actionable)); + + if (detailed_action_name == NULL) + { + gtk_actionable_set_action_name (actionable, NULL); + gtk_actionable_set_action_target_value (actionable, NULL); + return; + } + + parts = g_strsplit (detailed_action_name, "::", 2); + gtk_actionable_set_action_name (actionable, parts[0]); + if (parts[0] && parts[1]) + gtk_actionable_set_action_target (actionable, "s", parts[1]); + else + gtk_actionable_set_action_target_value (actionable, NULL); + g_strfreev (parts); +} diff --git a/gtk/gtkactionable.h b/gtk/gtkactionable.h new file mode 100644 index 0000000000..bef9bde3a9 --- /dev/null +++ b/gtk/gtkactionable.h @@ -0,0 +1,71 @@ +/* + * Copyright © 2012 Canonical Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * licence or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + * + * Authors: Ryan Lortie + */ + +#ifndef __GTK_ACTIONABLE_H__ +#define __GTK_ACTIONABLE_H__ + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_ACTIONABLE (gtk_actionable_get_type ()) +#define GTK_ACTIONABLE(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \ + GTK_TYPE_ACTIONABLE, GtkActionable)) +#define GTK_IS_ACTIONABLE(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), \ + GTK_TYPE_ACTIONABLE)) +#define GTK_ACTIONABLE_GET_IFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), \ + GTK_TYPE_ACTIONABLE, GtkActionableInterface)) + +typedef struct _GtkActionableInterface GtkActionableInterface; +typedef struct _GtkActionable GtkActionable; + +struct _GtkActionableInterface +{ + GTypeInterface g_iface; + + const gchar * (* get_action_name) (GtkActionable *actionable); + void (* set_action_name) (GtkActionable *actionable, + const gchar *action_name); + GVariant * (* get_action_target_value) (GtkActionable *actionable); + void (* set_action_target_value) (GtkActionable *actionable, + GVariant *action_target_value); +}; + +GType gtk_actionable_get_type (void) G_GNUC_CONST; + +const gchar * gtk_actionable_get_action_name (GtkActionable *actionable); +void gtk_actionable_set_action_name (GtkActionable *actionable, + const gchar *action_name); + +GVariant * gtk_actionable_get_action_target_value (GtkActionable *actionable); +void gtk_actionable_set_action_target_value (GtkActionable *actionable, + GVariant *target_value); + +void gtk_actionable_set_action_target (GtkActionable *actionable, + const gchar *format_string, + ...); + +void gtk_actionable_set_detailed_action_name (GtkActionable *actionable, + const gchar *detailed_action_name); + +G_END_DECLS + +#endif /* __GTK_ACTIONABLE_H__ */ diff --git a/gtk/gtkapplicationprivate.h b/gtk/gtkapplicationprivate.h index 9693f78d86..4ff3b2b27e 100644 --- a/gtk/gtkapplicationprivate.h +++ b/gtk/gtkapplicationprivate.h @@ -23,6 +23,7 @@ #ifndef __GTK_APPLICATION_PRIVATE_H__ #define __GTK_APPLICATION_PRIVATE_H__ +#include "gsimpleactionobserver.h" #include "gtkapplicationwindow.h" G_GNUC_INTERNAL @@ -33,4 +34,9 @@ gboolean gtk_application_window_publish (GtkAppl G_GNUC_INTERNAL void gtk_application_window_unpublish (GtkApplicationWindow *window); +G_GNUC_INTERNAL +GSimpleActionObserver * gtk_application_window_get_observer (GtkApplicationWindow *window, + const gchar *action_name, + GVariant *target); + #endif /* __GTK_APPLICATION_PRIVATE_H__ */ diff --git a/gtk/gtkapplicationwindow.c b/gtk/gtkapplicationwindow.c index 5f1d57c83d..341e57709a 100644 --- a/gtk/gtkapplicationwindow.c +++ b/gtk/gtkapplicationwindow.c @@ -182,6 +182,7 @@ struct _GtkApplicationWindowPrivate { GSimpleActionGroup *actions; GActionObservable *muxer; + gboolean muxer_initialised; GtkWidget *menubar; GtkAccelGroup *accels; GSList *accel_closures; @@ -671,14 +672,11 @@ gtk_application_window_real_realize (GtkWidget *widget) g_signal_connect (settings, "notify::gtk-shell-shows-menubar", G_CALLBACK (gtk_application_window_shell_shows_menubar_changed), window); - if (window->priv->muxer == NULL) + if (!window->priv->muxer_initialised) { - GActionMuxer *muxer; - - muxer = g_action_muxer_new (); - g_action_muxer_insert (muxer, "app", G_ACTION_GROUP (application)); - g_action_muxer_insert (muxer, "win", G_ACTION_GROUP (window)); - window->priv->muxer = G_ACTION_OBSERVABLE (muxer); + g_action_muxer_insert (G_ACTION_MUXER (window->priv->muxer), "app", G_ACTION_GROUP (application)); + g_action_muxer_insert (G_ACTION_MUXER (window->priv->muxer), "win", G_ACTION_GROUP (window)); + window->priv->muxer_initialised = TRUE; } gtk_application_window_update_shell_shows_app_menu (window, settings); @@ -875,6 +873,8 @@ gtk_application_window_init (GtkApplicationWindow *window) G_CALLBACK (g_action_group_action_state_changed), window); g_signal_connect_swapped (window->priv->actions, "action-removed", G_CALLBACK (g_action_group_action_removed), window); + + window->priv->muxer = G_ACTION_OBSERVABLE (g_action_muxer_new ()); } static void @@ -982,3 +982,13 @@ gtk_application_window_set_show_menubar (GtkApplicationWindow *window, g_object_notify_by_pspec (G_OBJECT (window), gtk_application_window_properties[PROP_SHOW_MENUBAR]); } } + +GSimpleActionObserver * +gtk_application_window_get_observer (GtkApplicationWindow *window, + const gchar *action_name, + GVariant *target) +{ + g_return_val_if_fail (GTK_IS_APPLICATION_WINDOW (window), NULL); + + return g_simple_action_observer_new (window->priv->muxer, action_name, target); +} diff --git a/gtk/gtkbutton.c b/gtk/gtkbutton.c index 3b3b5b28d6..f7a908443b 100644 --- a/gtk/gtkbutton.c +++ b/gtk/gtkbutton.c @@ -58,7 +58,8 @@ #include "gtkprivate.h" #include "gtkintl.h" #include "a11y/gtkbuttonaccessible.h" - +#include "gtkapplicationprivate.h" +#include "gtkactionable.h" static const GtkBorder default_default_border = { 1, 1, 1, 1 }; static const GtkBorder default_default_outside_border = { 0, 0, 0, 0 }; @@ -90,6 +91,8 @@ enum { PROP_XALIGN, PROP_YALIGN, PROP_IMAGE_POSITION, + PROP_ACTION_NAME, + PROP_ACTION_TARGET, /* activatable properties */ PROP_ACTIVATABLE_RELATED_ACTION, @@ -147,8 +150,11 @@ static void gtk_button_state_changed (GtkWidget *widget, GtkStateType previous_state); static void gtk_button_grab_notify (GtkWidget *widget, gboolean was_grabbed); +static void gtk_button_hierarchy_changed (GtkWidget *widget, + GtkWidget *previous_toplevel); +static void gtk_button_actionable_iface_init (GtkActionableInterface *iface); static void gtk_button_activatable_interface_init(GtkActivatableIface *iface); static void gtk_button_update (GtkActivatable *activatable, GtkAction *action, @@ -170,6 +176,7 @@ static void gtk_button_get_preferred_height (GtkWidget *widget, static guint button_signals[LAST_SIGNAL] = { 0 }; G_DEFINE_TYPE_WITH_CODE (GtkButton, gtk_button, GTK_TYPE_BIN, + G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTIONABLE, gtk_button_actionable_iface_init) G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTIVATABLE, gtk_button_activatable_interface_init)) @@ -208,6 +215,7 @@ gtk_button_class_init (GtkButtonClass *klass) widget_class->leave_notify_event = gtk_button_leave_notify; widget_class->state_changed = gtk_button_state_changed; widget_class->grab_notify = gtk_button_grab_notify; + widget_class->hierarchy_changed = gtk_button_hierarchy_changed; container_class->child_type = gtk_button_child_type; container_class->add = gtk_button_add; @@ -330,6 +338,9 @@ gtk_button_class_init (GtkButtonClass *klass) GTK_POS_LEFT, GTK_PARAM_READWRITE)); + g_object_class_override_property (gobject_class, PROP_ACTION_NAME, "action-name"); + g_object_class_override_property (gobject_class, PROP_ACTION_TARGET, "action-target"); + g_object_class_override_property (gobject_class, PROP_ACTIVATABLE_RELATED_ACTION, "related-action"); g_object_class_override_property (gobject_class, PROP_ACTIVATABLE_USE_ACTION_APPEARANCE, "use-action-appearance"); @@ -584,6 +595,12 @@ gtk_button_destroy (GtkWidget *widget) priv->label_text = NULL; } + if (priv->action_name) + { + g_free (priv->action_name); + priv->action_name = NULL; + } + GTK_WIDGET_CLASS (gtk_button_parent_class)->destroy (widget); } @@ -666,6 +683,8 @@ gtk_button_dispose (GObject *object) GtkButton *button = GTK_BUTTON (object); GtkButtonPrivate *priv = button->priv; + g_clear_object (&priv->action_observer); + if (priv->action) { gtk_activatable_do_set_related_action (GTK_ACTIVATABLE (button), NULL); @@ -674,6 +693,83 @@ gtk_button_dispose (GObject *object) G_OBJECT_CLASS (gtk_button_parent_class)->dispose (object); } +static void +gtk_button_update_action_observer (GtkButton *button) +{ + GtkWidget *window; + + g_signal_handlers_disconnect_by_func (button, gtk_real_button_clicked, NULL); + + /* we are the only owner so this will clear all the signals */ + g_clear_object (&button->priv->action_observer); + + window = gtk_widget_get_toplevel (GTK_WIDGET (button)); + + if (GTK_IS_APPLICATION_WINDOW (window) && button->priv->action_name) + { + GSimpleActionObserver *observer; + + observer = gtk_application_window_get_observer (GTK_APPLICATION_WINDOW (window), + button->priv->action_name, + button->priv->action_target); + + _gtk_button_set_depressed (button, g_simple_action_observer_get_active (observer)); + + if (g_object_class_find_property (G_OBJECT_GET_CLASS (button), "active")) + g_object_bind_property (observer, "active", button, "active", G_BINDING_SYNC_CREATE); + g_object_bind_property (observer, "enabled", button, "sensitive", G_BINDING_SYNC_CREATE); + + button->priv->action_observer = observer; + + g_signal_connect_after (button, "clicked", G_CALLBACK (gtk_real_button_clicked), NULL); + } +} + +static void +gtk_button_set_action_name (GtkActionable *actionable, + const gchar *action_name) +{ + GtkButton *button = GTK_BUTTON (actionable); + + g_return_if_fail (GTK_IS_BUTTON (button)); + + if (g_strcmp0 (action_name, button->priv->action_name) != 0) + { + g_free (button->priv->action_name); + button->priv->action_name = g_strdup (action_name); + + gtk_button_update_action_observer (button); + + g_object_notify (G_OBJECT (button), "action-name"); + } +} + +static void +gtk_button_set_action_target_value (GtkActionable *actionable, + GVariant *action_target) +{ + GtkButton *button = GTK_BUTTON (actionable); + + g_return_if_fail (GTK_IS_BUTTON (button)); + + if (action_target != button->priv->action_target && + (!action_target || !button->priv->action_target || + !g_variant_equal (action_target, button->priv->action_target))) + { + if (button->priv->action_target) + g_variant_unref (button->priv->action_target); + + button->priv->action_target = NULL; + + if (action_target) + button->priv->action_target = g_variant_ref_sink (action_target); + + gtk_button_update_action_observer (button); + + g_object_notify (G_OBJECT (button), "action-target"); + } +} + static void gtk_button_set_property (GObject *object, guint prop_id, @@ -718,6 +814,12 @@ gtk_button_set_property (GObject *object, case PROP_ACTIVATABLE_USE_ACTION_APPEARANCE: gtk_button_set_use_action_appearance (button, g_value_get_boolean (value)); break; + case PROP_ACTION_NAME: + gtk_button_set_action_name (GTK_ACTIONABLE (button), g_value_get_string (value)); + break; + case PROP_ACTION_TARGET: + gtk_button_set_action_target_value (GTK_ACTIONABLE (button), g_value_get_variant (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -768,12 +870,43 @@ gtk_button_get_property (GObject *object, case PROP_ACTIVATABLE_USE_ACTION_APPEARANCE: g_value_set_boolean (value, priv->use_action_appearance); break; + case PROP_ACTION_NAME: + g_value_set_string (value, priv->action_name); + break; + case PROP_ACTION_TARGET: + g_value_set_variant (value, priv->action_target); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } +static const gchar * +gtk_button_get_action_name (GtkActionable *actionable) +{ + GtkButton *button = GTK_BUTTON (actionable); + + return button->priv->action_name; +} + +static GVariant * +gtk_button_get_action_target_value (GtkActionable *actionable) +{ + GtkButton *button = GTK_BUTTON (actionable); + + return button->priv->action_target; +} + +static void +gtk_button_actionable_iface_init (GtkActionableInterface *iface) +{ + iface->get_action_name = gtk_button_get_action_name; + iface->set_action_name = gtk_button_set_action_name; + iface->get_action_target_value = gtk_button_get_action_target_value; + iface->set_action_target_value = gtk_button_set_action_target_value; +} + static void gtk_button_activatable_interface_init (GtkActivatableIface *iface) { @@ -1822,6 +1955,9 @@ gtk_real_button_clicked (GtkButton *button) { GtkButtonPrivate *priv = button->priv; + if (priv->action_observer) + g_simple_action_observer_activate (priv->action_observer); + if (priv->action) gtk_action_activate (priv->action); } @@ -2416,6 +2552,28 @@ gtk_button_grab_notify (GtkWidget *widget, } } +static void +gtk_button_hierarchy_changed (GtkWidget *widget, + GtkWidget *previous_toplevel) +{ + GtkButton *button = GTK_BUTTON (widget); + GtkWidgetClass *parent_class; + + parent_class = GTK_WIDGET_CLASS (gtk_button_parent_class); + if (parent_class->hierarchy_changed) + parent_class->hierarchy_changed (widget, previous_toplevel); + + if (button->priv->action_name) + { + GtkWidget *toplevel; + + toplevel = gtk_widget_get_toplevel (widget); + + if (toplevel != previous_toplevel) + gtk_button_update_action_observer (button); + } +} + /** * gtk_button_set_image: * @button: a #GtkButton @@ -2525,7 +2683,6 @@ gtk_button_get_image_position (GtkButton *button) return button->priv->image_position; } - /** * gtk_button_get_event_window: * @button: a #GtkButton diff --git a/gtk/gtkbuttonprivate.h b/gtk/gtkbuttonprivate.h index 5db1bc1171..232973c1fc 100644 --- a/gtk/gtkbuttonprivate.h +++ b/gtk/gtkbuttonprivate.h @@ -19,6 +19,7 @@ #ifndef __GTK_BUTTON_PRIVATE_H__ #define __GTK_BUTTON_PRIVATE_H__ +#include "gsimpleactionobserver.h" #include "gtkaction.h" G_BEGIN_DECLS @@ -29,6 +30,10 @@ struct _GtkButtonPrivate GtkAction *action; GtkWidget *image; + gchar *action_name; + GVariant *action_target; + GSimpleActionObserver *action_observer; + GdkDevice *grab_keyboard; GdkWindow *event_window; diff --git a/gtk/gtktoolbutton.c b/gtk/gtktoolbutton.c index 934ca61321..ea67c34c6a 100644 --- a/gtk/gtktoolbutton.c +++ b/gtk/gtktoolbutton.c @@ -32,6 +32,7 @@ #include "gtkintl.h" #include "gtktoolbar.h" #include "gtkactivatable.h" +#include "gtkactionable.h" #include "gtkprivate.h" #include @@ -81,7 +82,9 @@ enum { PROP_LABEL_WIDGET, PROP_STOCK_ID, PROP_ICON_NAME, - PROP_ICON_WIDGET + PROP_ICON_WIDGET, + PROP_ACTION_NAME, + PROP_ACTION_TARGET }; static void gtk_tool_button_init (GtkToolButton *button, @@ -107,6 +110,7 @@ static void gtk_tool_button_style_updated (GtkWidget *widget); static void gtk_tool_button_construct_contents (GtkToolItem *tool_item); +static void gtk_tool_button_actionable_iface_init (GtkActionableInterface *iface); static void gtk_tool_button_activatable_interface_init (GtkActivatableIface *iface); static void gtk_tool_button_update (GtkActivatable *activatable, GtkAction *action, @@ -135,7 +139,6 @@ static GObjectClass *parent_class = NULL; static GtkActivatableIface *parent_activatable_iface; static guint toolbutton_signals[LAST_SIGNAL] = { 0 }; - GType gtk_tool_button_get_type (void) { @@ -143,6 +146,12 @@ gtk_tool_button_get_type (void) if (!type) { + const GInterfaceInfo actionable_info = + { + (GInterfaceInitFunc) gtk_tool_button_actionable_iface_init, + (GInterfaceFinalizeFunc) NULL, + NULL + }; const GInterfaceInfo activatable_info = { (GInterfaceInitFunc) gtk_tool_button_activatable_interface_init, @@ -158,6 +167,7 @@ gtk_tool_button_get_type (void) (GInstanceInitFunc) gtk_tool_button_init, 0); + g_type_add_interface_static (type, GTK_TYPE_ACTIONABLE, &actionable_info); g_type_add_interface_static (type, GTK_TYPE_ACTIVATABLE, &activatable_info); } @@ -278,6 +288,9 @@ gtk_tool_button_class_init (GtkToolButtonClass *klass) GTK_TYPE_WIDGET, GTK_PARAM_READWRITE)); + g_object_class_override_property (object_class, PROP_ACTION_NAME, "action-name"); + g_object_class_override_property (object_class, PROP_ACTION_TARGET, "action-target"); + /** * GtkButton:icon-spacing: * @@ -612,6 +625,12 @@ gtk_tool_button_set_property (GObject *object, case PROP_ICON_WIDGET: gtk_tool_button_set_icon_widget (button, g_value_get_object (value)); break; + case PROP_ACTION_NAME: + g_object_set_property (G_OBJECT (button->priv->button), "action-name", value); + break; + case PROP_ACTION_TARGET: + g_object_set_property (G_OBJECT (button->priv->button), "action-target", value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -660,12 +679,61 @@ gtk_tool_button_get_property (GObject *object, case PROP_ICON_WIDGET: g_value_set_object (value, button->priv->icon_widget); break; + case PROP_ACTION_NAME: + g_object_get_property (G_OBJECT (button->priv->button), "action-name", value); + break; + case PROP_ACTION_TARGET: + g_object_get_property (G_OBJECT (button->priv->button), "action-target", value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } +static const gchar * +gtk_tool_button_get_action_name (GtkActionable *actionable) +{ + GtkToolButton *button = GTK_TOOL_BUTTON (actionable); + + return gtk_actionable_get_action_name (GTK_ACTIONABLE (button->priv->button)); +} + +static void +gtk_tool_button_set_action_name (GtkActionable *actionable, + const gchar *action_name) +{ + GtkToolButton *button = GTK_TOOL_BUTTON (actionable); + + gtk_actionable_set_action_name (GTK_ACTIONABLE (button->priv->button), action_name); +} + +static GVariant * +gtk_tool_button_get_action_target_value (GtkActionable *actionable) +{ + GtkToolButton *button = GTK_TOOL_BUTTON (actionable); + + return gtk_actionable_get_action_target_value (GTK_ACTIONABLE (button->priv->button)); +} + +static void +gtk_tool_button_set_action_target_value (GtkActionable *actionable, + GVariant *action_target) +{ + GtkToolButton *button = GTK_TOOL_BUTTON (actionable); + + gtk_actionable_set_action_target_value (GTK_ACTIONABLE (button->priv->button), action_target); +} + +static void +gtk_tool_button_actionable_iface_init (GtkActionableInterface *iface) +{ + iface->get_action_name = gtk_tool_button_get_action_name; + iface->set_action_name = gtk_tool_button_set_action_name; + iface->get_action_target_value = gtk_tool_button_get_action_target_value; + iface->set_action_target_value = gtk_tool_button_set_action_target_value; +} + static void gtk_tool_button_finalize (GObject *object) {