GtkActionMuxer: store primary accels

Reuse the existing infrastructure in GtkActionMuxer for propagation of
accelerator information: in particular, what accel label ought to appear
on menu items for a particular action and target.

This is a good idea because we want accels to travel along with the
actions that they're tied to and reusing GtkActionMuxer will allow us to
do that without creating another hierarchy of a different class for the
sole purpose of filling in accel labels on menu items.

Doing it this ways also allows those who copy/paste GtkActionMuxer to
insert the accels for themselves.

Add a new method on the GtkActionObserver interface to report changes.

This patch introduces a new concept: "action and target" notation for
actions.  This format looks like so:

  "'target'|app.action"

or for non-targeted actions:

  "|app.action"

and it is used over a number of possible alternative formats for some
good reasons:

 - it's very easy to get a nul-terminated action name out of this format
   when we need it, by using strrchr('|') + 1

 - we can also get the target out of it using g_variant_parse() because
   this function takes a pointer to a 'limit' character that is not
   parsed past: we use the '|' for this

 - it's extremely easy to hash on this format (just use a normal string
   hash) vs. attempting to hash on a string plus a GVariant

A close contender was to use detailed action strings here, but these are
not used for two reasons:

 - it's not possible to easily get the action name or target out of the
   strings without more work than the "action and target" format
   requires

 - we still intend to use detailed action strings on API (since they are
   a lot nicer to look at) but detailed action strings can be given in
   non-canonical forms (eg: 'foo::bar' and 'foo("bar")' are equivalent)
   so we'd have to go through a normalisation step anyway.  Since we're
   doing that already, we may as well convert to a more convenient
   internal format.

This new "action and target" format is going to start appearing in a lot
more places as action descriptions are introduced.

I suspect that nobody is using '|' in their action names, but in case I
am proven wrong, we can always switch to using something more exotic as
a separator character (such as '\x01' or '\xff' or the like).
This commit is contained in:
Ryan Lortie 2013-07-09 22:37:17 -04:00
parent abcddd3ae0
commit 3f8c235593
4 changed files with 180 additions and 0 deletions

View File

@ -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;
@ -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,
@ -514,6 +550,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 +630,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,
@ -715,6 +755,26 @@ 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;
}
}
/**
* gtk_action_muxer_set_parent:
* @muxer: a #GtkActionMuxer
@ -742,10 +802,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 +827,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 +837,72 @@ 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_if_fail (strchr (action_name, '|') == NULL);
g_return_if_fail (action_namespace == NULL || strchr (action_namespace, '|') == 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);
}

View File

@ -47,6 +47,18 @@ GtkActionMuxer * gtk_action_muxer_get_parent (GtkActi
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 this... */
gchar * gtk_print_action_and_target (const gchar *action_namespace,
const gchar *action_name,
GVariant *target);
G_END_DECLS
#endif /* __GTK_ACTION_MUXER_H__ */

View File

@ -157,3 +157,33 @@ gtk_action_observer_action_removed (GtkActionObserver *observer,
GTK_ACTION_OBSERVER_GET_IFACE (observer)
->action_removed (observer, observable, action_name);
}
/**
* 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);
}

View File

@ -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