summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gtk/Makefile.am2
-rw-r--r--gtk/gtkmenushell.c127
-rw-r--r--gtk/gtkmenutracker.c35
-rw-r--r--gtk/gtkmenutracker.h37
-rw-r--r--gtk/gtkmenutrackeritem.c731
-rw-r--r--gtk/gtkmenutrackeritem.h84
-rw-r--r--gtk/gtkmodelmenuitem.c380
-rw-r--r--gtk/gtkmodelmenuitem.h5
8 files changed, 1181 insertions, 220 deletions
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 <gio/gio.h>
+#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 <desrt@desrt.ca>
+ */
+
+#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 (&gtk_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 (&gtk_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, &parameter_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, &parameter_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 <http://www.gnu.org/licenses/>.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#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,
@@ -56,6 +61,12 @@ gtk_model_menu_item_toggle_size_request (GtkMenuItem *menu_item,
}
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)
{
@@ -67,227 +78,245 @@ gtk_model_menu_item_draw_indicator (GtkCheckMenuItem *check_item,
}
static void
-gtk_actionable_set_namespaced_action_name (GtkActionable *actionable,
- const gchar *namespace,
- const gchar *action_name)
+gtk_model_menu_item_set_has_indicator (GtkModelMenuItem *item,
+ gboolean has_indicator)
{
- 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);
- }
-}
+ if (has_indicator == item->has_indicator)
+ return;
-static void
-gtk_model_menu_item_submenu_shown (GtkWidget *widget,
- gpointer user_data)
-{
- const gchar *action_name = user_data;
- GActionMuxer *muxer;
+ item->has_indicator = has_indicator;
- muxer = _gtk_widget_get_action_muxer (widget);
- g_action_group_change_action_state (G_ACTION_GROUP (muxer), action_name, g_variant_new_boolean (TRUE));
+ gtk_widget_queue_resize (GTK_WIDGET (item));
}
static void
-gtk_model_menu_item_submenu_hidden (GtkWidget *widget,
- gpointer user_data)
+gtk_model_menu_item_set_action_role (GtkModelMenuItem *item,
+ GtkMenuTrackerItemRole role)
{
- 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));
-}
+ AtkObject *accessible;
+ AtkRole a11y_role;
-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;
+ if (role == item->role)
+ return;
- label = NULL;
+ 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);
- /* 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)))
+ accessible = gtk_widget_get_accessible (GTK_WIDGET (item));
+ switch (role)
{
- GIcon *icon;
+ case GTK_MENU_TRACKER_ITEM_ROLE_NORMAL:
+ a11y_role = ATK_ROLE_MENU_ITEM;
+ break;
- icon = g_icon_deserialize (value);
+ case GTK_MENU_TRACKER_ITEM_ROLE_CHECK:
+ a11y_role = ATK_ROLE_CHECK_MENU_ITEM;
+ break;
- if (icon != NULL)
- {
- GtkWidget *image;
- GtkWidget *box;
+ case GTK_MENU_TRACKER_ITEM_ROLE_RADIO:
+ a11y_role = ATK_ROLE_RADIO_MENU_ITEM;
+ break;
- box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ default:
+ g_assert_not_reached ();
+ }
- 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);
+ atk_object_set_role (accessible, a11y_role);
+}
- 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);
+static void
+gtk_model_menu_item_set_icon (GtkModelMenuItem *item,
+ GIcon *icon)
+{
+ GtkWidget *child;
- gtk_container_add (GTK_CONTAINER (item), box);
+ g_return_if_fail (GTK_IS_MODEL_MENU_ITEM (item));
+ g_return_if_fail (icon == NULL || G_IS_ICON (icon));
- gtk_widget_show_all (box);
- }
+ child = gtk_bin_get_child (GTK_BIN (item));
- g_variant_unref (value);
+ /* 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 (label == NULL)
+ /* If it is a box, make sure there are no images inside of it already.
+ */
+ if (GTK_IS_BOX (child))
{
- /* 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));
- }
+ GList *children;
+
+ children = gtk_container_get_children (GTK_CONTAINER (child));
+ while (children)
+ {
+ if (GTK_IS_IMAGE (children->data))
+ gtk_widget_destroy (children->data);
- g_assert (label != NULL);
+ children = g_list_delete_link (children, children);
+ }
+ }
- if ((submenu = g_menu_model_get_item_link (model, item_index, "submenu")))
+ /* If it is not a box, put it into a box, at the end */
+ if (!GTK_IS_BOX (child))
{
- gchar *section_namespace = NULL;
- GtkWidget *menu;
+ GtkWidget *box;
- g_menu_model_get_item_attribute (model, item_index, "action-namespace", "s", &section_namespace);
- menu = gtk_menu_new ();
+ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
- 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);
+ /* 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_menu_item_set_submenu (GTK_MENU_ITEM (item), menu);
+ gtk_container_add (GTK_CONTAINER (item), box);
+ gtk_widget_show (box);
- g_free (section_namespace);
- g_object_unref (submenu);
+ /* Now we have a box */
+ child = box;
}
- iter = g_menu_model_iterate_item_attributes (model, item_index);
- while (g_menu_attribute_iter_get_next (iter, &key, &value))
+ 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)
{
- 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));
+ GtkWidget *image;
- else if (g_str_equal (key, "accel") && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
- {
- GdkModifierType modifiers;
- guint key;
+ 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);
+ }
+}
- gtk_accelerator_parse (g_variant_get_string (value, NULL), &key, &modifiers);
+static void
+gtk_model_menu_item_set_text (GtkModelMenuItem *item,
+ const gchar *text)
+{
+ GtkWidget *child;
+ GList *children;
- if (key)
- gtk_accel_label_set_accel (GTK_ACCEL_LABEL (label), key, modifiers);
- }
+ 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));
+ }
- 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));
+ if (GTK_IS_LABEL (child))
+ {
+ gtk_label_set_text_with_mnemonic (GTK_LABEL (child), text);
+ return;
+ }
- else if (g_str_equal (key, "target"))
- gtk_actionable_set_action_target_value (GTK_ACTIONABLE (item), value);
+ if (!GTK_IS_CONTAINER (child))
+ return;
- else if (g_str_equal (key, "submenu-action") && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
- {
- GtkWidget *submenu;
+ children = gtk_container_get_children (GTK_CONTAINER (child));
- submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (item));
+ while (children)
+ {
+ if (GTK_IS_LABEL (children->data))
+ gtk_label_set_label (GTK_LABEL (children->data), text);
- if (submenu != NULL)
- {
- const gchar *action = g_variant_get_string (value, NULL);
- gchar *full_action;
+ children = g_list_delete_link (children, children);
+ }
+}
- if (action_namespace)
- full_action = g_strjoin (".", action_namespace, action, NULL);
- else
- full_action = g_strdup (action);
+static void
+gtk_model_menu_item_set_accel (GtkModelMenuItem *item,
+ const gchar *accel)
+{
+ GtkWidget *child;
+ GList *children;
+ GdkModifierType modifiers;
+ guint key;
- 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);
- }
- }
+ if (accel)
+ {
+ gtk_accelerator_parse (accel, &key, &modifiers);
+ if (!key)
+ modifiers = 0;
+ }
+ else
+ {
+ key = 0;
+ modifiers = 0;
+ }
- g_variant_unref (value);
+ 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));
}
- g_object_unref (iter);
- gtk_menu_item_set_use_underline (GTK_MENU_ITEM (item), TRUE);
-}
+ if (GTK_IS_LABEL (child))
+ {
+ gtk_accel_label_set_accel (GTK_ACCEL_LABEL (child), key, modifiers);
+ return;
+ }
-static void
-gtk_model_menu_item_set_has_indicator (GtkModelMenuItem *item,
- gboolean has_indicator)
-{
- if (has_indicator == item->has_indicator)
+ if (!GTK_IS_CONTAINER (child))
return;
- item->has_indicator = has_indicator;
+ children = gtk_container_get_children (GTK_CONTAINER (child));
- gtk_widget_queue_resize (GTK_WIDGET (item));
+ 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);
+ }
}
-static void
+void
gtk_model_menu_item_set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
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;
+ switch (prop_id)
+ {
+ case PROP_ACTION_ROLE:
+ gtk_model_menu_item_set_action_role (item, g_value_get_enum (value));
+ break;
- 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);
+ case PROP_ICON:
+ gtk_model_menu_item_set_icon (item, g_value_get_object (value));
+ break;
- accessible = gtk_widget_get_accessible (GTK_WIDGET (item));
- switch (role)
- {
- case GTK_ACTION_HELPER_ROLE_NORMAL:
- a11y_role = ATK_ROLE_MENU_ITEM;
+ case PROP_TEXT:
+ gtk_model_menu_item_set_text (item, g_value_get_string (value));
break;
- case GTK_ACTION_HELPER_ROLE_TOGGLE:
- a11y_role = ATK_ROLE_CHECK_MENU_ITEM;
+ case PROP_TOGGLED:
+ _gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), g_value_get_boolean (value));
break;
- case GTK_ACTION_HELPER_ROLE_RADIO:
- a11y_role = ATK_ROLE_RADIO_MENU_ITEM;
+ case PROP_ACCEL:
+ gtk_model_menu_item_set_accel (item, g_value_get_string (value));
break;
default:
g_assert_not_reached ();
}
-
- atk_object_set_role (accessible, a11y_role);
}
static void
@@ -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 <gtk/gtkcheckmenuitem.h>
+#include <gtk/gtkmenutrackeritem.h>
#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__ */