diff options
author | Mike Gabriel <mike.gabriel@das-netzwerkteam.de> | 2023-08-24 19:53:29 +0200 |
---|---|---|
committer | Mike Gabriel <mike.gabriel@das-netzwerkteam.de> | 2023-08-24 19:53:29 +0200 |
commit | c8b25f704b84636d657ad001414223f407822aee (patch) | |
tree | 78c35852d865f3d5229ef9ef0a10e17b87d5335b | |
parent | 017363f11010aa1ec859a5a3f5fc76a7341539a3 (diff) | |
parent | ceaa2df6e9f96245a0e99edeb29a8b28c39c79b1 (diff) | |
download | qmenumodel-main.tar.gz qmenumodel-main.tar.bz2 qmenumodel-main.zip |
Attributes GH PR #23: https://github.com/AyatanaIndicators/qmenumodel/pull/23
-rw-r--r-- | libqmenumodel/src/ayatanamenumodel.cpp | 23 | ||||
-rw-r--r-- | libqmenumodel/src/gtk/gtkactionmuxer.c | 211 | ||||
-rw-r--r-- | libqmenumodel/src/gtk/gtkactionmuxer.h | 17 | ||||
-rw-r--r-- | libqmenumodel/src/gtk/gtkactionobserver.c | 40 | ||||
-rw-r--r-- | libqmenumodel/src/gtk/gtkactionobserver.h | 8 | ||||
-rw-r--r-- | libqmenumodel/src/gtk/gtkmenutracker.c | 245 | ||||
-rw-r--r-- | libqmenumodel/src/gtk/gtkmenutracker.h | 12 | ||||
-rw-r--r-- | libqmenumodel/src/gtk/gtkmenutrackeritem.c | 370 | ||||
-rw-r--r-- | libqmenumodel/src/gtk/gtkmenutrackeritem.h | 28 |
9 files changed, 748 insertions, 206 deletions
diff --git a/libqmenumodel/src/ayatanamenumodel.cpp b/libqmenumodel/src/ayatanamenumodel.cpp index 9ab0ff4..265309d 100644 --- a/libqmenumodel/src/ayatanamenumodel.cpp +++ b/libqmenumodel/src/ayatanamenumodel.cpp @@ -210,9 +210,16 @@ void AyatanaMenuModelPrivate::updateMenuModel() 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, NULL, - menuItemInserted, menuItemRemoved, this); + this->menutracker = gtk_menu_tracker_new(GTK_ACTION_OBSERVABLE (this->muxer), + G_MENU_MODEL (menu), + TRUE, + FALSE, + FALSE, + NULL, + NULL, + menuItemInserted, + menuItemRemoved, + this); g_object_unref (menu); } @@ -499,7 +506,7 @@ QVariant AyatanaMenuModel::data(const QModelIndex &index, int role) const return QKeySequence(gtk_menu_tracker_item_get_accel (item), QKeySequence::NativeText); case HasSubmenuRole: - return gtk_menu_tracker_item_get_has_submenu (item) != FALSE; + return gtk_menu_tracker_item_get_has_link (item, G_MENU_LINK_SUBMENU) != FALSE; default: return QVariant(); @@ -549,7 +556,7 @@ QObject * AyatanaMenuModel::submenu(int position, QQmlComponent* actionStatePars } item = (GtkMenuTrackerItem *) g_sequence_get (it); - if (!item || !gtk_menu_tracker_item_get_has_submenu (item)) { + if (!item || !gtk_menu_tracker_item_get_has_link (item, G_MENU_LINK_SUBMENU)) { return NULL; } @@ -564,10 +571,14 @@ QObject * AyatanaMenuModel::submenu(int position, QQmlComponent* actionStatePars } } - model->priv->menutracker = gtk_menu_tracker_new_for_item_submenu (item, + model->priv->menutracker = gtk_menu_tracker_new_for_item_link (item, + G_MENU_LINK_SUBMENU, + FALSE, + FALSE, AyatanaMenuModelPrivate::menuItemInserted, AyatanaMenuModelPrivate::menuItemRemoved, model->priv); + g_object_set_qdata (G_OBJECT (item), ayatana_submenu_model_quark (), model); } diff --git a/libqmenumodel/src/gtk/gtkactionmuxer.c b/libqmenumodel/src/gtk/gtkactionmuxer.c index 4618564..3080b4b 100644 --- a/libqmenumodel/src/gtk/gtkactionmuxer.c +++ b/libqmenumodel/src/gtk/gtkactionmuxer.c @@ -26,7 +26,7 @@ #include <string.h> -/** +/*< private > * SECTION:gtkactionmuxer * @short_description: Aggregate and monitor several action groups * @@ -37,19 +37,19 @@ * 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 + * applicable to an entire application (such as “quit”) and one * containing actions applicable to a particular window in the - * application (such as 'fullscreen'). + * 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 + * #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 + * 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). */ @@ -65,6 +65,7 @@ struct _GtkActionMuxer GHashTable *observed_actions; GHashTable *groups; + GHashTable *primary_accels; GtkActionMuxer *parent; }; @@ -81,6 +82,8 @@ enum static GParamSpec *properties[NUM_PROPERTIES]; +guint accel_signal; + typedef struct { GtkActionMuxer *muxer; @@ -134,7 +137,7 @@ gtk_action_muxer_list_actions (GActionGroup *action_group) actions); } - return (gchar **) g_array_free (actions, FALSE); + return (gchar **)(void *) g_array_free (actions, FALSE); } static Group * @@ -333,6 +336,39 @@ gtk_action_muxer_action_removed_from_parent (GActionGroup *action_group, gtk_action_muxer_action_removed (muxer, action_name); } +static void +gtk_action_muxer_primary_accel_changed (GtkActionMuxer *muxer, + const gchar *action_name, + const gchar *action_and_target) +{ + Action *action; + GSList *node; + + if (!action_name) + action_name = strrchr (action_and_target, '|') + 1; + + action = g_hash_table_lookup (muxer->observed_actions, action_name); + for (node = action ? action->watchers : NULL; node; node = node->next) + gtk_action_observer_primary_accel_changed (node->data, GTK_ACTION_OBSERVABLE (muxer), + action_name, action_and_target); + g_signal_emit (muxer, accel_signal, 0, action_name, action_and_target); +} + +static void +gtk_action_muxer_parent_primary_accel_changed (GtkActionMuxer *parent, + const gchar *action_name, + const gchar *action_and_target, + gpointer user_data) +{ + GtkActionMuxer *muxer = user_data; + + /* If it's in our table then don't let the parent one filter through */ + if (muxer->primary_accels && g_hash_table_lookup (muxer->primary_accels, action_and_target)) + return; + + gtk_action_muxer_primary_accel_changed (muxer, action_name, action_and_target); +} + static gboolean gtk_action_muxer_query_action (GActionGroup *action_group, const gchar *action_name, @@ -498,6 +534,8 @@ gtk_action_muxer_finalize (GObject *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); + if (muxer->primary_accels) + g_hash_table_unref (muxer->primary_accels); G_OBJECT_CLASS (gtk_action_muxer_parent_class) ->finalize (object); @@ -514,6 +552,7 @@ gtk_action_muxer_dispose (GObject *object) 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_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_parent_primary_accel_changed, muxer); g_clear_object (&muxer->parent); } @@ -593,6 +632,9 @@ gtk_action_muxer_class_init (GObjectClass *class) class->finalize = gtk_action_muxer_finalize; class->dispose = gtk_action_muxer_dispose; + accel_signal = g_signal_new ("primary-accel-changed", GTK_TYPE_ACTION_MUXER, G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING); + properties[PROP_PARENT] = g_param_spec_object ("parent", "Parent", "The parent muxer", GTK_TYPE_ACTION_MUXER, @@ -602,7 +644,7 @@ gtk_action_muxer_class_init (GObjectClass *class) g_object_class_install_properties (class, NUM_PROPERTIES, properties); } -/** +/*< private > * gtk_action_muxer_insert: * @muxer: a #GtkActionMuxer * @prefix: the prefix string for the action group @@ -610,15 +652,15 @@ gtk_action_muxer_class_init (GObjectClass *class) * * 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 <varname>x</varname> in @action_group, there is an equivalent - * action @prefix<literal>.</literal><varname>x</varname> in @muxer. + * action `x` in @action_group, there is an equivalent + * action @prefix`.x` in @muxer. * - * For example, if @prefix is "<literal>app</literal>" and @action_group - * contains an action called "<literal>quit</literal>", then @muxer will - * now contain an action called "<literal>app.quit</literal>". + * 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. + * “action_added” notifications will be emitted, as appropriate. * * @prefix must not contain a dot ('.'). */ @@ -656,7 +698,7 @@ gtk_action_muxer_insert (GtkActionMuxer *muxer, G_CALLBACK (gtk_action_muxer_group_action_state_changed), group); } -/** +/*< private > * gtk_action_muxer_remove: * @muxer: a #GtkActionMuxer * @prefix: the prefix of the action group to remove @@ -664,7 +706,7 @@ gtk_action_muxer_insert (GtkActionMuxer *muxer, * Removes a #GActionGroup from the #GtkActionMuxer. * * If any #GtkActionObservers are registered for actions in the group, - * "action_removed" notifications will be emitted, as appropriate. + * “action_removed” notifications will be emitted, as appropriate. */ void gtk_action_muxer_remove (GtkActionMuxer *muxer, @@ -690,7 +732,27 @@ gtk_action_muxer_remove (GtkActionMuxer *muxer, } } -/** +const gchar ** +gtk_action_muxer_list_prefixes (GtkActionMuxer *muxer) +{ + return (const gchar **) g_hash_table_get_keys_as_array (muxer->groups, NULL); +} + +GActionGroup * +gtk_action_muxer_lookup (GtkActionMuxer *muxer, + const gchar *prefix) +{ + Group *group; + + group = g_hash_table_lookup (muxer->groups, prefix); + + if (group != NULL) + return group->group; + + return NULL; +} + +/*< private > * gtk_action_muxer_new: * * Creates a new #GtkActionMuxer. @@ -701,7 +763,7 @@ gtk_action_muxer_new (void) return g_object_new (GTK_TYPE_ACTION_MUXER, NULL); } -/** +/*< private > * gtk_action_muxer_get_parent: * @muxer: a #GtkActionMuxer * @@ -715,7 +777,27 @@ gtk_action_muxer_get_parent (GtkActionMuxer *muxer) return muxer->parent; } -/** +static void +emit_changed_accels (GtkActionMuxer *muxer, + GtkActionMuxer *parent) +{ + while (parent) + { + if (parent->primary_accels) + { + GHashTableIter iter; + gpointer key; + + g_hash_table_iter_init (&iter, parent->primary_accels); + while (g_hash_table_iter_next (&iter, &key, NULL)) + gtk_action_muxer_primary_accel_changed (muxer, NULL, key); + } + + parent = parent->parent; + } +} + +/*< private > * gtk_action_muxer_set_parent: * @muxer: a #GtkActionMuxer * @parent: (allow-none): the new parent #GtkActionMuxer @@ -742,10 +824,13 @@ gtk_action_muxer_set_parent (GtkActionMuxer *muxer, gtk_action_muxer_action_removed (muxer, *it); g_strfreev (actions); + emit_changed_accels (muxer, 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_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_parent_primary_accel_changed, muxer); g_object_unref (muxer->parent); } @@ -764,6 +849,8 @@ gtk_action_muxer_set_parent (GtkActionMuxer *muxer, gtk_action_muxer_action_added (muxer, *it, G_ACTION_GROUP (muxer->parent), *it); g_strfreev (actions); + emit_changed_accels (muxer, muxer->parent); + g_signal_connect (muxer->parent, "action-added", G_CALLBACK (gtk_action_muxer_action_added_to_parent), muxer); g_signal_connect (muxer->parent, "action-removed", @@ -772,7 +859,93 @@ gtk_action_muxer_set_parent (GtkActionMuxer *muxer, 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_signal_connect (muxer->parent, "primary-accel-changed", + G_CALLBACK (gtk_action_muxer_parent_primary_accel_changed), muxer); } g_object_notify_by_pspec (G_OBJECT (muxer), properties[PROP_PARENT]); } + +void +gtk_action_muxer_set_primary_accel (GtkActionMuxer *muxer, + const gchar *action_and_target, + const gchar *primary_accel) +{ + if (!muxer->primary_accels) + muxer->primary_accels = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + if (primary_accel) + g_hash_table_insert (muxer->primary_accels, g_strdup (action_and_target), g_strdup (primary_accel)); + else + g_hash_table_remove (muxer->primary_accels, action_and_target); + + gtk_action_muxer_primary_accel_changed (muxer, NULL, action_and_target); +} + +const gchar * +gtk_action_muxer_get_primary_accel (GtkActionMuxer *muxer, + const gchar *action_and_target) +{ + if (muxer->primary_accels) + { + const gchar *primary_accel; + + primary_accel = g_hash_table_lookup (muxer->primary_accels, action_and_target); + + if (primary_accel) + return primary_accel; + } + + if (!muxer->parent) + return NULL; + + return gtk_action_muxer_get_primary_accel (muxer->parent, action_and_target); +} + +gchar * +gtk_print_action_and_target (const gchar *action_namespace, + const gchar *action_name, + GVariant *target) +{ + GString *result; + + g_return_val_if_fail (strchr (action_name, '|') == NULL, NULL); + g_return_val_if_fail (action_namespace == NULL || strchr (action_namespace, '|') == NULL, NULL); + + result = g_string_new (NULL); + + if (target) + g_variant_print_string (target, result, TRUE); + g_string_append_c (result, '|'); + + if (action_namespace) + { + g_string_append (result, action_namespace); + g_string_append_c (result, '.'); + } + + g_string_append (result, action_name); + + return g_string_free (result, FALSE); +} + +gchar * +gtk_normalise_detailed_action_name (const gchar *detailed_action_name) +{ + GError *error = NULL; + gchar *action_and_target; + gchar *action_name; + GVariant *target; + + g_action_parse_detailed_name (detailed_action_name, &action_name, &target, &error); + g_assert_no_error (error); + + action_and_target = gtk_print_action_and_target (NULL, action_name, target); + + if (target) + g_variant_unref (target); + + g_free (action_name); + + return action_and_target; +} diff --git a/libqmenumodel/src/gtk/gtkactionmuxer.h b/libqmenumodel/src/gtk/gtkactionmuxer.h index 4014830..8d6e347 100644 --- a/libqmenumodel/src/gtk/gtkactionmuxer.h +++ b/libqmenumodel/src/gtk/gtkactionmuxer.h @@ -42,11 +42,28 @@ void gtk_action_muxer_insert (GtkActi void gtk_action_muxer_remove (GtkActionMuxer *muxer, const gchar *prefix); +const gchar ** gtk_action_muxer_list_prefixes (GtkActionMuxer *muxer); +GActionGroup * gtk_action_muxer_lookup (GtkActionMuxer *muxer, + const gchar *prefix); GtkActionMuxer * gtk_action_muxer_get_parent (GtkActionMuxer *muxer); void gtk_action_muxer_set_parent (GtkActionMuxer *muxer, GtkActionMuxer *parent); +void gtk_action_muxer_set_primary_accel (GtkActionMuxer *muxer, + const gchar *action_and_target, + const gchar *primary_accel); + +const gchar * gtk_action_muxer_get_primary_accel (GtkActionMuxer *muxer, + const gchar *action_and_target); + +/* No better place for these... */ +gchar * gtk_print_action_and_target (const gchar *action_namespace, + const gchar *action_name, + GVariant *target); + +gchar * gtk_normalise_detailed_action_name (const gchar *detailed_action_name); + G_END_DECLS #endif /* __GTK_ACTION_MUXER_H__ */ diff --git a/libqmenumodel/src/gtk/gtkactionobserver.c b/libqmenumodel/src/gtk/gtkactionobserver.c index cf70b20..fda0ae7 100644 --- a/libqmenumodel/src/gtk/gtkactionobserver.c +++ b/libqmenumodel/src/gtk/gtkactionobserver.c @@ -23,7 +23,7 @@ G_DEFINE_INTERFACE (GtkActionObserver, gtk_action_observer, G_TYPE_OBJECT) -/** +/*< private > * SECTION:gtkactionobserver * @short_description: an interface implemented by objects that are * interested in monitoring actions for changes @@ -54,7 +54,7 @@ gtk_action_observer_default_init (GtkActionObserverInterface *class) { } -/** +/*< private > * gtk_action_observer_action_added: * @observer: a #GtkActionObserver * @observable: the source of the event @@ -85,7 +85,7 @@ gtk_action_observer_action_added (GtkActionObserver *observer, ->action_added (observer, observable, action_name, parameter_type, enabled, state); } -/** +/*< private > * gtk_action_observer_action_enabled_changed: * @observer: a #GtkActionObserver * @observable: the source of the event @@ -110,7 +110,7 @@ gtk_action_observer_action_enabled_changed (GtkActionObserver *observer, ->action_enabled_changed (observer, observable, action_name, enabled); } -/** +/*< private > * gtk_action_observer_action_state_changed: * @observer: a #GtkActionObserver * @observable: the source of the event @@ -135,7 +135,7 @@ gtk_action_observer_action_state_changed (GtkActionObserver *observer, ->action_state_changed (observer, observable, action_name, state); } -/** +/*< private > * gtk_action_observer_action_removed: * @observer: a #GtkActionObserver * @observable: the source of the event @@ -157,3 +157,33 @@ gtk_action_observer_action_removed (GtkActionObserver *observer, GTK_ACTION_OBSERVER_GET_IFACE (observer) ->action_removed (observer, observable, action_name); } + +/*< private > + * gtk_action_observer_primary_accel_changed: + * @observer: a #GtkActionObserver + * @observable: the source of the event + * @action_name: the name of the action + * @action_and_target: detailed action of the changed accel, in “action and target” format + * + * This function is called when an action that the observer is + * registered to receive events for has one of its accelerators changed. + * + * Accelerator changes are reported for all targets associated with the + * action. The @action_and_target string should be used to check if the + * reported target is the one that the observer is interested in. + */ +void +gtk_action_observer_primary_accel_changed (GtkActionObserver *observer, + GtkActionObservable *observable, + const gchar *action_name, + const gchar *action_and_target) +{ + GtkActionObserverInterface *iface; + + g_return_if_fail (GTK_IS_ACTION_OBSERVER (observer)); + + iface = GTK_ACTION_OBSERVER_GET_IFACE (observer); + + if (iface->primary_accel_changed) + iface->primary_accel_changed (observer, observable, action_name, action_and_target); +} diff --git a/libqmenumodel/src/gtk/gtkactionobserver.h b/libqmenumodel/src/gtk/gtkactionobserver.h index 83629a7..a4e9659 100644 --- a/libqmenumodel/src/gtk/gtkactionobserver.h +++ b/libqmenumodel/src/gtk/gtkactionobserver.h @@ -57,6 +57,10 @@ struct _GtkActionObserverInterface void (* action_removed) (GtkActionObserver *observer, GtkActionObservable *observable, const gchar *action_name); + void (* primary_accel_changed) (GtkActionObserver *observer, + GtkActionObservable *observable, + const gchar *action_name, + const gchar *action_and_target); }; GType gtk_action_observer_get_type (void); @@ -77,6 +81,10 @@ void gtk_action_observer_action_state_changed (GtkActi void gtk_action_observer_action_removed (GtkActionObserver *observer, GtkActionObservable *observable, const gchar *action_name); +void gtk_action_observer_primary_accel_changed (GtkActionObserver *observer, + GtkActionObservable *observable, + const gchar *action_name, + const gchar *action_and_target); G_END_DECLS diff --git a/libqmenumodel/src/gtk/gtkmenutracker.c b/libqmenumodel/src/gtk/gtkmenutracker.c index 8b72965..95e08bc 100644 --- a/libqmenumodel/src/gtk/gtkmenutracker.c +++ b/libqmenumodel/src/gtk/gtkmenutracker.c @@ -12,9 +12,7 @@ * 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. + * License along with this library; if not, see <http://www.gnu.org/licenses/>. * * Author: Ryan Lortie <desrt@desrt.ca> */ @@ -23,7 +21,7 @@ #include "gtkmenutracker.h" -/** +/*< private > * SECTION:gtkmenutracker * @Title: GtkMenuTracker * @Short_description: A helper class for interpreting #GMenuModel @@ -48,11 +46,11 @@ * * 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. + * for #GtkMenuTrackerItem along with https://wiki.gnome.org/Projects/GLib/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 + * "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. */ @@ -62,6 +60,8 @@ typedef struct _GtkMenuTrackerSection GtkMenuTrackerSection; struct _GtkMenuTracker { GtkActionObservable *observable; + guint merge_sections : 1; + guint mac_os_mode : 1; GtkMenuTrackerInsertFunc insert_func; GtkMenuTrackerRemoveFunc remove_func; gpointer user_data; @@ -71,12 +71,14 @@ struct _GtkMenuTracker struct _GtkMenuTrackerSection { - GMenuModel *model; + gpointer model; /* may be a GtkMenuTrackerItem or a GMenuModel */ GSList *items; gchar *action_namespace; + guint separator_label : 1; guint with_separators : 1; guint has_separator : 1; + guint is_fake : 1; gulong handler; }; @@ -84,6 +86,7 @@ struct _GtkMenuTrackerSection static GtkMenuTrackerSection * gtk_menu_tracker_section_new (GtkMenuTracker *tracker, GMenuModel *model, gboolean with_separators, + gboolean separator_label, gint offset, const gchar *action_namespace, GPtrArray *items_already_created); @@ -91,7 +94,7 @@ static void gtk_menu_tracker_section_free (GtkMenuTrackerS static GtkMenuTrackerSection * gtk_menu_tracker_section_find_model (GtkMenuTrackerSection *section, - GMenuModel *model, + gpointer model, gint *offset) { GSList *item; @@ -119,7 +122,7 @@ gtk_menu_tracker_section_find_model (GtkMenuTrackerSection *section, (*offset)++; } - return FALSE; + return NULL; } /* this is responsible for syncing the showing of a separator for a @@ -127,7 +130,7 @@ gtk_menu_tracker_section_find_model (GtkMenuTrackerSection *section, * * 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 + * 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. @@ -142,11 +145,11 @@ gtk_menu_tracker_section_find_model (GtkMenuTrackerSection *section, * * 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 + * - our parent section had with_separators defined and there are items + * before us (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 + * - 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 @@ -168,6 +171,7 @@ gtk_menu_tracker_section_sync_separators (GtkMenuTrackerSection *section, gboolean should_have_separator; gint n_items = 0; GSList *item; + GPtrArray *items = g_ptr_array_new (); gint i = 0; for (item = section->items; item; item = item->next) @@ -176,13 +180,15 @@ gtk_menu_tracker_section_sync_separators (GtkMenuTrackerSection *section, if (subsection) { - gboolean could_have_separator; + gboolean separator; - could_have_separator = (section->with_separators && i > 0) || - g_menu_model_get_item_attribute (section->model, i, "label", "s", NULL); + separator = (section->with_separators && n_items > 0) || subsection->separator_label; + /* Only pass the parent_model and parent_index in case they may be used to create the separator. */ n_items += gtk_menu_tracker_section_sync_separators (subsection, tracker, offset + n_items, - could_have_separator, section->model, i); + separator, + separator ? section->model : NULL, + separator ? i : 0); } else n_items++; @@ -190,19 +196,18 @@ gtk_menu_tracker_section_sync_separators (GtkMenuTrackerSection *section, i++; } - should_have_separator = could_have_separator && n_items != 0; + should_have_separator = !section->is_fake && could_have_separator && n_items != 0; if (should_have_separator > section->has_separator) { /* Add a separator */ - GtkMenuTrackerItem *item; - GPtrArray *items = g_ptr_array_new (); + GtkMenuTrackerItem *separator; - item = _gtk_menu_tracker_item_new (tracker->observable, parent_model, parent_index, NULL, TRUE); - g_ptr_array_add (items, (gpointer) item); + separator = _gtk_menu_tracker_item_new (tracker->observable, parent_model, parent_index, FALSE, NULL, TRUE); + g_ptr_array_add (items, (gpointer) separator); (* tracker->insert_func) (items, offset, tracker->user_data); g_ptr_array_unref (items); - g_object_unref (item); + g_object_unref (separator); section->has_separator = TRUE; } @@ -218,6 +223,41 @@ gtk_menu_tracker_section_sync_separators (GtkMenuTrackerSection *section, return n_items; } +static void +gtk_menu_tracker_item_visibility_changed (GtkMenuTrackerItem *item, + GParamSpec *pspec, + gpointer user_data) +{ + GtkMenuTracker *tracker = user_data; + GtkMenuTrackerSection *section; + gboolean is_now_visible; + gboolean was_visible; + gint offset = 0; + + is_now_visible = gtk_menu_tracker_item_get_is_visible (item); + + /* remember: the item is our model */ + section = gtk_menu_tracker_section_find_model (tracker->toplevel, item, &offset); + + was_visible = section->items != NULL; + + if (is_now_visible == was_visible) + return; + + if (is_now_visible) + { + section->items = g_slist_prepend (NULL, NULL); + (* tracker->insert_func) (section->model, offset, tracker->user_data); + } + else + { + section->items = g_slist_delete_link (section->items, section->items); + (* tracker->remove_func) (offset, 1, tracker->user_data); + } + + gtk_menu_tracker_section_sync_separators (tracker->toplevel, tracker, 0, FALSE, NULL, 0); +} + static gint gtk_menu_tracker_section_measure (GtkMenuTrackerSection *section) { @@ -258,6 +298,7 @@ gtk_menu_tracker_remove_items (GtkMenuTracker *tracker, n = gtk_menu_tracker_section_measure (subsection); gtk_menu_tracker_section_free (subsection); + while (n--) n_total_items += n; } @@ -275,28 +316,32 @@ gtk_menu_tracker_add_items (GtkMenuTracker *tracker, GMenuModel *model, gint position, gint n_items, - GPtrArray *items_already_created - ) + GPtrArray *items_already_created) { - GPtrArray *items; - if (items_already_created) - { - items = items_already_created; - } - else - { - items = g_ptr_array_new (); - } + GPtrArray *items; + if (items_already_created) + { + items = items_already_created; + } + else + { + items = g_ptr_array_new (); + } 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) + + if (submenu != NULL && tracker->merge_sections) { GtkMenuTrackerSection *subsection; gchar *action_namespace = NULL; + gboolean has_label; + + has_label = g_menu_model_get_item_attribute (model, position + n_items, + G_MENU_ATTRIBUTE_LABEL, "s", NULL); g_menu_model_get_item_attribute (model, position + n_items, G_MENU_ATTRIBUTE_ACTION_NAMESPACE, "s", &action_namespace); @@ -306,11 +351,11 @@ gtk_menu_tracker_add_items (GtkMenuTracker *tracker, gchar *namespace; namespace = g_strjoin (".", section->action_namespace, action_namespace, NULL); - subsection = gtk_menu_tracker_section_new (tracker, submenu, FALSE, offset, namespace, items_already_created); + subsection = gtk_menu_tracker_section_new (tracker, submenu, FALSE, has_label, offset, namespace, items_already_created); g_free (namespace); } else - subsection = gtk_menu_tracker_section_new (tracker, submenu, FALSE, offset, section->action_namespace, items_already_created); + subsection = gtk_menu_tracker_section_new (tracker, submenu, FALSE, has_label, offset, action_namespace, items_already_created); *change_point = g_slist_prepend (*change_point, subsection); g_free (action_namespace); @@ -321,22 +366,76 @@ gtk_menu_tracker_add_items (GtkMenuTracker *tracker, GtkMenuTrackerItem *item; item = _gtk_menu_tracker_item_new (tracker->observable, model, position + n_items, - section->action_namespace, FALSE); - g_ptr_array_insert (items, 0, (gpointer) item); + tracker->mac_os_mode, + section->action_namespace, submenu != NULL); + + /* In the case that the item may disappear we handle that by + * treating the item that we just created as being its own + * subsection. This happens as so: + * + * - the subsection is created without the possibility of + * showing a separator + * + * - the subsection will have either 0 or 1 item in it at all + * times: either the shown item or not (in the case it is + * hidden) + * + * - the created item acts as the "model" for this section + * and we use its "visiblity-changed" signal in the same + * way that we use the "items-changed" signal from a real + * GMenuModel + * + * We almost never use the '->model' stored in the section for + * anything other than lookups and for dropped the ref and + * disconnecting the signal when we destroy the menu, and we + * need to do exactly those things in this case as well. + * + * The only other thing that '->model' is used for is in the + * case that we want to show a separator, but we will never do + * that because separators are not shown for this fake section. + */ + if (gtk_menu_tracker_item_may_disappear (item)) + { + GtkMenuTrackerSection *fake_section; + + fake_section = g_slice_new0 (GtkMenuTrackerSection); + fake_section->is_fake = TRUE; + fake_section->model = g_object_ref (item); + fake_section->handler = g_signal_connect (item, "notify::is-visible", + G_CALLBACK (gtk_menu_tracker_item_visibility_changed), + tracker); + *change_point = g_slist_prepend (*change_point, fake_section); + + if (gtk_menu_tracker_item_get_is_visible (item)) + { + (* tracker->insert_func) (items, offset, tracker->user_data); + fake_section->items = g_slist_prepend (NULL, NULL); + } + } + else + { + /* In the normal case, we store NULL in the linked list. + * The measurement and lookup code count NULL always as + * exactly 1: an item that will always be there. + */ + g_ptr_array_insert (items, 0, (gpointer) item); + *change_point = g_slist_prepend (*change_point, NULL); + } - *change_point = g_slist_prepend (*change_point, NULL); + g_object_unref (item); } } + if (!items_already_created) - { - if (items->len) - { - (* tracker->insert_func) (items, offset, tracker->user_data); - for (gint i = 0; i < items->len; ++i) - g_object_unref(g_ptr_array_index(items, i)); - } - g_ptr_array_unref (items); - } + { + if (items->len) + { + (* tracker->insert_func) (items, offset, tracker->user_data); + for (gint i = 0; i < items->len; ++i) + g_object_unref(g_ptr_array_index(items, i)); + } + g_ptr_array_unref (items); + } } static void @@ -406,6 +505,7 @@ static GtkMenuTrackerSection * gtk_menu_tracker_section_new (GtkMenuTracker *tracker, GMenuModel *model, gboolean with_separators, + gboolean separator_label, gint offset, const gchar *action_namespace, GPtrArray *items_already_created) @@ -416,6 +516,7 @@ gtk_menu_tracker_section_new (GtkMenuTracker *tracker, section->model = g_object_ref (model); section->with_separators = with_separators; section->action_namespace = g_strdup (action_namespace); + section->separator_label = separator_label; gtk_menu_tracker_add_items (tracker, section, §ion->items, offset, model, 0, g_menu_model_get_n_items (model), items_already_created); section->handler = g_signal_connect (model, "items-changed", G_CALLBACK (gtk_menu_tracker_model_changed), tracker); @@ -428,6 +529,10 @@ gtk_menu_tracker_section_new (GtkMenuTracker *tracker, * @model: the model to flatten * @with_separators: if the toplevel should have separators (ie: TRUE * for menus, FALSE for menubars) + * @merge_sections: if sections should have their items merged in the + * usual way or reported only as separators (which can be queried to + * manually handle the items) + * @mac_os_mode: if this is on behalf of the Mac OS menubar * @action_namespace: the passed-in action namespace * @insert_func: insert callback * @remove_func: remove callback @@ -458,7 +563,7 @@ gtk_menu_tracker_section_new (GtkMenuTracker *tracker, * 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 + * “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. * @@ -466,7 +571,7 @@ gtk_menu_tracker_section_new (GtkMenuTracker *tracker, * 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 + * there is an “action-namespace” attribute on this menu item then it * should be ignored by the consumer because #GtkMenuTracker has already * handled it. * @@ -478,7 +583,10 @@ GtkMenuTracker * gtk_menu_tracker_new (GtkActionObservable *observable, GMenuModel *model, gboolean with_separators, + gboolean merge_sections, + gboolean mac_os_mode, const gchar *action_namespace, + GPtrArray *items_already_created, GtkMenuTrackerInsertFunc insert_func, GtkMenuTrackerRemoveFunc remove_func, gpointer user_data) @@ -486,28 +594,43 @@ gtk_menu_tracker_new (GtkActionObservable *observable, GtkMenuTracker *tracker; tracker = g_slice_new (GtkMenuTracker); + tracker->merge_sections = merge_sections; + tracker->mac_os_mode = mac_os_mode; 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, NULL); + tracker->toplevel = gtk_menu_tracker_section_new (tracker, model, with_separators, FALSE, 0, action_namespace, NULL); 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) +gtk_menu_tracker_new_for_item_link (GtkMenuTrackerItem *item, + const gchar *link_name, + gboolean merge_sections, + gboolean mac_os_mode, + 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); + GtkMenuTracker *tracker; + GMenuModel *submenu; + gchar *namespace; + + submenu = _gtk_menu_tracker_item_get_link (item, link_name); + namespace = _gtk_menu_tracker_item_get_link_namespace (item); + + tracker = gtk_menu_tracker_new (_gtk_menu_tracker_item_get_observable (item), submenu, + TRUE, merge_sections, mac_os_mode, + namespace, NULL, insert_func, remove_func, user_data); + + g_object_unref (submenu); + g_free (namespace); + + return tracker; } /*< private > diff --git a/libqmenumodel/src/gtk/gtkmenutracker.h b/libqmenumodel/src/gtk/gtkmenutracker.h index 1ade63f..2ff0455 100644 --- a/libqmenumodel/src/gtk/gtkmenutracker.h +++ b/libqmenumodel/src/gtk/gtkmenutracker.h @@ -12,9 +12,7 @@ * 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. + * License along with this library; if not, see <http://www.gnu.org/licenses/>. * * Author: Ryan Lortie <desrt@desrt.ca> */ @@ -38,12 +36,18 @@ typedef void (* GtkMenuTrackerRemoveFunc) (gint GtkMenuTracker * gtk_menu_tracker_new (GtkActionObservable *observer, GMenuModel *model, gboolean with_separators, + gboolean merge_sections, + gboolean mac_os_mode, const gchar *action_namespace, + GPtrArray *items_already_created, GtkMenuTrackerInsertFunc insert_func, GtkMenuTrackerRemoveFunc remove_func, gpointer user_data); -GtkMenuTracker * gtk_menu_tracker_new_for_item_submenu (GtkMenuTrackerItem *item, +GtkMenuTracker * gtk_menu_tracker_new_for_item_link (GtkMenuTrackerItem *item, + const gchar *link_name, + gboolean merge_sections, + gboolean mac_os_mode, GtkMenuTrackerInsertFunc insert_func, GtkMenuTrackerRemoveFunc remove_func, gpointer user_data); diff --git a/libqmenumodel/src/gtk/gtkmenutrackeritem.c b/libqmenumodel/src/gtk/gtkmenutrackeritem.c index 650e719..d05f1ba 100644 --- a/libqmenumodel/src/gtk/gtkmenutrackeritem.c +++ b/libqmenumodel/src/gtk/gtkmenutrackeritem.c @@ -12,9 +12,7 @@ * 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. + * License along with this library; if not, see <http://www.gnu.org/licenses/>. * * Author: Ryan Lortie <desrt@desrt.ca> */ @@ -22,8 +20,13 @@ #include "config.h" #include "gtkmenutrackeritem.h" +#include "gtkactionmuxer.h" -/** +#include "gtkactionmuxer.h" + +#include <string.h> + +/*< private > * SECTION:gtkmenutrackeritem * @Title: GtkMenuTrackerItem * @Short_description: Small helper for model menu items @@ -34,7 +37,7 @@ * * 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. + * 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 @@ -63,7 +66,7 @@ * 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 + * 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 @@ -83,6 +86,7 @@ struct _GtkMenuTrackerItem GtkActionObservable *observable; gchar *action_namespace; + gchar *action_and_target; GMenuItem *item; GtkMenuTrackerItemRole role : 4; guint is_separator : 1; @@ -91,23 +95,30 @@ struct _GtkMenuTrackerItem guint toggled : 1; guint submenu_shown : 1; guint submenu_requested : 1; + guint hidden_when : 2; + guint is_visible : 1; GVariant *action_state; }; +#define HIDDEN_NEVER 0 +#define HIDDEN_WHEN_MISSING 1 +#define HIDDEN_WHEN_DISABLED 2 +#define HIDDEN_WHEN_ALWAYS 3 + enum { PROP_0, PROP_IS_SEPARATOR, - PROP_HAS_SUBMENU, PROP_LABEL, PROP_ICON, + PROP_VERB_ICON, PROP_SENSITIVE, - PROP_VISIBLE, PROP_ROLE, PROP_TOGGLED, PROP_ACCEL, PROP_SUBMENU_SHOWN, PROP_ACTION_NAME, PROP_ACTION_STATE, + PROP_IS_VISIBLE, N_PROPS }; @@ -153,21 +164,18 @@ gtk_menu_tracker_item_get_property (GObject *object, 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)); + g_value_take_object (value, gtk_menu_tracker_item_get_icon (self)); + break; + case PROP_VERB_ICON: + g_value_take_object (value, gtk_menu_tracker_item_get_verb_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; @@ -180,10 +188,12 @@ 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_take_string (value, gtk_menu_tracker_item_get_action_name (self)); case PROP_ACTION_STATE: g_value_set_variant (value, self->action_state); + case PROP_ACTION_NAME: + g_value_take_string (value, gtk_menu_tracker_item_get_action_name (self)); + case PROP_IS_VISIBLE: + g_value_set_boolean (value, gtk_menu_tracker_item_get_is_visible (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -197,6 +207,7 @@ gtk_menu_tracker_item_finalize (GObject *object) GtkMenuTrackerItem *self = GTK_MENU_TRACKER_ITEM (object); g_free (self->action_namespace); + g_free (self->action_and_target); if (self->observable) g_object_unref (self->observable); @@ -222,16 +233,14 @@ gtk_menu_tracker_item_class_init (GtkMenuTrackerItemClass *class) 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_VERB_ICON] = + g_param_spec_object ("verb-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, @@ -246,10 +255,51 @@ gtk_menu_tracker_item_class_init (GtkMenuTrackerItemClass *class) 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); + gtk_menu_tracker_item_pspecs[PROP_IS_VISIBLE] = + g_param_spec_boolean ("is-visible", "", "", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE); g_object_class_install_properties (class, N_PROPS, gtk_menu_tracker_item_pspecs); } +/* This syncs up the visibility for the hidden-when='' case. We call it + * from the action observer functions on changes to the action group and + * on initialisation (via the action observer functions that are invoked + * at that time). + */ +static void +gtk_menu_tracker_item_update_visibility (GtkMenuTrackerItem *self) +{ + gboolean visible; + + switch (self->hidden_when) + { + case HIDDEN_NEVER: + visible = TRUE; + break; + + case HIDDEN_WHEN_MISSING: + visible = self->can_activate; + break; + + case HIDDEN_WHEN_DISABLED: + visible = self->sensitive; + break; + + case HIDDEN_WHEN_ALWAYS: + visible = FALSE; + break; + + default: + g_assert_not_reached (); + } + + if (visible != self->is_visible) + { + self->is_visible = visible; + g_object_notify (G_OBJECT (self), "is-visible"); + } +} + static void gtk_menu_tracker_item_action_added (GtkActionObserver *observer, GtkActionObservable *observable, @@ -309,6 +359,12 @@ gtk_menu_tracker_item_action_added (GtkActionObserver *observer, if (action_target) g_variant_unref (action_target); + + /* In case of hidden-when='', we want to Wait until after refreshing + * all of the properties to emit the signal that will cause the + * tracker to expose us (to prevent too much thrashing). + */ + gtk_menu_tracker_item_update_visibility (self); } static void @@ -328,6 +384,8 @@ gtk_menu_tracker_item_action_enabled_changed (GtkActionObserver *observer, self->sensitive = enabled; g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_SENSITIVE]); + + gtk_menu_tracker_item_update_visibility (self); } static void @@ -373,58 +431,74 @@ gtk_menu_tracker_item_action_removed (GtkActionObserver *observer, const gchar *action_name) { GtkMenuTrackerItem *self = GTK_MENU_TRACKER_ITEM (observer); + gboolean was_sensitive, was_toggled; + GtkMenuTrackerItemRole old_role; if (!self->can_activate) return; - g_object_freeze_notify (G_OBJECT (self)); + was_sensitive = self->sensitive; + was_toggled = self->toggled; + old_role = self->role; - if (self->sensitive) - { - self->sensitive = FALSE; - g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_SENSITIVE]); - } + self->can_activate = FALSE; + self->sensitive = FALSE; + self->toggled = FALSE; + self->role = GTK_MENU_TRACKER_ITEM_ROLE_NORMAL; + self->action_state = NULL; - if (self->toggled) - { - self->toggled = FALSE; - g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_TOGGLED]); - } + /* Backwards from adding: we want to remove ourselves from the menu + * -before- thrashing the properties. + */ + gtk_menu_tracker_item_update_visibility (self); - 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_freeze_notify (G_OBJECT (self)); - if (self->action_state != NULL) - { - g_variant_unref (self->action_state); - self->action_state = NULL; - g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_ACTION_STATE]); - } + if (was_sensitive) + g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_SENSITIVE]); + + if (was_toggled) + g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_TOGGLED]); + + if (old_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_primary_accel_changed (GtkActionObserver *observer, + GtkActionObservable *observable, + const gchar *action_name, + const gchar *action_and_target) +{ + GtkMenuTrackerItem *self = GTK_MENU_TRACKER_ITEM (observer); + + if (g_str_equal (action_and_target, self->action_and_target)) + g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_ACCEL]); +} + +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; + iface->primary_accel_changed = gtk_menu_tracker_item_primary_accel_changed; } GtkMenuTrackerItem * _gtk_menu_tracker_item_new (GtkActionObservable *observable, GMenuModel *model, gint item_index, + gboolean mac_os_mode, const gchar *action_namespace, gboolean is_separator) { GtkMenuTrackerItem *self; const gchar *action_name; + const gchar *hidden_when; g_return_val_if_fail (GTK_IS_ACTION_OBSERVABLE (observable), NULL); g_return_val_if_fail (G_IS_MENU_MODEL (model), NULL); @@ -435,50 +509,72 @@ _gtk_menu_tracker_item_new (GtkActionObservable *observable, self->observable = g_object_ref (observable); self->is_separator = is_separator; + if (!is_separator && g_menu_item_get_attribute (self->item, "hidden-when", "&s", &hidden_when)) + { + if (g_str_equal (hidden_when, "action-disabled")) + self->hidden_when = HIDDEN_WHEN_DISABLED; + else if (g_str_equal (hidden_when, "action-missing")) + self->hidden_when = HIDDEN_WHEN_MISSING; + else if (mac_os_mode && g_str_equal (hidden_when, "macos-menubar")) + self->hidden_when = HIDDEN_WHEN_ALWAYS; + + /* Ignore other values -- this code may be running in context of a + * desktop shell or the like and should not spew criticals due to + * application bugs... + * + * Note: if we just set a hidden-when state, but don't get the + * action_name below then our visibility will be FALSE forever. + * That's to be expected since the action is missing... + */ + } + if (!is_separator && g_menu_item_get_attribute (self->item, "action", "&s", &action_name)) { GActionGroup *group = G_ACTION_GROUP (observable); const GVariantType *parameter_type; + GVariant *target; gboolean enabled; GVariant *state; gboolean found; + target = g_menu_item_get_attribute_value (self->item, "target", NULL); + + self->action_and_target = gtk_print_action_and_target (action_namespace, action_name, target); + + if (target) + g_variant_unref (target); + + action_name = strrchr (self->action_and_target, '|') + 1; + state = NULL; - if (action_namespace) - { - gchar *full_action; + 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); - 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); + if (found) + { + gtk_menu_tracker_item_action_added (GTK_ACTION_OBSERVER (self), observable, NULL, parameter_type, enabled, state); } 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); + gtk_menu_tracker_item_update_visibility (self); } - 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 { + gtk_menu_tracker_item_update_visibility (self); gboolean submenu_enabled; - if (g_menu_item_get_attribute (self->item, "submenu-enabled", "b", &submenu_enabled)) - { - self->sensitive = submenu_enabled; - } - else - { - self->sensitive = TRUE; - } + if (g_menu_item_get_attribute (self->item, "submenu-enabled", "b", &submenu_enabled)) + { + self->sensitive = submenu_enabled; + } + else + { + self->sensitive = TRUE; + } } return self; @@ -490,11 +586,11 @@ _gtk_menu_tracker_item_get_observable (GtkMenuTrackerItem *self) return self->observable; } -/** +/*< private > * gtk_menu_tracker_item_get_is_separator: * @self: A #GtkMenuTrackerItem instance * - * Returns whether the menu item is a separator. If so, only + * Returns: whether the menu item is a separator. If so, only * certain properties may need to be obeyed. See the documentation * for #GtkMenuTrackerItem. */ @@ -504,20 +600,21 @@ gtk_menu_tracker_item_get_is_separator (GtkMenuTrackerItem *self) return self->is_separator; } -/** +/*< private > * gtk_menu_tracker_item_get_has_submenu: * @self: A #GtkMenuTrackerItem instance * - * Returns whether the menu item has a submenu. If so, only + * 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) +gtk_menu_tracker_item_get_has_link (GtkMenuTrackerItem *self, + const gchar *link_name) { GMenuModel *link; - link = g_menu_item_get_link (self->item, G_MENU_LINK_SUBMENU); + link = g_menu_item_get_link (self->item, link_name); if (link) { @@ -538,7 +635,7 @@ gtk_menu_tracker_item_get_label (GtkMenuTrackerItem *self) return label; } -/** +/*< private > * gtk_menu_tracker_item_get_icon: * * Returns: (transfer full): @@ -560,16 +657,32 @@ gtk_menu_tracker_item_get_icon (GtkMenuTrackerItem *self) return icon; } -gboolean -gtk_menu_tracker_item_get_sensitive (GtkMenuTrackerItem *self) +/*< private > + * gtk_menu_tracker_item_get_verb_icon: + * + * Returns: (transfer full): + */ +GIcon * +gtk_menu_tracker_item_get_verb_icon (GtkMenuTrackerItem *self) { - return self->sensitive; + GVariant *icon_data; + GIcon *icon; + + icon_data = g_menu_item_get_attribute_value (self->item, "verb-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_visible (GtkMenuTrackerItem *self) +gtk_menu_tracker_item_get_sensitive (GtkMenuTrackerItem *self) { - return TRUE; + return self->sensitive; } GtkMenuTrackerItemRole @@ -587,21 +700,59 @@ gtk_menu_tracker_item_get_toggled (GtkMenuTrackerItem *self) const gchar * gtk_menu_tracker_item_get_accel (GtkMenuTrackerItem *self) { - const gchar *accel = NULL; + const gchar *accel; + + if (!self->action_and_target) + return NULL; - g_menu_item_get_attribute (self->item, "accel", "&s", &accel); + if (g_menu_item_get_attribute (self->item, "accel", "&s", &accel)) + return accel; + + if (!GTK_IS_ACTION_MUXER (self->observable)) + return NULL; - return accel; + return gtk_action_muxer_get_primary_accel (GTK_ACTION_MUXER (self->observable), self->action_and_target); +} + +const gchar * +gtk_menu_tracker_item_get_special (GtkMenuTrackerItem *self) +{ + const gchar *special = NULL; + + g_menu_item_get_attribute (self->item, "x-gtk-private-special", "&s", &special); + + return special; +} + +const gchar * +gtk_menu_tracker_item_get_display_hint (GtkMenuTrackerItem *self) +{ + const gchar *display_hint = NULL; + + g_menu_item_get_attribute (self->item, "display-hint", "&s", &display_hint); + + return display_hint; +} + +const gchar * +gtk_menu_tracker_item_get_text_direction (GtkMenuTrackerItem *self) +{ + const gchar *text_direction = NULL; + + g_menu_item_get_attribute (self->item, "text-direction", "&s", &text_direction); + + return text_direction; } GMenuModel * -_gtk_menu_tracker_item_get_submenu (GtkMenuTrackerItem *self) +_gtk_menu_tracker_item_get_link (GtkMenuTrackerItem *self, + const gchar *link_name) { - return g_menu_item_get_link (self->item, "submenu"); + return g_menu_item_get_link (self->item, link_name); } gchar * -_gtk_menu_tracker_item_get_submenu_namespace (GtkMenuTrackerItem *self) +_gtk_menu_tracker_item_get_link_namespace (GtkMenuTrackerItem *self) { const gchar *namespace; @@ -632,10 +783,7 @@ gtk_menu_tracker_item_get_submenu_shown (GtkMenuTrackerItem *self) * gtk_menu_tracker_item_get_action_name: * @self: A #GtkMenuTrackerItem instance * - * Returns a newly-allocated string containing the name of the action - * associated with this menu item. - * - * Returns: (transfer full): the action name, free it with g_free() + * Returns the action name */ gchar * gtk_menu_tracker_item_get_action_name (GtkMenuTrackerItem *self) @@ -643,12 +791,12 @@ gtk_menu_tracker_item_get_action_name (GtkMenuTrackerItem *self) const gchar *action_name; if (!g_menu_item_get_attribute (self->item, G_MENU_ATTRIBUTE_ACTION, "&s", &action_name)) - return NULL; + return NULL; if (self->action_namespace) - return g_strjoin (".", self->action_namespace, action_name, NULL); - else - return g_strdup (action_name); + return g_strjoin (".", self->action_namespace, action_name, NULL); + else + return g_strdup (action_name); } GVariant * @@ -688,19 +836,10 @@ gtk_menu_tracker_item_activated (GtkMenuTrackerItem *self) if (!self->can_activate) return; - g_menu_item_get_attribute (self->item, G_MENU_ATTRIBUTE_ACTION, "&s", &action_name); + action_name = strrchr (self->action_and_target, '|') + 1; 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); + g_action_group_activate_action (G_ACTION_GROUP (self->observable), action_name, action_target); if (action_target) g_variant_unref (action_target); @@ -930,3 +1069,30 @@ gtk_menu_tracker_item_get_attribute_value (GtkMenuTrackerItem *self, { return g_menu_item_get_attribute_value (self->item, attribute, expected_type); } + +/** + * gtk_menu_tracker_item_get_is_visible: + * @self: A #GtkMenuTrackerItem instance + * + * Don't use this unless you're tracking items for yourself -- normally + * the tracker will emit add/remove automatically when this changes. + * + * Returns: if the item should currently be shown + */ +gboolean +gtk_menu_tracker_item_get_is_visible (GtkMenuTrackerItem *self) +{ + return self->is_visible; +} + +/** + * gtk_menu_tracker_item_may_disappear: + * @self: A #GtkMenuTrackerItem instance + * + * Returns: if the item may disappear (ie: is-visible property may change) + */ +gboolean +gtk_menu_tracker_item_may_disappear (GtkMenuTrackerItem *self) +{ + return self->hidden_when != HIDDEN_NEVER; +} diff --git a/libqmenumodel/src/gtk/gtkmenutrackeritem.h b/libqmenumodel/src/gtk/gtkmenutrackeritem.h index dc62bf1..3fbec27 100644 --- a/libqmenumodel/src/gtk/gtkmenutrackeritem.h +++ b/libqmenumodel/src/gtk/gtkmenutrackeritem.h @@ -45,22 +45,30 @@ GType gtk_menu_tracker_item_role_get_type (void) G GtkMenuTrackerItem * _gtk_menu_tracker_item_new (GtkActionObservable *observable, GMenuModel *model, gint item_index, + gboolean mac_os_mode, const gchar *action_namespace, gboolean is_separator); +const gchar * gtk_menu_tracker_item_get_special (GtkMenuTrackerItem *self); + +const gchar * gtk_menu_tracker_item_get_display_hint (GtkMenuTrackerItem *self); + +const gchar * gtk_menu_tracker_item_get_text_direction (GtkMenuTrackerItem *self); + 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); +gboolean gtk_menu_tracker_item_get_has_link (GtkMenuTrackerItem *self, + const gchar *link_name); 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); +GIcon * gtk_menu_tracker_item_get_verb_icon (GtkMenuTrackerItem *self); -gboolean gtk_menu_tracker_item_get_visible (GtkMenuTrackerItem *self); +gboolean gtk_menu_tracker_item_get_sensitive (GtkMenuTrackerItem *self); GtkMenuTrackerItemRole gtk_menu_tracker_item_get_role (GtkMenuTrackerItem *self); @@ -68,9 +76,14 @@ gboolean gtk_menu_tracker_item_get_toggled (GtkMenu const gchar * gtk_menu_tracker_item_get_accel (GtkMenuTrackerItem *self); -GMenuModel * _gtk_menu_tracker_item_get_submenu (GtkMenuTrackerItem *self); +GMenuModel * _gtk_menu_tracker_item_get_link (GtkMenuTrackerItem *self, + const gchar *link_name); -gchar * _gtk_menu_tracker_item_get_submenu_namespace (GtkMenuTrackerItem *self); +gchar * _gtk_menu_tracker_item_get_link_namespace (GtkMenuTrackerItem *self); + +gboolean gtk_menu_tracker_item_may_disappear (GtkMenuTrackerItem *self); + +gboolean gtk_menu_tracker_item_get_is_visible (GtkMenuTrackerItem *self); gboolean gtk_menu_tracker_item_get_should_request_show (GtkMenuTrackerItem *self); @@ -79,15 +92,12 @@ void gtk_menu_tracker_item_activated (GtkMenu void gtk_menu_tracker_item_change_state (GtkMenuTrackerItem *self, GVariant *value); - - - void gtk_menu_tracker_item_request_submenu_shown (GtkMenuTrackerItem *self, gboolean shown); gboolean gtk_menu_tracker_item_get_submenu_shown (GtkMenuTrackerItem *self); -gchar * gtk_menu_tracker_item_get_action_name (GtkMenuTrackerItem *self); +gchar * gtk_menu_tracker_item_get_action_name (GtkMenuTrackerItem *self); GVariant * gtk_menu_tracker_item_get_action_state (GtkMenuTrackerItem *self); |