Back to index

indicator-appmenu  12.10.0
gactionmuxer.c
Go to the documentation of this file.
00001 /*
00002  * Copyright © 2011 Canonical Limited
00003  *
00004  * This library is free software; you can redistribute it and/or
00005  * modify it under the terms of the GNU Lesser General Public
00006  * License as published by the Free Software Foundation; either
00007  * version 2 of the licence, or (at your option) any later version.
00008  *
00009  * This library is distributed in the hope that it will be useful,
00010  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012  * Lesser General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU Lesser General Public
00015  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
00016  *
00017  * Author: Ryan Lortie <desrt@desrt.ca>
00018  */
00019 
00020 #include "config.h"
00021 
00022 #include "gactionmuxer.h"
00023 
00024 #include "gactionobservable.h"
00025 #include "gactionobserver.h"
00026 
00027 #include <string.h>
00028 
00029 /*
00030  * SECTION:gactionmuxer
00031  * @short_description: Aggregate and monitor several action groups
00032  *
00033  * #GActionMuxer is a #GActionGroup and #GActionObservable that is
00034  * capable of containing other #GActionGroup instances.
00035  *
00036  * The typical use is aggregating all of the actions applicable to a
00037  * particular context into a single action group, with namespacing.
00038  *
00039  * Consider the case of two action groups -- one containing actions
00040  * applicable to an entire application (such as 'quit') and one
00041  * containing actions applicable to a particular window in the
00042  * application (such as 'fullscreen').
00043  *
00044  * In this case, each of these action groups could be added to a
00045  * #GActionMuxer with the prefixes "app" and "win", respectively.  This
00046  * would expose the actions as "app.quit" and "win.fullscreen" on the
00047  * #GActionGroup interface presented by the #GActionMuxer.
00048  *
00049  * Activations and state change requests on the #GActionMuxer are wired
00050  * through to the underlying action group in the expected way.
00051  *
00052  * This class is typically only used at the site of "consumption" of
00053  * actions (eg: when displaying a menu that contains many actions on
00054  * different objects).
00055  */
00056 
00057 static void     g_action_muxer_group_iface_init         (GActionGroupInterface      *iface);
00058 static void     g_action_muxer_observable_iface_init    (GActionObservableInterface *iface);
00059 
00060 typedef GObjectClass GActionMuxerClass;
00061 
00062 struct _GActionMuxer
00063 {
00064   GObject parent_instance;
00065 
00066   GHashTable *actions;
00067   GHashTable *groups;
00068 };
00069 
00070 G_DEFINE_TYPE_WITH_CODE (GActionMuxer, g_action_muxer, G_TYPE_OBJECT,
00071                          G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP, g_action_muxer_group_iface_init)
00072                          G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_OBSERVABLE, g_action_muxer_observable_iface_init))
00073 
00074 typedef struct
00075 {
00076   GActionMuxer *muxer;
00077   GSList       *watchers;
00078   gchar        *fullname;
00079 } Action;
00080 
00081 typedef struct
00082 {
00083   GActionMuxer *muxer;
00084   GActionGroup *group;
00085   gchar        *prefix;
00086   gulong        handler_ids[4];
00087 } Group;
00088 
00089 static gchar **
00090 g_action_muxer_list_actions (GActionGroup *action_group)
00091 {
00092   GActionMuxer *muxer = G_ACTION_MUXER (action_group);
00093 
00094   return (gchar **) muxer->groups;
00095 }
00096 
00097 static Group *
00098 g_action_muxer_find_group (GActionMuxer  *muxer,
00099                               const gchar     **name)
00100 {
00101   const gchar *dot;
00102   gchar *prefix;
00103   Group *group;
00104 
00105   dot = strchr (*name, '.');
00106 
00107   if (!dot)
00108     return NULL;
00109 
00110   prefix = g_strndup (*name, dot - *name);
00111   group = g_hash_table_lookup (muxer->groups, prefix);
00112   g_free (prefix);
00113 
00114   *name = dot + 1;
00115 
00116   return group;
00117 }
00118 
00119 static Action *
00120 g_action_muxer_lookup_action (GActionMuxer  *muxer,
00121                               const gchar   *prefix,
00122                               const gchar   *action_name,
00123                               gchar        **fullname)
00124 {
00125   Action *action;
00126 
00127   *fullname = g_strconcat (prefix, ".", action_name, NULL);
00128   action = g_hash_table_lookup (muxer->actions, *fullname);
00129 
00130   return action;
00131 }
00132 
00133 static void
00134 g_action_muxer_action_enabled_changed (GActionGroup *action_group,
00135                                        const gchar  *action_name,
00136                                        gboolean      enabled,
00137                                        gpointer      user_data)
00138 {
00139   Group *group = user_data;
00140   gchar *fullname;
00141   Action *action;
00142   GSList *node;
00143 
00144   action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name, &fullname);
00145   for (node = action ? action->watchers : NULL; node; node = node->next)
00146     g_action_observer_action_enabled_changed (node->data, G_ACTION_OBSERVABLE (group->muxer), fullname, enabled);
00147   g_action_group_action_enabled_changed (G_ACTION_GROUP (group->muxer), fullname, enabled);
00148   g_free (fullname);
00149 }
00150 
00151 static void
00152 g_action_muxer_action_state_changed (GActionGroup *action_group,
00153                                      const gchar  *action_name,
00154                                      GVariant     *state,
00155                                      gpointer      user_data)
00156 {
00157   Group *group = user_data;
00158   gchar *fullname;
00159   Action *action;
00160   GSList *node;
00161 
00162   action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name, &fullname);
00163   for (node = action ? action->watchers : NULL; node; node = node->next)
00164     g_action_observer_action_state_changed (node->data, G_ACTION_OBSERVABLE (group->muxer), fullname, state);
00165   g_action_group_action_state_changed (G_ACTION_GROUP (group->muxer), fullname, state);
00166   g_free (fullname);
00167 }
00168 
00169 static void
00170 g_action_muxer_action_added (GActionGroup *action_group,
00171                              const gchar  *action_name,
00172                              gpointer      user_data)
00173 {
00174   const GVariantType *parameter_type;
00175   Group *group = user_data;
00176   gboolean enabled;
00177   GVariant *state;
00178 
00179   if (g_action_group_query_action (group->group, action_name, &enabled, &parameter_type, NULL, NULL, &state))
00180     {
00181       gchar *fullname;
00182       Action *action;
00183       GSList *node;
00184 
00185       action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name, &fullname);
00186 
00187       for (node = action ? action->watchers : NULL; node; node = node->next)
00188         g_action_observer_action_added (node->data,
00189                                         G_ACTION_OBSERVABLE (group->muxer),
00190                                         fullname, parameter_type, enabled, state);
00191 
00192       g_action_group_action_added (G_ACTION_GROUP (group->muxer), fullname);
00193 
00194       if (state)
00195         g_variant_unref (state);
00196 
00197       g_free (fullname);
00198     }
00199 }
00200 
00201 static void
00202 g_action_muxer_action_removed (GActionGroup *action_group,
00203                                const gchar  *action_name,
00204                                gpointer      user_data)
00205 {
00206   Group *group = user_data;
00207   gchar *fullname;
00208   Action *action;
00209   GSList *node;
00210 
00211   action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name, &fullname);
00212   for (node = action ? action->watchers : NULL; node; node = node->next)
00213     g_action_observer_action_removed (node->data, G_ACTION_OBSERVABLE (group->muxer), fullname);
00214   g_action_group_action_removed (G_ACTION_GROUP (group->muxer), fullname);
00215   g_free (fullname);
00216 }
00217 
00218 static gboolean
00219 g_action_muxer_query_action (GActionGroup        *action_group,
00220                              const gchar         *action_name,
00221                              gboolean            *enabled,
00222                              const GVariantType **parameter_type,
00223                              const GVariantType **state_type,
00224                              GVariant           **state_hint,
00225                              GVariant           **state)
00226 {
00227   GActionMuxer *muxer = G_ACTION_MUXER (action_group);
00228   Group *group;
00229 
00230   group = g_action_muxer_find_group (muxer, &action_name);
00231 
00232   if (!group)
00233     return FALSE;
00234 
00235   return g_action_group_query_action (group->group, action_name, enabled,
00236                                       parameter_type, state_type, state_hint, state);
00237 }
00238 
00239 static void
00240 g_action_muxer_activate_action (GActionGroup *action_group,
00241                                 const gchar  *action_name,
00242                                 GVariant     *parameter)
00243 {
00244   GActionMuxer *muxer = G_ACTION_MUXER (action_group);
00245   Group *group;
00246 
00247   group = g_action_muxer_find_group (muxer, &action_name);
00248 
00249   if (group)
00250     g_action_group_activate_action (group->group, action_name, parameter);
00251 }
00252 
00253 static void
00254 g_action_muxer_change_action_state (GActionGroup *action_group,
00255                                     const gchar  *action_name,
00256                                     GVariant     *state)
00257 {
00258   GActionMuxer *muxer = G_ACTION_MUXER (action_group);
00259   Group *group;
00260 
00261   group = g_action_muxer_find_group (muxer, &action_name);
00262 
00263   if (group)
00264     g_action_group_change_action_state (group->group, action_name, state);
00265 }
00266 
00267 static void
00268 g_action_muxer_unregister_internal (Action   *action,
00269                                     gpointer  observer)
00270 {
00271   GActionMuxer *muxer = action->muxer;
00272   GSList **ptr;
00273 
00274   for (ptr = &action->watchers; *ptr; ptr = &(*ptr)->next)
00275     if ((*ptr)->data == observer)
00276       {
00277         *ptr = g_slist_remove (*ptr, observer);
00278 
00279         if (action->watchers == NULL)
00280           {
00281             g_hash_table_remove (muxer->actions, action->fullname);
00282             g_free (action->fullname);
00283 
00284             g_slice_free (Action, action);
00285 
00286             g_object_unref (muxer);
00287           }
00288 
00289         break;
00290       }
00291 }
00292 
00293 static void
00294 g_action_muxer_weak_notify (gpointer  data,
00295                             GObject  *where_the_object_was)
00296 {
00297   Action *action = data;
00298 
00299   g_action_muxer_unregister_internal (action, where_the_object_was);
00300 }
00301 
00302 static void
00303 g_action_muxer_register_observer (GActionObservable *observable,
00304                                   const gchar       *name,
00305                                   GActionObserver   *observer)
00306 {
00307   GActionMuxer *muxer = G_ACTION_MUXER (observable);
00308   Action *action;
00309 
00310   action = g_hash_table_lookup (muxer->actions, name);
00311 
00312   if (action == NULL)
00313     {
00314       action = g_slice_new (Action);
00315       action->muxer = g_object_ref (muxer);
00316       action->fullname = g_strdup (name);
00317       action->watchers = NULL;
00318 
00319       g_hash_table_insert (muxer->actions, action->fullname, action);
00320     }
00321 
00322   action->watchers = g_slist_prepend (action->watchers, observer);
00323   g_object_weak_ref (G_OBJECT (observer), g_action_muxer_weak_notify, action);
00324 }
00325 
00326 static void
00327 g_action_muxer_unregister_observer (GActionObservable *observable,
00328                                     const gchar       *name,
00329                                     GActionObserver   *observer)
00330 {
00331   GActionMuxer *muxer = G_ACTION_MUXER (observable);
00332   Action *action;
00333 
00334   action = g_hash_table_lookup (muxer->actions, name);
00335   g_object_weak_unref (G_OBJECT (observer), g_action_muxer_weak_notify, action);
00336   g_action_muxer_unregister_internal (action, observer);
00337 }
00338 
00339 static void
00340 g_action_muxer_free_group (gpointer data)
00341 {
00342   Group *group = data;
00343 
00344   g_object_unref (group->group);
00345   g_free (group->prefix);
00346 
00347   g_slice_free (Group, group);
00348 }
00349 
00350 static void
00351 g_action_muxer_finalize (GObject *object)
00352 {
00353   GActionMuxer *muxer = G_ACTION_MUXER (object);
00354 
00355   g_assert_cmpint (g_hash_table_size (muxer->actions), ==, 0);
00356   g_hash_table_unref (muxer->actions);
00357   g_hash_table_unref (muxer->groups);
00358 
00359   G_OBJECT_CLASS (g_action_muxer_parent_class)
00360     ->finalize (object);
00361 }
00362 
00363 static void
00364 g_action_muxer_init (GActionMuxer *muxer)
00365 {
00366   muxer->actions = g_hash_table_new (g_str_hash, g_str_equal);
00367   muxer->groups = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_action_muxer_free_group);
00368 }
00369 
00370 static void
00371 g_action_muxer_observable_iface_init (GActionObservableInterface *iface)
00372 {
00373   iface->register_observer = g_action_muxer_register_observer;
00374   iface->unregister_observer = g_action_muxer_unregister_observer;
00375 }
00376 
00377 static void
00378 g_action_muxer_group_iface_init (GActionGroupInterface *iface)
00379 {
00380   iface->list_actions = g_action_muxer_list_actions;
00381   iface->query_action = g_action_muxer_query_action;
00382   iface->activate_action = g_action_muxer_activate_action;
00383   iface->change_action_state = g_action_muxer_change_action_state;
00384 }
00385 
00386 static void
00387 g_action_muxer_class_init (GObjectClass *class)
00388 {
00389   class->finalize = g_action_muxer_finalize;
00390 }
00391 
00392 /*
00393  * g_action_muxer_insert:
00394  * @muxer: a #GActionMuxer
00395  * @prefix: the prefix string for the action group
00396  * @action_group: a #GActionGroup
00397  *
00398  * Adds the actions in @action_group to the list of actions provided by
00399  * @muxer.  @prefix is prefixed to each action name, such that for each
00400  * action <varname>x</varname> in @action_group, there is an equivalent
00401  * action @prefix<literal>.</literal><varname>x</varname> in @muxer.
00402  *
00403  * For example, if @prefix is "<literal>app</literal>" and @action_group
00404  * contains an action called "<literal>quit</literal>", then @muxer will
00405  * now contain an action called "<literal>app.quit</literal>".
00406  *
00407  * If any #GActionObservers are registered for actions in the group,
00408  * "action_added" notifications will be emitted, as appropriate.
00409  *
00410  * @prefix must not contain a dot ('.').
00411  */
00412 void
00413 g_action_muxer_insert (GActionMuxer *muxer,
00414                        const gchar  *prefix,
00415                        GActionGroup *action_group)
00416 {
00417   gchar **actions;
00418   Group *group;
00419   gint i;
00420 
00421   /* TODO: diff instead of ripout and replace */
00422   g_action_muxer_remove (muxer, prefix);
00423 
00424   group = g_slice_new (Group);
00425   group->muxer = muxer;
00426   group->group = g_object_ref (action_group);
00427   group->prefix = g_strdup (prefix);
00428 
00429   g_hash_table_insert (muxer->groups, group->prefix, group);
00430 
00431   actions = g_action_group_list_actions (group->group);
00432   for (i = 0; actions[i]; i++)
00433     g_action_muxer_action_added (group->group, actions[i], group);
00434   g_strfreev (actions);
00435 
00436   group->handler_ids[0] = g_signal_connect (group->group, "action-added",
00437                                             G_CALLBACK (g_action_muxer_action_added), group);
00438   group->handler_ids[1] = g_signal_connect (group->group, "action-removed",
00439                                             G_CALLBACK (g_action_muxer_action_removed), group);
00440   group->handler_ids[2] = g_signal_connect (group->group, "action-enabled-changed",
00441                                             G_CALLBACK (g_action_muxer_action_enabled_changed), group);
00442   group->handler_ids[3] = g_signal_connect (group->group, "action-state-changed",
00443                                             G_CALLBACK (g_action_muxer_action_state_changed), group);
00444 }
00445 
00446 /*
00447  * g_action_muxer_remove:
00448  * @muxer: a #GActionMuxer
00449  * @prefix: the prefix of the action group to remove
00450  *
00451  * Removes a #GActionGroup from the #GActionMuxer.
00452  *
00453  * If any #GActionObservers are registered for actions in the group,
00454  * "action_removed" notifications will be emitted, as appropriate.
00455  */
00456 void
00457 g_action_muxer_remove (GActionMuxer *muxer,
00458                        const gchar  *prefix)
00459 {
00460   Group *group;
00461 
00462   group = g_hash_table_lookup (muxer->groups, prefix);
00463 
00464   if (group != NULL)
00465     {
00466       gchar **actions;
00467       gint i;
00468 
00469       g_hash_table_steal (muxer->groups, prefix);
00470 
00471       actions = g_action_group_list_actions (group->group);
00472       for (i = 0; actions[i]; i++)
00473         g_action_muxer_action_removed (group->group, actions[i], group);
00474       g_strfreev (actions);
00475 
00476       /* 'for loop' or 'four loop'? */
00477       for (i = 0; i < 4; i++)
00478         g_signal_handler_disconnect (group->group, group->handler_ids[i]);
00479 
00480       g_action_muxer_free_group (group);
00481     }
00482 }
00483 
00484 /*
00485  * g_action_muxer_new:
00486  *
00487  * Creates a new #GActionMuxer.
00488  */
00489 GActionMuxer *
00490 g_action_muxer_new (void)
00491 {
00492   return g_object_new (G_TYPE_ACTION_MUXER, NULL);
00493 }