From a4276a6c7959854e0654791e0cfdcca5b82ec213 Mon Sep 17 00:00:00 2001 From: Ryan Lortie Date: Wed, 8 May 2013 08:20:23 -0400 Subject: [PATCH] add GtkMenuTrackerItem Add a new class, GtkMenuTrackerItem that represents a menu item, to be used with GtkMenuTracker. GtkMenuTracker's insert callback now works in terms of this new type (instead of passing reference to the model and an index to the item). GtkMenuShell now handles all of the binding tasks internally, mostly through the use of property bindings. Having bindings for the label and visibility attributes, in partiular, will help with supporting upcoming extensions to GMenuModel. GtkModelMenu has been reduced to a helper class that has nothing to do with GMenuModel. It represents something closer to an "ideal" API for GtkMenuItem if we didn't have compatibility concerns (eg: not emitting "activate" when setting toggle state, no separate subclasses per menu item type, supporting icons, etc.) Improvements to GtkMenuItem could eventually shrink the size of this class or remove the need for it entirely. Some GtkActionHelper functionality has been duplicated in GtkMenuTracker, which is suboptimal. The duplication exists so that other codebases (such as Unity and gnome-shell) can reuse the GtkMenuTracker code, whereas GtkActionHelper is very much tied to GtkWidget. Supporting binding arbitrary GtkWidgets to actions vs. supporting the full range of GMenuModel features for menu items turns out to be two overlapping but not entirely similar problems. Some of the duplication (such as roles) can be removed from GtkActionHelper once Gtk's internal Mac OS menubar support is ported to GtkMenuTracker. The intent to reuse the code outside of Gtk is also the reason for the unusual treatment of the enum type introduced in this comment. This adds no new "public" API to the Gtk library, other than types that we cannot make private due to GType limitations. --- gtk/Makefile.am | 2 + gtk/gtkmenushell.c | 127 +++++-- gtk/gtkmenutracker.c | 35 +- gtk/gtkmenutracker.h | 37 +- gtk/gtkmenutrackeritem.c | 731 +++++++++++++++++++++++++++++++++++++++ gtk/gtkmenutrackeritem.h | 84 +++++ gtk/gtkmodelmenuitem.c | 440 ++++++++++++----------- gtk/gtkmodelmenuitem.h | 5 +- 8 files changed, 1211 insertions(+), 250 deletions(-) create mode 100644 gtk/gtkmenutrackeritem.c create mode 100644 gtk/gtkmenutrackeritem.h diff --git a/gtk/Makefile.am b/gtk/Makefile.am index 7edca5a05b..60fbc31e18 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -497,6 +497,7 @@ gtk_private_h_sources = \ gtkmenuitemprivate.h \ gtkmenushellprivate.h \ gtkmenutracker.h \ + gtkmenutrackeritem.h \ gtkmnemonichash.h \ gtkmodelmenuitem.h \ gtkmodifierstyle.h \ @@ -764,6 +765,7 @@ gtk_base_c_sources = \ gtkmenuitem.c \ gtkmenushell.c \ gtkmenutracker.c \ + gtkmenutrackeritem.c \ gtkmenutoolbutton.c \ gtkmessagedialog.c \ gtkmisc.c \ diff --git a/gtk/gtkmenushell.c b/gtk/gtkmenushell.c index a5ee2d465b..07ec537795 100644 --- a/gtk/gtkmenushell.c +++ b/gtk/gtkmenushell.c @@ -52,6 +52,7 @@ #include "gtkintl.h" #include "gtktypebuiltins.h" #include "gtkmodelmenuitem.h" +#include "gtkwidgetprivate.h" #include "deprecated/gtktearoffmenuitem.h" @@ -2039,33 +2040,30 @@ gtk_menu_shell_get_parent_shell (GtkMenuShell *menu_shell) } static void -gtk_menu_shell_tracker_insert_func (gint position, - GMenuModel *model, - gint item_index, - const gchar *action_namespace, - gboolean is_separator, - gpointer user_data) +gtk_menu_shell_item_activate (GtkMenuItem *menuitem, + gpointer user_data) { - GtkMenuShell *menu_shell = user_data; - GtkWidget *item; + GtkMenuTrackerItem *item = user_data; - if (is_separator) - { - gchar *label; + gtk_menu_tracker_item_activated (item); +} - item = gtk_separator_menu_item_new (); +static void +gtk_menu_shell_submenu_shown (GtkWidget *submenu, + gpointer user_data) +{ + GtkMenuTrackerItem *item = user_data; - if (g_menu_model_get_item_attribute (model, item_index, G_MENU_ATTRIBUTE_LABEL, "s", &label)) - { - gtk_menu_item_set_label (GTK_MENU_ITEM (item), label); - g_free (label); - } - } - else - item = gtk_model_menu_item_new (model, item_index, action_namespace); + gtk_menu_tracker_item_request_submenu_shown (item, TRUE); +} - gtk_menu_shell_insert (menu_shell, item, position); - gtk_widget_show (item); +static void +gtk_menu_shell_submenu_hidden (GtkWidget *submenu, + gpointer user_data) +{ + GtkMenuTrackerItem *item = user_data; + + gtk_menu_tracker_item_request_submenu_shown (item, FALSE); } static void @@ -2083,6 +2081,84 @@ gtk_menu_shell_tracker_remove_func (gint position, gtk_widget_destroy (child); } +static void +gtk_menu_shell_tracker_insert_func (GtkMenuTrackerItem *item, + gint position, + gpointer user_data) +{ + GtkMenuShell *menu_shell = user_data; + GtkWidget *widget; + + if (gtk_menu_tracker_item_get_is_separator (item)) + { + widget = gtk_separator_menu_item_new (); + + /* For separators, we bind to the "label" property in case there + * is a section heading. + */ + g_object_bind_property (item, "label", widget, "label", G_BINDING_SYNC_CREATE); + } + else if (gtk_menu_tracker_item_get_has_submenu (item)) + { + GtkMenuShell *submenu; + + widget = gtk_model_menu_item_new (); + g_object_bind_property (item, "label", widget, "text", G_BINDING_SYNC_CREATE); + + submenu = GTK_MENU_SHELL (gtk_menu_new ()); + + /* We recurse directly here: we could use an idle instead to + * prevent arbitrary recursion depth. We could also do it + * lazy... + */ + submenu->priv->tracker = gtk_menu_tracker_new_for_item_submenu (item, + gtk_menu_shell_tracker_insert_func, + gtk_menu_shell_tracker_remove_func, + submenu); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (widget), GTK_WIDGET (submenu)); + + if (gtk_menu_tracker_item_get_should_request_show (item)) + { + /* We don't request show in the strictest sense of the + * word: we just notify when we are showing and don't + * bother waiting for the reply. + * + * This could be fixed one day, but it would be slightly + * complicated and would have a strange interaction with + * the submenu pop-up delay. + * + * Note: 'item' is already kept alive from above. + */ + g_signal_connect (submenu, "show", G_CALLBACK (gtk_menu_shell_submenu_shown), item); + g_signal_connect (submenu, "hide", G_CALLBACK (gtk_menu_shell_submenu_hidden), item); + } + } + else + { + widget = gtk_model_menu_item_new (); + + /* We bind to "text" instead of "label" because GtkModelMenuItem + * uses this property (along with "icon") to control its child + * widget. Once this is merged into GtkMenuItem we can go back to + * using "label". + */ + g_object_bind_property (item, "label", widget, "text", G_BINDING_SYNC_CREATE); + g_object_bind_property (item, "icon", widget, "icon", G_BINDING_SYNC_CREATE); + g_object_bind_property (item, "sensitive", widget, "sensitive", G_BINDING_SYNC_CREATE); + g_object_bind_property (item, "visible", widget, "visible", G_BINDING_SYNC_CREATE); + g_object_bind_property (item, "role", widget, "action-role", G_BINDING_SYNC_CREATE); + g_object_bind_property (item, "toggled", widget, "toggled", G_BINDING_SYNC_CREATE); + g_object_bind_property (item, "accel", widget, "accel", G_BINDING_SYNC_CREATE); + + g_signal_connect (widget, "activate", G_CALLBACK (gtk_menu_shell_item_activate), item); + } + + /* TODO: drop this when we have bindings that ref the source */ + g_object_set_data_full (G_OBJECT (widget), "GtkMenuTrackerItem", g_object_ref (item), g_object_unref); + + gtk_menu_shell_insert (menu_shell, widget, position); +} + /** * gtk_menu_shell_bind_model: * @menu_shell: a #GtkMenuShell @@ -2134,16 +2210,21 @@ gtk_menu_shell_bind_model (GtkMenuShell *menu_shell, const gchar *action_namespace, gboolean with_separators) { + GtkActionMuxer *muxer; + g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell)); g_return_if_fail (model == NULL || G_IS_MENU_MODEL (model)); + muxer = _gtk_widget_get_action_muxer (GTK_WIDGET (menu_shell)); + g_clear_pointer (&menu_shell->priv->tracker, gtk_menu_tracker_free); while (menu_shell->priv->children) gtk_container_remove (GTK_CONTAINER (menu_shell), menu_shell->priv->children->data); if (model) - menu_shell->priv->tracker = gtk_menu_tracker_new (model, with_separators, action_namespace, + menu_shell->priv->tracker = gtk_menu_tracker_new (GTK_ACTION_OBSERVABLE (muxer), + model, with_separators, action_namespace, gtk_menu_shell_tracker_insert_func, gtk_menu_shell_tracker_remove_func, menu_shell); diff --git a/gtk/gtkmenutracker.c b/gtk/gtkmenutracker.c index f49457f115..ba8c4b9b10 100644 --- a/gtk/gtkmenutracker.c +++ b/gtk/gtkmenutracker.c @@ -27,6 +27,7 @@ typedef struct _GtkMenuTrackerSection GtkMenuTrackerSection; struct _GtkMenuTracker { + GtkActionObservable *observable; GtkMenuTrackerInsertFunc insert_func; GtkMenuTrackerRemoveFunc remove_func; gpointer user_data; @@ -159,7 +160,12 @@ gtk_menu_tracker_section_sync_separators (GtkMenuTrackerSection *section, if (should_have_separator > section->has_separator) { /* Add a separator */ - (* tracker->insert_func) (offset, parent_model, parent_index, NULL, TRUE, tracker->user_data); + GtkMenuTrackerItem *item; + + item = _gtk_menu_tracker_item_new (tracker->observable, parent_model, parent_index, NULL, TRUE); + (* tracker->insert_func) (item, offset, tracker->user_data); + g_object_unref (item); + section->has_separator = TRUE; } else if (should_have_separator < section->has_separator) @@ -258,8 +264,13 @@ gtk_menu_tracker_add_items (GtkMenuTracker *tracker, } else { - (* tracker->insert_func) (offset, model, position + n_items, - section->action_namespace, FALSE, tracker->user_data); + GtkMenuTrackerItem *item; + + item = _gtk_menu_tracker_item_new (tracker->observable, model, position + n_items, + section->action_namespace, FALSE); + (* tracker->insert_func) (item, offset, tracker->user_data); + g_object_unref (item); + *change_point = g_slist_prepend (*change_point, NULL); } } @@ -400,7 +411,8 @@ gtk_menu_tracker_section_new (GtkMenuTracker *tracker, * gtk_menu_tracker_free() is called. */ GtkMenuTracker * -gtk_menu_tracker_new (GMenuModel *model, +gtk_menu_tracker_new (GtkActionObservable *observable, + GMenuModel *model, gboolean with_separators, const gchar *action_namespace, GtkMenuTrackerInsertFunc insert_func, @@ -410,6 +422,7 @@ gtk_menu_tracker_new (GMenuModel *model, GtkMenuTracker *tracker; tracker = g_slice_new (GtkMenuTracker); + tracker->observable = g_object_ref (observable); tracker->insert_func = insert_func; tracker->remove_func = remove_func; tracker->user_data = user_data; @@ -420,6 +433,19 @@ gtk_menu_tracker_new (GMenuModel *model, return tracker; } +GtkMenuTracker * +gtk_menu_tracker_new_for_item_submenu (GtkMenuTrackerItem *item, + GtkMenuTrackerInsertFunc insert_func, + GtkMenuTrackerRemoveFunc remove_func, + gpointer user_data) +{ + return gtk_menu_tracker_new (_gtk_menu_tracker_item_get_observable (item), + _gtk_menu_tracker_item_get_submenu (item), + TRUE, + _gtk_menu_tracker_item_get_submenu_namespace (item), + insert_func, remove_func, user_data); +} + /*< private > * gtk_menu_tracker_free: * @tracker: a #GtkMenuTracker @@ -430,5 +456,6 @@ void gtk_menu_tracker_free (GtkMenuTracker *tracker) { gtk_menu_tracker_section_free (tracker->toplevel); + g_object_unref (tracker->observable); g_slice_free (GtkMenuTracker, tracker); } diff --git a/gtk/gtkmenutracker.h b/gtk/gtkmenutracker.h index 51810a104c..96370adcb8 100644 --- a/gtk/gtkmenutracker.h +++ b/gtk/gtkmenutracker.h @@ -22,30 +22,31 @@ #ifndef __GTK_MENU_TRACKER_H__ #define __GTK_MENU_TRACKER_H__ -#include +#include "gtkmenutrackeritem.h" typedef struct _GtkMenuTracker GtkMenuTracker; -typedef void (* GtkMenuTrackerInsertFunc) (gint position, - GMenuModel *model, - gint item_index, - const gchar *action_namespace, - gboolean is_separator, - gpointer user_data); +typedef void (* GtkMenuTrackerInsertFunc) (GtkMenuTrackerItem *item, + gint position, + gpointer user_data); -typedef void (* GtkMenuTrackerRemoveFunc) (gint position, - gpointer user_data); +typedef void (* GtkMenuTrackerRemoveFunc) (gint position, + gpointer user_data); -G_GNUC_INTERNAL -GtkMenuTracker * gtk_menu_tracker_new (GMenuModel *model, - gboolean with_separators, - const gchar *action_namespace, - GtkMenuTrackerInsertFunc insert_func, - GtkMenuTrackerRemoveFunc remove_func, - gpointer user_data); +GtkMenuTracker * gtk_menu_tracker_new (GtkActionObservable *observer, + GMenuModel *model, + gboolean with_separators, + const gchar *action_namespace, + GtkMenuTrackerInsertFunc insert_func, + GtkMenuTrackerRemoveFunc remove_func, + gpointer user_data); -G_GNUC_INTERNAL -void gtk_menu_tracker_free (GtkMenuTracker *tracker); +GtkMenuTracker * gtk_menu_tracker_new_for_item_submenu (GtkMenuTrackerItem *item, + GtkMenuTrackerInsertFunc insert_func, + GtkMenuTrackerRemoveFunc remove_func, + gpointer user_data); + +void gtk_menu_tracker_free (GtkMenuTracker *tracker); #endif /* __GTK_MENU_TRACKER_H__ */ diff --git a/gtk/gtkmenutrackeritem.c b/gtk/gtkmenutrackeritem.c new file mode 100644 index 0000000000..3c6e8f54a8 --- /dev/null +++ b/gtk/gtkmenutrackeritem.c @@ -0,0 +1,731 @@ +/* + * Copyright © 2013 Canonical Limited + * + * This library 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. + * + * Author: Ryan Lortie + */ + +#include "config.h" + +#include "gtkmenutrackeritem.h" + +typedef GObjectClass GtkMenuTrackerItemClass; + +struct _GtkMenuTrackerItem +{ + GObject parent_instance; + + GtkActionObservable *observable; + gchar *action_namespace; + GMenuItem *item; + GtkMenuTrackerItemRole role : 4; + guint is_separator : 1; + guint can_activate : 1; + guint sensitive : 1; + guint toggled : 1; + guint submenu_shown : 1; + guint submenu_requested : 1; +}; + +enum { + PROP_0, + PROP_IS_SEPARATOR, + PROP_HAS_SUBMENU, + PROP_LABEL, + PROP_ICON, + PROP_SENSITIVE, + PROP_VISIBLE, + PROP_ROLE, + PROP_TOGGLED, + PROP_ACCEL, + PROP_SUBMENU_SHOWN, + N_PROPS +}; + +static GParamSpec *gtk_menu_tracker_item_pspecs[N_PROPS]; + +static void gtk_menu_tracker_item_init_observer_iface (GtkActionObserverInterface *iface); +G_DEFINE_TYPE_WITH_CODE (GtkMenuTrackerItem, gtk_menu_tracker_item, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTION_OBSERVER, gtk_menu_tracker_item_init_observer_iface)) + +GType +gtk_menu_tracker_item_role_get_type (void) +{ + static gsize gtk_menu_tracker_item_role_type; + + if (g_once_init_enter (>k_menu_tracker_item_role_type)) + { + static const GEnumValue values[] = { + { GTK_MENU_TRACKER_ITEM_ROLE_NORMAL, "GTK_MENU_TRACKER_ITEM_ROLE_NORMAL", "normal" }, + { GTK_MENU_TRACKER_ITEM_ROLE_CHECK, "GTK_MENU_TRACKER_ITEM_ROLE_CHECK", "check" }, + { GTK_MENU_TRACKER_ITEM_ROLE_RADIO, "GTK_MENU_TRACKER_ITEM_ROLE_RADIO", "radio" }, + { 0, NULL, NULL } + }; + GType type; + + type = g_enum_register_static ("GtkMenuTrackerItemRole", values); + + g_once_init_leave (>k_menu_tracker_item_role_type, type); + } + + return gtk_menu_tracker_item_role_type; +} + +static void +gtk_menu_tracker_item_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkMenuTrackerItem *self = GTK_MENU_TRACKER_ITEM (object); + + switch (prop_id) + { + case PROP_IS_SEPARATOR: + g_value_set_boolean (value, gtk_menu_tracker_item_get_is_separator (self)); + break; + case PROP_HAS_SUBMENU: + g_value_set_boolean (value, gtk_menu_tracker_item_get_has_submenu (self)); + break; + case PROP_LABEL: + g_value_set_string (value, gtk_menu_tracker_item_get_label (self)); + break; + case PROP_ICON: + g_value_set_object (value, gtk_menu_tracker_item_get_icon (self)); + break; + case PROP_SENSITIVE: + g_value_set_boolean (value, gtk_menu_tracker_item_get_sensitive (self)); + break; + case PROP_VISIBLE: + g_value_set_boolean (value, gtk_menu_tracker_item_get_visible (self)); + break; + case PROP_ROLE: + g_value_set_enum (value, gtk_menu_tracker_item_get_role (self)); + break; + case PROP_TOGGLED: + g_value_set_boolean (value, gtk_menu_tracker_item_get_toggled (self)); + break; + case PROP_ACCEL: + g_value_set_string (value, gtk_menu_tracker_item_get_accel (self)); + break; + case PROP_SUBMENU_SHOWN: + g_value_set_boolean (value, gtk_menu_tracker_item_get_submenu_shown (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_menu_tracker_item_finalize (GObject *object) +{ + GtkMenuTrackerItem *self = GTK_MENU_TRACKER_ITEM (object); + + g_free (self->action_namespace); + + if (self->observable) + g_object_unref (self->observable); + + g_object_unref (self->item); + + G_OBJECT_CLASS (gtk_menu_tracker_item_parent_class)->finalize (object); +} + +static void +gtk_menu_tracker_item_init (GtkMenuTrackerItem * self) +{ +} + +static void +gtk_menu_tracker_item_class_init (GtkMenuTrackerItemClass *class) +{ + class->get_property = gtk_menu_tracker_item_get_property; + class->finalize = gtk_menu_tracker_item_finalize; + + gtk_menu_tracker_item_pspecs[PROP_IS_SEPARATOR] = + g_param_spec_boolean ("is-separator", "", "", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE); + gtk_menu_tracker_item_pspecs[PROP_HAS_SUBMENU] = + g_param_spec_boolean ("has-submenu", "", "", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE); + gtk_menu_tracker_item_pspecs[PROP_LABEL] = + g_param_spec_string ("label", "", "", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE); + gtk_menu_tracker_item_pspecs[PROP_ICON] = + g_param_spec_object ("icon", "", "", G_TYPE_ICON, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE); + gtk_menu_tracker_item_pspecs[PROP_SENSITIVE] = + g_param_spec_boolean ("sensitive", "", "", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE); + gtk_menu_tracker_item_pspecs[PROP_VISIBLE] = + g_param_spec_boolean ("visible", "", "", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE); + gtk_menu_tracker_item_pspecs[PROP_ROLE] = + g_param_spec_enum ("role", "", "", + GTK_TYPE_MENU_TRACKER_ITEM_ROLE, GTK_MENU_TRACKER_ITEM_ROLE_NORMAL, + G_PARAM_STATIC_STRINGS | G_PARAM_READABLE); + gtk_menu_tracker_item_pspecs[PROP_TOGGLED] = + g_param_spec_boolean ("toggled", "", "", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE); + gtk_menu_tracker_item_pspecs[PROP_ACCEL] = + g_param_spec_string ("accel", "", "", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE); + gtk_menu_tracker_item_pspecs[PROP_SUBMENU_SHOWN] = + g_param_spec_boolean ("submenu-shown", "", "", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE); + + g_object_class_install_properties (class, N_PROPS, gtk_menu_tracker_item_pspecs); +} + +static void +gtk_menu_tracker_item_action_added (GtkActionObserver *observer, + GtkActionObservable *observable, + const gchar *action_name, + const GVariantType *parameter_type, + gboolean enabled, + GVariant *state) +{ + GtkMenuTrackerItem *self = GTK_MENU_TRACKER_ITEM (observer); + GVariant *action_target; + + action_target = g_menu_item_get_attribute_value (self->item, G_MENU_ATTRIBUTE_TARGET, NULL); + + self->can_activate = (action_target == NULL && parameter_type == NULL) || + (action_target != NULL && parameter_type != NULL && + g_variant_is_of_type (action_target, parameter_type)); + + if (!self->can_activate) + { + if (action_target) + g_variant_unref (action_target); + return; + } + + self->sensitive = enabled; + + if (action_target != NULL && state != NULL) + { + self->toggled = g_variant_equal (state, action_target); + self->role = GTK_MENU_TRACKER_ITEM_ROLE_RADIO; + } + + else if (state != NULL && g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN)) + { + self->toggled = g_variant_get_boolean (state); + self->role = GTK_MENU_TRACKER_ITEM_ROLE_CHECK; + } + + g_object_freeze_notify (G_OBJECT (self)); + + if (self->sensitive) + g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_SENSITIVE]); + + if (self->toggled) + g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_TOGGLED]); + + if (self->role != GTK_MENU_TRACKER_ITEM_ROLE_NORMAL) + g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_ROLE]); + + g_object_thaw_notify (G_OBJECT (self)); + + if (action_target) + g_variant_unref (action_target); +} + +static void +gtk_menu_tracker_item_action_enabled_changed (GtkActionObserver *observer, + GtkActionObservable *observable, + const gchar *action_name, + gboolean enabled) +{ + GtkMenuTrackerItem *self = GTK_MENU_TRACKER_ITEM (observer); + + if (!self->can_activate) + return; + + if (self->sensitive == enabled) + return; + + self->sensitive = enabled; + + g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_SENSITIVE]); +} + +static void +gtk_menu_tracker_item_action_state_changed (GtkActionObserver *observer, + GtkActionObservable *observable, + const gchar *action_name, + GVariant *state) +{ + GtkMenuTrackerItem *self = GTK_MENU_TRACKER_ITEM (observer); + GVariant *action_target; + gboolean was_toggled; + + if (!self->can_activate) + return; + + action_target = g_menu_item_get_attribute_value (self->item, G_MENU_ATTRIBUTE_TARGET, NULL); + was_toggled = self->toggled; + + if (action_target) + { + self->toggled = g_variant_equal (state, action_target); + g_variant_unref (action_target); + } + + else if (g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN)) + self->toggled = g_variant_get_boolean (state); + + else + self->toggled = FALSE; + + if (self->toggled != was_toggled) + g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_TOGGLED]); +} + +static void +gtk_menu_tracker_item_action_removed (GtkActionObserver *observer, + GtkActionObservable *observable, + const gchar *action_name) +{ + GtkMenuTrackerItem *self = GTK_MENU_TRACKER_ITEM (observer); + + if (!self->can_activate) + return; + + g_object_freeze_notify (G_OBJECT (self)); + + if (self->sensitive) + { + self->sensitive = FALSE; + g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_SENSITIVE]); + } + + if (self->toggled) + { + self->toggled = FALSE; + g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_TOGGLED]); + } + + if (self->role != GTK_MENU_TRACKER_ITEM_ROLE_NORMAL) + { + self->role = GTK_MENU_TRACKER_ITEM_ROLE_NORMAL; + g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_ROLE]); + } + + g_object_thaw_notify (G_OBJECT (self)); +} + +static void +gtk_menu_tracker_item_init_observer_iface (GtkActionObserverInterface *iface) +{ + iface->action_added = gtk_menu_tracker_item_action_added; + iface->action_enabled_changed = gtk_menu_tracker_item_action_enabled_changed; + iface->action_state_changed = gtk_menu_tracker_item_action_state_changed; + iface->action_removed = gtk_menu_tracker_item_action_removed; +} + +GtkMenuTrackerItem * +_gtk_menu_tracker_item_new (GtkActionObservable *observable, + GMenuModel *model, + gint item_index, + const gchar *action_namespace, + gboolean is_separator) +{ + GtkMenuTrackerItem *self; + const gchar *action_name; + + g_return_val_if_fail (GTK_IS_ACTION_OBSERVABLE (observable), NULL); + g_return_val_if_fail (G_IS_MENU_MODEL (model), NULL); + + self = g_object_new (GTK_TYPE_MENU_TRACKER_ITEM, NULL); + self->item = g_menu_item_new_from_model (model, item_index); + self->action_namespace = g_strdup (action_namespace); + self->observable = g_object_ref (observable); + self->is_separator = is_separator; + + if (!is_separator && g_menu_item_get_attribute (self->item, "action", "&s", &action_name)) + { + GActionGroup *group = G_ACTION_GROUP (observable); + const GVariantType *parameter_type; + gboolean enabled; + GVariant *state; + gboolean found; + + state = NULL; + + if (action_namespace) + { + gchar *full_action; + + full_action = g_strjoin (".", action_namespace, action_name, NULL); + gtk_action_observable_register_observer (self->observable, full_action, GTK_ACTION_OBSERVER (self)); + found = g_action_group_query_action (group, full_action, &enabled, ¶meter_type, NULL, NULL, &state); + g_free (full_action); + } + else + { + gtk_action_observable_register_observer (self->observable, action_name, GTK_ACTION_OBSERVER (self)); + found = g_action_group_query_action (group, action_name, &enabled, ¶meter_type, NULL, NULL, &state); + } + + if (found) + gtk_menu_tracker_item_action_added (GTK_ACTION_OBSERVER (self), observable, NULL, parameter_type, enabled, state); + else + gtk_menu_tracker_item_action_removed (GTK_ACTION_OBSERVER (self), observable, NULL); + + if (state) + g_variant_unref (state); + } + else + self->sensitive = TRUE; + + return self; +} + +GtkActionObservable * +_gtk_menu_tracker_item_get_observable (GtkMenuTrackerItem *self) +{ + return self->observable; +} + +/** + * gtk_menu_tracker_item_get_is_separator: + * @self: A #GtkMenuTrackerItem instance + * + * Returns whether the menu item is a separator. If so, only + * certain properties may need to be obeyed. See the documentation + * for #GtkMenuTrackerItem. + */ +gboolean +gtk_menu_tracker_item_get_is_separator (GtkMenuTrackerItem *self) +{ + return self->is_separator; +} + +/** + * gtk_menu_tracker_item_get_has_submenu: + * @self: A #GtkMenuTrackerItem instance + * + * Returns whether the menu item has a submenu. If so, only + * certain properties may need to be obeyed. See the documentation + * for #GtkMenuTrackerItem. + */ +gboolean +gtk_menu_tracker_item_get_has_submenu (GtkMenuTrackerItem *self) +{ + GMenuModel *link; + + link = g_menu_item_get_link (self->item, G_MENU_LINK_SUBMENU); + + if (link) + { + g_object_unref (link); + return TRUE; + } + else + return FALSE; +} + +const gchar * +gtk_menu_tracker_item_get_label (GtkMenuTrackerItem *self) +{ + const gchar *label = NULL; + + g_menu_item_get_attribute (self->item, G_MENU_ATTRIBUTE_LABEL, "&s", &label); + + return label; +} + +/** + * gtk_menu_tracker_item_get_icon: + * + * Returns: (transfer full): + */ +GIcon * +gtk_menu_tracker_item_get_icon (GtkMenuTrackerItem *self) +{ + GVariant *icon_data; + GIcon *icon; + + icon_data = g_menu_item_get_attribute_value (self->item, "icon", NULL); + + if (icon_data == NULL) + return NULL; + + icon = g_icon_deserialize (icon_data); + g_variant_unref (icon_data); + + return icon; +} + +gboolean +gtk_menu_tracker_item_get_sensitive (GtkMenuTrackerItem *self) +{ + return self->sensitive; +} + +gboolean +gtk_menu_tracker_item_get_visible (GtkMenuTrackerItem *self) +{ + return TRUE; +} + +GtkMenuTrackerItemRole +gtk_menu_tracker_item_get_role (GtkMenuTrackerItem *self) +{ + return self->role; +} + +gboolean +gtk_menu_tracker_item_get_toggled (GtkMenuTrackerItem *self) +{ + return self->toggled; +} + +const gchar * +gtk_menu_tracker_item_get_accel (GtkMenuTrackerItem *self) +{ + const gchar *accel = NULL; + + g_menu_item_get_attribute (self->item, "accel", "&s", &accel); + + return accel; +} + +GMenuModel * +_gtk_menu_tracker_item_get_submenu (GtkMenuTrackerItem *self) +{ + return g_menu_item_get_link (self->item, "submenu"); +} + +gchar * +_gtk_menu_tracker_item_get_submenu_namespace (GtkMenuTrackerItem *self) +{ + const gchar *namespace; + + if (g_menu_item_get_attribute (self->item, "action-namespace", "&s", &namespace)) + { + if (self->action_namespace) + return g_strjoin (".", self->action_namespace, namespace, NULL); + else + return g_strdup (namespace); + } + else + return g_strdup (self->action_namespace); +} + +gboolean +gtk_menu_tracker_item_get_should_request_show (GtkMenuTrackerItem *self) +{ + return g_menu_item_get_attribute (self->item, "submenu-action", "&s", NULL); +} + +gboolean +gtk_menu_tracker_item_get_submenu_shown (GtkMenuTrackerItem *self) +{ + return self->submenu_shown; +} + +/* only called from the opener, internally */ +static void +gtk_menu_tracker_item_set_submenu_shown (GtkMenuTrackerItem *self, + gboolean submenu_shown) +{ + if (submenu_shown == self->submenu_shown) + return; + + self->submenu_shown = submenu_shown; + g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_SUBMENU_SHOWN]); +} + +void +gtk_menu_tracker_item_activated (GtkMenuTrackerItem *self) +{ + const gchar *action_name; + GVariant *action_target; + + g_return_if_fail (GTK_IS_MENU_TRACKER_ITEM (self)); + + if (!self->can_activate) + return; + + g_menu_item_get_attribute (self->item, G_MENU_ATTRIBUTE_ACTION, "&s", &action_name); + action_target = g_menu_item_get_attribute_value (self->item, G_MENU_ATTRIBUTE_TARGET, NULL); + + if (self->action_namespace) + { + gchar *full_action; + + full_action = g_strjoin (".", self->action_namespace, action_name, NULL); + g_action_group_activate_action (G_ACTION_GROUP (self->observable), full_action, action_target); + g_free (full_action); + } + else + g_action_group_activate_action (G_ACTION_GROUP (self->observable), action_name, action_target); + + if (action_target) + g_variant_unref (action_target); +} + +typedef struct +{ + GtkMenuTrackerItem *item; + gchar *submenu_action; + gboolean first_time; +} GtkMenuTrackerOpener; + +static void +gtk_menu_tracker_opener_update (GtkMenuTrackerOpener *opener) +{ + GActionGroup *group = G_ACTION_GROUP (opener->item->observable); + gboolean is_open = TRUE; + + /* We consider the menu as being "open" if the action does not exist + * or if there is another problem (no state, wrong state type, etc.). + * If the action exists, with the correct state then we consider it + * open if we have ever seen this state equal to TRUE. + * + * In the event that we see the state equal to FALSE, we force it back + * to TRUE. We do not signal that the menu was closed because this is + * likely to create UI thrashing. + * + * The only way the menu can have a true-to-false submenu-shown + * transition is if the user calls _request_submenu_shown (FALSE). + * That is handled in _free() below. + */ + + if (g_action_group_has_action (group, opener->submenu_action)) + { + GVariant *state = g_action_group_get_action_state (group, opener->submenu_action); + + if (state) + { + if (g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN)) + is_open = g_variant_get_boolean (state); + g_variant_unref (state); + } + } + + /* If it is already open, signal that. + * + * If it is not open, ask it to open. + */ + if (is_open) + gtk_menu_tracker_item_set_submenu_shown (opener->item, TRUE); + + if (!is_open || opener->first_time) + { + g_action_group_change_action_state (group, opener->submenu_action, g_variant_new_boolean (TRUE)); + opener->first_time = FALSE; + } +} + +static void +gtk_menu_tracker_opener_added (GActionGroup *group, + const gchar *action_name, + gpointer user_data) +{ + GtkMenuTrackerOpener *opener = user_data; + + if (g_str_equal (action_name, opener->submenu_action)) + gtk_menu_tracker_opener_update (opener); +} + +static void +gtk_menu_tracker_opener_removed (GActionGroup *action_group, + const gchar *action_name, + gpointer user_data) +{ + GtkMenuTrackerOpener *opener = user_data; + + if (g_str_equal (action_name, opener->submenu_action)) + gtk_menu_tracker_opener_update (opener); +} + +static void +gtk_menu_tracker_opener_changed (GActionGroup *action_group, + const gchar *action_name, + GVariant *new_state, + gpointer user_data) +{ + GtkMenuTrackerOpener *opener = user_data; + + if (g_str_equal (action_name, opener->submenu_action)) + gtk_menu_tracker_opener_update (opener); +} + +static void +gtk_menu_tracker_opener_free (gpointer data) +{ + GtkMenuTrackerOpener *opener = data; + + g_signal_handlers_disconnect_by_func (opener->item->observable, gtk_menu_tracker_opener_added, opener); + g_signal_handlers_disconnect_by_func (opener->item->observable, gtk_menu_tracker_opener_removed, opener); + g_signal_handlers_disconnect_by_func (opener->item->observable, gtk_menu_tracker_opener_changed, opener); + + g_action_group_change_action_state (G_ACTION_GROUP (opener->item->observable), + opener->submenu_action, + g_variant_new_boolean (FALSE)); + + gtk_menu_tracker_item_set_submenu_shown (opener->item, FALSE); + + g_free (opener->submenu_action); + + g_slice_free (GtkMenuTrackerOpener, opener); +} + +static GtkMenuTrackerOpener * +gtk_menu_tracker_opener_new (GtkMenuTrackerItem *item, + const gchar *submenu_action) +{ + GtkMenuTrackerOpener *opener; + + opener = g_slice_new (GtkMenuTrackerOpener); + opener->first_time = TRUE; + opener->item = item; + + if (item->action_namespace) + opener->submenu_action = g_strjoin (".", item->action_namespace, submenu_action, NULL); + else + opener->submenu_action = g_strdup (submenu_action); + + g_signal_connect (item->observable, "action-added", G_CALLBACK (gtk_menu_tracker_opener_added), opener); + g_signal_connect (item->observable, "action-removed", G_CALLBACK (gtk_menu_tracker_opener_removed), opener); + g_signal_connect (item->observable, "action-state-changed", G_CALLBACK (gtk_menu_tracker_opener_changed), opener); + + gtk_menu_tracker_opener_update (opener); + + return opener; +} + +void +gtk_menu_tracker_item_request_submenu_shown (GtkMenuTrackerItem *self, + gboolean shown) +{ + const gchar *submenu_action; + gboolean okay; + + if (shown == self->submenu_requested) + return; + + /* Should not be getting called unless we have submenu-action. + */ + okay = g_menu_item_get_attribute (self->item, "submenu-action", "&s", &submenu_action); + g_assert (okay); + + self->submenu_requested = shown; + + if (shown) + g_object_set_data_full (G_OBJECT (self), "submenu-opener", + gtk_menu_tracker_opener_new (self, submenu_action), + gtk_menu_tracker_opener_free); + else + g_object_set_data (G_OBJECT (self), "submenu-opener", NULL); +} diff --git a/gtk/gtkmenutrackeritem.h b/gtk/gtkmenutrackeritem.h new file mode 100644 index 0000000000..9db30eb880 --- /dev/null +++ b/gtk/gtkmenutrackeritem.h @@ -0,0 +1,84 @@ +/* + * Copyright © 2011, 2013 Canonical Limited + * + * This library 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, see . + * + * Author: Ryan Lortie + */ + +#ifndef __GTK_MENU_TRACKER_ITEM_H__ +#define __GTK_MENU_TRACKER_ITEM_H__ + +#include "gtkactionobservable.h" + +#define GTK_TYPE_MENU_TRACKER_ITEM (gtk_menu_tracker_item_get_type ()) +#define GTK_MENU_TRACKER_ITEM(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \ + GTK_TYPE_MENU_TRACKER_ITEM, GtkMenuTrackerItem)) +#define GTK_IS_MENU_TRACKER_ITEM(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), \ + GTK_TYPE_MENU_TRACKER_ITEM)) + +typedef struct _GtkMenuTrackerItem GtkMenuTrackerItem; + +#define GTK_TYPE_MENU_TRACKER_ITEM_ROLE (gtk_menu_tracker_item_role_get_type ()) + +typedef enum { + GTK_MENU_TRACKER_ITEM_ROLE_NORMAL, + GTK_MENU_TRACKER_ITEM_ROLE_CHECK, + GTK_MENU_TRACKER_ITEM_ROLE_RADIO, +} GtkMenuTrackerItemRole; + +GType gtk_menu_tracker_item_get_type (void) G_GNUC_CONST; + +GType gtk_menu_tracker_item_role_get_type (void) G_GNUC_CONST; + +GtkMenuTrackerItem * _gtk_menu_tracker_item_new (GtkActionObservable *observable, + GMenuModel *model, + gint item_index, + const gchar *action_namespace, + gboolean is_separator); + +GtkActionObservable * _gtk_menu_tracker_item_get_observable (GtkMenuTrackerItem *self); + +gboolean gtk_menu_tracker_item_get_is_separator (GtkMenuTrackerItem *self); + +gboolean gtk_menu_tracker_item_get_has_submenu (GtkMenuTrackerItem *self); + +const gchar * gtk_menu_tracker_item_get_label (GtkMenuTrackerItem *self); + +GIcon * gtk_menu_tracker_item_get_icon (GtkMenuTrackerItem *self); + +gboolean gtk_menu_tracker_item_get_sensitive (GtkMenuTrackerItem *self); + +gboolean gtk_menu_tracker_item_get_visible (GtkMenuTrackerItem *self); + +GtkMenuTrackerItemRole gtk_menu_tracker_item_get_role (GtkMenuTrackerItem *self); + +gboolean gtk_menu_tracker_item_get_toggled (GtkMenuTrackerItem *self); + +const gchar * gtk_menu_tracker_item_get_accel (GtkMenuTrackerItem *self); + +GMenuModel * _gtk_menu_tracker_item_get_submenu (GtkMenuTrackerItem *self); + +gchar * _gtk_menu_tracker_item_get_submenu_namespace (GtkMenuTrackerItem *self); + +gboolean gtk_menu_tracker_item_get_should_request_show (GtkMenuTrackerItem *self); + +void gtk_menu_tracker_item_activated (GtkMenuTrackerItem *self); + +void gtk_menu_tracker_item_request_submenu_shown (GtkMenuTrackerItem *self, + gboolean shown); + +gboolean gtk_menu_tracker_item_get_submenu_shown (GtkMenuTrackerItem *self); + +#endif diff --git a/gtk/gtkmodelmenuitem.c b/gtk/gtkmodelmenuitem.c index 1d30d3804d..c698a52e4e 100644 --- a/gtk/gtkmodelmenuitem.c +++ b/gtk/gtkmodelmenuitem.c @@ -1,5 +1,5 @@ /* - * Copyright © 2011 Canonical Limited + * Copyright © 2011, 2013 Canonical Limited * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -21,9 +21,6 @@ #include "gtkmodelmenuitem.h" -#include "gtkaccelmapprivate.h" -#include "gtkactionhelper.h" -#include "gtkwidgetprivate.h" #include "gtkaccellabel.h" #include "gtkimage.h" #include "gtkbox.h" @@ -31,7 +28,7 @@ struct _GtkModelMenuItem { GtkCheckMenuItem parent_instance; - GtkActionHelperRole role; + GtkMenuTrackerItemRole role; gboolean has_indicator; }; @@ -39,7 +36,15 @@ typedef GtkCheckMenuItemClass GtkModelMenuItemClass; G_DEFINE_TYPE (GtkModelMenuItem, gtk_model_menu_item, GTK_TYPE_CHECK_MENU_ITEM) -#define PROP_ACTION_ROLE 1 +enum +{ + PROP_0, + PROP_ACTION_ROLE, + PROP_ICON, + PROP_TEXT, + PROP_TOGGLED, + PROP_ACCEL +}; static void gtk_model_menu_item_toggle_size_request (GtkMenuItem *menu_item, @@ -55,6 +60,12 @@ gtk_model_menu_item_toggle_size_request (GtkMenuItem *menu_item, *requisition = 0; } +static void +gtk_model_menu_item_activate (GtkMenuItem *item) +{ + /* block the automatic toggle behaviour -- just do nothing */ +} + static void gtk_model_menu_item_draw_indicator (GtkCheckMenuItem *check_item, cairo_t *cr) @@ -66,177 +77,6 @@ gtk_model_menu_item_draw_indicator (GtkCheckMenuItem *check_item, ->draw_indicator (check_item, cr); } -static void -gtk_actionable_set_namespaced_action_name (GtkActionable *actionable, - const gchar *namespace, - const gchar *action_name) -{ - if (namespace) - { - gchar *name = g_strdup_printf ("%s.%s", namespace, action_name); - gtk_actionable_set_action_name (actionable, name); - g_free (name); - } - else - { - gtk_actionable_set_action_name (actionable, action_name); - } -} - -static void -gtk_model_menu_item_submenu_shown (GtkWidget *widget, - gpointer user_data) -{ - const gchar *action_name = user_data; - GActionMuxer *muxer; - - muxer = _gtk_widget_get_action_muxer (widget); - g_action_group_change_action_state (G_ACTION_GROUP (muxer), action_name, g_variant_new_boolean (TRUE)); -} - -static void -gtk_model_menu_item_submenu_hidden (GtkWidget *widget, - gpointer user_data) -{ - const gchar *action_name = user_data; - GActionMuxer *muxer; - - muxer = _gtk_widget_get_action_muxer (widget); - g_action_group_change_action_state (G_ACTION_GROUP (muxer), action_name, g_variant_new_boolean (FALSE)); -} - -static void -gtk_model_menu_item_setup (GtkModelMenuItem *item, - GMenuModel *model, - gint item_index, - const gchar *action_namespace) -{ - GMenuAttributeIter *iter; - GMenuModel *submenu; - const gchar *key; - GVariant *value; - GtkWidget *label; - - label = NULL; - - /* In the case that we have an icon, make an HBox and put it beside - * the label. Otherwise, we just have a label directly. - */ - if ((value = g_menu_model_get_item_attribute_value (model, item_index, "icon", NULL))) - { - GIcon *icon; - - icon = g_icon_deserialize (value); - - if (icon != NULL) - { - GtkWidget *image; - GtkWidget *box; - - box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); - - image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_MENU); - gtk_box_pack_start (GTK_BOX (box), image, FALSE, FALSE, 0); - g_object_unref (icon); - - label = gtk_accel_label_new (""); - gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); - gtk_accel_label_set_accel_widget (GTK_ACCEL_LABEL (label), GTK_WIDGET (item)); - gtk_box_pack_end (GTK_BOX (box), label, TRUE, TRUE, 0); - - gtk_container_add (GTK_CONTAINER (item), box); - - gtk_widget_show_all (box); - } - - g_variant_unref (value); - } - - if (label == NULL) - { - /* Ensure that the GtkAccelLabel has been created... */ - (void) gtk_menu_item_get_label (GTK_MENU_ITEM (item)); - label = gtk_bin_get_child (GTK_BIN (item)); - } - - g_assert (label != NULL); - - if ((submenu = g_menu_model_get_item_link (model, item_index, "submenu"))) - { - gchar *section_namespace = NULL; - GtkWidget *menu; - - g_menu_model_get_item_attribute (model, item_index, "action-namespace", "s", §ion_namespace); - menu = gtk_menu_new (); - - if (action_namespace) - { - gchar *namespace = g_strjoin (".", action_namespace, section_namespace, NULL); - gtk_menu_shell_bind_model (GTK_MENU_SHELL (menu), submenu, namespace, TRUE); - g_free (namespace); - } - else - gtk_menu_shell_bind_model (GTK_MENU_SHELL (menu), submenu, section_namespace, TRUE); - - gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), menu); - - g_free (section_namespace); - g_object_unref (submenu); - } - - iter = g_menu_model_iterate_item_attributes (model, item_index); - while (g_menu_attribute_iter_get_next (iter, &key, &value)) - { - if (g_str_equal (key, "label") && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) - gtk_label_set_text_with_mnemonic (GTK_LABEL (label), g_variant_get_string (value, NULL)); - - else if (g_str_equal (key, "accel") && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) - { - GdkModifierType modifiers; - guint key; - - gtk_accelerator_parse (g_variant_get_string (value, NULL), &key, &modifiers); - - if (key) - gtk_accel_label_set_accel (GTK_ACCEL_LABEL (label), key, modifiers); - } - - else if (g_str_equal (key, "action") && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) - gtk_actionable_set_namespaced_action_name (GTK_ACTIONABLE (item), action_namespace, - g_variant_get_string (value, NULL)); - - else if (g_str_equal (key, "target")) - gtk_actionable_set_action_target_value (GTK_ACTIONABLE (item), value); - - else if (g_str_equal (key, "submenu-action") && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) - { - GtkWidget *submenu; - - submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (item)); - - if (submenu != NULL) - { - const gchar *action = g_variant_get_string (value, NULL); - gchar *full_action; - - if (action_namespace) - full_action = g_strjoin (".", action_namespace, action, NULL); - else - full_action = g_strdup (action); - - g_object_set_data_full (G_OBJECT (submenu), "gtkmodelmenu-visibility-action", full_action, g_free); - g_signal_connect (submenu, "show", G_CALLBACK (gtk_model_menu_item_submenu_shown), full_action); - g_signal_connect (submenu, "hide", G_CALLBACK (gtk_model_menu_item_submenu_hidden), full_action); - } - } - - g_variant_unref (value); - } - g_object_unref (iter); - - gtk_menu_item_set_use_underline (GTK_MENU_ITEM (item), TRUE); -} - static void gtk_model_menu_item_set_has_indicator (GtkModelMenuItem *item, gboolean has_indicator) @@ -250,36 +90,30 @@ gtk_model_menu_item_set_has_indicator (GtkModelMenuItem *item, } static void -gtk_model_menu_item_set_property (GObject *object, guint prop_id, - const GValue *value, GParamSpec *pspec) +gtk_model_menu_item_set_action_role (GtkModelMenuItem *item, + GtkMenuTrackerItemRole role) { - GtkModelMenuItem *item = GTK_MODEL_MENU_ITEM (object); - GtkActionHelperRole role; AtkObject *accessible; AtkRole a11y_role; - g_assert (prop_id == PROP_ACTION_ROLE); - - role = g_value_get_uint (value); - if (role == item->role) return; - gtk_check_menu_item_set_draw_as_radio (GTK_CHECK_MENU_ITEM (item), role == GTK_ACTION_HELPER_ROLE_RADIO); - gtk_model_menu_item_set_has_indicator (item, role != GTK_ACTION_HELPER_ROLE_NORMAL); + gtk_check_menu_item_set_draw_as_radio (GTK_CHECK_MENU_ITEM (item), role == GTK_MENU_TRACKER_ITEM_ROLE_RADIO); + gtk_model_menu_item_set_has_indicator (item, role != GTK_MENU_TRACKER_ITEM_ROLE_NORMAL); accessible = gtk_widget_get_accessible (GTK_WIDGET (item)); switch (role) { - case GTK_ACTION_HELPER_ROLE_NORMAL: + case GTK_MENU_TRACKER_ITEM_ROLE_NORMAL: a11y_role = ATK_ROLE_MENU_ITEM; break; - case GTK_ACTION_HELPER_ROLE_TOGGLE: + case GTK_MENU_TRACKER_ITEM_ROLE_CHECK: a11y_role = ATK_ROLE_CHECK_MENU_ITEM; break; - case GTK_ACTION_HELPER_ROLE_RADIO: + case GTK_MENU_TRACKER_ITEM_ROLE_RADIO: a11y_role = ATK_ROLE_RADIO_MENU_ITEM; break; @@ -290,6 +124,201 @@ gtk_model_menu_item_set_property (GObject *object, guint prop_id, atk_object_set_role (accessible, a11y_role); } +static void +gtk_model_menu_item_set_icon (GtkModelMenuItem *item, + GIcon *icon) +{ + GtkWidget *child; + + g_return_if_fail (GTK_IS_MODEL_MENU_ITEM (item)); + g_return_if_fail (icon == NULL || G_IS_ICON (icon)); + + child = gtk_bin_get_child (GTK_BIN (item)); + + /* There are only three possibilities here: + * + * - no child + * - accel label child + * - already a box + * + * Handle the no-child case by having GtkMenuItem create the accel + * label, then we will only have two possible cases. + */ + if (child == NULL) + { + gtk_menu_item_get_label (GTK_MENU_ITEM (item)); + child = gtk_bin_get_child (GTK_BIN (item)); + g_assert (GTK_IS_LABEL (child)); + } + + /* If it is a box, make sure there are no images inside of it already. + */ + if (GTK_IS_BOX (child)) + { + GList *children; + + children = gtk_container_get_children (GTK_CONTAINER (child)); + while (children) + { + if (GTK_IS_IMAGE (children->data)) + gtk_widget_destroy (children->data); + + children = g_list_delete_link (children, children); + } + } + + /* If it is not a box, put it into a box, at the end */ + if (!GTK_IS_BOX (child)) + { + GtkWidget *box; + + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + + /* Reparent the child without destroying it */ + g_object_ref (child); + gtk_container_remove (GTK_CONTAINER (item), child); + gtk_box_pack_end (GTK_BOX (box), child, TRUE, TRUE, 0); + g_object_unref (child); + + gtk_container_add (GTK_CONTAINER (item), box); + gtk_widget_show (box); + + /* Now we have a box */ + child = box; + } + + g_assert (GTK_IS_BOX (child)); + + /* child is now a box containing a label and no image. Add the icon, + * if appropriate. + */ + if (icon != NULL) + { + GtkWidget *image; + + image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_MENU); + gtk_box_pack_start (GTK_BOX (child), image, FALSE, FALSE, 0); + gtk_widget_show (image); + } +} + +static void +gtk_model_menu_item_set_text (GtkModelMenuItem *item, + const gchar *text) +{ + GtkWidget *child; + GList *children; + + child = gtk_bin_get_child (GTK_BIN (item)); + if (child == NULL) + { + gtk_menu_item_get_label (GTK_MENU_ITEM (item)); + child = gtk_bin_get_child (GTK_BIN (item)); + g_assert (GTK_IS_LABEL (child)); + } + + if (GTK_IS_LABEL (child)) + { + gtk_label_set_text_with_mnemonic (GTK_LABEL (child), text); + return; + } + + if (!GTK_IS_CONTAINER (child)) + return; + + children = gtk_container_get_children (GTK_CONTAINER (child)); + + while (children) + { + if (GTK_IS_LABEL (children->data)) + gtk_label_set_label (GTK_LABEL (children->data), text); + + children = g_list_delete_link (children, children); + } +} + +static void +gtk_model_menu_item_set_accel (GtkModelMenuItem *item, + const gchar *accel) +{ + GtkWidget *child; + GList *children; + GdkModifierType modifiers; + guint key; + + if (accel) + { + gtk_accelerator_parse (accel, &key, &modifiers); + if (!key) + modifiers = 0; + } + else + { + key = 0; + modifiers = 0; + } + + child = gtk_bin_get_child (GTK_BIN (item)); + if (child == NULL) + { + gtk_menu_item_get_label (GTK_MENU_ITEM (item)); + child = gtk_bin_get_child (GTK_BIN (item)); + g_assert (GTK_IS_LABEL (child)); + } + + if (GTK_IS_LABEL (child)) + { + gtk_accel_label_set_accel (GTK_ACCEL_LABEL (child), key, modifiers); + return; + } + + if (!GTK_IS_CONTAINER (child)) + return; + + children = gtk_container_get_children (GTK_CONTAINER (child)); + + while (children) + { + if (GTK_IS_ACCEL_LABEL (children->data)) + gtk_accel_label_set_accel (children->data, key, modifiers); + + children = g_list_delete_link (children, children); + } +} + +void +gtk_model_menu_item_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + GtkModelMenuItem *item = GTK_MODEL_MENU_ITEM (object); + + switch (prop_id) + { + case PROP_ACTION_ROLE: + gtk_model_menu_item_set_action_role (item, g_value_get_enum (value)); + break; + + case PROP_ICON: + gtk_model_menu_item_set_icon (item, g_value_get_object (value)); + break; + + case PROP_TEXT: + gtk_model_menu_item_set_text (item, g_value_get_string (value)); + break; + + case PROP_TOGGLED: + _gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), g_value_get_boolean (value)); + break; + + case PROP_ACCEL: + gtk_model_menu_item_set_accel (item, g_value_get_string (value)); + break; + + default: + g_assert_not_reached (); + } +} + static void gtk_model_menu_item_init (GtkModelMenuItem *item) { @@ -305,24 +334,31 @@ gtk_model_menu_item_class_init (GtkModelMenuItemClass *class) check_class->draw_indicator = gtk_model_menu_item_draw_indicator; item_class->toggle_size_request = gtk_model_menu_item_toggle_size_request; + item_class->activate = gtk_model_menu_item_activate; object_class->set_property = gtk_model_menu_item_set_property; g_object_class_install_property (object_class, PROP_ACTION_ROLE, - g_param_spec_uint ("action-role", "action role", "action role", - 0, 2, 0, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS)); + g_param_spec_enum ("action-role", "action role", "action role", + GTK_TYPE_MENU_TRACKER_ITEM_ROLE, + GTK_MENU_TRACKER_ITEM_ROLE_NORMAL, + G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, PROP_ICON, + g_param_spec_object ("icon", "icon", "icon", G_TYPE_ICON, + G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, PROP_TEXT, + g_param_spec_string ("text", "text", "text", NULL, + G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, PROP_TOGGLED, + g_param_spec_boolean ("toggled", "toggled", "toggled", FALSE, + G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, PROP_ACCEL, + g_param_spec_string ("accel", "accel", "accel", NULL, + G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS)); } GtkWidget * -gtk_model_menu_item_new (GMenuModel *model, - gint item_index, - const gchar *action_namespace) +gtk_model_menu_item_new (void) { - GtkModelMenuItem *item; - - item = g_object_new (GTK_TYPE_MODEL_MENU_ITEM, NULL); - - gtk_model_menu_item_setup (item, model, item_index, action_namespace); - - return GTK_WIDGET (item); + return g_object_new (GTK_TYPE_MODEL_MENU_ITEM, NULL); } diff --git a/gtk/gtkmodelmenuitem.h b/gtk/gtkmodelmenuitem.h index 3f24163d5b..8dbdbb0e81 100644 --- a/gtk/gtkmodelmenuitem.h +++ b/gtk/gtkmodelmenuitem.h @@ -21,6 +21,7 @@ #define __GTK_MODEL_MENU_ITEM_H__ #include +#include #define GTK_TYPE_MODEL_MENU_ITEM (gtk_model_menu_item_get_type ()) #define GTK_MODEL_MENU_ITEM(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \ @@ -34,8 +35,6 @@ G_GNUC_INTERNAL GType gtk_model_menu_item_get_type (void) G_GNUC_CONST; G_GNUC_INTERNAL -GtkWidget * gtk_model_menu_item_new (GMenuModel *model, - gint item_index, - const gchar *action_namespace); +GtkWidget * gtk_model_menu_item_new (void); #endif /* __GTK_MODEL_MENU_ITEM_H__ */