aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libqmenumodel/src/ayatanamenumodel.cpp23
-rw-r--r--libqmenumodel/src/gtk/gtkactionmuxer.c211
-rw-r--r--libqmenumodel/src/gtk/gtkactionmuxer.h17
-rw-r--r--libqmenumodel/src/gtk/gtkactionobserver.c40
-rw-r--r--libqmenumodel/src/gtk/gtkactionobserver.h8
-rw-r--r--libqmenumodel/src/gtk/gtkmenutracker.c245
-rw-r--r--libqmenumodel/src/gtk/gtkmenutracker.h12
-rw-r--r--libqmenumodel/src/gtk/gtkmenutrackeritem.c370
-rw-r--r--libqmenumodel/src/gtk/gtkmenutrackeritem.h28
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, &section->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, &parameter_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, &parameter_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, &parameter_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);