From 612e24dfc6fcd93c0b02db7ccffe51fd31cc247b Mon Sep 17 00:00:00 2001 From: Ryan Lortie Date: Thu, 1 Dec 2011 20:39:11 -0500 Subject: [PATCH] introduce GtkModelMenuItem This GtkMenuItem subclass (and GActionObserver implementation) contains all the knowledge necessary for converting a GMenuModel item description into a GtkMenuItem. Remove much of the code that used to do this from gtkapplicationwindow.c. --- gtk/Makefile.am | 2 + gtk/gtkapplicationwindow.c | 189 +--------------------------- gtk/gtkmodelmenuitem.c | 247 +++++++++++++++++++++++++++++++++++++ gtk/gtkmodelmenuitem.h | 44 +++++++ 4 files changed, 299 insertions(+), 183 deletions(-) create mode 100644 gtk/gtkmodelmenuitem.c create mode 100644 gtk/gtkmodelmenuitem.h diff --git a/gtk/Makefile.am b/gtk/Makefile.am index aad7bc7103..3088bd0384 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -434,6 +434,7 @@ gtk_private_h_sources = \ gtkmenuitemprivate.h \ gtkmenushellprivate.h \ gtkmnemonichash.h \ + gtkmodelmenuitem.h \ gtkmodifierstyle.h \ gtkmodulesprivate.h \ gtkmountoperationprivate.h \ @@ -635,6 +636,7 @@ gtk_base_c_sources = \ gtkmessagedialog.c \ gtkmisc.c \ gtkmnemonichash.c \ + gtkmodelmenuitem.c \ gtkmodifierstyle.c \ gtkmodules.c \ gtkmountoperation.c \ diff --git a/gtk/gtkapplicationwindow.c b/gtk/gtkapplicationwindow.c index 87acbef349..56733edbaf 100644 --- a/gtk/gtkapplicationwindow.c +++ b/gtk/gtkapplicationwindow.c @@ -24,6 +24,7 @@ #include "gtkapplicationwindow.h" #include "gtkseparatormenuitem.h" +#include "gtkmodelmenuitem.h" #include "gtkcheckmenuitem.h" #include "gtkmenubar.h" #include "gactionmuxer.h" @@ -516,184 +517,6 @@ gtk_application_window_set_show_menubar (GtkApplicationWindow *window, /* GtkMenu construction {{{1 */ -typedef struct { - GActionGroup *group; - gchar *name; - gchar *target; - gulong enabled_changed_id; - gulong state_changed_id; - gulong activate_handler; -} ActionData; - -static void -action_data_free (gpointer data) -{ - ActionData *a = data; - - if (a->enabled_changed_id) - g_signal_handler_disconnect (a->group, a->enabled_changed_id); - - if (a->state_changed_id) - g_signal_handler_disconnect (a->group, a->state_changed_id); - - g_object_unref (a->group); - g_free (a->name); - g_free (a->target); - - g_free (a); -} - -static void -enabled_changed (GActionGroup *group, - const gchar *action_name, - gboolean enabled, - GtkWidget *widget) -{ - gtk_widget_set_sensitive (widget, enabled); -} - -static void -item_activated (GtkWidget *w, - gpointer data) -{ - ActionData *a; - GVariant *parameter; - - a = g_object_get_data (G_OBJECT (w), "action"); - if (a->target) - parameter = g_variant_ref_sink (g_variant_new_string (a->target)); - else - parameter = NULL; - g_action_group_activate_action (a->group, a->name, parameter); - if (parameter) - g_variant_unref (parameter); -} - -static void -toggle_state_changed (GActionGroup *group, - const gchar *name, - GVariant *state, - GtkCheckMenuItem *w) -{ - ActionData *a; - - a = g_object_get_data (G_OBJECT (w), "action"); - g_signal_handler_block (w, a->activate_handler); - gtk_check_menu_item_set_active (w, g_variant_get_boolean (state)); - g_signal_handler_unblock (w, a->activate_handler); -} - -static void -radio_state_changed (GActionGroup *group, - const gchar *name, - GVariant *state, - GtkCheckMenuItem *w) -{ - ActionData *a; - gboolean b; - - a = g_object_get_data (G_OBJECT (w), "action"); - g_signal_handler_block (w, a->activate_handler); - b = g_strcmp0 (a->target, g_variant_get_string (state, NULL)) == 0; - gtk_check_menu_item_set_active (w, b); - g_signal_handler_unblock (w, a->activate_handler); -} - -static GtkWidget * -create_menuitem_from_model (GMenuModel *model, - gint item, - GActionGroup *group) -{ - GtkWidget *w; - gchar *label; - gchar *action; - gchar *target; - gchar *s; - ActionData *a; - const GVariantType *type; - GVariant *v; - - label = NULL; - g_menu_model_get_item_attribute (model, item, G_MENU_ATTRIBUTE_LABEL, "s", &label); - - action = NULL; - g_menu_model_get_item_attribute (model, item, G_MENU_ATTRIBUTE_ACTION, "s", &action); - - if (action != NULL) - type = g_action_group_get_action_state_type (group, action); - else - type = NULL; - - if (type == NULL) - w = gtk_menu_item_new_with_label (label); - else if (g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN)) - w = gtk_check_menu_item_new_with_label (label); - else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING)) - { - w = gtk_check_menu_item_new_with_label (label); - gtk_check_menu_item_set_draw_as_radio (GTK_CHECK_MENU_ITEM (w), TRUE); - } - else - g_assert_not_reached (); - - gtk_menu_item_set_use_underline (GTK_MENU_ITEM (w), TRUE); - - if (action != NULL) - { - a = g_new0 (ActionData, 1); - a->group = g_object_ref (group); - a->name = g_strdup (action); - g_object_set_data_full (G_OBJECT (w), "action", a, action_data_free); - - if (!g_action_group_get_action_enabled (group, action)) - gtk_widget_set_sensitive (w, FALSE); - - s = g_strconcat ("action-enabled-changed::", action, NULL); - a->enabled_changed_id = g_signal_connect (group, s, - G_CALLBACK (enabled_changed), w); - g_free (s); - a->activate_handler = g_signal_connect (w, "activate", - G_CALLBACK (item_activated), NULL); - - if (type == NULL) - { - /* all set */ - } - else if (g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN)) - { - s = g_strconcat ("action-state-changed::", action, NULL); - a->state_changed_id = g_signal_connect (group, s, - G_CALLBACK (toggle_state_changed), w); - g_free (s); - v = g_action_group_get_action_state (group, action); - gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (w), - g_variant_get_boolean (v)); - g_variant_unref (v); - } - else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING)) - { - s = g_strconcat ("action-state-changed::", action, NULL); - a->state_changed_id = g_signal_connect (group, s, - G_CALLBACK (radio_state_changed), w); - g_free (s); - g_menu_model_get_item_attribute (model, item, G_MENU_ATTRIBUTE_TARGET, "s", &target); - a->target = g_strdup (target); - v = g_action_group_get_action_state (group, action); - gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (w), - g_strcmp0 (g_variant_get_string (v, NULL), target) == 0); - g_variant_unref (v); - g_free (target); - } - else - g_assert_not_reached (); - } - - g_free (label); - g_free (action); - - return w; -} - static void populate_menu_from_model (GtkMenuShell *menu, GMenuModel *model, GActionGroup *group); @@ -708,7 +531,7 @@ append_items_from_model (GtkMenuShell *menu, gint n; gint i; GtkWidget *w; - GtkWidget *menuitem; + GtkMenuItem *menuitem; GtkWidget *submenu; GMenuModel *m; gchar *label; @@ -752,18 +575,18 @@ append_items_from_model (GtkMenuShell *menu, continue; } - menuitem = create_menuitem_from_model (model, i, group); + menuitem = gtk_model_menu_item_new (model, i, G_ACTION_OBSERVABLE (group)); if ((m = g_menu_model_get_item_link (model, i, G_MENU_LINK_SUBMENU))) { submenu = gtk_menu_new (); populate_menu_from_model (GTK_MENU_SHELL (submenu), m, group); - gtk_menu_item_set_submenu (GTK_MENU_ITEM (menuitem), submenu); + gtk_menu_item_set_submenu (menuitem, submenu); g_object_unref (m); } - gtk_widget_show (menuitem); - gtk_menu_shell_append (menu, menuitem); + gtk_widget_show (GTK_WIDGET (menuitem)); + gtk_menu_shell_append (menu, GTK_WIDGET (menuitem)); *need_separator = TRUE; } diff --git a/gtk/gtkmodelmenuitem.c b/gtk/gtkmodelmenuitem.c new file mode 100644 index 0000000000..c76c9db945 --- /dev/null +++ b/gtk/gtkmodelmenuitem.c @@ -0,0 +1,247 @@ +/* + * Copyright © 2011 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 "gtkmodelmenuitem.h" + +struct _GtkModelMenuItem +{ + GtkCheckMenuItem parent_instance; + + GActionGroup *actions; + const gchar *action_name; + gboolean can_activate; + GVariant *target; +}; + +typedef GtkCheckMenuItemClass GtkModelMenuItemClass; + +static void gtk_model_menu_item_observer_iface_init (GActionObserverInterface *iface); +G_DEFINE_TYPE_WITH_CODE (GtkModelMenuItem, gtk_model_menu_item, GTK_TYPE_CHECK_MENU_ITEM, + G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_OBSERVER, gtk_model_menu_item_observer_iface_init)) + +static void +gtk_model_menu_item_activate (GtkMenuItem *menu_item) +{ + GtkModelMenuItem *item = GTK_MODEL_MENU_ITEM (menu_item); + + if (item->can_activate) + g_action_group_activate_action (item->actions, item->action_name, item->target); +} + +static void +gtk_model_menu_item_set_active (GtkModelMenuItem *item, + gboolean active) +{ + GtkCheckMenuItem *checkitem = GTK_CHECK_MENU_ITEM (item); + + if (gtk_check_menu_item_get_active (checkitem) != active) + { + _gtk_check_menu_item_set_active (checkitem, active); + g_object_notify (G_OBJECT (checkitem), "active"); + gtk_check_menu_item_toggled (checkitem); + gtk_widget_queue_draw (GTK_WIDGET (item)); + } +} + +static void +gtk_model_menu_item_action_added (GActionObserver *observer, + GActionObservable *observable, + const gchar *action_name, + const GVariantType *parameter_type, + gboolean enabled, + GVariant *state) +{ + GtkModelMenuItem *item = GTK_MODEL_MENU_ITEM (observer); + + /* we can only activate the item if we have the correct type of parameter */ + item->can_activate = (item->target == NULL && parameter_type == NULL) || + (item->target != NULL && parameter_type != NULL && + g_variant_is_of_type (item->target, parameter_type)); + + if (item->can_activate) + { + if (item->target != NULL && state != NULL) + { + /* actions with states and targets are radios */ + gboolean selected; + + selected = g_variant_equal (state, item->target); + gtk_check_menu_item_set_draw_as_radio (GTK_CHECK_MENU_ITEM (item), TRUE); + gtk_model_menu_item_set_active (item, selected); + } + + else if (state != NULL && g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN)) + { + /* boolean state actions without target are checks */ + gtk_check_menu_item_set_draw_as_radio (GTK_CHECK_MENU_ITEM (item), FALSE); + gtk_model_menu_item_set_active (item, g_variant_get_boolean (state)); + } + + else + { + /* stateless items are just plain actions */ + gtk_model_menu_item_set_active (item, FALSE); + } + + gtk_widget_set_sensitive (GTK_WIDGET (item), enabled); + } +} + +static void +gtk_model_menu_item_action_enabled_changed (GActionObserver *observer, + GActionObservable *observable, + const gchar *action_name, + gboolean enabled) +{ + GtkModelMenuItem *item = GTK_MODEL_MENU_ITEM (observer); + + if (!item->can_activate) + return; + + gtk_widget_set_sensitive (GTK_WIDGET (item), item->can_activate && enabled); +} + +static void +gtk_model_menu_item_action_state_changed (GActionObserver *observer, + GActionObservable *observable, + const gchar *action_name, + GVariant *state) +{ + GtkModelMenuItem *item = GTK_MODEL_MENU_ITEM (observer); + + if (!item->can_activate) + return; + + if (item->target) + gtk_model_menu_item_set_active (item, g_variant_equal (state, item->target)); + + else if (g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN)) + gtk_model_menu_item_set_active (item, g_variant_get_boolean (state)); +} + +static void +gtk_model_menu_item_action_removed (GActionObserver *observer, + GActionObservable *observable, + const gchar *action_name) +{ + GtkModelMenuItem *item = GTK_MODEL_MENU_ITEM (observer); + + if (!item->can_activate) + return; + + gtk_widget_set_sensitive (GTK_WIDGET (item), FALSE); + gtk_model_menu_item_set_active (item, FALSE); +} + +static void +gtk_model_menu_item_setup (GtkModelMenuItem *item, + GMenuModel *model, + gint item_index, + GActionObservable *actions) +{ + GMenuAttributeIter *iter; + const gchar *key; + GVariant *value; + + 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_menu_item_set_label (GTK_MENU_ITEM (item), g_variant_get_string (value, NULL)); + + else if (g_str_equal (key, "action") && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) + item->action_name = g_variant_get_string (value, NULL); + + else if (g_str_equal (key, "target")) + item->target = g_variant_ref (value); + + g_variant_unref (value); + } + g_object_unref (iter); + + gtk_menu_item_set_use_underline (GTK_MENU_ITEM (item), TRUE); + + if (item->action_name) + { + const GVariantType *type; + gboolean enabled; + GVariant *state; + + /* observer already causes us to hold a hard ref on the group */ + item->actions = G_ACTION_GROUP (actions); + + g_action_observable_register_observer (actions, item->action_name, G_ACTION_OBSERVER (item)); + + if (g_action_group_query_action (G_ACTION_GROUP (actions), item->action_name, &enabled, &type, NULL, NULL, &state)) + gtk_model_menu_item_action_added (G_ACTION_OBSERVER (item), actions, item->action_name, type, enabled, state); + + else + gtk_widget_set_sensitive (GTK_WIDGET (item), FALSE); + } +} + +static void +gtk_model_menu_item_finalize (GObject *object) +{ + G_OBJECT_CLASS (gtk_model_menu_item_parent_class) + ->finalize (object); +} + +static void +gtk_model_menu_item_init (GtkModelMenuItem *item) +{ +} + +static void +gtk_model_menu_item_observer_iface_init (GActionObserverInterface *iface) +{ + iface->action_added = gtk_model_menu_item_action_added; + iface->action_enabled_changed = gtk_model_menu_item_action_enabled_changed; + iface->action_state_changed = gtk_model_menu_item_action_state_changed; + iface->action_removed = gtk_model_menu_item_action_removed; +} + +static void +gtk_model_menu_item_class_init (GtkModelMenuItemClass *class) +{ + GtkMenuItemClass *item_class = GTK_MENU_ITEM_CLASS (class); + GObjectClass *object_class = G_OBJECT_CLASS (class); + + item_class->activate = gtk_model_menu_item_activate; + + object_class->finalize = gtk_model_menu_item_finalize; +} + +GtkMenuItem * +gtk_model_menu_item_new (GMenuModel *model, + gint item_index, + GActionObservable *actions) +{ + GtkModelMenuItem *item; + + item = g_object_new (GTK_TYPE_MODEL_MENU_ITEM, NULL); + + gtk_model_menu_item_setup (item, model, item_index, actions); + + return GTK_MENU_ITEM (item); +} diff --git a/gtk/gtkmodelmenuitem.h b/gtk/gtkmodelmenuitem.h new file mode 100644 index 0000000000..4146bf9ee1 --- /dev/null +++ b/gtk/gtkmodelmenuitem.h @@ -0,0 +1,44 @@ +/* + * Copyright © 2011 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 + */ + +#ifndef __GTK_MODEL_MENU_ITEM_H__ +#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), \ + GTK_TYPE_MODEL_MENU_ITEM, GtkModelMenuItem)) +#define GTK_IS_MODEL_MENU_ITEM(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), \ + GTK_TYPE_MODEL_MENU_ITEM)) + +typedef struct _GtkModelMenuItem GtkModelMenuItem; + +G_GNUC_INTERNAL +GType gtk_model_menu_item_get_type (void) G_GNUC_CONST; + +G_GNUC_INTERNAL +GtkMenuItem * gtk_model_menu_item_new (GMenuModel *model, + gint item_index, + GActionObservable *actions); + +#endif /* __GTK_MODEL_MENU_ITEM_H__ */