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;
70 G_DEFINE_TYPE_WITH_CODE (GActionMuxer, g_action_muxer, G_TYPE_OBJECT,
71 G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP, g_action_muxer_group_iface_init)
72 G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_OBSERVABLE, g_action_muxer_observable_iface_init))
86 gulong handler_ids[4];
90 g_action_muxer_list_actions (GActionGroup *action_group)
92 GActionMuxer *muxer = G_ACTION_MUXER (action_group);
98 keys = g_new (gchar *, g_hash_table_size (muxer->actions) + 1);
101 g_hash_table_iter_init (&iter, muxer->actions);
102 while (g_hash_table_iter_next (&iter, (gpointer *) &key, NULL))
103 keys[i++] = g_strdup (key);
110 g_action_muxer_find_group (GActionMuxer *muxer,
117 dot = strchr (*name, '.');
122 prefix = g_strndup (*name, dot - *name);
123 group = g_hash_table_lookup (muxer->groups, prefix);
132 g_action_muxer_lookup_action (GActionMuxer *muxer,
134 const gchar *action_name,
139 *fullname = g_strconcat (prefix, ".", action_name, NULL);
140 action = g_hash_table_lookup (muxer->actions, *fullname);
146 g_action_muxer_action_enabled_changed (GActionGroup *action_group,
147 const gchar *action_name,
151 Group *group = user_data;
156 action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name, &fullname);
157 for (node = action ? action->watchers : NULL; node; node = node->next)
158 g_action_observer_action_enabled_changed (node->data, G_ACTION_OBSERVABLE (group->muxer), fullname, enabled);
159 g_action_group_action_enabled_changed (G_ACTION_GROUP (group->muxer), fullname, enabled);
164 g_action_muxer_action_state_changed (GActionGroup *action_group,
165 const gchar *action_name,
169 Group *group = user_data;
174 action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name, &fullname);
175 for (node = action ? action->watchers : NULL; node; node = node->next)
176 g_action_observer_action_state_changed (node->data, G_ACTION_OBSERVABLE (group->muxer), fullname, state);
177 g_action_group_action_state_changed (G_ACTION_GROUP (group->muxer), fullname, state);
182 g_action_muxer_action_added (GActionGroup *action_group,
183 const gchar *action_name,
186 const GVariantType *parameter_type;
187 Group *group = user_data;
191 if (g_action_group_query_action (group->group, action_name, &enabled, ¶meter_type, NULL, NULL, &state))
197 action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name, &fullname);
199 for (node = action ? action->watchers : NULL; node; node = node->next)
200 g_action_observer_action_added (node->data,
201 G_ACTION_OBSERVABLE (group->muxer),
202 fullname, parameter_type, enabled, state);
204 g_action_group_action_added (G_ACTION_GROUP (group->muxer), fullname);
207 g_variant_unref (state);
214 g_action_muxer_action_removed (GActionGroup *action_group,
215 const gchar *action_name,
218 Group *group = user_data;
223 action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name, &fullname);
224 for (node = action ? action->watchers : NULL; node; node = node->next)
225 g_action_observer_action_removed (node->data, G_ACTION_OBSERVABLE (group->muxer), fullname);
226 g_action_group_action_removed (G_ACTION_GROUP (group->muxer), fullname);
231 g_action_muxer_query_action (GActionGroup *action_group,
232 const gchar *action_name,
234 const GVariantType **parameter_type,
235 const GVariantType **state_type,
236 GVariant **state_hint,
239 GActionMuxer *muxer = G_ACTION_MUXER (action_group);
242 group = g_action_muxer_find_group (muxer, &action_name);
247 return g_action_group_query_action (group->group, action_name, enabled,
248 parameter_type, state_type, state_hint, state);
252 g_action_muxer_activate_action (GActionGroup *action_group,
253 const gchar *action_name,
256 GActionMuxer *muxer = G_ACTION_MUXER (action_group);
259 group = g_action_muxer_find_group (muxer, &action_name);
262 g_action_group_activate_action (group->group, action_name, parameter);
266 g_action_muxer_change_action_state (GActionGroup *action_group,
267 const gchar *action_name,
270 GActionMuxer *muxer = G_ACTION_MUXER (action_group);
273 group = g_action_muxer_find_group (muxer, &action_name);
276 g_action_group_change_action_state (group->group, action_name, state);
280 g_action_muxer_unregister_internal (Action *action,
283 GActionMuxer *muxer = action->muxer;
286 for (ptr = &action->watchers; *ptr; ptr = &(*ptr)->next)
287 if ((*ptr)->data == observer)
289 *ptr = g_slist_remove (*ptr, observer);
291 if (action->watchers == NULL)
293 g_hash_table_remove (muxer->actions, action->fullname);
294 g_free (action->fullname);
296 g_slice_free (Action, action);
298 g_object_unref (muxer);
306 g_action_muxer_weak_notify (gpointer data,
307 GObject *where_the_object_was)
309 Action *action = data;
311 g_action_muxer_unregister_internal (action, where_the_object_was);
315 g_action_muxer_register_observer (GActionObservable *observable,
317 GActionObserver *observer)
319 GActionMuxer *muxer = G_ACTION_MUXER (observable);
322 action = g_hash_table_lookup (muxer->actions, name);
326 action = g_slice_new (Action);
327 action->muxer = g_object_ref (muxer);
328 action->fullname = g_strdup (name);
329 action->watchers = NULL;
331 g_hash_table_insert (muxer->actions, action->fullname, action);
334 action->watchers = g_slist_prepend (action->watchers, observer);
335 g_object_weak_ref (G_OBJECT (observer), g_action_muxer_weak_notify, action);
339 g_action_muxer_unregister_observer (GActionObservable *observable,
341 GActionObserver *observer)
343 GActionMuxer *muxer = G_ACTION_MUXER (observable);
346 action = g_hash_table_lookup (muxer->actions, name);
347 g_object_weak_unref (G_OBJECT (observer), g_action_muxer_weak_notify, action);
348 g_action_muxer_unregister_internal (action, observer);
352 g_action_muxer_free_group (gpointer data)
356 g_object_unref (group->group);
357 g_free (group->prefix);
359 g_slice_free (Group, group);
363 g_action_muxer_finalize (GObject *object)
365 GActionMuxer *muxer = G_ACTION_MUXER (object);
367 g_assert_cmpint (g_hash_table_size (muxer->actions), ==, 0);
368 g_hash_table_unref (muxer->actions);
369 g_hash_table_unref (muxer->groups);
371 G_OBJECT_CLASS (g_action_muxer_parent_class)
376 g_action_muxer_init (GActionMuxer *muxer)
378 muxer->actions = g_hash_table_new (g_str_hash, g_str_equal);
379 muxer->groups = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_action_muxer_free_group);
383 g_action_muxer_observable_iface_init (GActionObservableInterface *iface)
385 iface->register_observer = g_action_muxer_register_observer;
386 iface->unregister_observer = g_action_muxer_unregister_observer;
390 g_action_muxer_group_iface_init (GActionGroupInterface *iface)
392 iface->list_actions = g_action_muxer_list_actions;
393 iface->query_action = g_action_muxer_query_action;
394 iface->activate_action = g_action_muxer_activate_action;
395 iface->change_action_state = g_action_muxer_change_action_state;
399 g_action_muxer_class_init (GObjectClass *class)
401 class->finalize = g_action_muxer_finalize;
405 * g_action_muxer_insert:
406 * @muxer: a #GActionMuxer
407 * @prefix: the prefix string for the action group
408 * @action_group: a #GActionGroup
410 * Adds the actions in @action_group to the list of actions provided by
411 * @muxer. @prefix is prefixed to each action name, such that for each
412 * action <varname>x</varname> in @action_group, there is an equivalent
413 * action @prefix<literal>.</literal><varname>x</varname> in @muxer.
415 * For example, if @prefix is "<literal>app</literal>" and @action_group
416 * contains an action called "<literal>quit</literal>", then @muxer will
417 * now contain an action called "<literal>app.quit</literal>".
419 * If any #GActionObservers are registered for actions in the group,
420 * "action_added" notifications will be emitted, as appropriate.
422 * @prefix must not contain a dot ('.').
425 g_action_muxer_insert (GActionMuxer *muxer,
427 GActionGroup *action_group)
433 /* TODO: diff instead of ripout and replace */
434 g_action_muxer_remove (muxer, prefix);
436 group = g_slice_new (Group);
437 group->muxer = muxer;
438 group->group = g_object_ref (action_group);
439 group->prefix = g_strdup (prefix);
441 g_hash_table_insert (muxer->groups, group->prefix, group);
443 actions = g_action_group_list_actions (group->group);
444 for (i = 0; actions[i]; i++)
445 g_action_muxer_action_added (group->group, actions[i], group);
446 g_strfreev (actions);
448 group->handler_ids[0] = g_signal_connect (group->group, "action-added",
449 G_CALLBACK (g_action_muxer_action_added), group);
450 group->handler_ids[1] = g_signal_connect (group->group, "action-removed",
451 G_CALLBACK (g_action_muxer_action_removed), group);
452 group->handler_ids[2] = g_signal_connect (group->group, "action-enabled-changed",
453 G_CALLBACK (g_action_muxer_action_enabled_changed), group);
454 group->handler_ids[3] = g_signal_connect (group->group, "action-state-changed",
455 G_CALLBACK (g_action_muxer_action_state_changed), group);
459 * g_action_muxer_remove:
460 * @muxer: a #GActionMuxer
461 * @prefix: the prefix of the action group to remove
463 * Removes a #GActionGroup from the #GActionMuxer.
465 * If any #GActionObservers are registered for actions in the group,
466 * "action_removed" notifications will be emitted, as appropriate.
469 g_action_muxer_remove (GActionMuxer *muxer,
474 group = g_hash_table_lookup (muxer->groups, prefix);
481 g_hash_table_steal (muxer->groups, prefix);
483 actions = g_action_group_list_actions (group->group);
484 for (i = 0; actions[i]; i++)
485 g_action_muxer_action_removed (group->group, actions[i], group);
486 g_strfreev (actions);
488 /* 'for loop' or 'four loop'? */
489 for (i = 0; i < 4; i++)
490 g_signal_handler_disconnect (group->group, group->handler_ids[i]);
492 g_action_muxer_free_group (group);
497 * g_action_muxer_new:
499 * Creates a new #GActionMuxer.
502 g_action_muxer_new (void)
504 return g_object_new (G_TYPE_ACTION_MUXER, NULL);