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 "gactionmuxer.h"
24 #include "gactionobservable.h"
25 #include "gactionobserver.h"
30 * SECTION:gactionmuxer
31 * @short_description: Aggregate and monitor several action groups
33 * #GActionMuxer is a #GActionGroup and #GActionObservable 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 * #GActionMuxer 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 #GActionMuxer.
49 * Activations and state change requests on the #GActionMuxer 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 g_action_muxer_group_iface_init (GActionGroupInterface *iface);
58 static void g_action_muxer_observable_iface_init (GActionObservableInterface *iface);
60 typedef GObjectClass GActionMuxerClass;
64 GObject parent_instance;
66 GHashTable *observed_actions;
71 G_DEFINE_TYPE_WITH_CODE (GActionMuxer, g_action_muxer, G_TYPE_OBJECT,
72 G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP, g_action_muxer_group_iface_init)
73 G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_OBSERVABLE, g_action_muxer_observable_iface_init))
82 static GParamSpec *properties[NUM_PROPERTIES];
96 gulong handler_ids[4];
100 g_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 g_action_muxer_list_actions (GActionGroup *action_group)
125 GActionMuxer *muxer = G_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 g_action_muxer_append_group_actions,
137 return (gchar **) g_array_free (actions, FALSE);
141 g_action_muxer_find_group (GActionMuxer *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 g_action_muxer_action_enabled_changed (GActionMuxer *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 g_action_observer_action_enabled_changed (node->data, G_ACTION_OBSERVABLE (muxer), action_name, enabled);
175 g_action_group_action_enabled_changed (G_ACTION_GROUP (muxer), action_name, enabled);
179 g_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 g_action_muxer_action_enabled_changed (group->muxer, fullname, enabled);
194 g_action_muxer_parent_action_enabled_changed (GActionGroup *action_group,
195 const gchar *action_name,
199 GActionMuxer *muxer = user_data;
201 g_action_muxer_action_enabled_changed (muxer, action_name, enabled);
205 g_action_muxer_action_state_changed (GActionMuxer *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 g_action_observer_action_state_changed (node->data, G_ACTION_OBSERVABLE (muxer), action_name, state);
215 g_action_group_action_state_changed (G_ACTION_GROUP (muxer), action_name, state);
219 g_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 g_action_muxer_action_state_changed (group->muxer, fullname, state);
234 g_action_muxer_parent_action_state_changed (GActionGroup *action_group,
235 const gchar *action_name,
239 GActionMuxer *muxer = user_data;
241 g_action_muxer_action_state_changed (muxer, action_name, state);
245 g_action_muxer_action_added (GActionMuxer *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 g_action_observer_action_added (node->data,
265 G_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 g_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 g_action_muxer_action_added (group->muxer, fullname, action_group, action_name);
290 g_action_muxer_action_added_to_parent (GActionGroup *action_group,
291 const gchar *action_name,
294 GActionMuxer *muxer = user_data;
296 g_action_muxer_action_added (muxer, action_name, action_group, action_name);
300 g_action_muxer_action_removed (GActionMuxer *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 g_action_observer_action_removed (node->data, G_ACTION_OBSERVABLE (muxer), action_name);
309 g_action_group_action_removed (G_ACTION_GROUP (muxer), action_name);
313 g_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 g_action_muxer_action_removed (group->muxer, fullname);
327 g_action_muxer_action_removed_from_parent (GActionGroup *action_group,
328 const gchar *action_name,
331 GActionMuxer *muxer = user_data;
333 g_action_muxer_action_removed (muxer, action_name);
337 g_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 GActionMuxer *muxer = G_ACTION_MUXER (action_group);
347 const gchar *unprefixed_name;
349 group = g_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 g_action_muxer_activate_action (GActionGroup *action_group,
365 const gchar *action_name,
368 GActionMuxer *muxer = G_ACTION_MUXER (action_group);
370 const gchar *unprefixed_name;
372 group = g_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 g_action_muxer_change_action_state (GActionGroup *action_group,
382 const gchar *action_name,
385 GActionMuxer *muxer = G_ACTION_MUXER (action_group);
387 const gchar *unprefixed_name;
389 group = g_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 g_action_muxer_unregister_internal (Action *action,
401 GActionMuxer *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 g_action_muxer_weak_notify (gpointer data,
418 GObject *where_the_object_was)
420 Action *action = data;
422 g_action_muxer_unregister_internal (action, where_the_object_was);
426 g_action_muxer_register_observer (GActionObservable *observable,
428 GActionObserver *observer)
430 GActionMuxer *muxer = G_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), g_action_muxer_weak_notify, action);
450 g_action_muxer_unregister_observer (GActionObservable *observable,
452 GActionObserver *observer)
454 GActionMuxer *muxer = G_ACTION_MUXER (observable);
457 action = g_hash_table_lookup (muxer->observed_actions, name);
458 g_object_weak_unref (G_OBJECT (observer), g_action_muxer_weak_notify, action);
459 g_action_muxer_unregister_internal (action, observer);
463 g_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 g_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), g_action_muxer_weak_notify, action);
487 g_slist_free (action->watchers);
488 g_free (action->fullname);
490 g_slice_free (Action, action);
494 g_action_muxer_finalize (GObject *object)
496 GActionMuxer *muxer = G_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 (g_action_muxer_parent_class)
507 g_action_muxer_dispose (GObject *object)
509 GActionMuxer *muxer = G_ACTION_MUXER (object);
513 g_signal_handlers_disconnect_by_func (muxer->parent, g_action_muxer_action_added_to_parent, muxer);
514 g_signal_handlers_disconnect_by_func (muxer->parent, g_action_muxer_action_removed_from_parent, muxer);
515 g_signal_handlers_disconnect_by_func (muxer->parent, g_action_muxer_parent_action_enabled_changed, muxer);
516 g_signal_handlers_disconnect_by_func (muxer->parent, g_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 (g_action_muxer_parent_class)
528 g_action_muxer_get_property (GObject *object,
533 GActionMuxer *muxer = G_ACTION_MUXER (object);
538 g_value_set_object (value, g_action_muxer_get_parent (muxer));
542 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
547 g_action_muxer_set_property (GObject *object,
552 GActionMuxer *muxer = G_ACTION_MUXER (object);
557 g_action_muxer_set_parent (muxer, g_value_get_object (value));
561 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
566 g_action_muxer_init (GActionMuxer *muxer)
568 muxer->observed_actions = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_action_muxer_free_action);
569 muxer->groups = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_action_muxer_free_group);
573 g_action_muxer_observable_iface_init (GActionObservableInterface *iface)
575 iface->register_observer = g_action_muxer_register_observer;
576 iface->unregister_observer = g_action_muxer_unregister_observer;
580 g_action_muxer_group_iface_init (GActionGroupInterface *iface)
582 iface->list_actions = g_action_muxer_list_actions;
583 iface->query_action = g_action_muxer_query_action;
584 iface->activate_action = g_action_muxer_activate_action;
585 iface->change_action_state = g_action_muxer_change_action_state;
589 g_action_muxer_class_init (GObjectClass *class)
591 class->get_property = g_action_muxer_get_property;
592 class->set_property = g_action_muxer_set_property;
593 class->finalize = g_action_muxer_finalize;
594 class->dispose = g_action_muxer_dispose;
596 properties[PROP_PARENT] = g_param_spec_object ("parent", "Parent",
600 G_PARAM_STATIC_STRINGS);
602 g_object_class_install_properties (class, NUM_PROPERTIES, properties);
606 * g_action_muxer_insert:
607 * @muxer: a #GActionMuxer
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 #GActionObservers are registered for actions in the group,
621 * "action_added" notifications will be emitted, as appropriate.
623 * @prefix must not contain a dot ('.').
626 g_action_muxer_insert (GActionMuxer *muxer,
628 GActionGroup *action_group)
634 /* TODO: diff instead of ripout and replace */
635 g_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 g_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 (g_action_muxer_action_added_to_group), group);
651 group->handler_ids[1] = g_signal_connect (group->group, "action-removed",
652 G_CALLBACK (g_action_muxer_action_removed_from_group), group);
653 group->handler_ids[2] = g_signal_connect (group->group, "action-enabled-changed",
654 G_CALLBACK (g_action_muxer_group_action_enabled_changed), group);
655 group->handler_ids[3] = g_signal_connect (group->group, "action-state-changed",
656 G_CALLBACK (g_action_muxer_group_action_state_changed), group);
660 * g_action_muxer_remove:
661 * @muxer: a #GActionMuxer
662 * @prefix: the prefix of the action group to remove
664 * Removes a #GActionGroup from the #GActionMuxer.
666 * If any #GActionObservers are registered for actions in the group,
667 * "action_removed" notifications will be emitted, as appropriate.
670 g_action_muxer_remove (GActionMuxer *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 g_action_muxer_action_removed_from_group (group->group, actions[i], group);
687 g_strfreev (actions);
689 g_action_muxer_free_group (group);
694 * g_action_muxer_new:
696 * Creates a new #GActionMuxer.
699 g_action_muxer_new (void)
701 return g_object_new (G_TYPE_ACTION_MUXER, NULL);
704 /* g_action_muxer_get_parent:
705 * @muxer: a #GActionMuxer
707 * Returns: (transfer-none): the parent of @muxer, or NULL.
710 g_action_muxer_get_parent (GActionMuxer *muxer)
712 g_return_val_if_fail (G_IS_ACTION_MUXER (muxer), NULL);
714 return muxer->parent;
717 /* g_action_muxer_set_parent:
718 * @muxer: a #GActionMuxer
719 * @parent: (allow-none): the new parent #GActionMuxer
721 * Sets the parent of @muxer to @parent.
724 g_action_muxer_set_parent (GActionMuxer *muxer,
725 GActionMuxer *parent)
727 g_return_if_fail (G_IS_ACTION_MUXER (muxer));
728 g_return_if_fail (parent == NULL || G_IS_ACTION_MUXER (parent));
730 if (muxer->parent == parent)
733 if (muxer->parent != NULL)
738 actions = g_action_group_list_actions (G_ACTION_GROUP (muxer->parent));
739 for (it = actions; *it; it++)
740 g_action_muxer_action_removed (muxer, *it);
741 g_strfreev (actions);
743 g_signal_handlers_disconnect_by_func (muxer->parent, g_action_muxer_action_added_to_parent, muxer);
744 g_signal_handlers_disconnect_by_func (muxer->parent, g_action_muxer_action_removed_from_parent, muxer);
745 g_signal_handlers_disconnect_by_func (muxer->parent, g_action_muxer_parent_action_enabled_changed, muxer);
746 g_signal_handlers_disconnect_by_func (muxer->parent, g_action_muxer_parent_action_state_changed, muxer);
748 g_object_unref (muxer->parent);
751 muxer->parent = parent;
753 if (muxer->parent != NULL)
758 g_object_ref (muxer->parent);
760 actions = g_action_group_list_actions (G_ACTION_GROUP (muxer->parent));
761 for (it = actions; *it; it++)
762 g_action_muxer_action_added (muxer, *it, G_ACTION_GROUP (muxer->parent), *it);
763 g_strfreev (actions);
765 g_signal_connect (muxer->parent, "action-added",
766 G_CALLBACK (g_action_muxer_action_added_to_parent), muxer);
767 g_signal_connect (muxer->parent, "action-removed",
768 G_CALLBACK (g_action_muxer_action_removed_from_parent), muxer);
769 g_signal_connect (muxer->parent, "action-enabled-changed",
770 G_CALLBACK (g_action_muxer_parent_action_enabled_changed), muxer);
771 g_signal_connect (muxer->parent, "action-state-changed",
772 G_CALLBACK (g_action_muxer_parent_action_state_changed), muxer);
775 g_object_notify_by_pspec (G_OBJECT (muxer), properties[PROP_PARENT]);