From 4fc790c377f4ca154ea5fc8beca6fd9078edf2ee Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Tue, 4 Jun 2013 15:35:59 -0400 Subject: Introduce UnityMenuModel and UnityQmlMenuModel UnityMenuModel serves the same purpose as QMenuModel, but it is based on GtkMenuTracker and has a different API. GtkMenuTracker is maintained in gtk, but meant to be copy-and-pasted into other consumers of menu models. It does not introduce new dependencies. It does give us access to all the features that GMenuModel has. --- libqmenumodel/QMenuModel/plugin.cpp | 4 +- libqmenumodel/src/CMakeLists.txt | 14 + libqmenumodel/src/gtk/config.h | 0 libqmenumodel/src/gtk/gtkactionmuxer.c | 778 +++++++++++++++++++++++++++ libqmenumodel/src/gtk/gtkactionmuxer.h | 52 ++ libqmenumodel/src/gtk/gtkactionobservable.c | 78 +++ libqmenumodel/src/gtk/gtkactionobservable.h | 60 +++ libqmenumodel/src/gtk/gtkactionobserver.c | 159 ++++++ libqmenumodel/src/gtk/gtkactionobserver.h | 83 +++ libqmenumodel/src/gtk/gtkmenutracker.c | 495 +++++++++++++++++ libqmenumodel/src/gtk/gtkmenutracker.h | 52 ++ libqmenumodel/src/gtk/gtkmenutrackeritem.c | 788 ++++++++++++++++++++++++++++ libqmenumodel/src/gtk/gtkmenutrackeritem.h | 84 +++ libqmenumodel/src/unitymenumodel.cpp | 227 ++++++++ libqmenumodel/src/unitymenumodel.h | 56 ++ libqmenumodel/src/unityqmlmenumodel.cpp | 102 ++++ libqmenumodel/src/unityqmlmenumodel.h | 59 +++ 17 files changed, 3089 insertions(+), 2 deletions(-) create mode 100644 libqmenumodel/src/gtk/config.h create mode 100644 libqmenumodel/src/gtk/gtkactionmuxer.c create mode 100644 libqmenumodel/src/gtk/gtkactionmuxer.h create mode 100644 libqmenumodel/src/gtk/gtkactionobservable.c create mode 100644 libqmenumodel/src/gtk/gtkactionobservable.h create mode 100644 libqmenumodel/src/gtk/gtkactionobserver.c create mode 100644 libqmenumodel/src/gtk/gtkactionobserver.h create mode 100644 libqmenumodel/src/gtk/gtkmenutracker.c create mode 100644 libqmenumodel/src/gtk/gtkmenutracker.h create mode 100644 libqmenumodel/src/gtk/gtkmenutrackeritem.c create mode 100644 libqmenumodel/src/gtk/gtkmenutrackeritem.h create mode 100644 libqmenumodel/src/unitymenumodel.cpp create mode 100644 libqmenumodel/src/unitymenumodel.h create mode 100644 libqmenumodel/src/unityqmlmenumodel.cpp create mode 100644 libqmenumodel/src/unityqmlmenumodel.h diff --git a/libqmenumodel/QMenuModel/plugin.cpp b/libqmenumodel/QMenuModel/plugin.cpp index ee05fff..0205102 100644 --- a/libqmenumodel/QMenuModel/plugin.cpp +++ b/libqmenumodel/QMenuModel/plugin.cpp @@ -22,6 +22,7 @@ #include "qdbusmenumodel.h" #include "qdbusactiongroup.h" #include "qstateaction.h" +#include "unityqmlmenumodel.h" #include @@ -36,6 +37,5 @@ void QMenuModelQmlPlugin::registerTypes(const char *uri) qmlRegisterType(uri, 0, 1, "QDBusMenuModel"); qmlRegisterType(uri, 0, 1, "QDBusActionGroup"); - + qmlRegisterType(uri, 0, 1, "UnityMenuModel"); } - diff --git a/libqmenumodel/src/CMakeLists.txt b/libqmenumodel/src/CMakeLists.txt index 1fb89df..cd81371 100644 --- a/libqmenumodel/src/CMakeLists.txt +++ b/libqmenumodel/src/CMakeLists.txt @@ -9,6 +9,19 @@ set(QMENUMODEL_SRC qdbusmenumodel.cpp qdbusactiongroup.cpp qstateaction.cpp + unitymenumodel.cpp + unitymenumodel.h + unityqmlmenumodel.cpp + gtk/gtkactionmuxer.c + gtk/gtkactionmuxer.h + gtk/gtkactionobservable.c + gtk/gtkactionobservable.h + gtk/gtkactionobserver.c + gtk/gtkactionobserver.h + gtk/gtkmenutracker.c + gtk/gtkmenutracker.h + gtk/gtkmenutrackeritem.c + gtk/gtkmenutrackeritem.h ) set(SHAREDLIBNAME qmenumodel) @@ -43,6 +56,7 @@ set(QMENUMODEL_HEADERS qdbusobject.h qmenumodel.h qstateaction.h + unitymenumodel.h ) set(INCLUDEDIR qmenumodel) diff --git a/libqmenumodel/src/gtk/config.h b/libqmenumodel/src/gtk/config.h new file mode 100644 index 0000000..e69de29 diff --git a/libqmenumodel/src/gtk/gtkactionmuxer.c b/libqmenumodel/src/gtk/gtkactionmuxer.c new file mode 100644 index 0000000..4618564 --- /dev/null +++ b/libqmenumodel/src/gtk/gtkactionmuxer.c @@ -0,0 +1,778 @@ +/* + * 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, see . + * + * Author: Ryan Lortie + */ + +#include "config.h" + +#include "gtkactionmuxer.h" + +#include "gtkactionobservable.h" +#include "gtkactionobserver.h" + +#include + +/** + * SECTION:gtkactionmuxer + * @short_description: Aggregate and monitor several action groups + * + * #GtkActionMuxer is a #GActionGroup and #GtkActionObservable that is + * capable of containing other #GActionGroup instances. + * + * The typical use is aggregating all of the actions applicable to a + * particular context into a single action group, with namespacing. + * + * Consider the case of two action groups -- one containing actions + * applicable to an entire application (such as 'quit') and one + * containing actions applicable to a particular window in the + * application (such as 'fullscreen'). + * + * In this case, each of these action groups could be added to a + * #GtkActionMuxer with the prefixes "app" and "win", respectively. This + * would expose the actions as "app.quit" and "win.fullscreen" on the + * #GActionGroup interface presented by the #GtkActionMuxer. + * + * Activations and state change requests on the #GtkActionMuxer are wired + * through to the underlying action group in the expected way. + * + * This class is typically only used at the site of "consumption" of + * actions (eg: when displaying a menu that contains many actions on + * different objects). + */ + +static void gtk_action_muxer_group_iface_init (GActionGroupInterface *iface); +static void gtk_action_muxer_observable_iface_init (GtkActionObservableInterface *iface); + +typedef GObjectClass GtkActionMuxerClass; + +struct _GtkActionMuxer +{ + GObject parent_instance; + + GHashTable *observed_actions; + GHashTable *groups; + GtkActionMuxer *parent; +}; + +G_DEFINE_TYPE_WITH_CODE (GtkActionMuxer, gtk_action_muxer, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP, gtk_action_muxer_group_iface_init) + G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTION_OBSERVABLE, gtk_action_muxer_observable_iface_init)) + +enum +{ + PROP_0, + PROP_PARENT, + NUM_PROPERTIES +}; + +static GParamSpec *properties[NUM_PROPERTIES]; + +typedef struct +{ + GtkActionMuxer *muxer; + GSList *watchers; + gchar *fullname; +} Action; + +typedef struct +{ + GtkActionMuxer *muxer; + GActionGroup *group; + gchar *prefix; + gulong handler_ids[4]; +} Group; + +static void +gtk_action_muxer_append_group_actions (gpointer key, + gpointer value, + gpointer user_data) +{ + const gchar *prefix = key; + Group *group = value; + GArray *actions = user_data; + gchar **group_actions; + gchar **action; + + group_actions = g_action_group_list_actions (group->group); + for (action = group_actions; *action; action++) + { + gchar *fullname; + + fullname = g_strconcat (prefix, ".", *action, NULL); + g_array_append_val (actions, fullname); + } + + g_strfreev (group_actions); +} + +static gchar ** +gtk_action_muxer_list_actions (GActionGroup *action_group) +{ + GtkActionMuxer *muxer = GTK_ACTION_MUXER (action_group); + GArray *actions; + + actions = g_array_new (TRUE, FALSE, sizeof (gchar *)); + + for ( ; muxer != NULL; muxer = muxer->parent) + { + g_hash_table_foreach (muxer->groups, + gtk_action_muxer_append_group_actions, + actions); + } + + return (gchar **) g_array_free (actions, FALSE); +} + +static Group * +gtk_action_muxer_find_group (GtkActionMuxer *muxer, + const gchar *full_name, + const gchar **action_name) +{ + const gchar *dot; + gchar *prefix; + Group *group; + + dot = strchr (full_name, '.'); + + if (!dot) + return NULL; + + prefix = g_strndup (full_name, dot - full_name); + group = g_hash_table_lookup (muxer->groups, prefix); + g_free (prefix); + + if (action_name) + *action_name = dot + 1; + + return group; +} + +static void +gtk_action_muxer_action_enabled_changed (GtkActionMuxer *muxer, + const gchar *action_name, + gboolean enabled) +{ + Action *action; + GSList *node; + + action = g_hash_table_lookup (muxer->observed_actions, action_name); + for (node = action ? action->watchers : NULL; node; node = node->next) + gtk_action_observer_action_enabled_changed (node->data, GTK_ACTION_OBSERVABLE (muxer), action_name, enabled); + g_action_group_action_enabled_changed (G_ACTION_GROUP (muxer), action_name, enabled); +} + +static void +gtk_action_muxer_group_action_enabled_changed (GActionGroup *action_group, + const gchar *action_name, + gboolean enabled, + gpointer user_data) +{ + Group *group = user_data; + gchar *fullname; + + fullname = g_strconcat (group->prefix, ".", action_name, NULL); + gtk_action_muxer_action_enabled_changed (group->muxer, fullname, enabled); + + g_free (fullname); +} + +static void +gtk_action_muxer_parent_action_enabled_changed (GActionGroup *action_group, + const gchar *action_name, + gboolean enabled, + gpointer user_data) +{ + GtkActionMuxer *muxer = user_data; + + gtk_action_muxer_action_enabled_changed (muxer, action_name, enabled); +} + +static void +gtk_action_muxer_action_state_changed (GtkActionMuxer *muxer, + const gchar *action_name, + GVariant *state) +{ + Action *action; + GSList *node; + + action = g_hash_table_lookup (muxer->observed_actions, action_name); + for (node = action ? action->watchers : NULL; node; node = node->next) + gtk_action_observer_action_state_changed (node->data, GTK_ACTION_OBSERVABLE (muxer), action_name, state); + g_action_group_action_state_changed (G_ACTION_GROUP (muxer), action_name, state); +} + +static void +gtk_action_muxer_group_action_state_changed (GActionGroup *action_group, + const gchar *action_name, + GVariant *state, + gpointer user_data) +{ + Group *group = user_data; + gchar *fullname; + + fullname = g_strconcat (group->prefix, ".", action_name, NULL); + gtk_action_muxer_action_state_changed (group->muxer, fullname, state); + + g_free (fullname); +} + +static void +gtk_action_muxer_parent_action_state_changed (GActionGroup *action_group, + const gchar *action_name, + GVariant *state, + gpointer user_data) +{ + GtkActionMuxer *muxer = user_data; + + gtk_action_muxer_action_state_changed (muxer, action_name, state); +} + +static void +gtk_action_muxer_action_added (GtkActionMuxer *muxer, + const gchar *action_name, + GActionGroup *original_group, + const gchar *orignal_action_name) +{ + const GVariantType *parameter_type; + gboolean enabled; + GVariant *state; + Action *action; + + action = g_hash_table_lookup (muxer->observed_actions, action_name); + + if (action && action->watchers && + g_action_group_query_action (original_group, orignal_action_name, + &enabled, ¶meter_type, NULL, NULL, &state)) + { + GSList *node; + + for (node = action->watchers; node; node = node->next) + gtk_action_observer_action_added (node->data, + GTK_ACTION_OBSERVABLE (muxer), + action_name, parameter_type, enabled, state); + + if (state) + g_variant_unref (state); + } + + g_action_group_action_added (G_ACTION_GROUP (muxer), action_name); +} + +static void +gtk_action_muxer_action_added_to_group (GActionGroup *action_group, + const gchar *action_name, + gpointer user_data) +{ + Group *group = user_data; + gchar *fullname; + + fullname = g_strconcat (group->prefix, ".", action_name, NULL); + gtk_action_muxer_action_added (group->muxer, fullname, action_group, action_name); + + g_free (fullname); +} + +static void +gtk_action_muxer_action_added_to_parent (GActionGroup *action_group, + const gchar *action_name, + gpointer user_data) +{ + GtkActionMuxer *muxer = user_data; + + gtk_action_muxer_action_added (muxer, action_name, action_group, action_name); +} + +static void +gtk_action_muxer_action_removed (GtkActionMuxer *muxer, + const gchar *action_name) +{ + Action *action; + GSList *node; + + action = g_hash_table_lookup (muxer->observed_actions, action_name); + for (node = action ? action->watchers : NULL; node; node = node->next) + gtk_action_observer_action_removed (node->data, GTK_ACTION_OBSERVABLE (muxer), action_name); + g_action_group_action_removed (G_ACTION_GROUP (muxer), action_name); +} + +static void +gtk_action_muxer_action_removed_from_group (GActionGroup *action_group, + const gchar *action_name, + gpointer user_data) +{ + Group *group = user_data; + gchar *fullname; + + fullname = g_strconcat (group->prefix, ".", action_name, NULL); + gtk_action_muxer_action_removed (group->muxer, fullname); + + g_free (fullname); +} + +static void +gtk_action_muxer_action_removed_from_parent (GActionGroup *action_group, + const gchar *action_name, + gpointer user_data) +{ + GtkActionMuxer *muxer = user_data; + + gtk_action_muxer_action_removed (muxer, action_name); +} + +static gboolean +gtk_action_muxer_query_action (GActionGroup *action_group, + const gchar *action_name, + gboolean *enabled, + const GVariantType **parameter_type, + const GVariantType **state_type, + GVariant **state_hint, + GVariant **state) +{ + GtkActionMuxer *muxer = GTK_ACTION_MUXER (action_group); + Group *group; + const gchar *unprefixed_name; + + group = gtk_action_muxer_find_group (muxer, action_name, &unprefixed_name); + + if (group) + return g_action_group_query_action (group->group, unprefixed_name, enabled, + parameter_type, state_type, state_hint, state); + + if (muxer->parent) + return g_action_group_query_action (G_ACTION_GROUP (muxer->parent), action_name, + enabled, parameter_type, + state_type, state_hint, state); + + return FALSE; +} + +static void +gtk_action_muxer_activate_action (GActionGroup *action_group, + const gchar *action_name, + GVariant *parameter) +{ + GtkActionMuxer *muxer = GTK_ACTION_MUXER (action_group); + Group *group; + const gchar *unprefixed_name; + + group = gtk_action_muxer_find_group (muxer, action_name, &unprefixed_name); + + if (group) + g_action_group_activate_action (group->group, unprefixed_name, parameter); + else if (muxer->parent) + g_action_group_activate_action (G_ACTION_GROUP (muxer->parent), action_name, parameter); +} + +static void +gtk_action_muxer_change_action_state (GActionGroup *action_group, + const gchar *action_name, + GVariant *state) +{ + GtkActionMuxer *muxer = GTK_ACTION_MUXER (action_group); + Group *group; + const gchar *unprefixed_name; + + group = gtk_action_muxer_find_group (muxer, action_name, &unprefixed_name); + + if (group) + g_action_group_change_action_state (group->group, unprefixed_name, state); + else if (muxer->parent) + g_action_group_change_action_state (G_ACTION_GROUP (muxer->parent), action_name, state); +} + +static void +gtk_action_muxer_unregister_internal (Action *action, + gpointer observer) +{ + GtkActionMuxer *muxer = action->muxer; + GSList **ptr; + + for (ptr = &action->watchers; *ptr; ptr = &(*ptr)->next) + if ((*ptr)->data == observer) + { + *ptr = g_slist_remove (*ptr, observer); + + if (action->watchers == NULL) + g_hash_table_remove (muxer->observed_actions, action->fullname); + + break; + } +} + +static void +gtk_action_muxer_weak_notify (gpointer data, + GObject *where_the_object_was) +{ + Action *action = data; + + gtk_action_muxer_unregister_internal (action, where_the_object_was); +} + +static void +gtk_action_muxer_register_observer (GtkActionObservable *observable, + const gchar *name, + GtkActionObserver *observer) +{ + GtkActionMuxer *muxer = GTK_ACTION_MUXER (observable); + Action *action; + + action = g_hash_table_lookup (muxer->observed_actions, name); + + if (action == NULL) + { + action = g_slice_new (Action); + action->muxer = muxer; + action->fullname = g_strdup (name); + action->watchers = NULL; + + g_hash_table_insert (muxer->observed_actions, action->fullname, action); + } + + action->watchers = g_slist_prepend (action->watchers, observer); + g_object_weak_ref (G_OBJECT (observer), gtk_action_muxer_weak_notify, action); +} + +static void +gtk_action_muxer_unregister_observer (GtkActionObservable *observable, + const gchar *name, + GtkActionObserver *observer) +{ + GtkActionMuxer *muxer = GTK_ACTION_MUXER (observable); + Action *action; + + action = g_hash_table_lookup (muxer->observed_actions, name); + g_object_weak_unref (G_OBJECT (observer), gtk_action_muxer_weak_notify, action); + gtk_action_muxer_unregister_internal (action, observer); +} + +static void +gtk_action_muxer_free_group (gpointer data) +{ + Group *group = data; + gint i; + + /* 'for loop' or 'four loop'? */ + for (i = 0; i < 4; i++) + g_signal_handler_disconnect (group->group, group->handler_ids[i]); + + g_object_unref (group->group); + g_free (group->prefix); + + g_slice_free (Group, group); +} + +static void +gtk_action_muxer_free_action (gpointer data) +{ + Action *action = data; + GSList *it; + + for (it = action->watchers; it; it = it->next) + g_object_weak_unref (G_OBJECT (it->data), gtk_action_muxer_weak_notify, action); + + g_slist_free (action->watchers); + g_free (action->fullname); + + g_slice_free (Action, action); +} + +static void +gtk_action_muxer_finalize (GObject *object) +{ + GtkActionMuxer *muxer = GTK_ACTION_MUXER (object); + + g_assert_cmpint (g_hash_table_size (muxer->observed_actions), ==, 0); + g_hash_table_unref (muxer->observed_actions); + g_hash_table_unref (muxer->groups); + + G_OBJECT_CLASS (gtk_action_muxer_parent_class) + ->finalize (object); +} + +static void +gtk_action_muxer_dispose (GObject *object) +{ + GtkActionMuxer *muxer = GTK_ACTION_MUXER (object); + + if (muxer->parent) + { + g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_action_added_to_parent, muxer); + g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_action_removed_from_parent, muxer); + g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_parent_action_enabled_changed, muxer); + g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_parent_action_state_changed, muxer); + + g_clear_object (&muxer->parent); + } + + g_hash_table_remove_all (muxer->observed_actions); + + G_OBJECT_CLASS (gtk_action_muxer_parent_class) + ->dispose (object); +} + +static void +gtk_action_muxer_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GtkActionMuxer *muxer = GTK_ACTION_MUXER (object); + + switch (property_id) + { + case PROP_PARENT: + g_value_set_object (value, gtk_action_muxer_get_parent (muxer)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +gtk_action_muxer_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkActionMuxer *muxer = GTK_ACTION_MUXER (object); + + switch (property_id) + { + case PROP_PARENT: + gtk_action_muxer_set_parent (muxer, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +gtk_action_muxer_init (GtkActionMuxer *muxer) +{ + muxer->observed_actions = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, gtk_action_muxer_free_action); + muxer->groups = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, gtk_action_muxer_free_group); +} + +static void +gtk_action_muxer_observable_iface_init (GtkActionObservableInterface *iface) +{ + iface->register_observer = gtk_action_muxer_register_observer; + iface->unregister_observer = gtk_action_muxer_unregister_observer; +} + +static void +gtk_action_muxer_group_iface_init (GActionGroupInterface *iface) +{ + iface->list_actions = gtk_action_muxer_list_actions; + iface->query_action = gtk_action_muxer_query_action; + iface->activate_action = gtk_action_muxer_activate_action; + iface->change_action_state = gtk_action_muxer_change_action_state; +} + +static void +gtk_action_muxer_class_init (GObjectClass *class) +{ + class->get_property = gtk_action_muxer_get_property; + class->set_property = gtk_action_muxer_set_property; + class->finalize = gtk_action_muxer_finalize; + class->dispose = gtk_action_muxer_dispose; + + properties[PROP_PARENT] = g_param_spec_object ("parent", "Parent", + "The parent muxer", + GTK_TYPE_ACTION_MUXER, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (class, NUM_PROPERTIES, properties); +} + +/** + * gtk_action_muxer_insert: + * @muxer: a #GtkActionMuxer + * @prefix: the prefix string for the action group + * @action_group: a #GActionGroup + * + * Adds the actions in @action_group to the list of actions provided by + * @muxer. @prefix is prefixed to each action name, such that for each + * action x in @action_group, there is an equivalent + * action @prefix.x in @muxer. + * + * For example, if @prefix is "app" and @action_group + * contains an action called "quit", then @muxer will + * now contain an action called "app.quit". + * + * If any #GtkActionObservers are registered for actions in the group, + * "action_added" notifications will be emitted, as appropriate. + * + * @prefix must not contain a dot ('.'). + */ +void +gtk_action_muxer_insert (GtkActionMuxer *muxer, + const gchar *prefix, + GActionGroup *action_group) +{ + gchar **actions; + Group *group; + gint i; + + /* TODO: diff instead of ripout and replace */ + gtk_action_muxer_remove (muxer, prefix); + + group = g_slice_new (Group); + group->muxer = muxer; + group->group = g_object_ref (action_group); + group->prefix = g_strdup (prefix); + + g_hash_table_insert (muxer->groups, group->prefix, group); + + actions = g_action_group_list_actions (group->group); + for (i = 0; actions[i]; i++) + gtk_action_muxer_action_added_to_group (group->group, actions[i], group); + g_strfreev (actions); + + group->handler_ids[0] = g_signal_connect (group->group, "action-added", + G_CALLBACK (gtk_action_muxer_action_added_to_group), group); + group->handler_ids[1] = g_signal_connect (group->group, "action-removed", + G_CALLBACK (gtk_action_muxer_action_removed_from_group), group); + group->handler_ids[2] = g_signal_connect (group->group, "action-enabled-changed", + G_CALLBACK (gtk_action_muxer_group_action_enabled_changed), group); + group->handler_ids[3] = g_signal_connect (group->group, "action-state-changed", + G_CALLBACK (gtk_action_muxer_group_action_state_changed), group); +} + +/** + * gtk_action_muxer_remove: + * @muxer: a #GtkActionMuxer + * @prefix: the prefix of the action group to remove + * + * Removes a #GActionGroup from the #GtkActionMuxer. + * + * If any #GtkActionObservers are registered for actions in the group, + * "action_removed" notifications will be emitted, as appropriate. + */ +void +gtk_action_muxer_remove (GtkActionMuxer *muxer, + const gchar *prefix) +{ + Group *group; + + group = g_hash_table_lookup (muxer->groups, prefix); + + if (group != NULL) + { + gchar **actions; + gint i; + + g_hash_table_steal (muxer->groups, prefix); + + actions = g_action_group_list_actions (group->group); + for (i = 0; actions[i]; i++) + gtk_action_muxer_action_removed_from_group (group->group, actions[i], group); + g_strfreev (actions); + + gtk_action_muxer_free_group (group); + } +} + +/** + * gtk_action_muxer_new: + * + * Creates a new #GtkActionMuxer. + */ +GtkActionMuxer * +gtk_action_muxer_new (void) +{ + return g_object_new (GTK_TYPE_ACTION_MUXER, NULL); +} + +/** + * gtk_action_muxer_get_parent: + * @muxer: a #GtkActionMuxer + * + * Returns: (transfer none): the parent of @muxer, or NULL. + */ +GtkActionMuxer * +gtk_action_muxer_get_parent (GtkActionMuxer *muxer) +{ + g_return_val_if_fail (GTK_IS_ACTION_MUXER (muxer), NULL); + + return muxer->parent; +} + +/** + * gtk_action_muxer_set_parent: + * @muxer: a #GtkActionMuxer + * @parent: (allow-none): the new parent #GtkActionMuxer + * + * Sets the parent of @muxer to @parent. + */ +void +gtk_action_muxer_set_parent (GtkActionMuxer *muxer, + GtkActionMuxer *parent) +{ + g_return_if_fail (GTK_IS_ACTION_MUXER (muxer)); + g_return_if_fail (parent == NULL || GTK_IS_ACTION_MUXER (parent)); + + if (muxer->parent == parent) + return; + + if (muxer->parent != NULL) + { + gchar **actions; + gchar **it; + + actions = g_action_group_list_actions (G_ACTION_GROUP (muxer->parent)); + for (it = actions; *it; it++) + gtk_action_muxer_action_removed (muxer, *it); + g_strfreev (actions); + + g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_action_added_to_parent, muxer); + g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_action_removed_from_parent, muxer); + g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_parent_action_enabled_changed, muxer); + g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_parent_action_state_changed, muxer); + + g_object_unref (muxer->parent); + } + + muxer->parent = parent; + + if (muxer->parent != NULL) + { + gchar **actions; + gchar **it; + + g_object_ref (muxer->parent); + + actions = g_action_group_list_actions (G_ACTION_GROUP (muxer->parent)); + for (it = actions; *it; it++) + gtk_action_muxer_action_added (muxer, *it, G_ACTION_GROUP (muxer->parent), *it); + g_strfreev (actions); + + g_signal_connect (muxer->parent, "action-added", + G_CALLBACK (gtk_action_muxer_action_added_to_parent), muxer); + g_signal_connect (muxer->parent, "action-removed", + G_CALLBACK (gtk_action_muxer_action_removed_from_parent), muxer); + g_signal_connect (muxer->parent, "action-enabled-changed", + G_CALLBACK (gtk_action_muxer_parent_action_enabled_changed), muxer); + g_signal_connect (muxer->parent, "action-state-changed", + G_CALLBACK (gtk_action_muxer_parent_action_state_changed), muxer); + } + + g_object_notify_by_pspec (G_OBJECT (muxer), properties[PROP_PARENT]); +} diff --git a/libqmenumodel/src/gtk/gtkactionmuxer.h b/libqmenumodel/src/gtk/gtkactionmuxer.h new file mode 100644 index 0000000..4014830 --- /dev/null +++ b/libqmenumodel/src/gtk/gtkactionmuxer.h @@ -0,0 +1,52 @@ +/* + * 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, see . + * + * Author: Ryan Lortie + */ + +#ifndef __GTK_ACTION_MUXER_H__ +#define __GTK_ACTION_MUXER_H__ + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_ACTION_MUXER (gtk_action_muxer_get_type ()) +#define GTK_ACTION_MUXER(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \ + GTK_TYPE_ACTION_MUXER, GtkActionMuxer)) +#define GTK_IS_ACTION_MUXER(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), \ + GTK_TYPE_ACTION_MUXER)) + +typedef struct _GtkActionMuxer GtkActionMuxer; + +GType gtk_action_muxer_get_type (void); +GtkActionMuxer * gtk_action_muxer_new (void); + +void gtk_action_muxer_insert (GtkActionMuxer *muxer, + const gchar *prefix, + GActionGroup *action_group); + +void gtk_action_muxer_remove (GtkActionMuxer *muxer, + const gchar *prefix); + +GtkActionMuxer * gtk_action_muxer_get_parent (GtkActionMuxer *muxer); + +void gtk_action_muxer_set_parent (GtkActionMuxer *muxer, + GtkActionMuxer *parent); + +G_END_DECLS + +#endif /* __GTK_ACTION_MUXER_H__ */ diff --git a/libqmenumodel/src/gtk/gtkactionobservable.c b/libqmenumodel/src/gtk/gtkactionobservable.c new file mode 100644 index 0000000..ab90df2 --- /dev/null +++ b/libqmenumodel/src/gtk/gtkactionobservable.c @@ -0,0 +1,78 @@ +/* + * 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, see . + * + * Authors: Ryan Lortie + */ + +#include "config.h" + +#include "gtkactionobservable.h" + +G_DEFINE_INTERFACE (GtkActionObservable, gtk_action_observable, G_TYPE_OBJECT) + +/* + * SECTION:gtkactionobserable + * @short_description: an interface implemented by objects that report + * changes to actions + */ + +void +gtk_action_observable_default_init (GtkActionObservableInterface *iface) +{ +} + +/** + * gtk_action_observable_register_observer: + * @observable: a #GtkActionObservable + * @action_name: the name of the action + * @observer: the #GtkActionObserver to which the events will be reported + * + * Registers @observer as being interested in changes to @action_name on + * @observable. + */ +void +gtk_action_observable_register_observer (GtkActionObservable *observable, + const gchar *action_name, + GtkActionObserver *observer) +{ + g_return_if_fail (GTK_IS_ACTION_OBSERVABLE (observable)); + + GTK_ACTION_OBSERVABLE_GET_IFACE (observable) + ->register_observer (observable, action_name, observer); +} + +/** + * gtk_action_observable_unregister_observer: + * @observable: a #GtkActionObservable + * @action_name: the name of the action + * @observer: the #GtkActionObserver to which the events will be reported + * + * Removes the registration of @observer as being interested in changes + * to @action_name on @observable. + * + * If the observer was registered multiple times, it must be + * unregistered an equal number of times. + */ +void +gtk_action_observable_unregister_observer (GtkActionObservable *observable, + const gchar *action_name, + GtkActionObserver *observer) +{ + g_return_if_fail (GTK_IS_ACTION_OBSERVABLE (observable)); + + GTK_ACTION_OBSERVABLE_GET_IFACE (observable) + ->unregister_observer (observable, action_name, observer); +} diff --git a/libqmenumodel/src/gtk/gtkactionobservable.h b/libqmenumodel/src/gtk/gtkactionobservable.h new file mode 100644 index 0000000..aa1514b --- /dev/null +++ b/libqmenumodel/src/gtk/gtkactionobservable.h @@ -0,0 +1,60 @@ +/* + * 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, see . + * + * Authors: Ryan Lortie + */ + +#ifndef __GTK_ACTION_OBSERVABLE_H__ +#define __GTK_ACTION_OBSERVABLE_H__ + +#include "gtkactionobserver.h" + +G_BEGIN_DECLS + +#define GTK_TYPE_ACTION_OBSERVABLE (gtk_action_observable_get_type ()) +#define GTK_ACTION_OBSERVABLE(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \ + GTK_TYPE_ACTION_OBSERVABLE, GtkActionObservable)) +#define GTK_IS_ACTION_OBSERVABLE(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), \ + GTK_TYPE_ACTION_OBSERVABLE)) +#define GTK_ACTION_OBSERVABLE_GET_IFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), \ + GTK_TYPE_ACTION_OBSERVABLE, \ + GtkActionObservableInterface)) + +typedef struct _GtkActionObservableInterface GtkActionObservableInterface; + +struct _GtkActionObservableInterface +{ + GTypeInterface g_iface; + + void (* register_observer) (GtkActionObservable *observable, + const gchar *action_name, + GtkActionObserver *observer); + void (* unregister_observer) (GtkActionObservable *observable, + const gchar *action_name, + GtkActionObserver *observer); +}; + +GType gtk_action_observable_get_type (void); +void gtk_action_observable_register_observer (GtkActionObservable *observable, + const gchar *action_name, + GtkActionObserver *observer); +void gtk_action_observable_unregister_observer (GtkActionObservable *observable, + const gchar *action_name, + GtkActionObserver *observer); + +G_END_DECLS + +#endif /* __GTK_ACTION_OBSERVABLE_H__ */ diff --git a/libqmenumodel/src/gtk/gtkactionobserver.c b/libqmenumodel/src/gtk/gtkactionobserver.c new file mode 100644 index 0000000..cf70b20 --- /dev/null +++ b/libqmenumodel/src/gtk/gtkactionobserver.c @@ -0,0 +1,159 @@ +/* + * 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, see . + * + * Authors: Ryan Lortie + */ + +#include "config.h" + +#include "gtkactionobserver.h" + +G_DEFINE_INTERFACE (GtkActionObserver, gtk_action_observer, G_TYPE_OBJECT) + +/** + * SECTION:gtkactionobserver + * @short_description: an interface implemented by objects that are + * interested in monitoring actions for changes + * + * GtkActionObserver is a simple interface allowing objects that wish to + * be notified of changes to actions to be notified of those changes. + * + * It is also possible to monitor changes to action groups using + * #GObject signals, but there are a number of reasons that this + * approach could become problematic: + * + * - there are four separate signals that must be manually connected + * and disconnected + * + * - when a large number of different observers wish to monitor a + * (usually disjoint) set of actions within the same action group, + * there is only one way to avoid having all notifications delivered + * to all observers: signal detail. In order to use signal detail, + * each action name must be quarked, which is not always practical. + * + * - even if quarking is acceptable, #GObject signal details are + * implemented by scanning a linked list, so there is no real + * decrease in complexity + */ + +void +gtk_action_observer_default_init (GtkActionObserverInterface *class) +{ +} + +/** + * gtk_action_observer_action_added: + * @observer: a #GtkActionObserver + * @observable: the source of the event + * @action_name: the name of the action + * @enabled: %TRUE if the action is now enabled + * @parameter_type: the parameter type for action invocations, or %NULL + * if no parameter is required + * @state: the current state of the action, or %NULL if the action is + * stateless + * + * This function is called when an action that the observer is + * registered to receive events for is added. + * + * This function should only be called by objects with which the + * observer has explicitly registered itself to receive events. + */ +void +gtk_action_observer_action_added (GtkActionObserver *observer, + GtkActionObservable *observable, + const gchar *action_name, + const GVariantType *parameter_type, + gboolean enabled, + GVariant *state) +{ + g_return_if_fail (GTK_IS_ACTION_OBSERVER (observer)); + + GTK_ACTION_OBSERVER_GET_IFACE (observer) + ->action_added (observer, observable, action_name, parameter_type, enabled, state); +} + +/** + * gtk_action_observer_action_enabled_changed: + * @observer: a #GtkActionObserver + * @observable: the source of the event + * @action_name: the name of the action + * @enabled: %TRUE if the action is now enabled + * + * This function is called when an action that the observer is + * registered to receive events for becomes enabled or disabled. + * + * This function should only be called by objects with which the + * observer has explicitly registered itself to receive events. + */ +void +gtk_action_observer_action_enabled_changed (GtkActionObserver *observer, + GtkActionObservable *observable, + const gchar *action_name, + gboolean enabled) +{ + g_return_if_fail (GTK_IS_ACTION_OBSERVER (observer)); + + GTK_ACTION_OBSERVER_GET_IFACE (observer) + ->action_enabled_changed (observer, observable, action_name, enabled); +} + +/** + * gtk_action_observer_action_state_changed: + * @observer: a #GtkActionObserver + * @observable: the source of the event + * @action_name: the name of the action + * @state: the new state of the action + * + * This function is called when an action that the observer is + * registered to receive events for changes to its state. + * + * This function should only be called by objects with which the + * observer has explicitly registered itself to receive events. + */ +void +gtk_action_observer_action_state_changed (GtkActionObserver *observer, + GtkActionObservable *observable, + const gchar *action_name, + GVariant *state) +{ + g_return_if_fail (GTK_IS_ACTION_OBSERVER (observer)); + + GTK_ACTION_OBSERVER_GET_IFACE (observer) + ->action_state_changed (observer, observable, action_name, state); +} + +/** + * gtk_action_observer_action_removed: + * @observer: a #GtkActionObserver + * @observable: the source of the event + * @action_name: the name of the action + * + * This function is called when an action that the observer is + * registered to receive events for is removed. + * + * This function should only be called by objects with which the + * observer has explicitly registered itself to receive events. + */ +void +gtk_action_observer_action_removed (GtkActionObserver *observer, + GtkActionObservable *observable, + const gchar *action_name) +{ + g_return_if_fail (GTK_IS_ACTION_OBSERVER (observer)); + + GTK_ACTION_OBSERVER_GET_IFACE (observer) + ->action_removed (observer, observable, action_name); +} diff --git a/libqmenumodel/src/gtk/gtkactionobserver.h b/libqmenumodel/src/gtk/gtkactionobserver.h new file mode 100644 index 0000000..83629a7 --- /dev/null +++ b/libqmenumodel/src/gtk/gtkactionobserver.h @@ -0,0 +1,83 @@ +/* + * 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, see . + * + * Authors: Ryan Lortie + */ + +#ifndef __GTK_ACTION_OBSERVER_H__ +#define __GTK_ACTION_OBSERVER_H__ + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_ACTION_OBSERVER (gtk_action_observer_get_type ()) +#define GTK_ACTION_OBSERVER(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \ + GTK_TYPE_ACTION_OBSERVER, GtkActionObserver)) +#define GTK_IS_ACTION_OBSERVER(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), \ + GTK_TYPE_ACTION_OBSERVER)) +#define GTK_ACTION_OBSERVER_GET_IFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), \ + GTK_TYPE_ACTION_OBSERVER, GtkActionObserverInterface)) + +typedef struct _GtkActionObserverInterface GtkActionObserverInterface; +typedef struct _GtkActionObservable GtkActionObservable; +typedef struct _GtkActionObserver GtkActionObserver; + +struct _GtkActionObserverInterface +{ + GTypeInterface g_iface; + + void (* action_added) (GtkActionObserver *observer, + GtkActionObservable *observable, + const gchar *action_name, + const GVariantType *parameter_type, + gboolean enabled, + GVariant *state); + void (* action_enabled_changed) (GtkActionObserver *observer, + GtkActionObservable *observable, + const gchar *action_name, + gboolean enabled); + void (* action_state_changed) (GtkActionObserver *observer, + GtkActionObservable *observable, + const gchar *action_name, + GVariant *state); + void (* action_removed) (GtkActionObserver *observer, + GtkActionObservable *observable, + const gchar *action_name); +}; + +GType gtk_action_observer_get_type (void); +void gtk_action_observer_action_added (GtkActionObserver *observer, + GtkActionObservable *observable, + const gchar *action_name, + const GVariantType *parameter_type, + gboolean enabled, + GVariant *state); +void gtk_action_observer_action_enabled_changed (GtkActionObserver *observer, + GtkActionObservable *observable, + const gchar *action_name, + gboolean enabled); +void gtk_action_observer_action_state_changed (GtkActionObserver *observer, + GtkActionObservable *observable, + const gchar *action_name, + GVariant *state); +void gtk_action_observer_action_removed (GtkActionObserver *observer, + GtkActionObservable *observable, + const gchar *action_name); + +G_END_DECLS + +#endif /* __GTK_ACTION_OBSERVER_H__ */ diff --git a/libqmenumodel/src/gtk/gtkmenutracker.c b/libqmenumodel/src/gtk/gtkmenutracker.c new file mode 100644 index 0000000..ab369ab --- /dev/null +++ b/libqmenumodel/src/gtk/gtkmenutracker.c @@ -0,0 +1,495 @@ +/* + * 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 "gtkmenutracker.h" + +/** + * SECTION:gtkmenutracker + * @Title: GtkMenuTracker + * @Short_description: A helper class for interpreting #GMenuModel + * + * #GtkMenuTracker is a simple object to ease implementations of #GMenuModel. + * Given a #GtkActionObservable (usually a #GActionMuxer) along with a + * #GMenuModel, it will tell you which menu items to create and where to place + * them. If a menu item is removed, it will tell you the position of the menu + * item to remove. + * + * Using #GtkMenuTracker is fairly simple. The only guarantee you must make + * to #GtkMenuTracker is that you must obey all insert signals and track the + * position of items that #GtkMenuTracker gives you. That is, #GtkMenuTracker + * expects positions of all the latter items to change when it calls your + * insertion callback with an early position, as it may ask you to remove + * an item with a readjusted position later. + * + * #GtkMenuTracker will give you a #GtkMenuTrackerItem in your callback. You + * must hold onto this object until a remove signal is emitted. This item + * represents a single menu item, which can be one of three classes: normal item, + * separator, or submenu. + * + * Certain properties on the #GtkMenuTrackerItem are mutable, and you must + * listen for changes in the item. For more details, see the documentation + * for #GtkMenuTrackerItem along with https://live.gnome.org/GApplication/GMenuModel. + * + * The idea of @with_separators is for special cases where menu models may + * be tracked in places where separators are not available, like in toplevel + * "File", "Edit" menu bars. Ignoring separator items is wrong, as #GtkMenuTracker + * expects the position to change, so we must tell #GtkMenuTracker to ignore + * separators itself. + */ + +typedef struct _GtkMenuTrackerSection GtkMenuTrackerSection; + +struct _GtkMenuTracker +{ + GtkActionObservable *observable; + GtkMenuTrackerInsertFunc insert_func; + GtkMenuTrackerRemoveFunc remove_func; + gpointer user_data; + + GtkMenuTrackerSection *toplevel; +}; + +struct _GtkMenuTrackerSection +{ + GMenuModel *model; + GSList *items; + gchar *action_namespace; + + guint with_separators : 1; + guint has_separator : 1; + + gulong handler; +}; + +static GtkMenuTrackerSection * gtk_menu_tracker_section_new (GtkMenuTracker *tracker, + GMenuModel *model, + gboolean with_separators, + gint offset, + const gchar *action_namespace); +static void gtk_menu_tracker_section_free (GtkMenuTrackerSection *section); + +static GtkMenuTrackerSection * +gtk_menu_tracker_section_find_model (GtkMenuTrackerSection *section, + GMenuModel *model, + gint *offset) +{ + GSList *item; + + if (section->has_separator) + (*offset)++; + + if (section->model == model) + return section; + + for (item = section->items; item; item = item->next) + { + GtkMenuTrackerSection *subsection = item->data; + + if (subsection) + { + GtkMenuTrackerSection *found_section; + + found_section = gtk_menu_tracker_section_find_model (subsection, model, offset); + + if (found_section) + return found_section; + } + else + (*offset)++; + } + + return FALSE; +} + +/* this is responsible for syncing the showing of a separator for a + * single subsection (and its children). + * + * we only ever show separators if we have _actual_ children (ie: we do + * not show a separator if the section contains only empty child + * sections). it's difficult to determine this on-the-fly, so we have + * this separate function to come back later and figure it out. + * + * 'section' is that section. + * + * 'tracker' is passed in so that we can emit callbacks when we decide + * to add/remove separators. + * + * 'offset' is passed in so we know which position to emit in our + * callbacks. ie: if we add a separator right at the top of this + * section then we would emit it with this offset. deeper inside, we + * adjust accordingly. + * + * could_have_separator is true in two situations: + * + * - our parent section had with_separators defined and we are not the + * first section (ie: we should add a separator if we have content in + * order to divide us from the items above) + * + * - if we had a 'label' attribute set for this section + * + * parent_model and parent_index are passed in so that we can give them + * to the insertion callback so that it can see the label (and anything + * else that happens to be defined on the section). + * + * we iterate each item in ourselves. for subsections, we recursively + * run ourselves to sync separators. after we are done, we notice if we + * have any items in us or if we are completely empty and sync if our + * separator is shown or not. + */ +static gint +gtk_menu_tracker_section_sync_separators (GtkMenuTrackerSection *section, + GtkMenuTracker *tracker, + gint offset, + gboolean could_have_separator, + GMenuModel *parent_model, + gint parent_index) +{ + gboolean should_have_separator; + gint n_items = 0; + GSList *item; + gint i = 0; + + for (item = section->items; item; item = item->next) + { + GtkMenuTrackerSection *subsection = item->data; + + if (subsection) + { + gboolean could_have_separator; + + could_have_separator = (section->with_separators && i > 0) || + g_menu_model_get_item_attribute (section->model, i, "label", "s", NULL); + + n_items += gtk_menu_tracker_section_sync_separators (subsection, tracker, offset + n_items, + could_have_separator, section->model, i); + } + else + n_items++; + + i++; + } + + should_have_separator = could_have_separator && n_items != 0; + + if (should_have_separator > section->has_separator) + { + /* Add a separator */ + 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) + { + /* Remove a separator */ + (* tracker->remove_func) (offset, tracker->user_data); + section->has_separator = FALSE; + } + + n_items += section->has_separator; + + return n_items; +} + +static gint +gtk_menu_tracker_section_measure (GtkMenuTrackerSection *section) +{ + GSList *item; + gint n_items; + + if (section == NULL) + return 1; + + n_items = 0; + + if (section->has_separator) + n_items++; + + for (item = section->items; item; item = item->next) + n_items += gtk_menu_tracker_section_measure (item->data); + + return n_items; +} + +static void +gtk_menu_tracker_remove_items (GtkMenuTracker *tracker, + GSList **change_point, + gint offset, + gint n_items) +{ + gint i; + + for (i = 0; i < n_items; i++) + { + GtkMenuTrackerSection *subsection; + gint n; + + subsection = (*change_point)->data; + *change_point = g_slist_delete_link (*change_point, *change_point); + + n = gtk_menu_tracker_section_measure (subsection); + gtk_menu_tracker_section_free (subsection); + + while (n--) + (* tracker->remove_func) (offset, tracker->user_data); + } +} + +static void +gtk_menu_tracker_add_items (GtkMenuTracker *tracker, + GtkMenuTrackerSection *section, + GSList **change_point, + gint offset, + GMenuModel *model, + gint position, + gint n_items) +{ + while (n_items--) + { + GMenuModel *submenu; + + submenu = g_menu_model_get_item_link (model, position + n_items, G_MENU_LINK_SECTION); + g_assert (submenu != model); + if (submenu != NULL) + { + GtkMenuTrackerSection *subsection; + gchar *action_namespace = NULL; + + g_menu_model_get_item_attribute (model, position + n_items, + G_MENU_ATTRIBUTE_ACTION_NAMESPACE, "s", &action_namespace); + + if (section->action_namespace) + { + gchar *namespace; + + namespace = g_strjoin (".", section->action_namespace, action_namespace, NULL); + subsection = gtk_menu_tracker_section_new (tracker, submenu, FALSE, offset, namespace); + g_free (namespace); + } + else + subsection = gtk_menu_tracker_section_new (tracker, submenu, FALSE, offset, section->action_namespace); + + *change_point = g_slist_prepend (*change_point, subsection); + g_free (action_namespace); + g_object_unref (submenu); + } + else + { + 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); + } + } +} + +static void +gtk_menu_tracker_model_changed (GMenuModel *model, + gint position, + gint removed, + gint added, + gpointer user_data) +{ + GtkMenuTracker *tracker = user_data; + GtkMenuTrackerSection *section; + GSList **change_point; + gint offset = 0; + gint i; + + /* First find which section the changed model corresponds to, and the + * position of that section within the overall menu. + */ + section = gtk_menu_tracker_section_find_model (tracker->toplevel, model, &offset); + + /* Next, seek through that section to the change point. This gives us + * the correct GSList** to make the change to and also finds the final + * offset at which we will make the changes (by measuring the number + * of items within each item of the section before the change point). + */ + change_point = §ion->items; + for (i = 0; i < position; i++) + { + offset += gtk_menu_tracker_section_measure ((*change_point)->data); + change_point = &(*change_point)->next; + } + + /* We remove items in order and add items in reverse order. This + * means that the offset used for all inserts and removes caused by a + * single change will be the same. + * + * This also has a performance advantage: GtkMenuShell stores the + * menu items in a linked list. In the case where we are creating a + * menu for the first time, adding the items in reverse order means + * that we only ever insert at index zero, prepending the list. This + * means that we can populate in O(n) time instead of O(n^2) that we + * would do by appending. + */ + gtk_menu_tracker_remove_items (tracker, change_point, offset, removed); + gtk_menu_tracker_add_items (tracker, section, change_point, offset, model, position, added); + + /* The offsets for insertion/removal of separators will be all over + * the place, however... + */ + gtk_menu_tracker_section_sync_separators (tracker->toplevel, tracker, 0, FALSE, NULL, 0); +} + +static void +gtk_menu_tracker_section_free (GtkMenuTrackerSection *section) +{ + if (section == NULL) + return; + + g_signal_handler_disconnect (section->model, section->handler); + g_slist_free_full (section->items, (GDestroyNotify) gtk_menu_tracker_section_free); + g_free (section->action_namespace); + g_object_unref (section->model); + g_slice_free (GtkMenuTrackerSection, section); +} + +static GtkMenuTrackerSection * +gtk_menu_tracker_section_new (GtkMenuTracker *tracker, + GMenuModel *model, + gboolean with_separators, + gint offset, + const gchar *action_namespace) +{ + GtkMenuTrackerSection *section; + + section = g_slice_new0 (GtkMenuTrackerSection); + section->model = g_object_ref (model); + section->with_separators = with_separators; + section->action_namespace = g_strdup (action_namespace); + + gtk_menu_tracker_add_items (tracker, section, §ion->items, offset, model, 0, g_menu_model_get_n_items (model)); + section->handler = g_signal_connect (model, "items-changed", G_CALLBACK (gtk_menu_tracker_model_changed), tracker); + + return section; +} + +/*< private > + * gtk_menu_tracker_new: + * @model: the model to flatten + * @with_separators: if the toplevel should have separators (ie: TRUE + * for menus, FALSE for menubars) + * @action_namespace: the passed-in action namespace + * @insert_func: insert callback + * @remove_func: remove callback + * @user_data user data for callbacks + * + * Creates a GtkMenuTracker for @model, holding a ref on @model for as + * long as the tracker is alive. + * + * This flattens out the model, merging sections and inserting + * separators where appropriate. It monitors for changes and performs + * updates on the fly. It also handles action_namespace for subsections + * (but you will need to handle it yourself for submenus). + * + * When the tracker is first created, @insert_func will be called many + * times to populate the menu with the initial contents of @model + * (unless it is empty), before gtk_menu_tracker_new() returns. For + * this reason, the menu that is using the tracker ought to be empty + * when it creates the tracker. + * + * Future changes to @model will result in more calls to @insert_func + * and @remove_func. + * + * The position argument to both functions is the linear 0-based + * position in the menu at which the item in question should be inserted + * or removed. + * + * For @insert_func, @model and @item_index are used to get the + * information about the menu item to insert. @action_namespace is the + * action namespace that actions referred to from that item should place + * themselves in. Note that if the item is a submenu and the + * "action-namespace" attribute is defined on the item, it will _not_ be + * applied to the @action_namespace argument as it is meant for the + * items inside of the submenu, not the submenu item itself. + * + * @is_separator is set to %TRUE in case the item being added is a + * separator. @model and @item_index will still be meaningfully set in + * this case -- to the section menu item corresponding to the separator. + * This is useful if the section specifies a label, for example. If + * there is an "action-namespace" attribute on this menu item then it + * should be ignored by the consumer because #GtkMenuTracker has already + * handled it. + * + * When using #GtkMenuTracker there is no need to hold onto @model or + * monitor it for changes. The model will be unreffed when + * gtk_menu_tracker_free() is called. + */ +GtkMenuTracker * +gtk_menu_tracker_new (GtkActionObservable *observable, + GMenuModel *model, + gboolean with_separators, + const gchar *action_namespace, + GtkMenuTrackerInsertFunc insert_func, + GtkMenuTrackerRemoveFunc remove_func, + gpointer user_data) +{ + 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; + + tracker->toplevel = gtk_menu_tracker_section_new (tracker, model, with_separators, 0, action_namespace); + gtk_menu_tracker_section_sync_separators (tracker->toplevel, tracker, 0, FALSE, NULL, 0); + + 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 + * + * Frees the tracker, ... + */ +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/libqmenumodel/src/gtk/gtkmenutracker.h b/libqmenumodel/src/gtk/gtkmenutracker.h new file mode 100644 index 0000000..96370ad --- /dev/null +++ b/libqmenumodel/src/gtk/gtkmenutracker.h @@ -0,0 +1,52 @@ +/* + * 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 + */ + +#ifndef __GTK_MENU_TRACKER_H__ +#define __GTK_MENU_TRACKER_H__ + +#include "gtkmenutrackeritem.h" + +typedef struct _GtkMenuTracker GtkMenuTracker; + +typedef void (* GtkMenuTrackerInsertFunc) (GtkMenuTrackerItem *item, + gint position, + gpointer user_data); + +typedef void (* GtkMenuTrackerRemoveFunc) (gint position, + 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); + +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/libqmenumodel/src/gtk/gtkmenutrackeritem.c b/libqmenumodel/src/gtk/gtkmenutrackeritem.c new file mode 100644 index 0000000..2d712a1 --- /dev/null +++ b/libqmenumodel/src/gtk/gtkmenutrackeritem.c @@ -0,0 +1,788 @@ +/* + * 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" + +/** + * SECTION:gtkmenutrackeritem + * @Title: GtkMenuTrackerItem + * @Short_description: Small helper for model menu items + * + * A #GtkMenuTrackerItem is a small helper class used by #GtkMenuTracker to + * represent menu items. It has one of three classes: normal item, separator, + * or submenu. + * + * If an item is one of the non-normal classes (submenu, separator), only the + * label of the item needs to be respected. Otherwise, all the properties + * of the item contribute to the item's appearance and state. + * + * Implementing the appearance of the menu item is up to toolkits, and certain + * toolkits may choose to ignore certain properties, like icon or accel. The + * role of the item determines its accessibility role, along with its + * decoration if the GtkMenuTrackerItem::toggled property is true. As an + * example, if the item has the role %GTK_MENU_TRACKER_ITEM_ROLE_CHECK and + * GtkMenuTrackerItem::toggled is %FALSE, its accessible role should be that of + * a check menu item, and no decoration should be drawn. But if + * GtkMenuTrackerItem::toggled is %TRUE, a checkmark should be drawn. + * + * All properties except for the two class-determining properties, + * GtkMenuTrackerItem::is-separator and GtkMenuTrackerItem::has-submenu are + * allowed to change, so listen to the notify signals to update your item's + * appearance. When using a GObject library, this can conveniently be done + * with g_object_bind_property() and #GBinding, and this is how this is + * implemented in GTK+; the appearance side is implemented in #GtkModelMenuItem. + * + * When an item is clicked, simply call gtk_menu_tracker_item_activated() in + * response. The #GtkMenuTrackerItem will take care of everything related to + * activating the item and will itself update the state of all items in + * response. + * + * Submenus are a special case of menu item. When an item is a submenu, you + * should create a submenu for it with gtk_menu_tracker_new_item_for_submenu(), + * and apply the same menu tracking logic you would for a toplevel menu. + * Applications using submenus may want to lazily build their submenus in + * response to the user clicking on it, as building a submenu may be expensive. + * + * Thus, the submenu has two special controls -- the submenu's visibility + * should be controlled by the GtkMenuTrackerItem::submenu-shown property, + * and if a user clicks on the submenu, do not immediately show the menu, + * but call gtk_menu_tracker_item_request_submenu_shown() and wait for the + * GtkMenuTrackerItem::submenu-shown property to update. If the user navigates, + * the application may want to be notified so it can cancel the expensive + * operation that it was using to build the submenu. Thus, + * gtk_menu_tracker_item_request_submenu_shown() takes a boolean parameter. + * Use %TRUE when the user wants to open the submenu, and %FALSE when the + * user wants to close the submenu. + */ + +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; +} + +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 has_submenu_action; + + if (shown == self->submenu_requested) + return; + + has_submenu_action = g_menu_item_get_attribute (self->item, "submenu-action", "&s", &submenu_action); + + self->submenu_requested = shown; + + /* If we have a submenu action, start a submenu opener and wait + * for the reply from the client. Otherwise, simply open the + * submenu immediately. + */ + if (has_submenu_action) + { + 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); + } + else + gtk_menu_tracker_item_set_submenu_shown (self, shown); +} diff --git a/libqmenumodel/src/gtk/gtkmenutrackeritem.h b/libqmenumodel/src/gtk/gtkmenutrackeritem.h new file mode 100644 index 0000000..9db30eb --- /dev/null +++ b/libqmenumodel/src/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/libqmenumodel/src/unitymenumodel.cpp b/libqmenumodel/src/unitymenumodel.cpp new file mode 100644 index 0000000..25df10b --- /dev/null +++ b/libqmenumodel/src/unitymenumodel.cpp @@ -0,0 +1,227 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 3. + * + * This program 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 program. If not, see . + * + * Authors: Lars Uebernickel + */ + +#include "unitymenumodel.h" + +extern "C" { + #include "gtk/gtkactionmuxer.h" + #include "gtk/gtkmenutracker.h" +} + +G_DEFINE_QUARK (UNITY_MENU_MODEL, unity_menu_model) + +class UnityMenuModelPrivate +{ +public: + UnityMenuModelPrivate(UnityMenuModel *model, + const QByteArray &busName, + const QByteArray &actionGroupObjectPath, + const QByteArray &menuObjectPath); + + ~UnityMenuModelPrivate(); + + int nrItems(); + QVariant data(int position, int role); + +private: + UnityMenuModel *model; + GtkActionMuxer *muxer; + GtkMenuTracker *menutracker; + GSequence *items; + + static void menuItemInserted(GtkMenuTrackerItem *item, gint position, gpointer user_data); + static void menuItemRemoved(gint position, gpointer user_data); + static void menuItemChanged(GObject *object, GParamSpec *pspec, gpointer user_data); +}; + +UnityMenuModelPrivate::UnityMenuModelPrivate(UnityMenuModel *model, + const QByteArray &busName, + const QByteArray &actionGroupObjectPath, + const QByteArray &menuObjectPath) +{ + GDBusConnection *connection; + GDBusActionGroup *actions; + GDBusMenuModel *menu; + + this->model = model; + + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); + actions = g_dbus_action_group_get (connection, busName.constData(), actionGroupObjectPath.constData()); + menu = g_dbus_menu_model_get (connection, busName.constData(), menuObjectPath.constData()); + + this->muxer = gtk_action_muxer_new (); + g_object_set_qdata (G_OBJECT (this->muxer), unity_menu_model_quark (), model); + gtk_action_muxer_insert (this->muxer, "indicator", G_ACTION_GROUP (actions)); + + this->menutracker = gtk_menu_tracker_new (GTK_ACTION_OBSERVABLE (this->muxer), + G_MENU_MODEL (menu), TRUE, "indicator", + menuItemInserted, menuItemRemoved, this); + + this->items = g_sequence_new (NULL); + + g_object_unref (menu); + g_object_unref (actions); + g_object_unref (connection); +} + +UnityMenuModelPrivate::~UnityMenuModelPrivate() +{ + GSequenceIter *it; + + it = g_sequence_get_begin_iter (this->items); + while (!g_sequence_iter_is_end (it)) { + GtkMenuTrackerItem *item = (GtkMenuTrackerItem *) g_sequence_get (it); + g_signal_handlers_disconnect_by_func (item, (gpointer) menuItemChanged, it); + g_object_unref (item); + it = g_sequence_iter_next (it); + } + g_sequence_free (this->items); + + g_object_unref (this->muxer); + gtk_menu_tracker_free (this->menutracker); +} + +int UnityMenuModelPrivate::nrItems() +{ + return g_sequence_get_length (this->items); +} + +QVariant UnityMenuModelPrivate::data(int position, int role) +{ + GtkMenuTrackerItem *item; + + item = (GtkMenuTrackerItem *) g_sequence_get (g_sequence_get_iter_at_pos (this->items, position)); + + switch (role) { + case UnityMenuModel::LabelRole: + return gtk_menu_tracker_item_get_label (item); + + case UnityMenuModel::SensitiveRole: + return gtk_menu_tracker_item_get_sensitive (item); + + default: + return QVariant(); + } +} + +void UnityMenuModelPrivate::menuItemInserted(GtkMenuTrackerItem *item, gint position, gpointer user_data) +{ + UnityMenuModelPrivate *priv = (UnityMenuModelPrivate *)user_data; + GSequenceIter *it; + + priv->model->beginInsertRows(QModelIndex(), position, position); + + it = g_sequence_get_iter_at_pos (priv->items, position); + g_signal_connect (item, "notify", G_CALLBACK (menuItemChanged), it); + g_sequence_insert_before (it, g_object_ref (item)); + + priv->model->endInsertRows(); +} + +void UnityMenuModelPrivate::menuItemRemoved(gint position, gpointer user_data) +{ + UnityMenuModelPrivate *priv = (UnityMenuModelPrivate *)user_data; + GSequenceIter *it; + GtkMenuTrackerItem *item; + + priv->model->beginRemoveRows(QModelIndex(), position, position); + + it = g_sequence_get_iter_at_pos (priv->items, position); + item = (GtkMenuTrackerItem *) g_sequence_get (it); + g_signal_handlers_disconnect_by_func (item, (gpointer) menuItemChanged, it); + g_object_unref (item); + g_sequence_remove (it); + + priv->model->endRemoveRows(); +} + +void UnityMenuModelPrivate::menuItemChanged(GObject *object, GParamSpec *pspec, gpointer user_data) +{ + GSequenceIter *it = (GSequenceIter *) user_data; + GtkMenuTrackerItem *item; + GtkActionObservable *muxer; + UnityMenuModel *model; + gint position; + + item = (GtkMenuTrackerItem *) g_sequence_get (it); + muxer = _gtk_menu_tracker_item_get_observable (item); + model = (UnityMenuModel *) g_object_get_qdata (G_OBJECT (muxer), unity_menu_model_quark ()); + position = g_sequence_iter_get_position (it); + + Q_EMIT model->dataChanged(model->index(position, 0), model->index(position, 0)); +} + +UnityMenuModel::UnityMenuModel(QObject *parent): + QAbstractListModel(parent) +{ +} + +UnityMenuModel::UnityMenuModel(const QByteArray &busName, + const QByteArray &actionGroupObjectPath, + const QByteArray &menuObjectPath, + QObject *parent): + QAbstractListModel(parent), + priv(NULL) +{ +} + +void UnityMenuModel::init(const QByteArray &busName, const QByteArray &actionGroupObjectPath, const QByteArray &menuObjectPath) +{ + priv = new UnityMenuModelPrivate (this, busName, actionGroupObjectPath, menuObjectPath); +} + +UnityMenuModel::~UnityMenuModel() +{ + delete priv; +} + +int UnityMenuModel::rowCount(const QModelIndex &parent) const +{ + return priv && !parent.isValid() ? priv->nrItems() : 0; +} + +int UnityMenuModel::columnCount(const QModelIndex &parent) const +{ + return 1; +} + +QVariant UnityMenuModel::data(const QModelIndex &index, int role) const +{ + return priv ? priv->data(index.row(), role) : QVariant(); +} + +QModelIndex UnityMenuModel::index(int row, int column, const QModelIndex &parent) const +{ + return createIndex(row, column); +} + +QModelIndex UnityMenuModel::parent(const QModelIndex &index) const +{ + return QModelIndex(); +} + +QHash UnityMenuModel::roleNames() const +{ + QHash names; + + names[LabelRole] = "label"; + names[ActionRole] = "action"; + names[SensitiveRole] = "sensitive"; + + return names; +} diff --git a/libqmenumodel/src/unitymenumodel.h b/libqmenumodel/src/unitymenumodel.h new file mode 100644 index 0000000..12a5891 --- /dev/null +++ b/libqmenumodel/src/unitymenumodel.h @@ -0,0 +1,56 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 3. + * + * This program 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 program. If not, see . + * + * Authors: Lars Uebernickel + */ + +#ifndef UNITYMENUMODEL_H +#define UNITYMENUMODEL_H + +#include + +class UnityMenuModel: public QAbstractListModel +{ + Q_OBJECT + +public: + enum MenuRoles { + ActionRole = Qt::DisplayRole + 1, + LabelRole, + SensitiveRole + }; + +public: + UnityMenuModel(const QByteArray &busName, const QByteArray &actionGroupObjectPath, + const QByteArray &menuObjectPath, QObject *parent = NULL); + virtual ~UnityMenuModel(); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &index) const; + QHash roleNames() const; + +protected: + UnityMenuModel(QObject *parent = NULL); + void init(const QByteArray &busName, const QByteArray &actionGroupObjectPath, const QByteArray &menuObjectPath); + +private: + class UnityMenuModelPrivate *priv; + friend class UnityMenuModelPrivate; +}; + +#endif diff --git a/libqmenumodel/src/unityqmlmenumodel.cpp b/libqmenumodel/src/unityqmlmenumodel.cpp new file mode 100644 index 0000000..775d974 --- /dev/null +++ b/libqmenumodel/src/unityqmlmenumodel.cpp @@ -0,0 +1,102 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 3. + * + * This program 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 program. If not, see . + * + * Authors: Lars Uebernickel + */ + +#include "unityqmlmenumodel.h" + +struct UnityQmlMenuModelPrivate +{ + QByteArray busName; + QByteArray actionObjectPath; + QByteArray menuObjectPath; +}; + +UnityQmlMenuModel::UnityQmlMenuModel(QObject *parent): + UnityMenuModel(parent) +{ + priv = new UnityQmlMenuModelPrivate; +} + +UnityQmlMenuModel::~UnityQmlMenuModel() +{ + delete priv; +} + +void UnityQmlMenuModel::classBegin() +{ +} + +void UnityQmlMenuModel::componentComplete() +{ + if (priv->busName.isEmpty()) + qWarning("UnityQmlMenuModel: property 'busName' must be set"); + else if (priv->actionObjectPath.isEmpty()) + qWarning("UnityQmlMenuModel: property 'actionObjectPath' must be set"); + else if (priv->menuObjectPath.isEmpty()) + qWarning("UnityQmlMenuModel: property 'menuObjectPath' must be set"); + else + UnityQmlMenuModel::init(priv->busName, priv->actionObjectPath, priv->menuObjectPath); +} + +QByteArray UnityQmlMenuModel::busName() const +{ + return priv->busName; +} + +void UnityQmlMenuModel::setBusName(const QByteArray &name) +{ + if (!priv->busName.isEmpty()) { + qWarning("UnityQmlMenuModel: cannot change bus name after creation"); + return; + } + + priv->busName = name; + Q_EMIT busNameChanged(name); +} + +QByteArray UnityQmlMenuModel::actionObjectPath() const +{ + return priv->actionObjectPath; +} + +void UnityQmlMenuModel::setActionObjectPath(const QByteArray &path) +{ + if (!priv->actionObjectPath.isEmpty()) { + qWarning("UnityQmlMenuModel: cannot change object paths after creation"); + return; + } + + priv->actionObjectPath = path; + Q_EMIT actionObjectPathChanged(path); +} + +QByteArray UnityQmlMenuModel::menuObjectPath() const +{ + return priv->menuObjectPath; +} + +void UnityQmlMenuModel::setMenuObjectPath(const QByteArray &path) +{ + if (!priv->menuObjectPath.isEmpty()) { + qWarning("UnityQmlMenuModel: cannot change object paths after creation"); + return; + } + + priv->menuObjectPath = path; + Q_EMIT menuObjectPathChanged(path); +} + diff --git a/libqmenumodel/src/unityqmlmenumodel.h b/libqmenumodel/src/unityqmlmenumodel.h new file mode 100644 index 0000000..a375e8e --- /dev/null +++ b/libqmenumodel/src/unityqmlmenumodel.h @@ -0,0 +1,59 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 3. + * + * This program 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 program. If not, see . + * + * Authors: Lars Uebernickel + */ + +#ifndef UNITYQMLENUMODEL_H +#define UNITYQMLENUMODEL_H + +#include "unitymenumodel.h" + +#include + +class UnityQmlMenuModel: public UnityMenuModel, public QQmlParserStatus +{ + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + Q_PROPERTY(QByteArray busName READ busName WRITE setBusName NOTIFY busNameChanged) + Q_PROPERTY(QByteArray actionObjectPath READ actionObjectPath WRITE setActionObjectPath NOTIFY actionObjectPathChanged) + Q_PROPERTY(QByteArray menuObjectPath READ menuObjectPath WRITE setMenuObjectPath NOTIFY menuObjectPathChanged) + +public: + UnityQmlMenuModel(QObject *parent = NULL); + ~UnityQmlMenuModel(); + + void classBegin(); + void componentComplete(); + + QByteArray busName() const; + void setBusName(const QByteArray &name); + + QByteArray actionObjectPath() const; + void setActionObjectPath(const QByteArray &path); + + QByteArray menuObjectPath() const; + void setMenuObjectPath(const QByteArray &path); + +Q_SIGNALS: + void busNameChanged(const QByteArray &name); + void actionObjectPathChanged(const QByteArray &path); + void menuObjectPathChanged(const QByteArray &path); + +private: + struct UnityQmlMenuModelPrivate *priv; +}; + +#endif -- cgit v1.2.3 From 550306421fa0ece045d3e8930fdc7f135fe70c47 Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Tue, 4 Jun 2013 15:40:43 -0400 Subject: CMakeLists.txt: explicitly specify qml include path CMake doesn't call moc correctly if this is not specified. --- libqmenumodel/src/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/libqmenumodel/src/CMakeLists.txt b/libqmenumodel/src/CMakeLists.txt index cd81371..d1d19c9 100644 --- a/libqmenumodel/src/CMakeLists.txt +++ b/libqmenumodel/src/CMakeLists.txt @@ -36,6 +36,7 @@ set_target_properties(${SHAREDLIBNAME} PROPERTIES ) include_directories( + /usr/include/qt5/QtQml ${GLIB_INCLUDE_DIRS} ${GIO_INCLUDE_DIRS} ) -- cgit v1.2.3 From b1009a800f4e64b17e19e578deedaf0c9bd18902 Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Tue, 4 Jun 2013 15:42:16 -0400 Subject: Add qml example for UnityMenuModel --- examples/exportmenu.py | 6 +++++- examples/unityqmlmenumodel.qml | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 examples/unityqmlmenumodel.qml diff --git a/examples/exportmenu.py b/examples/exportmenu.py index 4be3deb..767d8b9 100755 --- a/examples/exportmenu.py +++ b/examples/exportmenu.py @@ -54,7 +54,7 @@ if __name__ == '__main__': GLib.Variant.new_string('lorem ipsum')) foo.set_attribute_value('x-enabled', GLib.Variant.new_boolean(True)) menu.append_item(foo) - bar = Gio.MenuItem.new('bar', 'app.bar') + bar = Gio.MenuItem.new('bar', 'bar') bar.set_attribute_value('x-defaultvalue', GLib.Variant.new_string('Hello World!')) bar.set_attribute_value('x-canonical-currentvalue', @@ -74,5 +74,9 @@ if __name__ == '__main__': menu.append('baz', 'app.baz') bus.export_menu_model(BUS_OBJECT_PATH, menu) + actions = Gio.SimpleActionGroup.new() + actions.add_action(Gio.SimpleAction.new("bar", None)) + bus.export_action_group(BUS_OBJECT_PATH, actions) + GLib.MainLoop().run() diff --git a/examples/unityqmlmenumodel.qml b/examples/unityqmlmenumodel.qml new file mode 100644 index 0000000..257866d --- /dev/null +++ b/examples/unityqmlmenumodel.qml @@ -0,0 +1,34 @@ + +import QtQuick 2.0 +import QMenuModel 0.1 + +Item { + width: 400; + height: 500; + + UnityMenuModel { + id: menu + busName: "com.canonical.testmenu" + actionObjectPath: "/com/canonical/testmenu" + menuObjectPath: "/com/canonical/testmenu" + } + + ListView { + anchors.fill: parent + anchors.margins: 10 + spacing: 3 + model: menu + delegate: Rectangle { + width: parent.width + height: 40 + color: "#ddd" + Text { + anchors.fill: parent + anchors.margins: 5 + verticalAlignment: Text.AlignVCenter + color: sensitive ? "black" : "#aaa"; + text: label + } + } + } +} -- cgit v1.2.3 From 9f9560dd1f0f01d50c3c31b7fb2f083e1dc80a0e Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Tue, 4 Jun 2013 20:22:08 -0400 Subject: unitymenumodel: watch the bus name This makes it possible to restart the service that provides the menu. --- libqmenumodel/src/unitymenumodel.cpp | 113 +++++++++++++++++++++++++---------- 1 file changed, 83 insertions(+), 30 deletions(-) diff --git a/libqmenumodel/src/unitymenumodel.cpp b/libqmenumodel/src/unitymenumodel.cpp index 25df10b..926d2f1 100644 --- a/libqmenumodel/src/unitymenumodel.cpp +++ b/libqmenumodel/src/unitymenumodel.cpp @@ -43,57 +43,62 @@ private: GtkActionMuxer *muxer; GtkMenuTracker *menutracker; GSequence *items; + QByteArray actionGroupObjectPath; + QByteArray menuObjectPath; + static void freeMenuItem(gpointer data, gpointer user_data); + static void nameAppeared(GDBusConnection *connection, const gchar *name, const gchar *owner, gpointer user_data); + static void nameVanished(GDBusConnection *connection, const gchar *name, gpointer user_data); static void menuItemInserted(GtkMenuTrackerItem *item, gint position, gpointer user_data); static void menuItemRemoved(gint position, gpointer user_data); static void menuItemChanged(GObject *object, GParamSpec *pspec, gpointer user_data); }; +/* + * Same as g_sequence_foreach_range, but calls func with the GSequenceIter + * instead of the item. + */ +static void +g_sequence_foreach_iter_range (GSequenceIter *begin, + GSequenceIter *end, + GFunc func, + gpointer user_data) +{ + GSequenceIter *it; + + for (it = begin; it != end; it = g_sequence_iter_next (it)) + func (it, user_data); +} + UnityMenuModelPrivate::UnityMenuModelPrivate(UnityMenuModel *model, const QByteArray &busName, const QByteArray &actionGroupObjectPath, const QByteArray &menuObjectPath) { - GDBusConnection *connection; - GDBusActionGroup *actions; - GDBusMenuModel *menu; - this->model = model; - - connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); - actions = g_dbus_action_group_get (connection, busName.constData(), actionGroupObjectPath.constData()); - menu = g_dbus_menu_model_get (connection, busName.constData(), menuObjectPath.constData()); + this->actionGroupObjectPath = actionGroupObjectPath; + this->menuObjectPath = menuObjectPath; + this->menutracker = NULL; this->muxer = gtk_action_muxer_new (); g_object_set_qdata (G_OBJECT (this->muxer), unity_menu_model_quark (), model); - gtk_action_muxer_insert (this->muxer, "indicator", G_ACTION_GROUP (actions)); - - this->menutracker = gtk_menu_tracker_new (GTK_ACTION_OBSERVABLE (this->muxer), - G_MENU_MODEL (menu), TRUE, "indicator", - menuItemInserted, menuItemRemoved, this); this->items = g_sequence_new (NULL); - g_object_unref (menu); - g_object_unref (actions); - g_object_unref (connection); + g_bus_watch_name (G_BUS_TYPE_SESSION, busName.constData(), G_BUS_NAME_WATCHER_FLAGS_AUTO_START, + nameAppeared, nameVanished, this, NULL); } UnityMenuModelPrivate::~UnityMenuModelPrivate() { - GSequenceIter *it; - - it = g_sequence_get_begin_iter (this->items); - while (!g_sequence_iter_is_end (it)) { - GtkMenuTrackerItem *item = (GtkMenuTrackerItem *) g_sequence_get (it); - g_signal_handlers_disconnect_by_func (item, (gpointer) menuItemChanged, it); - g_object_unref (item); - it = g_sequence_iter_next (it); - } + g_sequence_foreach_iter_range (g_sequence_get_begin_iter (this->items), g_sequence_get_end_iter (this->items), + freeMenuItem, NULL); g_sequence_free (this->items); + if (this->menutracker) + gtk_menu_tracker_free (this->menutracker); + g_object_unref (this->muxer); - gtk_menu_tracker_free (this->menutracker); } int UnityMenuModelPrivate::nrItems() @@ -119,6 +124,57 @@ QVariant UnityMenuModelPrivate::data(int position, int role) } } +void UnityMenuModelPrivate::freeMenuItem (gpointer data, gpointer user_data) +{ + GSequenceIter *it = (GSequenceIter *) data; + GtkMenuTrackerItem *item; + + item = (GtkMenuTrackerItem *) g_sequence_get (it); + g_signal_handlers_disconnect_by_func (item, (gpointer) menuItemChanged, it); + g_object_unref (item); +} + +void UnityMenuModelPrivate::nameAppeared(GDBusConnection *connection, const gchar *name, const gchar *owner, gpointer user_data) +{ + UnityMenuModelPrivate *priv = (UnityMenuModelPrivate *)user_data; + GDBusActionGroup *actions; + GDBusMenuModel *menu; + + priv->model->beginResetModel(); + + actions = g_dbus_action_group_get (connection, owner, priv->actionGroupObjectPath.constData()); + menu = g_dbus_menu_model_get (connection, owner, priv->menuObjectPath.constData()); + + gtk_action_muxer_insert (priv->muxer, "indicator", G_ACTION_GROUP (actions)); + priv->menutracker = gtk_menu_tracker_new (GTK_ACTION_OBSERVABLE (priv->muxer), + G_MENU_MODEL (menu), TRUE, "indicator", + menuItemInserted, menuItemRemoved, priv); + + priv->model->endResetModel(); + + g_object_unref (menu); + g_object_unref (actions); +} + +void UnityMenuModelPrivate::nameVanished(GDBusConnection *connection, const gchar *name, gpointer user_data) +{ + UnityMenuModelPrivate *priv = (UnityMenuModelPrivate *)user_data; + GSequenceIter *begin; + GSequenceIter *end; + + priv->model->beginResetModel(); + + begin = g_sequence_get_begin_iter (priv->items); + end = g_sequence_get_end_iter (priv->items); + g_sequence_foreach_iter_range (begin, end, freeMenuItem, NULL); + g_sequence_remove_range (begin, end); + + gtk_action_muxer_remove (priv->muxer, "indicator"); + g_clear_pointer (&priv->menutracker, gtk_menu_tracker_free); + + priv->model->endResetModel(); +} + void UnityMenuModelPrivate::menuItemInserted(GtkMenuTrackerItem *item, gint position, gpointer user_data) { UnityMenuModelPrivate *priv = (UnityMenuModelPrivate *)user_data; @@ -137,14 +193,11 @@ void UnityMenuModelPrivate::menuItemRemoved(gint position, gpointer user_data) { UnityMenuModelPrivate *priv = (UnityMenuModelPrivate *)user_data; GSequenceIter *it; - GtkMenuTrackerItem *item; priv->model->beginRemoveRows(QModelIndex(), position, position); it = g_sequence_get_iter_at_pos (priv->items, position); - item = (GtkMenuTrackerItem *) g_sequence_get (it); - g_signal_handlers_disconnect_by_func (item, (gpointer) menuItemChanged, it); - g_object_unref (item); + freeMenuItem ((gpointer) it, NULL); g_sequence_remove (it); priv->model->endRemoveRows(); -- cgit v1.2.3 From 2a01b1cba8af5891bac632683286951e2c3f95f0 Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Tue, 4 Jun 2013 20:23:22 -0400 Subject: examples/exportmenu.py: use g_bus_own_name to avoid race This avoids the common d-bus race where a name is owned but objects aren't exported on it yet. --- examples/exportmenu.py | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/examples/exportmenu.py b/examples/exportmenu.py index 767d8b9..b7c37b1 100755 --- a/examples/exportmenu.py +++ b/examples/exportmenu.py @@ -32,22 +32,7 @@ from gi.repository import GLib BUS_NAME = 'com.canonical.testmenu' BUS_OBJECT_PATH = '/com/canonical/testmenu' - -if __name__ == '__main__': - bus = Gio.bus_get_sync(Gio.BusType.SESSION, None) - # Claim well-known bus name and ensure only one instance of self is running - # at any given time. - # http://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-names - proxy = Gio.DBusProxy.new_sync(bus, 0, None, - 'org.freedesktop.DBus', - '/org/freedesktop/DBus', - 'org.freedesktop.DBus', None) - result = proxy.RequestName('(su)', BUS_NAME, 0x4) - if result != 1 : - print >> sys.stderr, ("Name '%s' is already owned on the session bus." - "Aborting.") % BUS_NAME - sys.exit(1) - +def bus_acquired(bus, name): menu = Gio.Menu() foo = Gio.MenuItem.new('foo', 'app.foo') foo.set_attribute_value('x-additionaltext', @@ -78,5 +63,6 @@ if __name__ == '__main__': actions.add_action(Gio.SimpleAction.new("bar", None)) bus.export_action_group(BUS_OBJECT_PATH, actions) +if __name__ == '__main__': + Gio.bus_own_name(Gio.BusType.SESSION, BUS_NAME, 0, bus_acquired, None, None) GLib.MainLoop().run() - -- cgit v1.2.3 From 00885c1ddd04819c44d6a0ca2c7f2af8a1298a97 Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Tue, 4 Jun 2013 21:05:40 -0400 Subject: unitymenumodel: expose isSeparator --- examples/unityqmlmenumodel.qml | 37 ++++++++++++++++++++++++++---------- libqmenumodel/src/unitymenumodel.cpp | 4 ++++ libqmenumodel/src/unitymenumodel.h | 3 ++- 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/examples/unityqmlmenumodel.qml b/examples/unityqmlmenumodel.qml index 257866d..d38d6e5 100644 --- a/examples/unityqmlmenumodel.qml +++ b/examples/unityqmlmenumodel.qml @@ -18,16 +18,33 @@ Item { anchors.margins: 10 spacing: 3 model: menu - delegate: Rectangle { - width: parent.width - height: 40 - color: "#ddd" - Text { - anchors.fill: parent - anchors.margins: 5 - verticalAlignment: Text.AlignVCenter - color: sensitive ? "black" : "#aaa"; - text: label + + delegate: Loader { + sourceComponent: isSeparator ? separator : menuitem; + + Component { + id: separator + Rectangle { + width: parent.width + height: 4 + color: "blue" + } + } + + Component { + id: menuitem + Rectangle { + width: parent.width + height: 40 + color: "#ddd" + Text { + anchors.fill: parent + anchors.margins: 5 + verticalAlignment: Text.AlignVCenter + color: sensitive ? "black" : "#aaa"; + text: label + } + } } } } diff --git a/libqmenumodel/src/unitymenumodel.cpp b/libqmenumodel/src/unitymenumodel.cpp index 926d2f1..336b72a 100644 --- a/libqmenumodel/src/unitymenumodel.cpp +++ b/libqmenumodel/src/unitymenumodel.cpp @@ -119,6 +119,9 @@ QVariant UnityMenuModelPrivate::data(int position, int role) case UnityMenuModel::SensitiveRole: return gtk_menu_tracker_item_get_sensitive (item); + case UnityMenuModel::IsSeparatorRole: + return gtk_menu_tracker_item_get_is_separator (item); + default: return QVariant(); } @@ -275,6 +278,7 @@ QHash UnityMenuModel::roleNames() const names[LabelRole] = "label"; names[ActionRole] = "action"; names[SensitiveRole] = "sensitive"; + names[IsSeparatorRole] = "isSeparator"; return names; } diff --git a/libqmenumodel/src/unitymenumodel.h b/libqmenumodel/src/unitymenumodel.h index 12a5891..75af13a 100644 --- a/libqmenumodel/src/unitymenumodel.h +++ b/libqmenumodel/src/unitymenumodel.h @@ -29,7 +29,8 @@ public: enum MenuRoles { ActionRole = Qt::DisplayRole + 1, LabelRole, - SensitiveRole + SensitiveRole, + IsSeparatorRole }; public: -- cgit v1.2.3 From 692926cb12c8f8e6d3969c249dbf924a0bd1499b Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Wed, 5 Jun 2013 10:29:43 -0400 Subject: Link against the qml module Reverts r69, that was nonsense. --- CMakeLists.txt | 2 ++ libqmenumodel/src/CMakeLists.txt | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f3799b3..95440ca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,8 @@ cmake_minimum_required(VERSION 2.8.9) include(GNUInstallDirs) find_package(Qt5Core REQUIRED) +find_package(Qt5Qml REQUIRED) +find_package(Qt5Gui REQUIRED) include(FindPkgConfig) pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32) pkg_check_modules(GIO REQUIRED gio-2.0>=2.32) diff --git a/libqmenumodel/src/CMakeLists.txt b/libqmenumodel/src/CMakeLists.txt index d1d19c9..a2a6177 100644 --- a/libqmenumodel/src/CMakeLists.txt +++ b/libqmenumodel/src/CMakeLists.txt @@ -36,7 +36,6 @@ set_target_properties(${SHAREDLIBNAME} PROPERTIES ) include_directories( - /usr/include/qt5/QtQml ${GLIB_INCLUDE_DIRS} ${GIO_INCLUDE_DIRS} ) @@ -46,7 +45,7 @@ target_link_libraries(${SHAREDLIBNAME} ${GIO_LDFLAGS} ) -qt5_use_modules(${SHAREDLIBNAME} Core) +qt5_use_modules(${SHAREDLIBNAME} Core Qml) install(TARGETS ${SHAREDLIBNAME} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) -- cgit v1.2.3 From e735d95e613e2ee6170799002183e0770d34590e Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Wed, 5 Jun 2013 13:50:12 -0400 Subject: unitymenumodel: add activate() --- examples/unityqmlmenumodel.qml | 9 +++++++-- libqmenumodel/src/unitymenumodel.cpp | 17 +++++++++++++++++ libqmenumodel/src/unitymenumodel.h | 3 +++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/examples/unityqmlmenumodel.qml b/examples/unityqmlmenumodel.qml index d38d6e5..a66ba8e 100644 --- a/examples/unityqmlmenumodel.qml +++ b/examples/unityqmlmenumodel.qml @@ -14,6 +14,7 @@ Item { } ListView { + id: listview anchors.fill: parent anchors.margins: 10 spacing: 3 @@ -25,7 +26,7 @@ Item { Component { id: separator Rectangle { - width: parent.width + width: listview.width height: 4 color: "blue" } @@ -34,7 +35,7 @@ Item { Component { id: menuitem Rectangle { - width: parent.width + width: listview.width height: 40 color: "#ddd" Text { @@ -44,6 +45,10 @@ Item { color: sensitive ? "black" : "#aaa"; text: label } + MouseArea { + anchors.fill: parent + onClicked: listview.model.activate(index); + } } } } diff --git a/libqmenumodel/src/unitymenumodel.cpp b/libqmenumodel/src/unitymenumodel.cpp index 336b72a..8000222 100644 --- a/libqmenumodel/src/unitymenumodel.cpp +++ b/libqmenumodel/src/unitymenumodel.cpp @@ -37,6 +37,7 @@ public: int nrItems(); QVariant data(int position, int role); + void activate(int position); private: UnityMenuModel *model; @@ -127,6 +128,14 @@ QVariant UnityMenuModelPrivate::data(int position, int role) } } +void UnityMenuModelPrivate::activate(int position) +{ + GtkMenuTrackerItem *item; + + item = (GtkMenuTrackerItem *) g_sequence_get (g_sequence_get_iter_at_pos (this->items, position)); + gtk_menu_tracker_item_activated (item); +} + void UnityMenuModelPrivate::freeMenuItem (gpointer data, gpointer user_data) { GSequenceIter *it = (GSequenceIter *) data; @@ -282,3 +291,11 @@ QHash UnityMenuModel::roleNames() const return names; } + +#include + +void UnityMenuModel::activate(int index) +{ + if (priv) + priv->activate(index); +} diff --git a/libqmenumodel/src/unitymenumodel.h b/libqmenumodel/src/unitymenumodel.h index 75af13a..38dd472 100644 --- a/libqmenumodel/src/unitymenumodel.h +++ b/libqmenumodel/src/unitymenumodel.h @@ -45,6 +45,9 @@ public: QModelIndex parent(const QModelIndex &index) const; QHash roleNames() const; +public Q_SLOTS: + void activate(int index); + protected: UnityMenuModel(QObject *parent = NULL); void init(const QByteArray &busName, const QByteArray &actionGroupObjectPath, const QByteArray &menuObjectPath); -- cgit v1.2.3 From 7892fd969906e6e695c4389a893249181a31d590 Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Wed, 5 Jun 2013 17:07:05 -0400 Subject: unitymenumodel: add support for submenus --- libqmenumodel/src/unitymenumodel.cpp | 83 ++++++++++++++++++++++++++++-------- libqmenumodel/src/unitymenumodel.h | 2 + 2 files changed, 67 insertions(+), 18 deletions(-) diff --git a/libqmenumodel/src/unitymenumodel.cpp b/libqmenumodel/src/unitymenumodel.cpp index 8000222..130b73b 100644 --- a/libqmenumodel/src/unitymenumodel.cpp +++ b/libqmenumodel/src/unitymenumodel.cpp @@ -24,22 +24,24 @@ extern "C" { } G_DEFINE_QUARK (UNITY_MENU_MODEL, unity_menu_model) +G_DEFINE_QUARK (UNITY_SUBMENU_MODEL, unity_submenu_model) class UnityMenuModelPrivate { public: - UnityMenuModelPrivate(UnityMenuModel *model, - const QByteArray &busName, - const QByteArray &actionGroupObjectPath, - const QByteArray &menuObjectPath); + static UnityMenuModelPrivate * forBusMenu(UnityMenuModel *model, const QByteArray &busName, + const QByteArray &actionGroupObjectPath, const QByteArray &menuObjectPath); + static UnityMenuModelPrivate * forSubMenu(UnityMenuModel *model, GtkMenuTrackerItem *item); ~UnityMenuModelPrivate(); - int nrItems(); QVariant data(int position, int role); void activate(int position); + UnityMenuModel * submenu(int position); private: + UnityMenuModelPrivate(UnityMenuModel *model); + UnityMenuModel *model; GtkActionMuxer *muxer; GtkMenuTracker *menutracker; @@ -71,35 +73,53 @@ g_sequence_foreach_iter_range (GSequenceIter *begin, func (it, user_data); } -UnityMenuModelPrivate::UnityMenuModelPrivate(UnityMenuModel *model, - const QByteArray &busName, - const QByteArray &actionGroupObjectPath, - const QByteArray &menuObjectPath) +UnityMenuModelPrivate::UnityMenuModelPrivate(UnityMenuModel *model) { this->model = model; - this->actionGroupObjectPath = actionGroupObjectPath; - this->menuObjectPath = menuObjectPath; this->menutracker = NULL; this->muxer = gtk_action_muxer_new (); g_object_set_qdata (G_OBJECT (this->muxer), unity_menu_model_quark (), model); this->items = g_sequence_new (NULL); +} + +UnityMenuModelPrivate * UnityMenuModelPrivate::forBusMenu(UnityMenuModel *model, const QByteArray &busName, + const QByteArray &actionGroupObjectPath, const QByteArray &menuObjectPath) +{ + UnityMenuModelPrivate *priv = new UnityMenuModelPrivate(model); + + priv->actionGroupObjectPath = actionGroupObjectPath; + priv->menuObjectPath = menuObjectPath; g_bus_watch_name (G_BUS_TYPE_SESSION, busName.constData(), G_BUS_NAME_WATCHER_FLAGS_AUTO_START, - nameAppeared, nameVanished, this, NULL); + nameAppeared, nameVanished, priv, NULL); + + return priv; +} + +UnityMenuModelPrivate * UnityMenuModelPrivate::forSubMenu(UnityMenuModel *model, GtkMenuTrackerItem *item) +{ + UnityMenuModelPrivate *priv = new UnityMenuModelPrivate(model); + + priv->menutracker = gtk_menu_tracker_new_for_item_submenu (item, menuItemInserted, menuItemRemoved, priv); + + return priv; } UnityMenuModelPrivate::~UnityMenuModelPrivate() { - g_sequence_foreach_iter_range (g_sequence_get_begin_iter (this->items), g_sequence_get_end_iter (this->items), - freeMenuItem, NULL); - g_sequence_free (this->items); + if (this->items) { + g_sequence_foreach_iter_range (g_sequence_get_begin_iter (this->items), g_sequence_get_end_iter (this->items), + freeMenuItem, NULL); + g_sequence_free (this->items); + } if (this->menutracker) gtk_menu_tracker_free (this->menutracker); - g_object_unref (this->muxer); + if (this->muxer) + g_object_unref (this->muxer); } int UnityMenuModelPrivate::nrItems() @@ -136,6 +156,30 @@ void UnityMenuModelPrivate::activate(int position) gtk_menu_tracker_item_activated (item); } +UnityMenuModel * UnityMenuModelPrivate::submenu(int position) +{ + GSequenceIter *it; + GtkMenuTrackerItem *item; + UnityMenuModel *model; + + it = g_sequence_get_iter_at_pos (this->items, position); + if (g_sequence_iter_is_end (it)) + return NULL; + + item = (GtkMenuTrackerItem *) g_sequence_get (it); + if (!gtk_menu_tracker_item_get_has_submenu (item)) + return NULL; + + model = (UnityMenuModel *) g_object_get_qdata (G_OBJECT (item), unity_submenu_model_quark ()); + if (model == NULL) { + model = new UnityMenuModel(this->model); + model->priv = UnityMenuModelPrivate::forSubMenu(model, item); + g_object_set_qdata (G_OBJECT (item), unity_submenu_model_quark (), model); + } + + return model; +} + void UnityMenuModelPrivate::freeMenuItem (gpointer data, gpointer user_data) { GSequenceIter *it = (GSequenceIter *) data; @@ -247,7 +291,7 @@ UnityMenuModel::UnityMenuModel(const QByteArray &busName, void UnityMenuModel::init(const QByteArray &busName, const QByteArray &actionGroupObjectPath, const QByteArray &menuObjectPath) { - priv = new UnityMenuModelPrivate (this, busName, actionGroupObjectPath, menuObjectPath); + priv = UnityMenuModelPrivate::forBusMenu(this, busName, actionGroupObjectPath, menuObjectPath); } UnityMenuModel::~UnityMenuModel() @@ -292,7 +336,10 @@ QHash UnityMenuModel::roleNames() const return names; } -#include +QObject * UnityMenuModel::submenu(int position) +{ + return priv ? priv->submenu(position) : NULL; +} void UnityMenuModel::activate(int index) { diff --git a/libqmenumodel/src/unitymenumodel.h b/libqmenumodel/src/unitymenumodel.h index 38dd472..6e735a4 100644 --- a/libqmenumodel/src/unitymenumodel.h +++ b/libqmenumodel/src/unitymenumodel.h @@ -45,6 +45,8 @@ public: QModelIndex parent(const QModelIndex &index) const; QHash roleNames() const; + Q_INVOKABLE QObject * submenu(int position); + public Q_SLOTS: void activate(int index); -- cgit v1.2.3 From 45ffd9162f48bab8607a426b72b38f937f4b65f3 Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Wed, 5 Jun 2013 17:47:08 -0400 Subject: unitymenumodel: call init in the the constructor that is meant to be used from c++ --- libqmenumodel/src/unitymenumodel.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libqmenumodel/src/unitymenumodel.cpp b/libqmenumodel/src/unitymenumodel.cpp index 130b73b..45cb9c4 100644 --- a/libqmenumodel/src/unitymenumodel.cpp +++ b/libqmenumodel/src/unitymenumodel.cpp @@ -287,6 +287,7 @@ UnityMenuModel::UnityMenuModel(const QByteArray &busName, QAbstractListModel(parent), priv(NULL) { + init(busName, actionGroupObjectPath, menuObjectPath); } void UnityMenuModel::init(const QByteArray &busName, const QByteArray &actionGroupObjectPath, const QByteArray &menuObjectPath) -- cgit v1.2.3 From bf4e73f64ddc76a8c1ed98bddcb665401adc5402 Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Mon, 24 Jun 2013 12:10:56 -0400 Subject: Move unityqmlmenumodel's properties to unitymenumodel This gets rid of the ad-hoc construct-only properties and ::init() vfunc. Names and paths can now be changed after creating the menu model. This will probably be used seldom in practice, but it makes the code a bit cleaner. Unityqmlmenumodel doesn't do anything interesting right now. --- libqmenumodel/src/unitymenumodel.cpp | 175 +++++++++++++++++++++----------- libqmenumodel/src/unitymenumodel.h | 24 +++-- libqmenumodel/src/unityqmlmenumodel.cpp | 66 ------------ libqmenumodel/src/unityqmlmenumodel.h | 17 ---- 4 files changed, 133 insertions(+), 149 deletions(-) diff --git a/libqmenumodel/src/unitymenumodel.cpp b/libqmenumodel/src/unitymenumodel.cpp index 45cb9c4..ba1fac4 100644 --- a/libqmenumodel/src/unitymenumodel.cpp +++ b/libqmenumodel/src/unitymenumodel.cpp @@ -29,8 +29,8 @@ G_DEFINE_QUARK (UNITY_SUBMENU_MODEL, unity_submenu_model) class UnityMenuModelPrivate { public: - static UnityMenuModelPrivate * forBusMenu(UnityMenuModel *model, const QByteArray &busName, - const QByteArray &actionGroupObjectPath, const QByteArray &menuObjectPath); + UnityMenuModelPrivate(UnityMenuModel *model); + static UnityMenuModelPrivate * forSubMenu(UnityMenuModel *model, GtkMenuTrackerItem *item); ~UnityMenuModelPrivate(); @@ -39,14 +39,20 @@ public: void activate(int position); UnityMenuModel * submenu(int position); -private: - UnityMenuModelPrivate(UnityMenuModel *model); + void clearItems(bool resetModel=true); + void clearName(); + void updateActions(); + void updateMenuModel(); UnityMenuModel *model; GtkActionMuxer *muxer; GtkMenuTracker *menutracker; GSequence *items; - QByteArray actionGroupObjectPath; + GDBusConnection *connection; + QByteArray busName; + QByteArray nameOwner; + guint nameWatchId; + QByteArray actionObjectPath; QByteArray menuObjectPath; static void freeMenuItem(gpointer data, gpointer user_data); @@ -77,6 +83,7 @@ UnityMenuModelPrivate::UnityMenuModelPrivate(UnityMenuModel *model) { this->model = model; this->menutracker = NULL; + this->nameWatchId = 0; this->muxer = gtk_action_muxer_new (); g_object_set_qdata (G_OBJECT (this->muxer), unity_menu_model_quark (), model); @@ -84,20 +91,6 @@ UnityMenuModelPrivate::UnityMenuModelPrivate(UnityMenuModel *model) this->items = g_sequence_new (NULL); } -UnityMenuModelPrivate * UnityMenuModelPrivate::forBusMenu(UnityMenuModel *model, const QByteArray &busName, - const QByteArray &actionGroupObjectPath, const QByteArray &menuObjectPath) -{ - UnityMenuModelPrivate *priv = new UnityMenuModelPrivate(model); - - priv->actionGroupObjectPath = actionGroupObjectPath; - priv->menuObjectPath = menuObjectPath; - - g_bus_watch_name (G_BUS_TYPE_SESSION, busName.constData(), G_BUS_NAME_WATCHER_FLAGS_AUTO_START, - nameAppeared, nameVanished, priv, NULL); - - return priv; -} - UnityMenuModelPrivate * UnityMenuModelPrivate::forSubMenu(UnityMenuModel *model, GtkMenuTrackerItem *item) { UnityMenuModelPrivate *priv = new UnityMenuModelPrivate(model); @@ -109,17 +102,18 @@ UnityMenuModelPrivate * UnityMenuModelPrivate::forSubMenu(UnityMenuModel *model, UnityMenuModelPrivate::~UnityMenuModelPrivate() { - if (this->items) { - g_sequence_foreach_iter_range (g_sequence_get_begin_iter (this->items), g_sequence_get_end_iter (this->items), - freeMenuItem, NULL); - g_sequence_free (this->items); - } + this->clearItems(false); if (this->menutracker) gtk_menu_tracker_free (this->menutracker); if (this->muxer) g_object_unref (this->muxer); + + g_clear_object (&this->connection); + + if (this->nameWatchId) + g_bus_unwatch_name (this->nameWatchId); } int UnityMenuModelPrivate::nrItems() @@ -190,45 +184,81 @@ void UnityMenuModelPrivate::freeMenuItem (gpointer data, gpointer user_data) g_object_unref (item); } -void UnityMenuModelPrivate::nameAppeared(GDBusConnection *connection, const gchar *name, const gchar *owner, gpointer user_data) +void UnityMenuModelPrivate::clearItems(bool resetModel) { - UnityMenuModelPrivate *priv = (UnityMenuModelPrivate *)user_data; - GDBusActionGroup *actions; - GDBusMenuModel *menu; + GSequenceIter *begin; + GSequenceIter *end; + + if (resetModel) + model->beginResetModel(); - priv->model->beginResetModel(); + begin = g_sequence_get_begin_iter (this->items); + end = g_sequence_get_end_iter (this->items); + g_sequence_foreach_iter_range (begin, end, freeMenuItem, NULL); + g_sequence_remove_range (begin, end); - actions = g_dbus_action_group_get (connection, owner, priv->actionGroupObjectPath.constData()); - menu = g_dbus_menu_model_get (connection, owner, priv->menuObjectPath.constData()); + if (resetModel) + model->endResetModel(); +} - gtk_action_muxer_insert (priv->muxer, "indicator", G_ACTION_GROUP (actions)); - priv->menutracker = gtk_menu_tracker_new (GTK_ACTION_OBSERVABLE (priv->muxer), - G_MENU_MODEL (menu), TRUE, "indicator", - menuItemInserted, menuItemRemoved, priv); +void UnityMenuModelPrivate::clearName() +{ + this->clearItems(); - priv->model->endResetModel(); + this->nameOwner = QByteArray(); - g_object_unref (menu); - g_object_unref (actions); + this->updateActions(); + this->updateMenuModel(); } -void UnityMenuModelPrivate::nameVanished(GDBusConnection *connection, const gchar *name, gpointer user_data) +void UnityMenuModelPrivate::updateActions() +{ + if (!this->nameOwner.isEmpty()) { + GDBusActionGroup *actions; + + actions = g_dbus_action_group_get (this->connection, this->nameOwner, this->actionObjectPath.constData()); + gtk_action_muxer_insert (this->muxer, "indicator", G_ACTION_GROUP (actions)); + + g_object_unref (actions); + } + else { + gtk_action_muxer_remove (this->muxer, "indicator"); + } +} + +void UnityMenuModelPrivate::updateMenuModel() +{ + this->clearItems(); + g_clear_pointer (&this->menutracker, gtk_menu_tracker_free); + + if (!this->nameOwner.isEmpty()) { + GDBusMenuModel *menu; + + menu = g_dbus_menu_model_get (this->connection, this->nameOwner, this->menuObjectPath.constData()); + this->menutracker = gtk_menu_tracker_new (GTK_ACTION_OBSERVABLE (this->muxer), + G_MENU_MODEL (menu), TRUE, "indicator", + menuItemInserted, menuItemRemoved, this); + + g_object_unref (menu); + } +} + +void UnityMenuModelPrivate::nameAppeared(GDBusConnection *connection, const gchar *name, const gchar *owner, gpointer user_data) { UnityMenuModelPrivate *priv = (UnityMenuModelPrivate *)user_data; - GSequenceIter *begin; - GSequenceIter *end; - priv->model->beginResetModel(); + priv->connection = (GDBusConnection *) g_object_ref (connection); + priv->nameOwner = owner; - begin = g_sequence_get_begin_iter (priv->items); - end = g_sequence_get_end_iter (priv->items); - g_sequence_foreach_iter_range (begin, end, freeMenuItem, NULL); - g_sequence_remove_range (begin, end); + priv->updateActions(); + priv->updateMenuModel(); +} - gtk_action_muxer_remove (priv->muxer, "indicator"); - g_clear_pointer (&priv->menutracker, gtk_menu_tracker_free); +void UnityMenuModelPrivate::nameVanished(GDBusConnection *connection, const gchar *name, gpointer user_data) +{ + UnityMenuModelPrivate *priv = (UnityMenuModelPrivate *)user_data; - priv->model->endResetModel(); + priv->clearName(); } void UnityMenuModelPrivate::menuItemInserted(GtkMenuTrackerItem *item, gint position, gpointer user_data) @@ -278,26 +308,51 @@ void UnityMenuModelPrivate::menuItemChanged(GObject *object, GParamSpec *pspec, UnityMenuModel::UnityMenuModel(QObject *parent): QAbstractListModel(parent) { + priv = new UnityMenuModelPrivate(this); +} + +UnityMenuModel::~UnityMenuModel() +{ + delete priv; } -UnityMenuModel::UnityMenuModel(const QByteArray &busName, - const QByteArray &actionGroupObjectPath, - const QByteArray &menuObjectPath, - QObject *parent): - QAbstractListModel(parent), - priv(NULL) +QByteArray UnityMenuModel::busName() const { - init(busName, actionGroupObjectPath, menuObjectPath); + return priv->busName; } -void UnityMenuModel::init(const QByteArray &busName, const QByteArray &actionGroupObjectPath, const QByteArray &menuObjectPath) +void UnityMenuModel::setBusName(const QByteArray &name) { - priv = UnityMenuModelPrivate::forBusMenu(this, busName, actionGroupObjectPath, menuObjectPath); + priv->clearName(); + + if (priv->nameWatchId) + g_bus_unwatch_name (priv->nameWatchId); + + priv->nameWatchId = g_bus_watch_name (G_BUS_TYPE_SESSION, name.constData(), G_BUS_NAME_WATCHER_FLAGS_AUTO_START, + UnityMenuModelPrivate::nameAppeared, UnityMenuModelPrivate::nameVanished, + priv, NULL); } -UnityMenuModel::~UnityMenuModel() +QByteArray UnityMenuModel::actionObjectPath() const { - delete priv; + return priv->actionObjectPath; +} + +void UnityMenuModel::setActionObjectPath(const QByteArray &path) +{ + priv->actionObjectPath = path; + priv->updateActions(); +} + +QByteArray UnityMenuModel::menuObjectPath() const +{ + return priv->menuObjectPath; +} + +void UnityMenuModel::setMenuObjectPath(const QByteArray &path) +{ + priv->menuObjectPath = path; + priv->updateMenuModel(); } int UnityMenuModel::rowCount(const QModelIndex &parent) const diff --git a/libqmenumodel/src/unitymenumodel.h b/libqmenumodel/src/unitymenumodel.h index 6e735a4..cdfceac 100644 --- a/libqmenumodel/src/unitymenumodel.h +++ b/libqmenumodel/src/unitymenumodel.h @@ -24,6 +24,9 @@ class UnityMenuModel: public QAbstractListModel { Q_OBJECT + Q_PROPERTY(QByteArray busName READ busName WRITE setBusName NOTIFY busNameChanged) + Q_PROPERTY(QByteArray actionObjectPath READ actionObjectPath WRITE setActionObjectPath NOTIFY actionObjectPathChanged) + Q_PROPERTY(QByteArray menuObjectPath READ menuObjectPath WRITE setMenuObjectPath NOTIFY menuObjectPathChanged) public: enum MenuRoles { @@ -34,10 +37,18 @@ public: }; public: - UnityMenuModel(const QByteArray &busName, const QByteArray &actionGroupObjectPath, - const QByteArray &menuObjectPath, QObject *parent = NULL); + UnityMenuModel(QObject *parent = NULL); virtual ~UnityMenuModel(); + QByteArray busName() const; + void setBusName(const QByteArray &name); + + QByteArray actionObjectPath() const; + void setActionObjectPath(const QByteArray &path); + + QByteArray menuObjectPath() const; + void setMenuObjectPath(const QByteArray &path); + int rowCount(const QModelIndex &parent = QModelIndex()) const; int columnCount(const QModelIndex &parent = QModelIndex()) const; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; @@ -47,13 +58,14 @@ public: Q_INVOKABLE QObject * submenu(int position); +Q_SIGNALS: + void busNameChanged(const QByteArray &name); + void actionObjectPathChanged(const QByteArray &path); + void menuObjectPathChanged(const QByteArray &path); + public Q_SLOTS: void activate(int index); -protected: - UnityMenuModel(QObject *parent = NULL); - void init(const QByteArray &busName, const QByteArray &actionGroupObjectPath, const QByteArray &menuObjectPath); - private: class UnityMenuModelPrivate *priv; friend class UnityMenuModelPrivate; diff --git a/libqmenumodel/src/unityqmlmenumodel.cpp b/libqmenumodel/src/unityqmlmenumodel.cpp index 775d974..36bb2bd 100644 --- a/libqmenumodel/src/unityqmlmenumodel.cpp +++ b/libqmenumodel/src/unityqmlmenumodel.cpp @@ -18,22 +18,13 @@ #include "unityqmlmenumodel.h" -struct UnityQmlMenuModelPrivate -{ - QByteArray busName; - QByteArray actionObjectPath; - QByteArray menuObjectPath; -}; - UnityQmlMenuModel::UnityQmlMenuModel(QObject *parent): UnityMenuModel(parent) { - priv = new UnityQmlMenuModelPrivate; } UnityQmlMenuModel::~UnityQmlMenuModel() { - delete priv; } void UnityQmlMenuModel::classBegin() @@ -42,61 +33,4 @@ void UnityQmlMenuModel::classBegin() void UnityQmlMenuModel::componentComplete() { - if (priv->busName.isEmpty()) - qWarning("UnityQmlMenuModel: property 'busName' must be set"); - else if (priv->actionObjectPath.isEmpty()) - qWarning("UnityQmlMenuModel: property 'actionObjectPath' must be set"); - else if (priv->menuObjectPath.isEmpty()) - qWarning("UnityQmlMenuModel: property 'menuObjectPath' must be set"); - else - UnityQmlMenuModel::init(priv->busName, priv->actionObjectPath, priv->menuObjectPath); -} - -QByteArray UnityQmlMenuModel::busName() const -{ - return priv->busName; -} - -void UnityQmlMenuModel::setBusName(const QByteArray &name) -{ - if (!priv->busName.isEmpty()) { - qWarning("UnityQmlMenuModel: cannot change bus name after creation"); - return; - } - - priv->busName = name; - Q_EMIT busNameChanged(name); -} - -QByteArray UnityQmlMenuModel::actionObjectPath() const -{ - return priv->actionObjectPath; } - -void UnityQmlMenuModel::setActionObjectPath(const QByteArray &path) -{ - if (!priv->actionObjectPath.isEmpty()) { - qWarning("UnityQmlMenuModel: cannot change object paths after creation"); - return; - } - - priv->actionObjectPath = path; - Q_EMIT actionObjectPathChanged(path); -} - -QByteArray UnityQmlMenuModel::menuObjectPath() const -{ - return priv->menuObjectPath; -} - -void UnityQmlMenuModel::setMenuObjectPath(const QByteArray &path) -{ - if (!priv->menuObjectPath.isEmpty()) { - qWarning("UnityQmlMenuModel: cannot change object paths after creation"); - return; - } - - priv->menuObjectPath = path; - Q_EMIT menuObjectPathChanged(path); -} - diff --git a/libqmenumodel/src/unityqmlmenumodel.h b/libqmenumodel/src/unityqmlmenumodel.h index a375e8e..d57d52e 100644 --- a/libqmenumodel/src/unityqmlmenumodel.h +++ b/libqmenumodel/src/unityqmlmenumodel.h @@ -27,9 +27,6 @@ class UnityQmlMenuModel: public UnityMenuModel, public QQmlParserStatus { Q_OBJECT Q_INTERFACES(QQmlParserStatus) - Q_PROPERTY(QByteArray busName READ busName WRITE setBusName NOTIFY busNameChanged) - Q_PROPERTY(QByteArray actionObjectPath READ actionObjectPath WRITE setActionObjectPath NOTIFY actionObjectPathChanged) - Q_PROPERTY(QByteArray menuObjectPath READ menuObjectPath WRITE setMenuObjectPath NOTIFY menuObjectPathChanged) public: UnityQmlMenuModel(QObject *parent = NULL); @@ -38,20 +35,6 @@ public: void classBegin(); void componentComplete(); - QByteArray busName() const; - void setBusName(const QByteArray &name); - - QByteArray actionObjectPath() const; - void setActionObjectPath(const QByteArray &path); - - QByteArray menuObjectPath() const; - void setMenuObjectPath(const QByteArray &path); - -Q_SIGNALS: - void busNameChanged(const QByteArray &name); - void actionObjectPathChanged(const QByteArray &path); - void menuObjectPathChanged(const QByteArray &path); - private: struct UnityQmlMenuModelPrivate *priv; }; -- cgit v1.2.3 From a74628a3db43bdf8c0f350ebcee28c99bd494a91 Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Mon, 24 Jun 2013 12:20:42 -0400 Subject: unitymenumodel: remove redundant priv functions --- libqmenumodel/src/unitymenumodel.cpp | 109 ++++++++++++++--------------------- 1 file changed, 42 insertions(+), 67 deletions(-) diff --git a/libqmenumodel/src/unitymenumodel.cpp b/libqmenumodel/src/unitymenumodel.cpp index ba1fac4..2f20a8b 100644 --- a/libqmenumodel/src/unitymenumodel.cpp +++ b/libqmenumodel/src/unitymenumodel.cpp @@ -34,10 +34,6 @@ public: static UnityMenuModelPrivate * forSubMenu(UnityMenuModel *model, GtkMenuTrackerItem *item); ~UnityMenuModelPrivate(); - int nrItems(); - QVariant data(int position, int role); - void activate(int position); - UnityMenuModel * submenu(int position); void clearItems(bool resetModel=true); void clearName(); @@ -116,64 +112,6 @@ UnityMenuModelPrivate::~UnityMenuModelPrivate() g_bus_unwatch_name (this->nameWatchId); } -int UnityMenuModelPrivate::nrItems() -{ - return g_sequence_get_length (this->items); -} - -QVariant UnityMenuModelPrivate::data(int position, int role) -{ - GtkMenuTrackerItem *item; - - item = (GtkMenuTrackerItem *) g_sequence_get (g_sequence_get_iter_at_pos (this->items, position)); - - switch (role) { - case UnityMenuModel::LabelRole: - return gtk_menu_tracker_item_get_label (item); - - case UnityMenuModel::SensitiveRole: - return gtk_menu_tracker_item_get_sensitive (item); - - case UnityMenuModel::IsSeparatorRole: - return gtk_menu_tracker_item_get_is_separator (item); - - default: - return QVariant(); - } -} - -void UnityMenuModelPrivate::activate(int position) -{ - GtkMenuTrackerItem *item; - - item = (GtkMenuTrackerItem *) g_sequence_get (g_sequence_get_iter_at_pos (this->items, position)); - gtk_menu_tracker_item_activated (item); -} - -UnityMenuModel * UnityMenuModelPrivate::submenu(int position) -{ - GSequenceIter *it; - GtkMenuTrackerItem *item; - UnityMenuModel *model; - - it = g_sequence_get_iter_at_pos (this->items, position); - if (g_sequence_iter_is_end (it)) - return NULL; - - item = (GtkMenuTrackerItem *) g_sequence_get (it); - if (!gtk_menu_tracker_item_get_has_submenu (item)) - return NULL; - - model = (UnityMenuModel *) g_object_get_qdata (G_OBJECT (item), unity_submenu_model_quark ()); - if (model == NULL) { - model = new UnityMenuModel(this->model); - model->priv = UnityMenuModelPrivate::forSubMenu(model, item); - g_object_set_qdata (G_OBJECT (item), unity_submenu_model_quark (), model); - } - - return model; -} - void UnityMenuModelPrivate::freeMenuItem (gpointer data, gpointer user_data) { GSequenceIter *it = (GSequenceIter *) data; @@ -357,7 +295,7 @@ void UnityMenuModel::setMenuObjectPath(const QByteArray &path) int UnityMenuModel::rowCount(const QModelIndex &parent) const { - return priv && !parent.isValid() ? priv->nrItems() : 0; + return !parent.isValid() ? g_sequence_get_length (priv->items) : 0; } int UnityMenuModel::columnCount(const QModelIndex &parent) const @@ -367,7 +305,23 @@ int UnityMenuModel::columnCount(const QModelIndex &parent) const QVariant UnityMenuModel::data(const QModelIndex &index, int role) const { - return priv ? priv->data(index.row(), role) : QVariant(); + GtkMenuTrackerItem *item; + + item = (GtkMenuTrackerItem *) g_sequence_get (g_sequence_get_iter_at_pos (priv->items, index.row())); + + switch (role) { + case UnityMenuModel::LabelRole: + return gtk_menu_tracker_item_get_label (item); + + case UnityMenuModel::SensitiveRole: + return gtk_menu_tracker_item_get_sensitive (item); + + case UnityMenuModel::IsSeparatorRole: + return gtk_menu_tracker_item_get_is_separator (item); + + default: + return QVariant(); + } } QModelIndex UnityMenuModel::index(int row, int column, const QModelIndex &parent) const @@ -394,11 +348,32 @@ QHash UnityMenuModel::roleNames() const QObject * UnityMenuModel::submenu(int position) { - return priv ? priv->submenu(position) : NULL; + GSequenceIter *it; + GtkMenuTrackerItem *item; + UnityMenuModel *model; + + it = g_sequence_get_iter_at_pos (priv->items, position); + if (g_sequence_iter_is_end (it)) + return NULL; + + item = (GtkMenuTrackerItem *) g_sequence_get (it); + if (!gtk_menu_tracker_item_get_has_submenu (item)) + return NULL; + + model = (UnityMenuModel *) g_object_get_qdata (G_OBJECT (item), unity_submenu_model_quark ()); + if (model == NULL) { + model = new UnityMenuModel(this); + model->priv = UnityMenuModelPrivate::forSubMenu(model, item); + g_object_set_qdata (G_OBJECT (item), unity_submenu_model_quark (), model); + } + + return model; } void UnityMenuModel::activate(int index) { - if (priv) - priv->activate(index); + GtkMenuTrackerItem *item; + + item = (GtkMenuTrackerItem *) g_sequence_get (g_sequence_get_iter_at_pos (priv->items, index)); + gtk_menu_tracker_item_activated (item); } -- cgit v1.2.3 From f59e06eaf20e69ee5ab3c7b5cc2734463b37dae4 Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Mon, 24 Jun 2013 12:22:28 -0400 Subject: unitymenumodel: move role enum into .cpp No need for that to be public. --- libqmenumodel/src/unitymenumodel.cpp | 13 ++++++++++--- libqmenumodel/src/unitymenumodel.h | 8 -------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/libqmenumodel/src/unitymenumodel.cpp b/libqmenumodel/src/unitymenumodel.cpp index 2f20a8b..f370832 100644 --- a/libqmenumodel/src/unitymenumodel.cpp +++ b/libqmenumodel/src/unitymenumodel.cpp @@ -26,6 +26,13 @@ extern "C" { G_DEFINE_QUARK (UNITY_MENU_MODEL, unity_menu_model) G_DEFINE_QUARK (UNITY_SUBMENU_MODEL, unity_submenu_model) +enum MenuRoles { + ActionRole = Qt::DisplayRole + 1, + LabelRole, + SensitiveRole, + IsSeparatorRole +}; + class UnityMenuModelPrivate { public: @@ -310,13 +317,13 @@ QVariant UnityMenuModel::data(const QModelIndex &index, int role) const item = (GtkMenuTrackerItem *) g_sequence_get (g_sequence_get_iter_at_pos (priv->items, index.row())); switch (role) { - case UnityMenuModel::LabelRole: + case LabelRole: return gtk_menu_tracker_item_get_label (item); - case UnityMenuModel::SensitiveRole: + case SensitiveRole: return gtk_menu_tracker_item_get_sensitive (item); - case UnityMenuModel::IsSeparatorRole: + case IsSeparatorRole: return gtk_menu_tracker_item_get_is_separator (item); default: diff --git a/libqmenumodel/src/unitymenumodel.h b/libqmenumodel/src/unitymenumodel.h index cdfceac..27a9fff 100644 --- a/libqmenumodel/src/unitymenumodel.h +++ b/libqmenumodel/src/unitymenumodel.h @@ -28,14 +28,6 @@ class UnityMenuModel: public QAbstractListModel Q_PROPERTY(QByteArray actionObjectPath READ actionObjectPath WRITE setActionObjectPath NOTIFY actionObjectPathChanged) Q_PROPERTY(QByteArray menuObjectPath READ menuObjectPath WRITE setMenuObjectPath NOTIFY menuObjectPathChanged) -public: - enum MenuRoles { - ActionRole = Qt::DisplayRole + 1, - LabelRole, - SensitiveRole, - IsSeparatorRole - }; - public: UnityMenuModel(QObject *parent = NULL); virtual ~UnityMenuModel(); -- cgit v1.2.3 From edad4063970e74ac826c5a01c09e9c3917eca40f Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Mon, 24 Jun 2013 12:48:06 -0400 Subject: unitymenumodel: remove forSubMenu --- libqmenumodel/src/unitymenumodel.cpp | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/libqmenumodel/src/unitymenumodel.cpp b/libqmenumodel/src/unitymenumodel.cpp index f370832..4507c29 100644 --- a/libqmenumodel/src/unitymenumodel.cpp +++ b/libqmenumodel/src/unitymenumodel.cpp @@ -37,9 +37,6 @@ class UnityMenuModelPrivate { public: UnityMenuModelPrivate(UnityMenuModel *model); - - static UnityMenuModelPrivate * forSubMenu(UnityMenuModel *model, GtkMenuTrackerItem *item); - ~UnityMenuModelPrivate(); void clearItems(bool resetModel=true); @@ -86,6 +83,7 @@ UnityMenuModelPrivate::UnityMenuModelPrivate(UnityMenuModel *model) { this->model = model; this->menutracker = NULL; + this->connection = NULL; this->nameWatchId = 0; this->muxer = gtk_action_muxer_new (); @@ -94,15 +92,6 @@ UnityMenuModelPrivate::UnityMenuModelPrivate(UnityMenuModel *model) this->items = g_sequence_new (NULL); } -UnityMenuModelPrivate * UnityMenuModelPrivate::forSubMenu(UnityMenuModel *model, GtkMenuTrackerItem *item) -{ - UnityMenuModelPrivate *priv = new UnityMenuModelPrivate(model); - - priv->menutracker = gtk_menu_tracker_new_for_item_submenu (item, menuItemInserted, menuItemRemoved, priv); - - return priv; -} - UnityMenuModelPrivate::~UnityMenuModelPrivate() { this->clearItems(false); @@ -370,7 +359,11 @@ QObject * UnityMenuModel::submenu(int position) model = (UnityMenuModel *) g_object_get_qdata (G_OBJECT (item), unity_submenu_model_quark ()); if (model == NULL) { model = new UnityMenuModel(this); - model->priv = UnityMenuModelPrivate::forSubMenu(model, item); + model->priv = new UnityMenuModelPrivate(model); + model->priv->menutracker = gtk_menu_tracker_new_for_item_submenu (item, + UnityMenuModelPrivate::menuItemInserted, + UnityMenuModelPrivate::menuItemRemoved, + model->priv); g_object_set_qdata (G_OBJECT (item), unity_submenu_model_quark (), model); } -- cgit v1.2.3 From 7b0620547a3a42c3aefa926312cc9d06b87e817c Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Mon, 24 Jun 2013 12:50:25 -0400 Subject: unitymenumodel: use g_clear_object --- libqmenumodel/src/unitymenumodel.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/libqmenumodel/src/unitymenumodel.cpp b/libqmenumodel/src/unitymenumodel.cpp index 4507c29..525e115 100644 --- a/libqmenumodel/src/unitymenumodel.cpp +++ b/libqmenumodel/src/unitymenumodel.cpp @@ -96,12 +96,8 @@ UnityMenuModelPrivate::~UnityMenuModelPrivate() { this->clearItems(false); - if (this->menutracker) - gtk_menu_tracker_free (this->menutracker); - - if (this->muxer) - g_object_unref (this->muxer); - + g_clear_pointer (&this->menutracker, gtk_menu_tracker_free); + g_clear_object (&this->muxer); g_clear_object (&this->connection); if (this->nameWatchId) -- cgit v1.2.3 From 1bc24273997f94cae871613dd655ab548f97565c Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Mon, 24 Jun 2013 13:09:43 -0400 Subject: unitymenumodel: set free func on GSequence Makes item removal code much cleaner (no need for the explicit free), at the cost of another qdata. --- libqmenumodel/src/unitymenumodel.cpp | 39 +++++++++--------------------------- 1 file changed, 10 insertions(+), 29 deletions(-) diff --git a/libqmenumodel/src/unitymenumodel.cpp b/libqmenumodel/src/unitymenumodel.cpp index 525e115..58b9fda 100644 --- a/libqmenumodel/src/unitymenumodel.cpp +++ b/libqmenumodel/src/unitymenumodel.cpp @@ -25,6 +25,7 @@ extern "C" { G_DEFINE_QUARK (UNITY_MENU_MODEL, unity_menu_model) G_DEFINE_QUARK (UNITY_SUBMENU_MODEL, unity_submenu_model) +G_DEFINE_QUARK (UNITY_MENU_ITEM_ITERATOR, unity_menu_item_iterator) enum MenuRoles { ActionRole = Qt::DisplayRole + 1, @@ -55,7 +56,6 @@ public: QByteArray actionObjectPath; QByteArray menuObjectPath; - static void freeMenuItem(gpointer data, gpointer user_data); static void nameAppeared(GDBusConnection *connection, const gchar *name, const gchar *owner, gpointer user_data); static void nameVanished(GDBusConnection *connection, const gchar *name, gpointer user_data); static void menuItemInserted(GtkMenuTrackerItem *item, gint position, gpointer user_data); @@ -63,20 +63,12 @@ public: static void menuItemChanged(GObject *object, GParamSpec *pspec, gpointer user_data); }; -/* - * Same as g_sequence_foreach_range, but calls func with the GSequenceIter - * instead of the item. - */ -static void -g_sequence_foreach_iter_range (GSequenceIter *begin, - GSequenceIter *end, - GFunc func, - gpointer user_data) +void menu_item_free (gpointer data) { - GSequenceIter *it; + GtkMenuTrackerItem *item = (GtkMenuTrackerItem *) data; - for (it = begin; it != end; it = g_sequence_iter_next (it)) - func (it, user_data); + g_signal_handlers_disconnect_by_func (item, (gpointer) UnityMenuModelPrivate::menuItemChanged, NULL); + g_object_unref (item); } UnityMenuModelPrivate::UnityMenuModelPrivate(UnityMenuModel *model) @@ -89,7 +81,7 @@ UnityMenuModelPrivate::UnityMenuModelPrivate(UnityMenuModel *model) this->muxer = gtk_action_muxer_new (); g_object_set_qdata (G_OBJECT (this->muxer), unity_menu_model_quark (), model); - this->items = g_sequence_new (NULL); + this->items = g_sequence_new (menu_item_free); } UnityMenuModelPrivate::~UnityMenuModelPrivate() @@ -104,16 +96,6 @@ UnityMenuModelPrivate::~UnityMenuModelPrivate() g_bus_unwatch_name (this->nameWatchId); } -void UnityMenuModelPrivate::freeMenuItem (gpointer data, gpointer user_data) -{ - GSequenceIter *it = (GSequenceIter *) data; - GtkMenuTrackerItem *item; - - item = (GtkMenuTrackerItem *) g_sequence_get (it); - g_signal_handlers_disconnect_by_func (item, (gpointer) menuItemChanged, it); - g_object_unref (item); -} - void UnityMenuModelPrivate::clearItems(bool resetModel) { GSequenceIter *begin; @@ -124,7 +106,6 @@ void UnityMenuModelPrivate::clearItems(bool resetModel) begin = g_sequence_get_begin_iter (this->items); end = g_sequence_get_end_iter (this->items); - g_sequence_foreach_iter_range (begin, end, freeMenuItem, NULL); g_sequence_remove_range (begin, end); if (resetModel) @@ -199,6 +180,7 @@ void UnityMenuModelPrivate::menuItemInserted(GtkMenuTrackerItem *item, gint posi priv->model->beginInsertRows(QModelIndex(), position, position); it = g_sequence_get_iter_at_pos (priv->items, position); + g_object_set_qdata (G_OBJECT (item), unity_menu_item_iterator_quark (), it); g_signal_connect (item, "notify", G_CALLBACK (menuItemChanged), it); g_sequence_insert_before (it, g_object_ref (item)); @@ -212,21 +194,20 @@ void UnityMenuModelPrivate::menuItemRemoved(gint position, gpointer user_data) priv->model->beginRemoveRows(QModelIndex(), position, position); - it = g_sequence_get_iter_at_pos (priv->items, position); - freeMenuItem ((gpointer) it, NULL); - g_sequence_remove (it); + g_sequence_remove (g_sequence_get_iter_at_pos (priv->items, position)); priv->model->endRemoveRows(); } void UnityMenuModelPrivate::menuItemChanged(GObject *object, GParamSpec *pspec, gpointer user_data) { - GSequenceIter *it = (GSequenceIter *) user_data; + GSequenceIter *it; GtkMenuTrackerItem *item; GtkActionObservable *muxer; UnityMenuModel *model; gint position; + it = (GSequenceIter *) g_object_get_qdata (object, unity_menu_item_iterator_quark ()); item = (GtkMenuTrackerItem *) g_sequence_get (it); muxer = _gtk_menu_tracker_item_get_observable (item); model = (UnityMenuModel *) g_object_get_qdata (G_OBJECT (muxer), unity_menu_model_quark ()); -- cgit v1.2.3 From 3dd6f6571c033809b16464aba8a32b88051c60eb Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Mon, 24 Jun 2013 17:02:12 -0400 Subject: unitymenumodel: allow setting multiple prefixed action groups --- libqmenumodel/src/unitymenumodel.cpp | 27 +++++++++++++++------------ libqmenumodel/src/unitymenumodel.h | 8 ++++---- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/libqmenumodel/src/unitymenumodel.cpp b/libqmenumodel/src/unitymenumodel.cpp index 58b9fda..81f5121 100644 --- a/libqmenumodel/src/unitymenumodel.cpp +++ b/libqmenumodel/src/unitymenumodel.cpp @@ -53,7 +53,7 @@ public: QByteArray busName; QByteArray nameOwner; guint nameWatchId; - QByteArray actionObjectPath; + QVariantMap actions; QByteArray menuObjectPath; static void nameAppeared(GDBusConnection *connection, const gchar *name, const gchar *owner, gpointer user_data); @@ -124,17 +124,20 @@ void UnityMenuModelPrivate::clearName() void UnityMenuModelPrivate::updateActions() { - if (!this->nameOwner.isEmpty()) { + Q_FOREACH (QString prefix, this->actions.keys()) + gtk_action_muxer_remove (this->muxer, prefix.toUtf8()); + + if (this->nameOwner.isEmpty()) + return; + + for (QVariantMap::const_iterator it = this->actions.constBegin(); it != this->actions.constEnd(); ++it) { GDBusActionGroup *actions; - actions = g_dbus_action_group_get (this->connection, this->nameOwner, this->actionObjectPath.constData()); - gtk_action_muxer_insert (this->muxer, "indicator", G_ACTION_GROUP (actions)); + actions = g_dbus_action_group_get (this->connection, this->nameOwner, it.value().toByteArray()); + gtk_action_muxer_insert (this->muxer, it.key().toUtf8(), G_ACTION_GROUP (actions)); g_object_unref (actions); } - else { - gtk_action_muxer_remove (this->muxer, "indicator"); - } } void UnityMenuModelPrivate::updateMenuModel() @@ -147,7 +150,7 @@ void UnityMenuModelPrivate::updateMenuModel() menu = g_dbus_menu_model_get (this->connection, this->nameOwner, this->menuObjectPath.constData()); this->menutracker = gtk_menu_tracker_new (GTK_ACTION_OBSERVABLE (this->muxer), - G_MENU_MODEL (menu), TRUE, "indicator", + G_MENU_MODEL (menu), TRUE, NULL, menuItemInserted, menuItemRemoved, this); g_object_unref (menu); @@ -244,14 +247,14 @@ void UnityMenuModel::setBusName(const QByteArray &name) priv, NULL); } -QByteArray UnityMenuModel::actionObjectPath() const +QVariantMap UnityMenuModel::actions() const { - return priv->actionObjectPath; + return priv->actions; } -void UnityMenuModel::setActionObjectPath(const QByteArray &path) +void UnityMenuModel::setActions(const QVariantMap &actions) { - priv->actionObjectPath = path; + priv->actions = actions; priv->updateActions(); } diff --git a/libqmenumodel/src/unitymenumodel.h b/libqmenumodel/src/unitymenumodel.h index 27a9fff..896c806 100644 --- a/libqmenumodel/src/unitymenumodel.h +++ b/libqmenumodel/src/unitymenumodel.h @@ -25,7 +25,7 @@ class UnityMenuModel: public QAbstractListModel { Q_OBJECT Q_PROPERTY(QByteArray busName READ busName WRITE setBusName NOTIFY busNameChanged) - Q_PROPERTY(QByteArray actionObjectPath READ actionObjectPath WRITE setActionObjectPath NOTIFY actionObjectPathChanged) + Q_PROPERTY(QVariantMap actions READ actions WRITE setActions NOTIFY actionsChanged) Q_PROPERTY(QByteArray menuObjectPath READ menuObjectPath WRITE setMenuObjectPath NOTIFY menuObjectPathChanged) public: @@ -35,8 +35,8 @@ public: QByteArray busName() const; void setBusName(const QByteArray &name); - QByteArray actionObjectPath() const; - void setActionObjectPath(const QByteArray &path); + QVariantMap actions() const; + void setActions(const QVariantMap &actions); QByteArray menuObjectPath() const; void setMenuObjectPath(const QByteArray &path); @@ -52,7 +52,7 @@ public: Q_SIGNALS: void busNameChanged(const QByteArray &name); - void actionObjectPathChanged(const QByteArray &path); + void actionsChanged(const QByteArray &path); void menuObjectPathChanged(const QByteArray &path); public Q_SLOTS: -- cgit v1.2.3 From 050958c58924934b10cf3a6272cff3ebe867f1dd Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Mon, 24 Jun 2013 17:04:57 -0400 Subject: Remove unityqmlmenumodel It was used for calling ::init() on the unitymenumodel when parsing finished (so that unitymenumodel wouldn't have to implement QQmlParserStatus). Now that init() is gone, unityqmlmenumodel doesn't serve any purpose. --- libqmenumodel/QMenuModel/plugin.cpp | 4 ++-- libqmenumodel/src/CMakeLists.txt | 1 - libqmenumodel/src/unityqmlmenumodel.cpp | 36 ---------------------------- libqmenumodel/src/unityqmlmenumodel.h | 42 --------------------------------- 4 files changed, 2 insertions(+), 81 deletions(-) delete mode 100644 libqmenumodel/src/unityqmlmenumodel.cpp delete mode 100644 libqmenumodel/src/unityqmlmenumodel.h diff --git a/libqmenumodel/QMenuModel/plugin.cpp b/libqmenumodel/QMenuModel/plugin.cpp index 0205102..f529c0a 100644 --- a/libqmenumodel/QMenuModel/plugin.cpp +++ b/libqmenumodel/QMenuModel/plugin.cpp @@ -22,7 +22,7 @@ #include "qdbusmenumodel.h" #include "qdbusactiongroup.h" #include "qstateaction.h" -#include "unityqmlmenumodel.h" +#include "unitymenumodel.h" #include @@ -37,5 +37,5 @@ void QMenuModelQmlPlugin::registerTypes(const char *uri) qmlRegisterType(uri, 0, 1, "QDBusMenuModel"); qmlRegisterType(uri, 0, 1, "QDBusActionGroup"); - qmlRegisterType(uri, 0, 1, "UnityMenuModel"); + qmlRegisterType(uri, 0, 1, "UnityMenuModel"); } diff --git a/libqmenumodel/src/CMakeLists.txt b/libqmenumodel/src/CMakeLists.txt index a2a6177..d74f2cc 100644 --- a/libqmenumodel/src/CMakeLists.txt +++ b/libqmenumodel/src/CMakeLists.txt @@ -11,7 +11,6 @@ set(QMENUMODEL_SRC qstateaction.cpp unitymenumodel.cpp unitymenumodel.h - unityqmlmenumodel.cpp gtk/gtkactionmuxer.c gtk/gtkactionmuxer.h gtk/gtkactionobservable.c diff --git a/libqmenumodel/src/unityqmlmenumodel.cpp b/libqmenumodel/src/unityqmlmenumodel.cpp deleted file mode 100644 index 36bb2bd..0000000 --- a/libqmenumodel/src/unityqmlmenumodel.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2013 Canonical Ltd. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; version 3. - * - * This program 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 program. If not, see . - * - * Authors: Lars Uebernickel - */ - -#include "unityqmlmenumodel.h" - -UnityQmlMenuModel::UnityQmlMenuModel(QObject *parent): - UnityMenuModel(parent) -{ -} - -UnityQmlMenuModel::~UnityQmlMenuModel() -{ -} - -void UnityQmlMenuModel::classBegin() -{ -} - -void UnityQmlMenuModel::componentComplete() -{ -} diff --git a/libqmenumodel/src/unityqmlmenumodel.h b/libqmenumodel/src/unityqmlmenumodel.h deleted file mode 100644 index d57d52e..0000000 --- a/libqmenumodel/src/unityqmlmenumodel.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2013 Canonical Ltd. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; version 3. - * - * This program 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 program. If not, see . - * - * Authors: Lars Uebernickel - */ - -#ifndef UNITYQMLENUMODEL_H -#define UNITYQMLENUMODEL_H - -#include "unitymenumodel.h" - -#include - -class UnityQmlMenuModel: public UnityMenuModel, public QQmlParserStatus -{ - Q_OBJECT - Q_INTERFACES(QQmlParserStatus) - -public: - UnityQmlMenuModel(QObject *parent = NULL); - ~UnityQmlMenuModel(); - - void classBegin(); - void componentComplete(); - -private: - struct UnityQmlMenuModelPrivate *priv; -}; - -#endif -- cgit v1.2.3 From 6aaafd55328c1860fd1b734fa29ff77673538a2b Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Tue, 25 Jun 2013 15:39:11 -0400 Subject: unitymenumodel: add support for icons For now, this includes themed icons, file icons, and icons send as raw data. --- libqmenumodel/QMenuModel/CMakeLists.txt | 2 +- libqmenumodel/QMenuModel/plugin.cpp | 6 +++ libqmenumodel/QMenuModel/plugin.h | 1 + libqmenumodel/src/CMakeLists.txt | 5 ++- libqmenumodel/src/unitymenumodel.cpp | 56 ++++++++++++++++++++++++++- libqmenumodel/src/unitythemediconprovider.cpp | 35 +++++++++++++++++ libqmenumodel/src/unitythemediconprovider.h | 31 +++++++++++++++ 7 files changed, 132 insertions(+), 4 deletions(-) create mode 100644 libqmenumodel/src/unitythemediconprovider.cpp create mode 100644 libqmenumodel/src/unitythemediconprovider.h diff --git a/libqmenumodel/QMenuModel/CMakeLists.txt b/libqmenumodel/QMenuModel/CMakeLists.txt index 7367e18..78e062b 100644 --- a/libqmenumodel/QMenuModel/CMakeLists.txt +++ b/libqmenumodel/QMenuModel/CMakeLists.txt @@ -19,7 +19,7 @@ target_link_libraries(qmenumodel-qml ${GIO_LDFLAGS} ) -qt5_use_modules(qmenumodel-qml Qml Widgets) +qt5_use_modules(qmenumodel-qml Qml Quick) execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/qmldir" "${CMAKE_CURRENT_BINARY_DIR}/qmldir") diff --git a/libqmenumodel/QMenuModel/plugin.cpp b/libqmenumodel/QMenuModel/plugin.cpp index f529c0a..0629099 100644 --- a/libqmenumodel/QMenuModel/plugin.cpp +++ b/libqmenumodel/QMenuModel/plugin.cpp @@ -23,9 +23,15 @@ #include "qdbusactiongroup.h" #include "qstateaction.h" #include "unitymenumodel.h" +#include "unitythemediconprovider.h" #include +void QMenuModelQmlPlugin::initializeEngine(QQmlEngine *engine, const char *uri) +{ + engine->addImageProvider("theme", new UnityThemedIconProvider); +} + void QMenuModelQmlPlugin::registerTypes(const char *uri) { qmlRegisterUncreatableType(uri, 0, 1, "QMenuModel", diff --git a/libqmenumodel/QMenuModel/plugin.h b/libqmenumodel/QMenuModel/plugin.h index fc732d2..3474139 100644 --- a/libqmenumodel/QMenuModel/plugin.h +++ b/libqmenumodel/QMenuModel/plugin.h @@ -28,6 +28,7 @@ class QMenuModelQmlPlugin : public QQmlExtensionPlugin Q_PLUGIN_METADATA(IID "com.canonical.qmenumodel") public: + void initializeEngine(QQmlEngine *engine, const char *uri); void registerTypes(const char *uri); }; diff --git a/libqmenumodel/src/CMakeLists.txt b/libqmenumodel/src/CMakeLists.txt index d74f2cc..24b65d2 100644 --- a/libqmenumodel/src/CMakeLists.txt +++ b/libqmenumodel/src/CMakeLists.txt @@ -10,7 +10,7 @@ set(QMENUMODEL_SRC qdbusactiongroup.cpp qstateaction.cpp unitymenumodel.cpp - unitymenumodel.h + unitythemediconprovider.cpp gtk/gtkactionmuxer.c gtk/gtkactionmuxer.h gtk/gtkactionobservable.c @@ -44,7 +44,7 @@ target_link_libraries(${SHAREDLIBNAME} ${GIO_LDFLAGS} ) -qt5_use_modules(${SHAREDLIBNAME} Core Qml) +qt5_use_modules(${SHAREDLIBNAME} Core Qml Quick) install(TARGETS ${SHAREDLIBNAME} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) @@ -56,6 +56,7 @@ set(QMENUMODEL_HEADERS qmenumodel.h qstateaction.h unitymenumodel.h + unitythemediconprovider.h ) set(INCLUDEDIR qmenumodel) diff --git a/libqmenumodel/src/unitymenumodel.cpp b/libqmenumodel/src/unitymenumodel.cpp index 81f5121..db70936 100644 --- a/libqmenumodel/src/unitymenumodel.cpp +++ b/libqmenumodel/src/unitymenumodel.cpp @@ -31,7 +31,8 @@ enum MenuRoles { ActionRole = Qt::DisplayRole + 1, LabelRole, SensitiveRole, - IsSeparatorRole + IsSeparatorRole, + IconRole }; class UnityMenuModelPrivate @@ -279,6 +280,47 @@ int UnityMenuModel::columnCount(const QModelIndex &parent) const return 1; } +static QString iconUri(GIcon *icon) +{ + QString uri; + + if (G_IS_THEMED_ICON (icon)) { + const gchar * const *names; + + names = g_themed_icon_get_names (G_THEMED_ICON (icon)); + if (names && names[0] && *names[0]) + uri = QString("image://theme/") + names[0]; + } + else if (G_IS_FILE_ICON (icon)) { + GFile *file; + + file = g_file_icon_get_file (G_FILE_ICON (icon)); + if (g_file_is_native (file)) { + gchar *fileuri; + + fileuri = g_file_get_path (file); + uri = QString(fileuri); + + g_free (fileuri); + } + } + else if (G_IS_BYTES_ICON (icon)) { + gsize size; + gconstpointer data; + gchar *base64; + + data = g_bytes_get_data (g_bytes_icon_get_bytes (G_BYTES_ICON (icon)), &size); + base64 = g_base64_encode ((const guchar *) data, size); + + uri = QString("data://"); + uri.append (base64); + + g_free (base64); + } + + return uri; +} + QVariant UnityMenuModel::data(const QModelIndex &index, int role) const { GtkMenuTrackerItem *item; @@ -295,6 +337,17 @@ QVariant UnityMenuModel::data(const QModelIndex &index, int role) const case IsSeparatorRole: return gtk_menu_tracker_item_get_is_separator (item); + case IconRole: { + GIcon *icon = gtk_menu_tracker_item_get_icon (item); + if (icon) { + QString uri = iconUri(icon); + g_object_unref (icon); + return uri; + } + else + return QString(); + } + default: return QVariant(); } @@ -318,6 +371,7 @@ QHash UnityMenuModel::roleNames() const names[ActionRole] = "action"; names[SensitiveRole] = "sensitive"; names[IsSeparatorRole] = "isSeparator"; + names[IconRole] = "icon"; return names; } diff --git a/libqmenumodel/src/unitythemediconprovider.cpp b/libqmenumodel/src/unitythemediconprovider.cpp new file mode 100644 index 0000000..69afd76 --- /dev/null +++ b/libqmenumodel/src/unitythemediconprovider.cpp @@ -0,0 +1,35 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 3. + * + * This program 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 program. If not, see . + * + * Authors: Lars Uebernickel + */ + +#include "unitythemediconprovider.h" + +#include + +UnityThemedIconProvider::UnityThemedIconProvider(): + QQuickImageProvider(QQuickImageProvider::Pixmap) +{ +} + +#include + +QPixmap UnityThemedIconProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) +{ + QPixmap pixmap = QIcon::fromTheme(id).pixmap(requestedSize.isValid() ? requestedSize : QSize(32, 32)); + *size = pixmap.size(); + return pixmap; +} diff --git a/libqmenumodel/src/unitythemediconprovider.h b/libqmenumodel/src/unitythemediconprovider.h new file mode 100644 index 0000000..7e71ea8 --- /dev/null +++ b/libqmenumodel/src/unitythemediconprovider.h @@ -0,0 +1,31 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 3. + * + * This program 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 program. If not, see . + * + * Authors: Lars Uebernickel + */ + +#ifndef UNITY_THEMED_ICON_PROVIDER_H +#define UNITY_THEMED_ICON_PROVIDER_H + +#include + +class UnityThemedIconProvider: public QQuickImageProvider +{ +public: + UnityThemedIconProvider(); + QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize); +}; + +#endif -- cgit v1.2.3 From ca81974f1fe8454c725e80aa943705d40398a2ad Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Thu, 25 Jul 2013 06:09:59 +0200 Subject: unitymenumodel: expose type and extended attributes to qml --- libqmenumodel/src/gtk/gtkmenutrackeritem.c | 41 +++++++++++ libqmenumodel/src/gtk/gtkmenutrackeritem.h | 9 +++ libqmenumodel/src/unitymenumodel.cpp | 114 ++++++++++++++++++++++++++++- libqmenumodel/src/unitymenumodel.h | 1 + 4 files changed, 164 insertions(+), 1 deletion(-) diff --git a/libqmenumodel/src/gtk/gtkmenutrackeritem.c b/libqmenumodel/src/gtk/gtkmenutrackeritem.c index 2d712a1..356422d 100644 --- a/libqmenumodel/src/gtk/gtkmenutrackeritem.c +++ b/libqmenumodel/src/gtk/gtkmenutrackeritem.c @@ -786,3 +786,44 @@ gtk_menu_tracker_item_request_submenu_shown (GtkMenuTrackerItem *self, else gtk_menu_tracker_item_set_submenu_shown (self, shown); } + +gboolean +gtk_menu_tracker_item_get_attribute (GtkMenuTrackerItem *self, + const gchar *attribute, + const gchar *format, + ...) +{ + gboolean success = FALSE; + GVariant *value; + + g_return_val_if_fail (GTK_IS_MENU_TRACKER_ITEM (self), FALSE); + g_return_val_if_fail (attribute != NULL, FALSE); + g_return_val_if_fail (format != NULL, FALSE); + + value = g_menu_item_get_attribute_value (self->item, attribute, NULL); + if (value) + { + if (g_variant_check_format_string (value, format, TRUE)) + { + va_list args; + + va_start (args, format); + g_variant_get_va (value, format, NULL, &args); + va_end (args); + + success = TRUE; + } + + g_variant_unref (value); + } + + return success; +} + +GVariant * +gtk_menu_tracker_item_get_attribute_value (GtkMenuTrackerItem *self, + const gchar *attribute, + const GVariantType *expected_type) +{ + return g_menu_item_get_attribute_value (self->item, attribute, expected_type); +} diff --git a/libqmenumodel/src/gtk/gtkmenutrackeritem.h b/libqmenumodel/src/gtk/gtkmenutrackeritem.h index 9db30eb..7e98a55 100644 --- a/libqmenumodel/src/gtk/gtkmenutrackeritem.h +++ b/libqmenumodel/src/gtk/gtkmenutrackeritem.h @@ -81,4 +81,13 @@ void gtk_menu_tracker_item_request_submenu_shown (GtkMenu gboolean gtk_menu_tracker_item_get_submenu_shown (GtkMenuTrackerItem *self); +gboolean gtk_menu_tracker_item_get_attribute (GtkMenuTrackerItem *self, + const gchar *attribute, + const gchar *format, + ...); + +GVariant * gtk_menu_tracker_item_get_attribute_value (GtkMenuTrackerItem *self, + const gchar *attribute, + const GVariantType *expected_type); + #endif diff --git a/libqmenumodel/src/unitymenumodel.cpp b/libqmenumodel/src/unitymenumodel.cpp index db70936..0fc5612 100644 --- a/libqmenumodel/src/unitymenumodel.cpp +++ b/libqmenumodel/src/unitymenumodel.cpp @@ -26,13 +26,16 @@ extern "C" { G_DEFINE_QUARK (UNITY_MENU_MODEL, unity_menu_model) G_DEFINE_QUARK (UNITY_SUBMENU_MODEL, unity_submenu_model) G_DEFINE_QUARK (UNITY_MENU_ITEM_ITERATOR, unity_menu_item_iterator) +G_DEFINE_QUARK (UNITY_MENU_ITEM_EXTENDED_ATTRIBUTES, unity_menu_item_extended_attributes) enum MenuRoles { ActionRole = Qt::DisplayRole + 1, LabelRole, SensitiveRole, IsSeparatorRole, - IconRole + IconRole, + TypeRole, + ExtendedAttributesRole, }; class UnityMenuModelPrivate @@ -348,6 +351,22 @@ QVariant UnityMenuModel::data(const QModelIndex &index, int role) const return QString(); } + case TypeRole: { + gchar *type; + if (gtk_menu_tracker_item_get_attribute (item, "x-canonical-type", "s", &type)) { + QVariant v(type); + g_free (type); + return v; + } + else + return QVariant(); + } + + case ExtendedAttributesRole: { + QVariantMap *map = (QVariantMap *) g_object_get_qdata (G_OBJECT (item), unity_menu_item_extended_attributes_quark ()); + return map ? *map : QVariant(); + } + default: return QVariant(); } @@ -372,6 +391,8 @@ QHash UnityMenuModel::roleNames() const names[SensitiveRole] = "sensitive"; names[IsSeparatorRole] = "isSeparator"; names[IconRole] = "icon"; + names[TypeRole] = "type"; + names[ExtendedAttributesRole] = "ext"; return names; } @@ -411,3 +432,94 @@ void UnityMenuModel::activate(int index) item = (GtkMenuTrackerItem *) g_sequence_get (g_sequence_get_iter_at_pos (priv->items, index)); gtk_menu_tracker_item_activated (item); } + +static void freeExtendedAttrs(gpointer data) +{ + QVariantMap *extendedAttrs = (QVariantMap *) data; + delete extendedAttrs; +} + +static QVariant attributeToQVariant(GVariant *value, const QString &type) +{ + QVariant result; + + if (type == "int") { + if (g_variant_is_of_type (value, G_VARIANT_TYPE_INT32)) + result = QVariant(g_variant_get_int32(value)); + } + if (type == "bool") { + if (g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN)) + result = QVariant(g_variant_get_int32(value)); + } + else if (type == "string") { + if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) + result = QVariant(g_variant_get_string(value, NULL)); + } + else if (type == "icon") { + GIcon *icon = g_icon_deserialize (value); + if (icon) { + result = iconUri(icon); + g_object_unref (icon); + } + else { + result = QVariant(""); + } + } + + return result; +} + +/* convert 'some-key' to 'someKey' or 'SomeKey'. (from dconf-qt) */ +static QString qtify_name(const char *name) +{ + bool next_cap = false; + QString result; + + while (*name) { + if (*name == '-') { + next_cap = true; + } else if (next_cap) { + result.append(toupper(*name)); + next_cap = false; + } else { + result.append(*name); + } + + name++; + } + + return result; +} + +bool UnityMenuModel::loadExtendedAttributes(int position, const QVariantMap &schema) +{ + GtkMenuTrackerItem *item; + QVariantMap *extendedAttrs; + + item = (GtkMenuTrackerItem *) g_sequence_get (g_sequence_get_iter_at_pos (priv->items, position)); + + extendedAttrs = new QVariantMap; + + for (QVariantMap::const_iterator it = schema.constBegin(); it != schema.constEnd(); ++it) { + QString name = it.key(); + QString type = it.value().toString(); + + GVariant *value = gtk_menu_tracker_item_get_attribute_value (item, name.toUtf8(), NULL); + if (value == NULL) { + qWarning("loadExtendedAttributes: menu item does not contain '%s'", it.key().toUtf8().constData()); + continue; + } + + QVariant qvalue = attributeToQVariant(value, type); + if (qvalue.isValid()) + extendedAttrs->insert(qtify_name (name.toUtf8()), qvalue); + else + qWarning("loadExtendedAttributes: key '%s' is of type '%s' (expected '%s')", + name.toUtf8().constData(), g_variant_get_type_string(value), type.constData()); + + g_variant_unref (value); + } + + g_object_set_qdata_full (G_OBJECT (item), unity_menu_item_extended_attributes_quark (), + extendedAttrs, freeExtendedAttrs); +} diff --git a/libqmenumodel/src/unitymenumodel.h b/libqmenumodel/src/unitymenumodel.h index 896c806..9151217 100644 --- a/libqmenumodel/src/unitymenumodel.h +++ b/libqmenumodel/src/unitymenumodel.h @@ -49,6 +49,7 @@ public: QHash roleNames() const; Q_INVOKABLE QObject * submenu(int position); + Q_INVOKABLE bool loadExtendedAttributes(int position, const QVariantMap &schema); Q_SIGNALS: void busNameChanged(const QByteArray &name); -- cgit v1.2.3 From 192e9c85e6d7441a6115d1ead859b8449cfb07ec Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Thu, 25 Jul 2013 06:28:54 +0200 Subject: examples/unitymenumodel.qml: update to newest API --- examples/unityqmlmenumodel.qml | 55 +++++++++++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/examples/unityqmlmenumodel.qml b/examples/unityqmlmenumodel.qml index a66ba8e..5dcb1ff 100644 --- a/examples/unityqmlmenumodel.qml +++ b/examples/unityqmlmenumodel.qml @@ -8,9 +8,9 @@ Item { UnityMenuModel { id: menu - busName: "com.canonical.testmenu" - actionObjectPath: "/com/canonical/testmenu" - menuObjectPath: "/com/canonical/testmenu" + busName: "com.canonical.indicator.sound" + actions: { "indicator": "/com/canonical/indicator/sound" } + menuObjectPath: "/com/canonical/indicator/sound/desktop" } ListView { @@ -21,7 +21,19 @@ Item { model: menu delegate: Loader { - sourceComponent: isSeparator ? separator : menuitem; + sourceComponent: { + if (isSeparator) { + return separator; + } + else if (type == "com.canonical.unity.slider") { + listview.model.loadExtendedAttributes(index, {'min-icon': 'icon', + 'max-icon': 'icon'}); + return slider; + } + else { + return menuitem; + } + } Component { id: separator @@ -32,22 +44,47 @@ Item { } } + Component { + id: slider + Row { + anchors.fill: parent + Image { + source: ext.minIcon + } + Image { + source: ext.maxIcon + } + } + } + Component { id: menuitem Rectangle { width: listview.width height: 40 color: "#ddd" - Text { + Row { anchors.fill: parent anchors.margins: 5 - verticalAlignment: Text.AlignVCenter - color: sensitive ? "black" : "#aaa"; - text: label + Image { + source: icon + } + Text { + height: parent.height + verticalAlignment: Text.AlignVCenter + color: sensitive ? "black" : "#aaa"; + text: label + } } MouseArea { anchors.fill: parent - onClicked: listview.model.activate(index); + onClicked: { + var submenu = listview.model.submenu(index); + if (submenu) + listview.model = submenu; + else + listview.model.activate(index); + } } } } -- cgit v1.2.3 From 57f5b0730a9f001201458d2759797bba17cabaef Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Thu, 25 Jul 2013 10:18:31 +0200 Subject: examples/unityqmlmenumodel.qml: add license header --- examples/unityqmlmenumodel.qml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/examples/unityqmlmenumodel.qml b/examples/unityqmlmenumodel.qml index 5dcb1ff..8028674 100644 --- a/examples/unityqmlmenumodel.qml +++ b/examples/unityqmlmenumodel.qml @@ -1,3 +1,20 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 3. + * + * This program 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 program. If not, see . + * + * Authors: Lars Uebernickel + */ import QtQuick 2.0 import QMenuModel 0.1 -- cgit v1.2.3 From 58afcb80014d14ab95a031e19bdc11664c8a1d5b Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Thu, 25 Jul 2013 12:52:23 +0200 Subject: Fix crash in UnityMenuModel::menuItemChanged menuItemInserted set the iter _after_ the one that was just inserted as qdata. This is (a) wrong and (b) leads to the end iter being in there in some cases. menuItemChanged can't deal with that (it calls g_sequence_get() without checking for the end iter). --- libqmenumodel/src/unitymenumodel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libqmenumodel/src/unitymenumodel.cpp b/libqmenumodel/src/unitymenumodel.cpp index 0fc5612..418de61 100644 --- a/libqmenumodel/src/unitymenumodel.cpp +++ b/libqmenumodel/src/unitymenumodel.cpp @@ -187,9 +187,9 @@ void UnityMenuModelPrivate::menuItemInserted(GtkMenuTrackerItem *item, gint posi priv->model->beginInsertRows(QModelIndex(), position, position); it = g_sequence_get_iter_at_pos (priv->items, position); + it = g_sequence_insert_before (it, g_object_ref (item)); g_object_set_qdata (G_OBJECT (item), unity_menu_item_iterator_quark (), it); g_signal_connect (item, "notify", G_CALLBACK (menuItemChanged), it); - g_sequence_insert_before (it, g_object_ref (item)); priv->model->endInsertRows(); } -- cgit v1.2.3 From d57caa61f787c4bd7cb0823eadbbc5d84f542f0a Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Fri, 26 Jul 2013 15:04:50 +0200 Subject: Expose the state of a menu item's action in model.actionState --- examples/unityqmlmenumodel.qml | 22 ++++++++++++------ libqmenumodel/src/gtk/gtkmenutrackeritem.c | 36 ++++++++++++++++++++++++++++++ libqmenumodel/src/gtk/gtkmenutrackeritem.h | 2 ++ libqmenumodel/src/unitymenumodel.cpp | 25 ++++++++++++++++++--- 4 files changed, 75 insertions(+), 10 deletions(-) diff --git a/examples/unityqmlmenumodel.qml b/examples/unityqmlmenumodel.qml index 8028674..e1de3c8 100644 --- a/examples/unityqmlmenumodel.qml +++ b/examples/unityqmlmenumodel.qml @@ -63,13 +63,21 @@ Item { Component { id: slider - Row { - anchors.fill: parent - Image { - source: ext.minIcon - } - Image { - source: ext.maxIcon + Rectangle { + width: listview.width + color: "#ddd" + height: 40 + Row { + anchors.fill: parent + Image { + source: ext.minIcon + } + Text { + text: model.actionState + } + Image { + source: ext.maxIcon + } } } } diff --git a/libqmenumodel/src/gtk/gtkmenutrackeritem.c b/libqmenumodel/src/gtk/gtkmenutrackeritem.c index 356422d..fb856c9 100644 --- a/libqmenumodel/src/gtk/gtkmenutrackeritem.c +++ b/libqmenumodel/src/gtk/gtkmenutrackeritem.c @@ -91,6 +91,7 @@ struct _GtkMenuTrackerItem guint toggled : 1; guint submenu_shown : 1; guint submenu_requested : 1; + GVariant *action_state; }; enum { @@ -105,6 +106,7 @@ enum { PROP_TOGGLED, PROP_ACCEL, PROP_SUBMENU_SHOWN, + PROP_ACTION_STATE, N_PROPS }; @@ -177,6 +179,9 @@ gtk_menu_tracker_item_get_property (GObject *object, case PROP_SUBMENU_SHOWN: g_value_set_boolean (value, gtk_menu_tracker_item_get_submenu_shown (self)); break; + case PROP_ACTION_STATE: + g_value_set_variant (value, self->action_state); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -193,6 +198,9 @@ gtk_menu_tracker_item_finalize (GObject *object) if (self->observable) g_object_unref (self->observable); + if (self->action_state) + g_variant_unref (self->action_state); + g_object_unref (self->item); G_OBJECT_CLASS (gtk_menu_tracker_item_parent_class)->finalize (object); @@ -231,6 +239,8 @@ gtk_menu_tracker_item_class_init (GtkMenuTrackerItemClass *class) 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); + gtk_menu_tracker_item_pspecs[PROP_ACTION_STATE] = + g_param_spec_boolean ("action-state", "", "", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE); g_object_class_install_properties (class, N_PROPS, gtk_menu_tracker_item_pspecs); } @@ -284,6 +294,12 @@ gtk_menu_tracker_item_action_added (GtkActionObserver *observer, if (self->role != GTK_MENU_TRACKER_ITEM_ROLE_NORMAL) g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_ROLE]); + if (state != NULL) + { + self->action_state = g_variant_ref (state); + g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_ACTION_STATE]); + } + g_object_thaw_notify (G_OBJECT (self)); if (action_target) @@ -339,6 +355,11 @@ gtk_menu_tracker_item_action_state_changed (GtkActionObserver *observer, if (self->toggled != was_toggled) g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_TOGGLED]); + + if (self->action_state) + g_variant_unref (self->action_state); + self->action_state = g_variant_ref (state); + g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_ACTION_STATE]); } static void @@ -371,6 +392,12 @@ gtk_menu_tracker_item_action_removed (GtkActionObserver *observer, g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_ROLE]); } + if (self->action_state != NULL) + { + g_variant_unref (self->action_state); + g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_ACTION_STATE]); + } + g_object_thaw_notify (G_OBJECT (self)); } @@ -585,6 +612,15 @@ gtk_menu_tracker_item_get_submenu_shown (GtkMenuTrackerItem *self) return self->submenu_shown; } +GVariant * +gtk_menu_tracker_item_get_action_state (GtkMenuTrackerItem *self) +{ + if (self->action_state != NULL) + return g_variant_ref (self->action_state); + + return NULL; +} + static void gtk_menu_tracker_item_set_submenu_shown (GtkMenuTrackerItem *self, gboolean submenu_shown) diff --git a/libqmenumodel/src/gtk/gtkmenutrackeritem.h b/libqmenumodel/src/gtk/gtkmenutrackeritem.h index 7e98a55..16c4415 100644 --- a/libqmenumodel/src/gtk/gtkmenutrackeritem.h +++ b/libqmenumodel/src/gtk/gtkmenutrackeritem.h @@ -81,6 +81,8 @@ void gtk_menu_tracker_item_request_submenu_shown (GtkMenu gboolean gtk_menu_tracker_item_get_submenu_shown (GtkMenuTrackerItem *self); +GVariant * gtk_menu_tracker_item_get_action_state (GtkMenuTrackerItem *self); + gboolean gtk_menu_tracker_item_get_attribute (GtkMenuTrackerItem *self, const gchar *attribute, const gchar *format, diff --git a/libqmenumodel/src/unitymenumodel.cpp b/libqmenumodel/src/unitymenumodel.cpp index 418de61..114c985 100644 --- a/libqmenumodel/src/unitymenumodel.cpp +++ b/libqmenumodel/src/unitymenumodel.cpp @@ -17,6 +17,7 @@ */ #include "unitymenumodel.h" +#include "converter.h" extern "C" { #include "gtk/gtkactionmuxer.h" @@ -29,13 +30,13 @@ G_DEFINE_QUARK (UNITY_MENU_ITEM_ITERATOR, unity_menu_item_iterator) G_DEFINE_QUARK (UNITY_MENU_ITEM_EXTENDED_ATTRIBUTES, unity_menu_item_extended_attributes) enum MenuRoles { - ActionRole = Qt::DisplayRole + 1, - LabelRole, + LabelRole = Qt::DisplayRole + 1, SensitiveRole, IsSeparatorRole, IconRole, TypeRole, ExtendedAttributesRole, + ActionStateRole }; class UnityMenuModelPrivate @@ -48,6 +49,7 @@ public: void clearName(); void updateActions(); void updateMenuModel(); + QVariant itemState(GtkMenuTrackerItem *item); UnityMenuModel *model; GtkActionMuxer *muxer; @@ -161,6 +163,19 @@ void UnityMenuModelPrivate::updateMenuModel() } } +QVariant UnityMenuModelPrivate::itemState(GtkMenuTrackerItem *item) +{ + QVariant result; + + GVariant *state = gtk_menu_tracker_item_get_action_state (item); + if (state != NULL) { + result = Converter::toQVariant(state); + g_variant_unref (state); + } + + return result; +} + void UnityMenuModelPrivate::nameAppeared(GDBusConnection *connection, const gchar *name, const gchar *owner, gpointer user_data) { UnityMenuModelPrivate *priv = (UnityMenuModelPrivate *)user_data; @@ -367,6 +382,9 @@ QVariant UnityMenuModel::data(const QModelIndex &index, int role) const return map ? *map : QVariant(); } + case ActionStateRole: + return priv->itemState(item); + default: return QVariant(); } @@ -382,17 +400,18 @@ QModelIndex UnityMenuModel::parent(const QModelIndex &index) const return QModelIndex(); } +#include QHash UnityMenuModel::roleNames() const { QHash names; names[LabelRole] = "label"; - names[ActionRole] = "action"; names[SensitiveRole] = "sensitive"; names[IsSeparatorRole] = "isSeparator"; names[IconRole] = "icon"; names[TypeRole] = "type"; names[ExtendedAttributesRole] = "ext"; + names[ActionStateRole] = "actionState"; return names; } -- cgit v1.2.3 From 16b480d00dd5c1fba5252e728cfc71aaa5cc916f Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Fri, 26 Jul 2013 16:29:32 +0200 Subject: unitymenumodel: add get() --- libqmenumodel/src/unitymenumodel.cpp | 12 ++++++++++++ libqmenumodel/src/unitymenumodel.h | 1 + 2 files changed, 13 insertions(+) diff --git a/libqmenumodel/src/unitymenumodel.cpp b/libqmenumodel/src/unitymenumodel.cpp index 114c985..d40c813 100644 --- a/libqmenumodel/src/unitymenumodel.cpp +++ b/libqmenumodel/src/unitymenumodel.cpp @@ -61,6 +61,7 @@ public: guint nameWatchId; QVariantMap actions; QByteArray menuObjectPath; + QHash roles; static void nameAppeared(GDBusConnection *connection, const gchar *name, const gchar *owner, gpointer user_data); static void nameVanished(GDBusConnection *connection, const gchar *name, gpointer user_data); @@ -542,3 +543,14 @@ bool UnityMenuModel::loadExtendedAttributes(int position, const QVariantMap &sch g_object_set_qdata_full (G_OBJECT (item), unity_menu_item_extended_attributes_quark (), extendedAttrs, freeExtendedAttrs); } + +QVariant UnityMenuModel::get(int row, const QByteArray &role) +{ + if (priv->roles.isEmpty()) { + QHash names = roleNames(); + Q_FOREACH (int role, names.keys()) + priv->roles.insert(names[role], role); + } + + return this->data(this->index(row, 0), priv->roles[role]); +} diff --git a/libqmenumodel/src/unitymenumodel.h b/libqmenumodel/src/unitymenumodel.h index 9151217..396eea3 100644 --- a/libqmenumodel/src/unitymenumodel.h +++ b/libqmenumodel/src/unitymenumodel.h @@ -50,6 +50,7 @@ public: Q_INVOKABLE QObject * submenu(int position); Q_INVOKABLE bool loadExtendedAttributes(int position, const QVariantMap &schema); + Q_INVOKABLE QVariant get(int row, const QByteArray &role); Q_SIGNALS: void busNameChanged(const QByteArray &name); -- cgit v1.2.3 From ed4b69482684c7070c928af4966ba52c32bf041b Mon Sep 17 00:00:00 2001 From: Nick Dedekind Date: Tue, 30 Jul 2013 06:25:22 +0200 Subject: unitymenumodel: lookup themed icon before turning it into a uri --- libqmenumodel/src/unitymenumodel.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/libqmenumodel/src/unitymenumodel.cpp b/libqmenumodel/src/unitymenumodel.cpp index d40c813..58dd84b 100644 --- a/libqmenumodel/src/unitymenumodel.cpp +++ b/libqmenumodel/src/unitymenumodel.cpp @@ -19,6 +19,8 @@ #include "unitymenumodel.h" #include "converter.h" +#include + extern "C" { #include "gtk/gtkactionmuxer.h" #include "gtk/gtkmenutracker.h" @@ -304,11 +306,15 @@ static QString iconUri(GIcon *icon) QString uri; if (G_IS_THEMED_ICON (icon)) { - const gchar * const *names; - - names = g_themed_icon_get_names (G_THEMED_ICON (icon)); - if (names && names[0] && *names[0]) - uri = QString("image://theme/") + names[0]; + const gchar* const* iconNames = g_themed_icon_get_names (G_THEMED_ICON (icon)); + guint index = 0; + while(iconNames[index] != NULL) { + if (QIcon::hasThemeIcon(iconNames[index])) { + uri = QString("image://theme/") + iconNames[index]; + break; + } + index++; + } } else if (G_IS_FILE_ICON (icon)) { GFile *file; -- cgit v1.2.3 From 01d57d800729a0ca7b9fd023b47b4517495883b3 Mon Sep 17 00:00:00 2001 From: Nick Dedekind Date: Tue, 30 Jul 2013 07:01:48 +0200 Subject: Add ActionStateParser --- libqmenumodel/src/CMakeLists.txt | 2 ++ libqmenumodel/src/actionstateparser.cpp | 34 ++++++++++++++++++++++++++++++ libqmenumodel/src/actionstateparser.h | 37 +++++++++++++++++++++++++++++++++ libqmenumodel/src/unitymenumodel.cpp | 37 +++++++++++++++++++++++++++++++-- libqmenumodel/src/unitymenumodel.h | 9 +++++++- 5 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 libqmenumodel/src/actionstateparser.cpp create mode 100644 libqmenumodel/src/actionstateparser.h diff --git a/libqmenumodel/src/CMakeLists.txt b/libqmenumodel/src/CMakeLists.txt index 24b65d2..cf9c426 100644 --- a/libqmenumodel/src/CMakeLists.txt +++ b/libqmenumodel/src/CMakeLists.txt @@ -1,6 +1,7 @@ project(src) set(QMENUMODEL_SRC + actionstateparser.cpp converter.cpp dbus-enums.h menunode.cpp @@ -49,6 +50,7 @@ qt5_use_modules(${SHAREDLIBNAME} Core Qml Quick) install(TARGETS ${SHAREDLIBNAME} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) set(QMENUMODEL_HEADERS + actionstateparser.h dbus-enums.h qdbusactiongroup.h qdbusmenumodel.h diff --git a/libqmenumodel/src/actionstateparser.cpp b/libqmenumodel/src/actionstateparser.cpp new file mode 100644 index 0000000..6637b56 --- /dev/null +++ b/libqmenumodel/src/actionstateparser.cpp @@ -0,0 +1,34 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 3. + * + * This program 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 program. If not, see . + * + * Authors: + * Nick Dedekind + */ + +#include "actionstateparser.h" +#include "converter.h" + +ActionStateParser::ActionStateParser(QObject* parent) + : QObject(parent) +{ +} + +QVariant ActionStateParser::toQVariant(GVariant* state) const +{ + if (state) { + return Converter::toQVariant(state); + } + return QVariant(); +} \ No newline at end of file diff --git a/libqmenumodel/src/actionstateparser.h b/libqmenumodel/src/actionstateparser.h new file mode 100644 index 0000000..044dea1 --- /dev/null +++ b/libqmenumodel/src/actionstateparser.h @@ -0,0 +1,37 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 3. + * + * This program 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 program. If not, see . + * + * Authors: + * Nick Dedekind + */ + +#ifndef ACTIONSTATEPARSER_H +#define ACTIONSTATEPARSER_H + +#include +#include + +typedef struct _GVariant GVariant; + +class ActionStateParser : public QObject +{ + Q_OBJECT +public: + ActionStateParser(QObject* parent = 0); + + virtual QVariant toQVariant(GVariant* state) const; +}; + +#endif // ACTIONSTATEPARSER_H diff --git a/libqmenumodel/src/unitymenumodel.cpp b/libqmenumodel/src/unitymenumodel.cpp index 58dd84b..6108006 100644 --- a/libqmenumodel/src/unitymenumodel.cpp +++ b/libqmenumodel/src/unitymenumodel.cpp @@ -18,6 +18,10 @@ #include "unitymenumodel.h" #include "converter.h" +#include "actionstateparser.h" + +#include +#include #include @@ -64,6 +68,7 @@ public: QVariantMap actions; QByteArray menuObjectPath; QHash roles; + ActionStateParser* actionStateParser; static void nameAppeared(GDBusConnection *connection, const gchar *name, const gchar *owner, gpointer user_data); static void nameVanished(GDBusConnection *connection, const gchar *name, gpointer user_data); @@ -86,6 +91,7 @@ UnityMenuModelPrivate::UnityMenuModelPrivate(UnityMenuModel *model) this->menutracker = NULL; this->connection = NULL; this->nameWatchId = 0; + this->actionStateParser = new ActionStateParser(model); this->muxer = gtk_action_muxer_new (); g_object_set_qdata (G_OBJECT (this->muxer), unity_menu_model_quark (), model); @@ -172,7 +178,9 @@ QVariant UnityMenuModelPrivate::itemState(GtkMenuTrackerItem *item) GVariant *state = gtk_menu_tracker_item_get_action_state (item); if (state != NULL) { - result = Converter::toQVariant(state); + if (actionStateParser != NULL) { + result = actionStateParser->toQVariant(state); + } g_variant_unref (state); } @@ -267,6 +275,7 @@ void UnityMenuModel::setBusName(const QByteArray &name) priv->nameWatchId = g_bus_watch_name (G_BUS_TYPE_SESSION, name.constData(), G_BUS_NAME_WATCHER_FLAGS_AUTO_START, UnityMenuModelPrivate::nameAppeared, UnityMenuModelPrivate::nameVanished, priv, NULL); + priv->busName = name; } QVariantMap UnityMenuModel::actions() const @@ -291,6 +300,22 @@ void UnityMenuModel::setMenuObjectPath(const QByteArray &path) priv->updateMenuModel(); } +ActionStateParser* UnityMenuModel::actionStateParser() const +{ + return priv->actionStateParser; +} + +void UnityMenuModel::setActionStateParser(ActionStateParser* actionStateParser) +{ + if (priv->actionStateParser != actionStateParser) { + if (priv->actionStateParser && priv->actionStateParser->parent() == this) { + delete priv->actionStateParser; + } + priv->actionStateParser = actionStateParser; + Q_EMIT actionStateParserChanged(actionStateParser); + } +} + int UnityMenuModel::rowCount(const QModelIndex &parent) const { return !parent.isValid() ? g_sequence_get_length (priv->items) : 0; @@ -423,7 +448,7 @@ QHash UnityMenuModel::roleNames() const return names; } -QObject * UnityMenuModel::submenu(int position) +QObject * UnityMenuModel::submenu(int position, QQmlComponent* actionStateParser) { GSequenceIter *it; GtkMenuTrackerItem *item; @@ -441,6 +466,14 @@ QObject * UnityMenuModel::submenu(int position) if (model == NULL) { model = new UnityMenuModel(this); model->priv = new UnityMenuModelPrivate(model); + + if (actionStateParser) { + ActionStateParser* parser = qobject_cast(actionStateParser->create()); + if (parser) { + model->setActionStateParser(parser); + } + } + model->priv->menutracker = gtk_menu_tracker_new_for_item_submenu (item, UnityMenuModelPrivate::menuItemInserted, UnityMenuModelPrivate::menuItemRemoved, diff --git a/libqmenumodel/src/unitymenumodel.h b/libqmenumodel/src/unitymenumodel.h index 396eea3..ad409ba 100644 --- a/libqmenumodel/src/unitymenumodel.h +++ b/libqmenumodel/src/unitymenumodel.h @@ -20,6 +20,8 @@ #define UNITYMENUMODEL_H #include +class ActionStateParser; +class QQmlComponent; class UnityMenuModel: public QAbstractListModel { @@ -27,6 +29,7 @@ class UnityMenuModel: public QAbstractListModel Q_PROPERTY(QByteArray busName READ busName WRITE setBusName NOTIFY busNameChanged) Q_PROPERTY(QVariantMap actions READ actions WRITE setActions NOTIFY actionsChanged) Q_PROPERTY(QByteArray menuObjectPath READ menuObjectPath WRITE setMenuObjectPath NOTIFY menuObjectPathChanged) + Q_PROPERTY(ActionStateParser* actionStateParser READ actionStateParser WRITE setActionStateParser NOTIFY actionStateParserChanged) public: UnityMenuModel(QObject *parent = NULL); @@ -41,6 +44,9 @@ public: QByteArray menuObjectPath() const; void setMenuObjectPath(const QByteArray &path); + ActionStateParser* actionStateParser() const; + void setActionStateParser(ActionStateParser* actionStateParser); + int rowCount(const QModelIndex &parent = QModelIndex()) const; int columnCount(const QModelIndex &parent = QModelIndex()) const; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; @@ -48,7 +54,7 @@ public: QModelIndex parent(const QModelIndex &index) const; QHash roleNames() const; - Q_INVOKABLE QObject * submenu(int position); + Q_INVOKABLE QObject * submenu(int position, QQmlComponent* actionStateParser = NULL); Q_INVOKABLE bool loadExtendedAttributes(int position, const QVariantMap &schema); Q_INVOKABLE QVariant get(int row, const QByteArray &role); @@ -56,6 +62,7 @@ Q_SIGNALS: void busNameChanged(const QByteArray &name); void actionsChanged(const QByteArray &path); void menuObjectPathChanged(const QByteArray &path); + void actionStateParserChanged(ActionStateParser* parser); public Q_SLOTS: void activate(int index); -- cgit v1.2.3 From d47194b0794968fa7713a8c7c9cfec7f1e4f8a17 Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Tue, 30 Jul 2013 10:33:36 +0200 Subject: Set model qdata on items instead of the muxer --- libqmenumodel/src/unitymenumodel.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/libqmenumodel/src/unitymenumodel.cpp b/libqmenumodel/src/unitymenumodel.cpp index 6108006..8bbb601 100644 --- a/libqmenumodel/src/unitymenumodel.cpp +++ b/libqmenumodel/src/unitymenumodel.cpp @@ -32,7 +32,6 @@ extern "C" { G_DEFINE_QUARK (UNITY_MENU_MODEL, unity_menu_model) G_DEFINE_QUARK (UNITY_SUBMENU_MODEL, unity_submenu_model) -G_DEFINE_QUARK (UNITY_MENU_ITEM_ITERATOR, unity_menu_item_iterator) G_DEFINE_QUARK (UNITY_MENU_ITEM_EXTENDED_ATTRIBUTES, unity_menu_item_extended_attributes) enum MenuRoles { @@ -94,7 +93,6 @@ UnityMenuModelPrivate::UnityMenuModelPrivate(UnityMenuModel *model) this->actionStateParser = new ActionStateParser(model); this->muxer = gtk_action_muxer_new (); - g_object_set_qdata (G_OBJECT (this->muxer), unity_menu_model_quark (), model); this->items = g_sequence_new (menu_item_free); } @@ -214,7 +212,7 @@ void UnityMenuModelPrivate::menuItemInserted(GtkMenuTrackerItem *item, gint posi it = g_sequence_get_iter_at_pos (priv->items, position); it = g_sequence_insert_before (it, g_object_ref (item)); - g_object_set_qdata (G_OBJECT (item), unity_menu_item_iterator_quark (), it); + g_object_set_qdata (G_OBJECT (item), unity_menu_model_quark (), priv->model); g_signal_connect (item, "notify", G_CALLBACK (menuItemChanged), it); priv->model->endInsertRows(); @@ -234,16 +232,15 @@ void UnityMenuModelPrivate::menuItemRemoved(gint position, gpointer user_data) void UnityMenuModelPrivate::menuItemChanged(GObject *object, GParamSpec *pspec, gpointer user_data) { - GSequenceIter *it; + GSequenceIter *it = (GSequenceIter *) user_data; GtkMenuTrackerItem *item; GtkActionObservable *muxer; UnityMenuModel *model; gint position; - it = (GSequenceIter *) g_object_get_qdata (object, unity_menu_item_iterator_quark ()); item = (GtkMenuTrackerItem *) g_sequence_get (it); muxer = _gtk_menu_tracker_item_get_observable (item); - model = (UnityMenuModel *) g_object_get_qdata (G_OBJECT (muxer), unity_menu_model_quark ()); + model = (UnityMenuModel *) g_object_get_qdata (G_OBJECT (item), unity_menu_model_quark ()); position = g_sequence_iter_get_position (it); Q_EMIT model->dataChanged(model->index(position, 0), model->index(position, 0)); @@ -465,7 +462,6 @@ QObject * UnityMenuModel::submenu(int position, QQmlComponent* actionStateParser model = (UnityMenuModel *) g_object_get_qdata (G_OBJECT (item), unity_submenu_model_quark ()); if (model == NULL) { model = new UnityMenuModel(this); - model->priv = new UnityMenuModelPrivate(model); if (actionStateParser) { ActionStateParser* parser = qobject_cast(actionStateParser->create()); -- cgit v1.2.3 From d1c96de04570d8365c2ab2fde597ccd21c7a3dc2 Mon Sep 17 00:00:00 2001 From: Nick Dedekind Date: Tue, 30 Jul 2013 12:23:42 +0200 Subject: Add action role (replacing actionState role) --- libqmenumodel/QMenuModel/plugin.cpp | 3 ++ libqmenumodel/src/CMakeLists.txt | 2 ++ libqmenumodel/src/unitymenuaction.cpp | 60 +++++++++++++++++++++++++++++++++++ libqmenumodel/src/unitymenuaction.h | 59 ++++++++++++++++++++++++++++++++++ libqmenumodel/src/unitymenumodel.cpp | 36 +++++++++++++++++---- 5 files changed, 154 insertions(+), 6 deletions(-) create mode 100644 libqmenumodel/src/unitymenuaction.cpp create mode 100644 libqmenumodel/src/unitymenuaction.h diff --git a/libqmenumodel/QMenuModel/plugin.cpp b/libqmenumodel/QMenuModel/plugin.cpp index 0629099..c1a5e40 100644 --- a/libqmenumodel/QMenuModel/plugin.cpp +++ b/libqmenumodel/QMenuModel/plugin.cpp @@ -22,6 +22,7 @@ #include "qdbusmenumodel.h" #include "qdbusactiongroup.h" #include "qstateaction.h" +#include "unitymenuaction.h" #include "unitymenumodel.h" #include "unitythemediconprovider.h" @@ -40,6 +41,8 @@ void QMenuModelQmlPlugin::registerTypes(const char *uri) "QStateAction must be created by QDBusActionGroup::action"); qmlRegisterUncreatableType(uri, 0, 1, "DBus", "DBus is only a namespace"); + qmlRegisterUncreatableType(uri, 0, 1, "UnityMenuAction", + "UnityMenuAction must be created by UnityMenuModel"); qmlRegisterType(uri, 0, 1, "QDBusMenuModel"); qmlRegisterType(uri, 0, 1, "QDBusActionGroup"); diff --git a/libqmenumodel/src/CMakeLists.txt b/libqmenumodel/src/CMakeLists.txt index cf9c426..e0ee902 100644 --- a/libqmenumodel/src/CMakeLists.txt +++ b/libqmenumodel/src/CMakeLists.txt @@ -10,6 +10,7 @@ set(QMENUMODEL_SRC qdbusmenumodel.cpp qdbusactiongroup.cpp qstateaction.cpp + unitymenuaction.cpp unitymenumodel.cpp unitythemediconprovider.cpp gtk/gtkactionmuxer.c @@ -57,6 +58,7 @@ set(QMENUMODEL_HEADERS qdbusobject.h qmenumodel.h qstateaction.h + unitymenuaction.h unitymenumodel.h unitythemediconprovider.h ) diff --git a/libqmenumodel/src/unitymenuaction.cpp b/libqmenumodel/src/unitymenuaction.cpp new file mode 100644 index 0000000..f391bfe --- /dev/null +++ b/libqmenumodel/src/unitymenuaction.cpp @@ -0,0 +1,60 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 3. + * + * This program 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 program. If not, see . + * + * Authors: + * Nick Dedekind + */ + +#include "unitymenuaction.h" +#include "unitymenumodel.h" + +UnityMenuAction::UnityMenuAction(QObject* parent) + : QObject(parent), + m_model(NULL), + m_index(-1) +{ +} + +int UnityMenuAction::index() const +{ + return m_index; +} + +void UnityMenuAction::setIndex(int index) +{ + if (m_index != index) { + m_index = index; + Q_EMIT indexChanged(index); + } +} + +UnityMenuModel* UnityMenuAction::model() const +{ + return m_model; +} + +void UnityMenuAction::setModel(UnityMenuModel* model) +{ + if (m_model != model) { + if (m_model) { + disconnect(m_model); + } + m_model = model; + + connect(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector&)), SIGNAL(stateChanged())); + + Q_EMIT modelChanged(model); + } +} diff --git a/libqmenumodel/src/unitymenuaction.h b/libqmenumodel/src/unitymenuaction.h new file mode 100644 index 0000000..26be02b --- /dev/null +++ b/libqmenumodel/src/unitymenuaction.h @@ -0,0 +1,59 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 3. + * + * This program 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 program. If not, see . + * + * Authors: + * Nick Dedekind + */ + +#ifndef UNITYMENUACTION_H +#define UNITYMENUACTION_H + +#include +#include + +typedef struct _GVariant GVariant; +class UnityMenuModel; + +class UnityMenuAction : public QObject +{ + Q_OBJECT + Q_PROPERTY(QVariant state READ state WRITE updateState NOTIFY stateChanged) + Q_PROPERTY(int index READ index WRITE setIndex NOTIFY indexChanged) + Q_PROPERTY(UnityMenuModel* model READ model WRITE setModel NOTIFY modelChanged) +public: + UnityMenuAction(QObject* parent=0); + + int index() const; + void setIndex(int index); + + UnityMenuModel* model() const; + void setModel(UnityMenuModel* model); + + virtual QVariant state() const = 0; + Q_INVOKABLE virtual void updateState(const QVariant& = QVariant()) = 0; + +Q_SIGNALS: + void stateChanged(); + void indexChanged(int index); + void modelChanged(UnityMenuModel* model); + +private: + UnityMenuModel* m_model; + int m_index; +}; + +Q_DECLARE_METATYPE(UnityMenuAction*) + +#endif // UNITYMENUACTION_H diff --git a/libqmenumodel/src/unitymenumodel.cpp b/libqmenumodel/src/unitymenumodel.cpp index 8bbb601..72a5985 100644 --- a/libqmenumodel/src/unitymenumodel.cpp +++ b/libqmenumodel/src/unitymenumodel.cpp @@ -19,12 +19,11 @@ #include "unitymenumodel.h" #include "converter.h" #include "actionstateparser.h" +#include "unitymenuaction.h" #include #include -#include - extern "C" { #include "gtk/gtkactionmuxer.h" #include "gtk/gtkmenutracker.h" @@ -41,7 +40,7 @@ enum MenuRoles { IconRole, TypeRole, ExtendedAttributesRole, - ActionStateRole + ActionRole }; class UnityMenuModelPrivate @@ -76,6 +75,31 @@ public: static void menuItemChanged(GObject *object, GParamSpec *pspec, gpointer user_data); }; +class UnityGtkMenuTrackerItemAction : public UnityMenuAction +{ +public: + UnityGtkMenuTrackerItemAction(int index, UnityMenuModelPrivate* priv) + : UnityMenuAction(priv->model), + d(priv) + { + setModel(priv->model); + setIndex(index); + } + + virtual QVariant state() const { + GtkMenuTrackerItem* item = (GtkMenuTrackerItem *) g_sequence_get (g_sequence_get_iter_at_pos (d->items, index())); + if (!item) { + return QVariant(); + } + return d->itemState(item); + } + + virtual void updateState(const QVariant& param = QVariant()) { } + +private: + UnityMenuModelPrivate* d; +}; + void menu_item_free (gpointer data) { GtkMenuTrackerItem *item = (GtkMenuTrackerItem *) data; @@ -411,8 +435,8 @@ QVariant UnityMenuModel::data(const QModelIndex &index, int role) const return map ? *map : QVariant(); } - case ActionStateRole: - return priv->itemState(item); + case ActionRole: + return QVariant::fromValue(new UnityGtkMenuTrackerItemAction(index.row(), priv)); default: return QVariant(); @@ -440,7 +464,7 @@ QHash UnityMenuModel::roleNames() const names[IconRole] = "icon"; names[TypeRole] = "type"; names[ExtendedAttributesRole] = "ext"; - names[ActionStateRole] = "actionState"; + names[ActionRole] = "action"; return names; } -- cgit v1.2.3 From 56374f1cfdbe68658a139cfad9dac8936f6ae60e Mon Sep 17 00:00:00 2001 From: Nick Dedekind Date: Tue, 30 Jul 2013 18:59:20 +0100 Subject: Added action name --- libqmenumodel/src/gtk/gtkmenutrackeritem.c | 24 ++++++++++++++++++++++++ libqmenumodel/src/gtk/gtkmenutrackeritem.h | 2 ++ libqmenumodel/src/unitymenuaction.h | 4 ++++ libqmenumodel/src/unitymenumodel.cpp | 12 ++++++++++++ 4 files changed, 42 insertions(+) diff --git a/libqmenumodel/src/gtk/gtkmenutrackeritem.c b/libqmenumodel/src/gtk/gtkmenutrackeritem.c index fb856c9..c14bbb8 100644 --- a/libqmenumodel/src/gtk/gtkmenutrackeritem.c +++ b/libqmenumodel/src/gtk/gtkmenutrackeritem.c @@ -106,6 +106,7 @@ enum { PROP_TOGGLED, PROP_ACCEL, PROP_SUBMENU_SHOWN, + PROP_ACTION_NAME, PROP_ACTION_STATE, N_PROPS }; @@ -179,6 +180,8 @@ gtk_menu_tracker_item_get_property (GObject *object, case PROP_SUBMENU_SHOWN: g_value_set_boolean (value, gtk_menu_tracker_item_get_submenu_shown (self)); break; + case PROP_ACTION_NAME: + g_value_set_string (value, gtk_menu_tracker_item_get_action_name (self)); case PROP_ACTION_STATE: g_value_set_variant (value, self->action_state); break; @@ -239,12 +242,16 @@ gtk_menu_tracker_item_class_init (GtkMenuTrackerItemClass *class) 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); + gtk_menu_tracker_item_pspecs[PROP_ACTION_NAME] = + g_param_spec_boolean ("action-name", "", "", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE); gtk_menu_tracker_item_pspecs[PROP_ACTION_STATE] = g_param_spec_boolean ("action-state", "", "", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE); g_object_class_install_properties (class, N_PROPS, gtk_menu_tracker_item_pspecs); } +#include + static void gtk_menu_tracker_item_action_added (GtkActionObserver *observer, GtkActionObservable *observable, @@ -268,6 +275,7 @@ gtk_menu_tracker_item_action_added (GtkActionObserver *observer, g_variant_unref (action_target); return; } + printf("INDICATORS: activateable %p\n", action_target); self->sensitive = enabled; @@ -612,6 +620,22 @@ gtk_menu_tracker_item_get_submenu_shown (GtkMenuTrackerItem *self) return self->submenu_shown; } +/** + * gtk_menu_tracker_item_get_action_name: + * @self: A #GtkMenuTrackerItem instance + * + * Returns the action name + */ +const gchar * +gtk_menu_tracker_item_get_action_name (GtkMenuTrackerItem *self) +{ + const gchar *action_name = NULL; + + g_menu_item_get_attribute (self->item, G_MENU_ATTRIBUTE_ACTION, "&s", &action_name); + + return action_name; +} + GVariant * gtk_menu_tracker_item_get_action_state (GtkMenuTrackerItem *self) { diff --git a/libqmenumodel/src/gtk/gtkmenutrackeritem.h b/libqmenumodel/src/gtk/gtkmenutrackeritem.h index 16c4415..8b0afae 100644 --- a/libqmenumodel/src/gtk/gtkmenutrackeritem.h +++ b/libqmenumodel/src/gtk/gtkmenutrackeritem.h @@ -81,6 +81,8 @@ void gtk_menu_tracker_item_request_submenu_shown (GtkMenu gboolean gtk_menu_tracker_item_get_submenu_shown (GtkMenuTrackerItem *self); +const gchar * gtk_menu_tracker_item_get_action_name (GtkMenuTrackerItem *self); + GVariant * gtk_menu_tracker_item_get_action_state (GtkMenuTrackerItem *self); gboolean gtk_menu_tracker_item_get_attribute (GtkMenuTrackerItem *self, diff --git a/libqmenumodel/src/unitymenuaction.h b/libqmenumodel/src/unitymenuaction.h index 26be02b..4dea311 100644 --- a/libqmenumodel/src/unitymenuaction.h +++ b/libqmenumodel/src/unitymenuaction.h @@ -29,6 +29,7 @@ class UnityMenuModel; class UnityMenuAction : public QObject { Q_OBJECT + Q_PROPERTY(QVariant name READ name NOTIFY nameChanged) Q_PROPERTY(QVariant state READ state WRITE updateState NOTIFY stateChanged) Q_PROPERTY(int index READ index WRITE setIndex NOTIFY indexChanged) Q_PROPERTY(UnityMenuModel* model READ model WRITE setModel NOTIFY modelChanged) @@ -41,10 +42,13 @@ public: UnityMenuModel* model() const; void setModel(UnityMenuModel* model); + virtual QString name() const = 0; + virtual QVariant state() const = 0; Q_INVOKABLE virtual void updateState(const QVariant& = QVariant()) = 0; Q_SIGNALS: + void nameChanged(); void stateChanged(); void indexChanged(int index); void modelChanged(UnityMenuModel* model); diff --git a/libqmenumodel/src/unitymenumodel.cpp b/libqmenumodel/src/unitymenumodel.cpp index 72a5985..7c5d597 100644 --- a/libqmenumodel/src/unitymenumodel.cpp +++ b/libqmenumodel/src/unitymenumodel.cpp @@ -86,6 +86,14 @@ public: setIndex(index); } + virtual QString name() const { + GtkMenuTrackerItem* item = (GtkMenuTrackerItem *) g_sequence_get (g_sequence_get_iter_at_pos (d->items, index())); + if (!item) { + return ""; + } + return gtk_menu_tracker_item_get_action_name(item); + } + virtual QVariant state() const { GtkMenuTrackerItem* item = (GtkMenuTrackerItem *) g_sequence_get (g_sequence_get_iter_at_pos (d->items, index())); if (!item) { @@ -534,6 +542,10 @@ static QVariant attributeToQVariant(GVariant *value, const QString &type) if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) result = QVariant(g_variant_get_string(value, NULL)); } + if (type == "double") { + if (g_variant_is_of_type (value, G_VARIANT_TYPE_DOUBLE)) + result = QVariant(g_variant_get_double(value)); + } else if (type == "icon") { GIcon *icon = g_icon_deserialize (value); if (icon) { -- cgit v1.2.3 From 37e62467aaa50babb48a45dcba8ab3ce5f5a7ec8 Mon Sep 17 00:00:00 2001 From: Nick Dedekind Date: Tue, 30 Jul 2013 20:49:44 +0100 Subject: Added menu action updateState --- libqmenumodel/src/gtk/gtkmenutrackeritem.c | 21 +++++++++++++ libqmenumodel/src/gtk/gtkmenutrackeritem.h | 6 ++++ libqmenumodel/src/unitymenuaction.h | 5 +++- libqmenumodel/src/unitymenumodel.cpp | 48 +++++++++++++++++++----------- libqmenumodel/src/unitymenumodel.h | 3 -- 5 files changed, 62 insertions(+), 21 deletions(-) diff --git a/libqmenumodel/src/gtk/gtkmenutrackeritem.c b/libqmenumodel/src/gtk/gtkmenutrackeritem.c index c14bbb8..7187921 100644 --- a/libqmenumodel/src/gtk/gtkmenutrackeritem.c +++ b/libqmenumodel/src/gtk/gtkmenutrackeritem.c @@ -685,6 +685,27 @@ gtk_menu_tracker_item_activated (GtkMenuTrackerItem *self) g_variant_unref (action_target); } +void +gtk_menu_tracker_item_update_state (GtkMenuTrackerItem *self, GVariant *value) +{ + const gchar *action_name; + + g_return_if_fail (GTK_IS_MENU_TRACKER_ITEM (self)); + + g_menu_item_get_attribute (self->item, G_MENU_ATTRIBUTE_ACTION, "&s", &action_name); + + if (self->action_namespace) + { + gchar *full_action; + + full_action = g_strjoin (".", self->action_namespace, action_name, NULL); + g_action_group_change_action_state (G_ACTION_GROUP (self->observable), full_action, g_variant_ref(value)); + g_free (full_action); + } + else + g_action_group_change_action_state (G_ACTION_GROUP (self->observable), action_name, g_variant_ref(value)); +} + typedef struct { GtkMenuTrackerItem *item; diff --git a/libqmenumodel/src/gtk/gtkmenutrackeritem.h b/libqmenumodel/src/gtk/gtkmenutrackeritem.h index 8b0afae..f38d9fe 100644 --- a/libqmenumodel/src/gtk/gtkmenutrackeritem.h +++ b/libqmenumodel/src/gtk/gtkmenutrackeritem.h @@ -76,6 +76,12 @@ gboolean gtk_menu_tracker_item_get_should_request_show (GtkMenu void gtk_menu_tracker_item_activated (GtkMenuTrackerItem *self); +void gtk_menu_tracker_item_update_state (GtkMenuTrackerItem *self, + GVariant *value); + + + + void gtk_menu_tracker_item_request_submenu_shown (GtkMenuTrackerItem *self, gboolean shown); diff --git a/libqmenumodel/src/unitymenuaction.h b/libqmenumodel/src/unitymenuaction.h index 4dea311..ac5eadb 100644 --- a/libqmenumodel/src/unitymenuaction.h +++ b/libqmenumodel/src/unitymenuaction.h @@ -45,7 +45,10 @@ public: virtual QString name() const = 0; virtual QVariant state() const = 0; - Q_INVOKABLE virtual void updateState(const QVariant& = QVariant()) = 0; + + Q_INVOKABLE virtual void activate() = 0; + + Q_INVOKABLE virtual void updateState(const QVariant& vvalue) = 0; Q_SIGNALS: void nameChanged(); diff --git a/libqmenumodel/src/unitymenumodel.cpp b/libqmenumodel/src/unitymenumodel.cpp index 7c5d597..9cad955 100644 --- a/libqmenumodel/src/unitymenumodel.cpp +++ b/libqmenumodel/src/unitymenumodel.cpp @@ -87,22 +87,44 @@ public: } virtual QString name() const { - GtkMenuTrackerItem* item = (GtkMenuTrackerItem *) g_sequence_get (g_sequence_get_iter_at_pos (d->items, index())); - if (!item) { - return ""; - } + GtkMenuTrackerItem* item; + + item = (GtkMenuTrackerItem *) g_sequence_get (g_sequence_get_iter_at_pos (d->items, index())); + if (!item) return QString(); + return gtk_menu_tracker_item_get_action_name(item); } virtual QVariant state() const { - GtkMenuTrackerItem* item = (GtkMenuTrackerItem *) g_sequence_get (g_sequence_get_iter_at_pos (d->items, index())); - if (!item) { - return QVariant(); - } + GtkMenuTrackerItem* item; + + item = (GtkMenuTrackerItem *) g_sequence_get (g_sequence_get_iter_at_pos (d->items, index())); + if (!item) return QVariant(); + return d->itemState(item); } - virtual void updateState(const QVariant& param = QVariant()) { } + virtual void activate() + { + GtkMenuTrackerItem* item; + + item = (GtkMenuTrackerItem *) g_sequence_get (g_sequence_get_iter_at_pos (d->items, index())); + if (!item) return; + + gtk_menu_tracker_item_activated (item); + } + + virtual void updateState(const QVariant& vvalue) + { + GtkMenuTrackerItem* item; + + item = (GtkMenuTrackerItem *) g_sequence_get (g_sequence_get_iter_at_pos (d->items, index())); + if (!item) return; + + GVariant* data = Converter::toGVariant(vvalue); + gtk_menu_tracker_item_update_state (item, data); + g_variant_unref(data); + } private: UnityMenuModelPrivate* d; @@ -512,14 +534,6 @@ QObject * UnityMenuModel::submenu(int position, QQmlComponent* actionStateParser return model; } -void UnityMenuModel::activate(int index) -{ - GtkMenuTrackerItem *item; - - item = (GtkMenuTrackerItem *) g_sequence_get (g_sequence_get_iter_at_pos (priv->items, index)); - gtk_menu_tracker_item_activated (item); -} - static void freeExtendedAttrs(gpointer data) { QVariantMap *extendedAttrs = (QVariantMap *) data; diff --git a/libqmenumodel/src/unitymenumodel.h b/libqmenumodel/src/unitymenumodel.h index ad409ba..5401331 100644 --- a/libqmenumodel/src/unitymenumodel.h +++ b/libqmenumodel/src/unitymenumodel.h @@ -64,9 +64,6 @@ Q_SIGNALS: void menuObjectPathChanged(const QByteArray &path); void actionStateParserChanged(ActionStateParser* parser); -public Q_SLOTS: - void activate(int index); - private: class UnityMenuModelPrivate *priv; friend class UnityMenuModelPrivate; -- cgit v1.2.3 From 27e7c3bf6ee276c5956331f292a72a5dde5869ed Mon Sep 17 00:00:00 2001 From: Nick Dedekind Date: Wed, 31 Jul 2013 12:14:53 +0100 Subject: removed debug code --- libqmenumodel/src/gtk/gtkmenutrackeritem.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/libqmenumodel/src/gtk/gtkmenutrackeritem.c b/libqmenumodel/src/gtk/gtkmenutrackeritem.c index 7187921..dbc1580 100644 --- a/libqmenumodel/src/gtk/gtkmenutrackeritem.c +++ b/libqmenumodel/src/gtk/gtkmenutrackeritem.c @@ -250,8 +250,6 @@ gtk_menu_tracker_item_class_init (GtkMenuTrackerItemClass *class) g_object_class_install_properties (class, N_PROPS, gtk_menu_tracker_item_pspecs); } -#include - static void gtk_menu_tracker_item_action_added (GtkActionObserver *observer, GtkActionObservable *observable, @@ -275,7 +273,6 @@ gtk_menu_tracker_item_action_added (GtkActionObserver *observer, g_variant_unref (action_target); return; } - printf("INDICATORS: activateable %p\n", action_target); self->sensitive = enabled; -- cgit v1.2.3 From c458dff48f2ae10781f587bff93e00f3b461c4da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mirco=20M=C3=BCller?= Date: Thu, 1 Aug 2013 18:48:19 +0200 Subject: Fix exporting of boolean extended-attribute in attributeToQVariant() --- libqmenumodel/src/unitymenumodel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libqmenumodel/src/unitymenumodel.cpp b/libqmenumodel/src/unitymenumodel.cpp index 72a5985..adec861 100644 --- a/libqmenumodel/src/unitymenumodel.cpp +++ b/libqmenumodel/src/unitymenumodel.cpp @@ -528,7 +528,7 @@ static QVariant attributeToQVariant(GVariant *value, const QString &type) } if (type == "bool") { if (g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN)) - result = QVariant(g_variant_get_int32(value)); + result = QVariant(g_variant_get_boolean(value)); } else if (type == "string") { if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) -- cgit v1.2.3 From 2bbdf4e2347ec566280b9d878a12b7b1e732f632 Mon Sep 17 00:00:00 2001 From: Nick Dedekind Date: Fri, 9 Aug 2013 10:50:12 +0100 Subject: glib callbacks pass events through qt. --- examples/unityqmlmenumodel.qml | 2 +- libqmenumodel/src/CMakeLists.txt | 1 + libqmenumodel/src/unitymenumodel.cpp | 95 +++++++++++++++++++++--------- libqmenumodel/src/unitymenumodel.h | 3 + libqmenumodel/src/unitymenumodelevents.cpp | 63 ++++++++++++++++++++ libqmenumodel/src/unitymenumodelevents.h | 69 ++++++++++++++++++++++ 6 files changed, 204 insertions(+), 29 deletions(-) create mode 100644 libqmenumodel/src/unitymenumodelevents.cpp create mode 100644 libqmenumodel/src/unitymenumodelevents.h diff --git a/examples/unityqmlmenumodel.qml b/examples/unityqmlmenumodel.qml index e1de3c8..c0e5adc 100644 --- a/examples/unityqmlmenumodel.qml +++ b/examples/unityqmlmenumodel.qml @@ -108,7 +108,7 @@ Item { if (submenu) listview.model = submenu; else - listview.model.activate(index); + action.activate(); } } } diff --git a/libqmenumodel/src/CMakeLists.txt b/libqmenumodel/src/CMakeLists.txt index e0ee902..db8095a 100644 --- a/libqmenumodel/src/CMakeLists.txt +++ b/libqmenumodel/src/CMakeLists.txt @@ -12,6 +12,7 @@ set(QMENUMODEL_SRC qstateaction.cpp unitymenuaction.cpp unitymenumodel.cpp + unitymenumodelevents.cpp unitythemediconprovider.cpp gtk/gtkactionmuxer.c gtk/gtkactionmuxer.h diff --git a/libqmenumodel/src/unitymenumodel.cpp b/libqmenumodel/src/unitymenumodel.cpp index 5226747..8e0f0d1 100644 --- a/libqmenumodel/src/unitymenumodel.cpp +++ b/libqmenumodel/src/unitymenumodel.cpp @@ -20,9 +20,11 @@ #include "converter.h" #include "actionstateparser.h" #include "unitymenuaction.h" +#include "unitymenumodelevents.h" #include #include +#include extern "C" { #include "gtk/gtkactionmuxer.h" @@ -165,18 +167,8 @@ UnityMenuModelPrivate::~UnityMenuModelPrivate() void UnityMenuModelPrivate::clearItems(bool resetModel) { - GSequenceIter *begin; - GSequenceIter *end; - - if (resetModel) - model->beginResetModel(); - - begin = g_sequence_get_begin_iter (this->items); - end = g_sequence_get_end_iter (this->items); - g_sequence_remove_range (begin, end); - - if (resetModel) - model->endResetModel(); + UnityMenuModelClearEvent ummce(resetModel); + QCoreApplication::sendEvent(model, &ummce); } void UnityMenuModelPrivate::clearName() @@ -260,28 +252,17 @@ void UnityMenuModelPrivate::nameVanished(GDBusConnection *connection, const gcha void UnityMenuModelPrivate::menuItemInserted(GtkMenuTrackerItem *item, gint position, gpointer user_data) { UnityMenuModelPrivate *priv = (UnityMenuModelPrivate *)user_data; - GSequenceIter *it; - - priv->model->beginInsertRows(QModelIndex(), position, position); - - it = g_sequence_get_iter_at_pos (priv->items, position); - it = g_sequence_insert_before (it, g_object_ref (item)); - g_object_set_qdata (G_OBJECT (item), unity_menu_model_quark (), priv->model); - g_signal_connect (item, "notify", G_CALLBACK (menuItemChanged), it); - priv->model->endInsertRows(); + UnityMenuModelAddRowEvent ummare(item, position); + QCoreApplication::sendEvent(priv->model, &ummare); } void UnityMenuModelPrivate::menuItemRemoved(gint position, gpointer user_data) { UnityMenuModelPrivate *priv = (UnityMenuModelPrivate *)user_data; - GSequenceIter *it; - - priv->model->beginRemoveRows(QModelIndex(), position, position); - g_sequence_remove (g_sequence_get_iter_at_pos (priv->items, position)); - - priv->model->endRemoveRows(); + UnityMenuModelRemoveRowEvent ummrre(position); + QCoreApplication::sendEvent(priv->model, &ummrre); } void UnityMenuModelPrivate::menuItemChanged(GObject *object, GParamSpec *pspec, gpointer user_data) @@ -297,7 +278,9 @@ void UnityMenuModelPrivate::menuItemChanged(GObject *object, GParamSpec *pspec, model = (UnityMenuModel *) g_object_get_qdata (G_OBJECT (item), unity_menu_model_quark ()); position = g_sequence_iter_get_position (it); - Q_EMIT model->dataChanged(model->index(position, 0), model->index(position, 0)); + // FIXME + UnityMenuModelDataChangeEvent ummdce(position); + QCoreApplication::sendEvent(model, &ummdce); } UnityMenuModel::UnityMenuModel(QObject *parent): @@ -639,3 +622,59 @@ QVariant UnityMenuModel::get(int row, const QByteArray &role) return this->data(this->index(row, 0), priv->roles[role]); } + +bool UnityMenuModel::event(QEvent* e) +{ + if (e->type() == UnityMenuModelClearEvent::eventType) { + UnityMenuModelClearEvent *emmce = static_cast(e); + + GSequenceIter *begin; + GSequenceIter *end; + + if (emmce->reset) + beginResetModel(); + + begin = g_sequence_get_begin_iter (priv->items); + end = g_sequence_get_end_iter (priv->items); + g_sequence_remove_range (begin, end); + + if (emmce->reset) + endResetModel(); + + return true; + } else if (e->type() == UnityMenuModelAddRowEvent::eventType) { + UnityMenuModelAddRowEvent *ummrce = static_cast(e); + + GSequenceIter *it; + it = g_sequence_get_iter_at_pos (priv->items, ummrce->position); + if (it) { + beginInsertRows(QModelIndex(), ummrce->position, ummrce->position); + + it = g_sequence_insert_before (it, g_object_ref (ummrce->item)); + g_object_set_qdata (G_OBJECT (ummrce->item), unity_menu_model_quark (), this); + g_signal_connect (ummrce->item, "notify", G_CALLBACK (UnityMenuModelPrivate::menuItemChanged), it); + + endInsertRows(); + } + return true; + } else if (e->type() == UnityMenuModelRemoveRowEvent::eventType) { + UnityMenuModelRemoveRowEvent *ummrre = static_cast(e); + + GSequenceIter *it; + it = g_sequence_get_iter_at_pos (priv->items, ummrre->position); + if (it) { + beginRemoveRows(QModelIndex(), ummrre->position, ummrre->position); + + g_sequence_remove (it); + + endRemoveRows(); + } + return true; + } else if (e->type() == UnityMenuModelDataChangeEvent::eventType) { + UnityMenuModelDataChangeEvent *ummdce = static_cast(e); + + Q_EMIT dataChanged(index(ummdce->position, 0), index(ummdce->position, 0)); + return true; + } + return QAbstractListModel::event(e); +} diff --git a/libqmenumodel/src/unitymenumodel.h b/libqmenumodel/src/unitymenumodel.h index 5401331..4aaadfd 100644 --- a/libqmenumodel/src/unitymenumodel.h +++ b/libqmenumodel/src/unitymenumodel.h @@ -64,6 +64,9 @@ Q_SIGNALS: void menuObjectPathChanged(const QByteArray &path); void actionStateParserChanged(ActionStateParser* parser); +protected: + virtual bool event(QEvent* e); + private: class UnityMenuModelPrivate *priv; friend class UnityMenuModelPrivate; diff --git a/libqmenumodel/src/unitymenumodelevents.cpp b/libqmenumodel/src/unitymenumodelevents.cpp new file mode 100644 index 0000000..e03d1c7 --- /dev/null +++ b/libqmenumodel/src/unitymenumodelevents.cpp @@ -0,0 +1,63 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 3. + * + * This program 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 program. If not, see . + * + * Authors: + * Nicholas Dedekind +#include +} + +#include "unitymenumodelevents.h" +#include "unitymenumodel.h" + +const QEvent::Type UnityMenuModelClearEvent::eventType = static_cast(QEvent::registerEventType()); +const QEvent::Type UnityMenuModelAddRowEvent::eventType = static_cast(QEvent::registerEventType()); +const QEvent::Type UnityMenuModelRemoveRowEvent::eventType = static_cast(QEvent::registerEventType()); +const QEvent::Type UnityMenuModelDataChangeEvent::eventType = static_cast(QEvent::registerEventType()); + +UnityMenuModelClearEvent::UnityMenuModelClearEvent(bool _reset) + : QEvent(UnityMenuModelClearEvent::eventType), + reset(_reset) +{} + +UnityMenuModelAddRowEvent::UnityMenuModelAddRowEvent(GtkMenuTrackerItem *_item, int _position) + : QEvent(UnityMenuModelAddRowEvent::eventType), + item(_item), + position(_position) +{ + if (item) { + g_object_ref(item); + } +} + +UnityMenuModelAddRowEvent::~UnityMenuModelAddRowEvent() +{ + if (item) { + g_object_unref(item); + } +} + +UnityMenuModelRemoveRowEvent::UnityMenuModelRemoveRowEvent(int _position) + : QEvent(UnityMenuModelRemoveRowEvent::eventType), + position(_position) +{} + +UnityMenuModelDataChangeEvent::UnityMenuModelDataChangeEvent(int _position) + : QEvent(UnityMenuModelDataChangeEvent::eventType), + position(_position) +{} diff --git a/libqmenumodel/src/unitymenumodelevents.h b/libqmenumodel/src/unitymenumodelevents.h new file mode 100644 index 0000000..dcb27ff --- /dev/null +++ b/libqmenumodel/src/unitymenumodelevents.h @@ -0,0 +1,69 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 3. + * + * This program 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 program. If not, see . + * + * Authors: + * Nicholas Dedekind + +typedef struct _GtkMenuTrackerItem GtkMenuTrackerItem; + +/* Event for a unitymenumodel clear */ +class UnityMenuModelClearEvent : public QEvent +{ +public: + static const QEvent::Type eventType; + UnityMenuModelClearEvent(bool reset); + + bool reset; +}; + +/* Event for a row add for unitymenumodel */ +class UnityMenuModelAddRowEvent : public QEvent +{ +public: + static const QEvent::Type eventType; + UnityMenuModelAddRowEvent(GtkMenuTrackerItem *item, int position); + ~UnityMenuModelAddRowEvent(); + + GtkMenuTrackerItem *item; + int position; +}; + +/* Event for a row remove for unitymenumodel */ +class UnityMenuModelRemoveRowEvent : public QEvent +{ +public: + static const QEvent::Type eventType; + UnityMenuModelRemoveRowEvent(int position); + + int position; +}; + +/* Event for a row data change for unitymenumodel */ +class UnityMenuModelDataChangeEvent : public QEvent +{ +public: + static const QEvent::Type eventType; + UnityMenuModelDataChangeEvent(int position); + + int position; +}; + +#endif //UNITYMENUMODELEVENTS_H -- cgit v1.2.3 From 08a5bb3a2037a4c47059a4be1165874fa81c6a62 Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Fri, 9 Aug 2013 13:06:10 +0200 Subject: Re-add UnityMenuModel::activate() --- libqmenumodel/src/unitymenumodel.cpp | 8 ++++++++ libqmenumodel/src/unitymenumodel.h | 1 + 2 files changed, 9 insertions(+) diff --git a/libqmenumodel/src/unitymenumodel.cpp b/libqmenumodel/src/unitymenumodel.cpp index 5226747..9b62eee 100644 --- a/libqmenumodel/src/unitymenumodel.cpp +++ b/libqmenumodel/src/unitymenumodel.cpp @@ -639,3 +639,11 @@ QVariant UnityMenuModel::get(int row, const QByteArray &role) return this->data(this->index(row, 0), priv->roles[role]); } + +void UnityMenuModel::activate(int index) +{ + GtkMenuTrackerItem *item; + + item = (GtkMenuTrackerItem *) g_sequence_get (g_sequence_get_iter_at_pos (priv->items, index)); + gtk_menu_tracker_item_activated (item); +} diff --git a/libqmenumodel/src/unitymenumodel.h b/libqmenumodel/src/unitymenumodel.h index 5401331..79dbaa9 100644 --- a/libqmenumodel/src/unitymenumodel.h +++ b/libqmenumodel/src/unitymenumodel.h @@ -57,6 +57,7 @@ public: Q_INVOKABLE QObject * submenu(int position, QQmlComponent* actionStateParser = NULL); Q_INVOKABLE bool loadExtendedAttributes(int position, const QVariantMap &schema); Q_INVOKABLE QVariant get(int row, const QByteArray &role); + Q_INVOKABLE void activate(int index); Q_SIGNALS: void busNameChanged(const QByteArray &name); -- cgit v1.2.3 From 4e1a2603a84117210064e2ef970ce4b81157427c Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Fri, 9 Aug 2013 13:33:39 +0200 Subject: Add parameter to unitymenumodel.action.activate --- libqmenumodel/src/unitymenuaction.h | 2 +- libqmenumodel/src/unitymenumodel.cpp | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/libqmenumodel/src/unitymenuaction.h b/libqmenumodel/src/unitymenuaction.h index ef844fd..bcbb50d 100644 --- a/libqmenumodel/src/unitymenuaction.h +++ b/libqmenumodel/src/unitymenuaction.h @@ -46,7 +46,7 @@ public: virtual QVariant state() const = 0; - Q_INVOKABLE virtual void activate() = 0; + Q_INVOKABLE virtual void activate(const QVariant ¶meter) = 0; Q_INVOKABLE virtual void changeState(const QVariant& vvalue) = 0; diff --git a/libqmenumodel/src/unitymenumodel.cpp b/libqmenumodel/src/unitymenumodel.cpp index 9b62eee..08f220e 100644 --- a/libqmenumodel/src/unitymenumodel.cpp +++ b/libqmenumodel/src/unitymenumodel.cpp @@ -104,14 +104,18 @@ public: return d->itemState(item); } - virtual void activate() + virtual void activate(const QVariant ¶meter) { GtkMenuTrackerItem* item; + gchar *action; item = (GtkMenuTrackerItem *) g_sequence_get (g_sequence_get_iter_at_pos (d->items, index())); if (!item) return; - gtk_menu_tracker_item_activated (item); + gtk_menu_tracker_item_get_attribute (item, "action", "s", &action); + g_action_group_activate_action (G_ACTION_GROUP (d->muxer), action, Converter::toGVariant(parameter)); + + g_free (action); } virtual void changeState(const QVariant& vvalue) -- cgit v1.2.3 From 62514129ef270f9fcc7c2be628fd2f7ec5c2199a Mon Sep 17 00:00:00 2001 From: Nick Dedekind Date: Fri, 9 Aug 2013 18:52:48 +0100 Subject: use correct qvariant types for sensitive and isSeparator roles --- libqmenumodel/src/unitymenumodel.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libqmenumodel/src/unitymenumodel.cpp b/libqmenumodel/src/unitymenumodel.cpp index 5226747..b1c9a6a 100644 --- a/libqmenumodel/src/unitymenumodel.cpp +++ b/libqmenumodel/src/unitymenumodel.cpp @@ -433,10 +433,10 @@ QVariant UnityMenuModel::data(const QModelIndex &index, int role) const return gtk_menu_tracker_item_get_label (item); case SensitiveRole: - return gtk_menu_tracker_item_get_sensitive (item); + return gtk_menu_tracker_item_get_sensitive (item) == TRUE ? true : false; case IsSeparatorRole: - return gtk_menu_tracker_item_get_is_separator (item); + return gtk_menu_tracker_item_get_is_separator (item) == TRUE ? true : false; case IconRole: { GIcon *icon = gtk_menu_tracker_item_get_icon (item); -- cgit v1.2.3 From 6bd9b6831e7046b5c414840f88aee8002be3cbb6 Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Mon, 12 Aug 2013 10:55:11 +0200 Subject: Expose isCheck and isRadio roles --- libqmenumodel/src/unitymenumodel.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/libqmenumodel/src/unitymenumodel.cpp b/libqmenumodel/src/unitymenumodel.cpp index 08f220e..a164476 100644 --- a/libqmenumodel/src/unitymenumodel.cpp +++ b/libqmenumodel/src/unitymenumodel.cpp @@ -40,7 +40,9 @@ enum MenuRoles { IconRole, TypeRole, ExtendedAttributesRole, - ActionRole + ActionRole, + IsCheckRole, + IsRadioRole }; class UnityMenuModelPrivate @@ -472,6 +474,12 @@ QVariant UnityMenuModel::data(const QModelIndex &index, int role) const case ActionRole: return QVariant::fromValue(new UnityGtkMenuTrackerItemAction(index.row(), priv)); + case IsCheckRole: + return gtk_menu_tracker_item_get_role (item) == GTK_MENU_TRACKER_ITEM_ROLE_CHECK; + + case IsRadioRole: + return gtk_menu_tracker_item_get_role (item) == GTK_MENU_TRACKER_ITEM_ROLE_RADIO; + default: return QVariant(); } @@ -499,6 +507,8 @@ QHash UnityMenuModel::roleNames() const names[TypeRole] = "type"; names[ExtendedAttributesRole] = "ext"; names[ActionRole] = "action"; + names[IsCheckRole] = "isCheck"; + names[IsRadioRole] = "isRadio"; return names; } -- cgit v1.2.3 From 94a0809279ae8768fa53e19f9ad06832112b15de Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Mon, 12 Aug 2013 12:33:40 +0200 Subject: Add isToggled role --- libqmenumodel/src/unitymenumodel.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libqmenumodel/src/unitymenumodel.cpp b/libqmenumodel/src/unitymenumodel.cpp index 8045d57..8191dee 100644 --- a/libqmenumodel/src/unitymenumodel.cpp +++ b/libqmenumodel/src/unitymenumodel.cpp @@ -44,7 +44,8 @@ enum MenuRoles { ExtendedAttributesRole, ActionRole, IsCheckRole, - IsRadioRole + IsRadioRole, + IsToggledRole }; class UnityMenuModelPrivate @@ -462,6 +463,9 @@ QVariant UnityMenuModel::data(const QModelIndex &index, int role) const case IsRadioRole: return gtk_menu_tracker_item_get_role (item) == GTK_MENU_TRACKER_ITEM_ROLE_RADIO; + case IsToggledRole: + return gtk_menu_tracker_item_get_toggled (item) == TRUE ? true : false; + default: return QVariant(); } @@ -491,6 +495,7 @@ QHash UnityMenuModel::roleNames() const names[ActionRole] = "action"; names[IsCheckRole] = "isCheck"; names[IsRadioRole] = "isRadio"; + names[IsToggledRole] = "isToggled"; return names; } -- cgit v1.2.3