2
* Copyright © 2011 Canonical Limited
4
* This library is free software; you can redistribute it and/or
5
* modify it under the terms of the GNU Lesser General Public
6
* License as published by the Free Software Foundation; either
7
* version 2 of the licence, or (at your option) any later version.
9
* This library is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
* Lesser General Public License for more details.
14
* You should have received a copy of the GNU Lesser General Public
15
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
17
* Author: Ryan Lortie <desrt@desrt.ca>
22
#include "gtkactionmuxer.h"
24
#include "gtkactionobservable.h"
25
#include "gtkactionobserver.h"
30
* SECTION:gtkactionmuxer
31
* @short_description: Aggregate and monitor several action groups
33
* #GtkActionMuxer is a #GActionGroup and #GtkActionObservable that is
34
* capable of containing other #GActionGroup instances.
36
* The typical use is aggregating all of the actions applicable to a
37
* particular context into a single action group, with namespacing.
39
* Consider the case of two action groups -- one containing actions
40
* applicable to an entire application (such as 'quit') and one
41
* containing actions applicable to a particular window in the
42
* application (such as 'fullscreen').
44
* In this case, each of these action groups could be added to a
45
* #GtkActionMuxer with the prefixes "app" and "win", respectively. This
46
* would expose the actions as "app.quit" and "win.fullscreen" on the
47
* #GActionGroup interface presented by the #GtkActionMuxer.
49
* Activations and state change requests on the #GtkActionMuxer are wired
50
* through to the underlying action group in the expected way.
52
* This class is typically only used at the site of "consumption" of
53
* actions (eg: when displaying a menu that contains many actions on
57
static void gtk_action_muxer_group_iface_init (GActionGroupInterface *iface);
58
static void gtk_action_muxer_observable_iface_init (GtkActionObservableInterface *iface);
60
typedef GObjectClass GtkActionMuxerClass;
62
struct _GtkActionMuxer
64
GObject parent_instance;
66
GHashTable *observed_actions;
68
GtkActionMuxer *parent;
71
G_DEFINE_TYPE_WITH_CODE (GtkActionMuxer, gtk_action_muxer, G_TYPE_OBJECT,
72
G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP, gtk_action_muxer_group_iface_init)
73
G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTION_OBSERVABLE, gtk_action_muxer_observable_iface_init))
82
static GParamSpec *properties[NUM_PROPERTIES];
86
GtkActionMuxer *muxer;
93
GtkActionMuxer *muxer;
96
gulong handler_ids[4];
100
gtk_action_muxer_append_group_actions (gpointer key,
104
const gchar *prefix = key;
105
Group *group = value;
106
GArray *actions = user_data;
107
gchar **group_actions;
110
group_actions = g_action_group_list_actions (group->group);
111
for (action = group_actions; *action; action++)
115
fullname = g_strconcat (prefix, ".", *action, NULL);
116
g_array_append_val (actions, fullname);
119
g_strfreev (group_actions);
123
gtk_action_muxer_list_actions (GActionGroup *action_group)
125
GtkActionMuxer *muxer = GTK_ACTION_MUXER (action_group);
128
actions = g_array_new (TRUE, FALSE, sizeof (gchar *));
130
for ( ; muxer != NULL; muxer = muxer->parent)
132
g_hash_table_foreach (muxer->groups,
133
gtk_action_muxer_append_group_actions,
137
return (gchar **) g_array_free (actions, FALSE);
141
gtk_action_muxer_find_group (GtkActionMuxer *muxer,
142
const gchar *full_name,
143
const gchar **action_name)
149
dot = strchr (full_name, '.');
154
prefix = g_strndup (full_name, dot - full_name);
155
group = g_hash_table_lookup (muxer->groups, prefix);
159
*action_name = dot + 1;
165
gtk_action_muxer_action_enabled_changed (GtkActionMuxer *muxer,
166
const gchar *action_name,
172
action = g_hash_table_lookup (muxer->observed_actions, action_name);
173
for (node = action ? action->watchers : NULL; node; node = node->next)
174
gtk_action_observer_action_enabled_changed (node->data, GTK_ACTION_OBSERVABLE (muxer), action_name, enabled);
175
g_action_group_action_enabled_changed (G_ACTION_GROUP (muxer), action_name, enabled);
179
gtk_action_muxer_group_action_enabled_changed (GActionGroup *action_group,
180
const gchar *action_name,
184
Group *group = user_data;
187
fullname = g_strconcat (group->prefix, ".", action_name, NULL);
188
gtk_action_muxer_action_enabled_changed (group->muxer, fullname, enabled);
194
gtk_action_muxer_parent_action_enabled_changed (GActionGroup *action_group,
195
const gchar *action_name,
199
GtkActionMuxer *muxer = user_data;
201
gtk_action_muxer_action_enabled_changed (muxer, action_name, enabled);
205
gtk_action_muxer_action_state_changed (GtkActionMuxer *muxer,
206
const gchar *action_name,
212
action = g_hash_table_lookup (muxer->observed_actions, action_name);
213
for (node = action ? action->watchers : NULL; node; node = node->next)
214
gtk_action_observer_action_state_changed (node->data, GTK_ACTION_OBSERVABLE (muxer), action_name, state);
215
g_action_group_action_state_changed (G_ACTION_GROUP (muxer), action_name, state);
219
gtk_action_muxer_group_action_state_changed (GActionGroup *action_group,
220
const gchar *action_name,
224
Group *group = user_data;
227
fullname = g_strconcat (group->prefix, ".", action_name, NULL);
228
gtk_action_muxer_action_state_changed (group->muxer, fullname, state);
234
gtk_action_muxer_parent_action_state_changed (GActionGroup *action_group,
235
const gchar *action_name,
239
GtkActionMuxer *muxer = user_data;
241
gtk_action_muxer_action_state_changed (muxer, action_name, state);
245
gtk_action_muxer_action_added (GtkActionMuxer *muxer,
246
const gchar *action_name,
247
GActionGroup *original_group,
248
const gchar *orignal_action_name)
250
const GVariantType *parameter_type;
255
action = g_hash_table_lookup (muxer->observed_actions, action_name);
257
if (action && action->watchers &&
258
g_action_group_query_action (original_group, orignal_action_name,
259
&enabled, ¶meter_type, NULL, NULL, &state))
263
for (node = action->watchers; node; node = node->next)
264
gtk_action_observer_action_added (node->data,
265
GTK_ACTION_OBSERVABLE (muxer),
266
action_name, parameter_type, enabled, state);
269
g_variant_unref (state);
272
g_action_group_action_added (G_ACTION_GROUP (muxer), action_name);
276
gtk_action_muxer_action_added_to_group (GActionGroup *action_group,
277
const gchar *action_name,
280
Group *group = user_data;
283
fullname = g_strconcat (group->prefix, ".", action_name, NULL);
284
gtk_action_muxer_action_added (group->muxer, fullname, action_group, action_name);
290
gtk_action_muxer_action_added_to_parent (GActionGroup *action_group,
291
const gchar *action_name,
294
GtkActionMuxer *muxer = user_data;
296
gtk_action_muxer_action_added (muxer, action_name, action_group, action_name);
300
gtk_action_muxer_action_removed (GtkActionMuxer *muxer,
301
const gchar *action_name)
306
action = g_hash_table_lookup (muxer->observed_actions, action_name);
307
for (node = action ? action->watchers : NULL; node; node = node->next)
308
gtk_action_observer_action_removed (node->data, GTK_ACTION_OBSERVABLE (muxer), action_name);
309
g_action_group_action_removed (G_ACTION_GROUP (muxer), action_name);
313
gtk_action_muxer_action_removed_from_group (GActionGroup *action_group,
314
const gchar *action_name,
317
Group *group = user_data;
320
fullname = g_strconcat (group->prefix, ".", action_name, NULL);
321
gtk_action_muxer_action_removed (group->muxer, fullname);
327
gtk_action_muxer_action_removed_from_parent (GActionGroup *action_group,
328
const gchar *action_name,
331
GtkActionMuxer *muxer = user_data;
333
gtk_action_muxer_action_removed (muxer, action_name);
337
gtk_action_muxer_query_action (GActionGroup *action_group,
338
const gchar *action_name,
340
const GVariantType **parameter_type,
341
const GVariantType **state_type,
342
GVariant **state_hint,
345
GtkActionMuxer *muxer = GTK_ACTION_MUXER (action_group);
347
const gchar *unprefixed_name;
349
group = gtk_action_muxer_find_group (muxer, action_name, &unprefixed_name);
352
return g_action_group_query_action (group->group, unprefixed_name, enabled,
353
parameter_type, state_type, state_hint, state);
356
return g_action_group_query_action (G_ACTION_GROUP (muxer->parent), action_name,
357
enabled, parameter_type,
358
state_type, state_hint, state);
364
gtk_action_muxer_activate_action (GActionGroup *action_group,
365
const gchar *action_name,
368
GtkActionMuxer *muxer = GTK_ACTION_MUXER (action_group);
370
const gchar *unprefixed_name;
372
group = gtk_action_muxer_find_group (muxer, action_name, &unprefixed_name);
375
g_action_group_activate_action (group->group, unprefixed_name, parameter);
376
else if (muxer->parent)
377
g_action_group_activate_action (G_ACTION_GROUP (muxer->parent), action_name, parameter);
381
gtk_action_muxer_change_action_state (GActionGroup *action_group,
382
const gchar *action_name,
385
GtkActionMuxer *muxer = GTK_ACTION_MUXER (action_group);
387
const gchar *unprefixed_name;
389
group = gtk_action_muxer_find_group (muxer, action_name, &unprefixed_name);
392
g_action_group_change_action_state (group->group, unprefixed_name, state);
393
else if (muxer->parent)
394
g_action_group_change_action_state (G_ACTION_GROUP (muxer->parent), action_name, state);
398
gtk_action_muxer_unregister_internal (Action *action,
401
GtkActionMuxer *muxer = action->muxer;
404
for (ptr = &action->watchers; *ptr; ptr = &(*ptr)->next)
405
if ((*ptr)->data == observer)
407
*ptr = g_slist_remove (*ptr, observer);
409
if (action->watchers == NULL)
410
g_hash_table_remove (muxer->observed_actions, action->fullname);
417
gtk_action_muxer_weak_notify (gpointer data,
418
GObject *where_the_object_was)
420
Action *action = data;
422
gtk_action_muxer_unregister_internal (action, where_the_object_was);
426
gtk_action_muxer_register_observer (GtkActionObservable *observable,
428
GtkActionObserver *observer)
430
GtkActionMuxer *muxer = GTK_ACTION_MUXER (observable);
433
action = g_hash_table_lookup (muxer->observed_actions, name);
437
action = g_slice_new (Action);
438
action->muxer = muxer;
439
action->fullname = g_strdup (name);
440
action->watchers = NULL;
442
g_hash_table_insert (muxer->observed_actions, action->fullname, action);
445
action->watchers = g_slist_prepend (action->watchers, observer);
446
g_object_weak_ref (G_OBJECT (observer), gtk_action_muxer_weak_notify, action);
450
gtk_action_muxer_unregister_observer (GtkActionObservable *observable,
452
GtkActionObserver *observer)
454
GtkActionMuxer *muxer = GTK_ACTION_MUXER (observable);
457
action = g_hash_table_lookup (muxer->observed_actions, name);
458
g_object_weak_unref (G_OBJECT (observer), gtk_action_muxer_weak_notify, action);
459
gtk_action_muxer_unregister_internal (action, observer);
463
gtk_action_muxer_free_group (gpointer data)
468
/* 'for loop' or 'four loop'? */
469
for (i = 0; i < 4; i++)
470
g_signal_handler_disconnect (group->group, group->handler_ids[i]);
472
g_object_unref (group->group);
473
g_free (group->prefix);
475
g_slice_free (Group, group);
479
gtk_action_muxer_free_action (gpointer data)
481
Action *action = data;
484
for (it = action->watchers; it; it = it->next)
485
g_object_weak_unref (G_OBJECT (it->data), gtk_action_muxer_weak_notify, action);
487
g_slist_free (action->watchers);
488
g_free (action->fullname);
490
g_slice_free (Action, action);
494
gtk_action_muxer_finalize (GObject *object)
496
GtkActionMuxer *muxer = GTK_ACTION_MUXER (object);
498
g_assert_cmpint (g_hash_table_size (muxer->observed_actions), ==, 0);
499
g_hash_table_unref (muxer->observed_actions);
500
g_hash_table_unref (muxer->groups);
502
G_OBJECT_CLASS (gtk_action_muxer_parent_class)
507
gtk_action_muxer_dispose (GObject *object)
509
GtkActionMuxer *muxer = GTK_ACTION_MUXER (object);
513
g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_action_added_to_parent, muxer);
514
g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_action_removed_from_parent, muxer);
515
g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_parent_action_enabled_changed, muxer);
516
g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_parent_action_state_changed, muxer);
518
g_clear_object (&muxer->parent);
521
g_hash_table_remove_all (muxer->observed_actions);
523
G_OBJECT_CLASS (gtk_action_muxer_parent_class)
528
gtk_action_muxer_get_property (GObject *object,
533
GtkActionMuxer *muxer = GTK_ACTION_MUXER (object);
538
g_value_set_object (value, gtk_action_muxer_get_parent (muxer));
542
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
547
gtk_action_muxer_set_property (GObject *object,
552
GtkActionMuxer *muxer = GTK_ACTION_MUXER (object);
557
gtk_action_muxer_set_parent (muxer, g_value_get_object (value));
561
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
566
gtk_action_muxer_init (GtkActionMuxer *muxer)
568
muxer->observed_actions = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, gtk_action_muxer_free_action);
569
muxer->groups = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, gtk_action_muxer_free_group);
573
gtk_action_muxer_observable_iface_init (GtkActionObservableInterface *iface)
575
iface->register_observer = gtk_action_muxer_register_observer;
576
iface->unregister_observer = gtk_action_muxer_unregister_observer;
580
gtk_action_muxer_group_iface_init (GActionGroupInterface *iface)
582
iface->list_actions = gtk_action_muxer_list_actions;
583
iface->query_action = gtk_action_muxer_query_action;
584
iface->activate_action = gtk_action_muxer_activate_action;
585
iface->change_action_state = gtk_action_muxer_change_action_state;
589
gtk_action_muxer_class_init (GObjectClass *class)
591
class->get_property = gtk_action_muxer_get_property;
592
class->set_property = gtk_action_muxer_set_property;
593
class->finalize = gtk_action_muxer_finalize;
594
class->dispose = gtk_action_muxer_dispose;
596
properties[PROP_PARENT] = g_param_spec_object ("parent", "Parent",
598
GTK_TYPE_ACTION_MUXER,
600
G_PARAM_STATIC_STRINGS);
602
g_object_class_install_properties (class, NUM_PROPERTIES, properties);
606
* gtk_action_muxer_insert:
607
* @muxer: a #GtkActionMuxer
608
* @prefix: the prefix string for the action group
609
* @action_group: a #GActionGroup
611
* Adds the actions in @action_group to the list of actions provided by
612
* @muxer. @prefix is prefixed to each action name, such that for each
613
* action <varname>x</varname> in @action_group, there is an equivalent
614
* action @prefix<literal>.</literal><varname>x</varname> in @muxer.
616
* For example, if @prefix is "<literal>app</literal>" and @action_group
617
* contains an action called "<literal>quit</literal>", then @muxer will
618
* now contain an action called "<literal>app.quit</literal>".
620
* If any #GtkActionObservers are registered for actions in the group,
621
* "action_added" notifications will be emitted, as appropriate.
623
* @prefix must not contain a dot ('.').
626
gtk_action_muxer_insert (GtkActionMuxer *muxer,
628
GActionGroup *action_group)
634
/* TODO: diff instead of ripout and replace */
635
gtk_action_muxer_remove (muxer, prefix);
637
group = g_slice_new (Group);
638
group->muxer = muxer;
639
group->group = g_object_ref (action_group);
640
group->prefix = g_strdup (prefix);
642
g_hash_table_insert (muxer->groups, group->prefix, group);
644
actions = g_action_group_list_actions (group->group);
645
for (i = 0; actions[i]; i++)
646
gtk_action_muxer_action_added_to_group (group->group, actions[i], group);
647
g_strfreev (actions);
649
group->handler_ids[0] = g_signal_connect (group->group, "action-added",
650
G_CALLBACK (gtk_action_muxer_action_added_to_group), group);
651
group->handler_ids[1] = g_signal_connect (group->group, "action-removed",
652
G_CALLBACK (gtk_action_muxer_action_removed_from_group), group);
653
group->handler_ids[2] = g_signal_connect (group->group, "action-enabled-changed",
654
G_CALLBACK (gtk_action_muxer_group_action_enabled_changed), group);
655
group->handler_ids[3] = g_signal_connect (group->group, "action-state-changed",
656
G_CALLBACK (gtk_action_muxer_group_action_state_changed), group);
660
* gtk_action_muxer_remove:
661
* @muxer: a #GtkActionMuxer
662
* @prefix: the prefix of the action group to remove
664
* Removes a #GActionGroup from the #GtkActionMuxer.
666
* If any #GtkActionObservers are registered for actions in the group,
667
* "action_removed" notifications will be emitted, as appropriate.
670
gtk_action_muxer_remove (GtkActionMuxer *muxer,
675
group = g_hash_table_lookup (muxer->groups, prefix);
682
g_hash_table_steal (muxer->groups, prefix);
684
actions = g_action_group_list_actions (group->group);
685
for (i = 0; actions[i]; i++)
686
gtk_action_muxer_action_removed_from_group (group->group, actions[i], group);
687
g_strfreev (actions);
689
gtk_action_muxer_free_group (group);
694
* gtk_action_muxer_new:
696
* Creates a new #GtkActionMuxer.
699
gtk_action_muxer_new (void)
701
return g_object_new (GTK_TYPE_ACTION_MUXER, NULL);
705
* gtk_action_muxer_get_parent:
706
* @muxer: a #GtkActionMuxer
708
* Returns: (transfer none): the parent of @muxer, or NULL.
711
gtk_action_muxer_get_parent (GtkActionMuxer *muxer)
713
g_return_val_if_fail (GTK_IS_ACTION_MUXER (muxer), NULL);
715
return muxer->parent;
719
* gtk_action_muxer_set_parent:
720
* @muxer: a #GtkActionMuxer
721
* @parent: (allow-none): the new parent #GtkActionMuxer
723
* Sets the parent of @muxer to @parent.
726
gtk_action_muxer_set_parent (GtkActionMuxer *muxer,
727
GtkActionMuxer *parent)
729
g_return_if_fail (GTK_IS_ACTION_MUXER (muxer));
730
g_return_if_fail (parent == NULL || GTK_IS_ACTION_MUXER (parent));
732
if (muxer->parent == parent)
735
if (muxer->parent != NULL)
740
actions = g_action_group_list_actions (G_ACTION_GROUP (muxer->parent));
741
for (it = actions; *it; it++)
742
gtk_action_muxer_action_removed (muxer, *it);
743
g_strfreev (actions);
745
g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_action_added_to_parent, muxer);
746
g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_action_removed_from_parent, muxer);
747
g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_parent_action_enabled_changed, muxer);
748
g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_parent_action_state_changed, muxer);
750
g_object_unref (muxer->parent);
753
muxer->parent = parent;
755
if (muxer->parent != NULL)
760
g_object_ref (muxer->parent);
762
actions = g_action_group_list_actions (G_ACTION_GROUP (muxer->parent));
763
for (it = actions; *it; it++)
764
gtk_action_muxer_action_added (muxer, *it, G_ACTION_GROUP (muxer->parent), *it);
765
g_strfreev (actions);
767
g_signal_connect (muxer->parent, "action-added",
768
G_CALLBACK (gtk_action_muxer_action_added_to_parent), muxer);
769
g_signal_connect (muxer->parent, "action-removed",
770
G_CALLBACK (gtk_action_muxer_action_removed_from_parent), muxer);
771
g_signal_connect (muxer->parent, "action-enabled-changed",
772
G_CALLBACK (gtk_action_muxer_parent_action_enabled_changed), muxer);
773
g_signal_connect (muxer->parent, "action-state-changed",
774
G_CALLBACK (gtk_action_muxer_parent_action_state_changed), muxer);
777
g_object_notify_by_pspec (G_OBJECT (muxer), properties[PROP_PARENT]);