gtkactionmuxer.c 29.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/*
 * Copyright © 2011 Canonical Limited
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the licence, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
Javier Jardón's avatar
Javier Jardón committed
15
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 17 18 19 20 21
 *
 * Author: Ryan Lortie <desrt@desrt.ca>
 */

#include "config.h"

22
#include "gtkactionmuxer.h"
23

24 25
#include "gtkactionobservable.h"
#include "gtkactionobserver.h"
26
#include "gtkintl.h"
27 28 29

#include <string.h>

30
/*< private >
31
 * SECTION:gtkactionmuxer
32 33
 * @short_description: Aggregate and monitor several action groups
 *
34
 * #GtkActionMuxer is a #GActionGroup and #GtkActionObservable that is
35 36
 * capable of containing other #GActionGroup instances.
 *
Matthias Clasen's avatar
Matthias Clasen committed
37
 * The typical use is aggregating all of the actions applicable to a
38 39 40
 * particular context into a single action group, with namespacing.
 *
 * Consider the case of two action groups -- one containing actions
41
 * applicable to an entire application (such as “quit”) and one
42
 * containing actions applicable to a particular window in the
43
 * application (such as “fullscreen”).
44 45
 *
 * In this case, each of these action groups could be added to a
46 47
 * #GtkActionMuxer with the prefixes “app” and “win”, respectively.  This
 * would expose the actions as “app.quit” and “win.fullscreen” on the
48
 * #GActionGroup interface presented by the #GtkActionMuxer.
49
 *
50
 * Activations and state change requests on the #GtkActionMuxer are wired
51 52
 * through to the underlying action group in the expected way.
 *
53
 * This class is typically only used at the site of “consumption” of
54 55
 * actions (eg: when displaying a menu that contains many actions on
 * different objects).
56
 */
57

58 59
static void     gtk_action_muxer_group_iface_init         (GActionGroupInterface        *iface);
static void     gtk_action_muxer_observable_iface_init    (GtkActionObservableInterface *iface);
60

61
typedef GObjectClass GtkActionMuxerClass;
62

63
struct _GtkActionMuxer
64 65 66
{
  GObject parent_instance;

67
  GHashTable *observed_actions;
68
  GHashTable *groups;
69
  GHashTable *primary_accels;
70
  GtkActionMuxer *parent;
71 72
};

73 74 75
G_DEFINE_TYPE_WITH_CODE (GtkActionMuxer, gtk_action_muxer, G_TYPE_OBJECT,
                         G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP, gtk_action_muxer_group_iface_init)
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTION_OBSERVABLE, gtk_action_muxer_observable_iface_init))
76

77 78 79 80 81 82 83 84 85
enum
{
  PROP_0,
  PROP_PARENT,
  NUM_PROPERTIES
};

static GParamSpec *properties[NUM_PROPERTIES];

86 87
guint accel_signal;

88 89
typedef struct
{
90
  GtkActionMuxer *muxer;
91 92 93 94 95 96
  GSList       *watchers;
  gchar        *fullname;
} Action;

typedef struct
{
97
  GtkActionMuxer *muxer;
98 99 100 101 102
  GActionGroup *group;
  gchar        *prefix;
  gulong        handler_ids[4];
} Group;

103
static void
104 105 106
gtk_action_muxer_append_group_actions (gpointer key,
                                       gpointer value,
                                       gpointer user_data)
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
{
  const gchar *prefix = key;
  Group *group = value;
  GArray *actions = user_data;
  gchar **group_actions;
  gchar **action;

  group_actions = g_action_group_list_actions (group->group);
  for (action = group_actions; *action; action++)
    {
      gchar *fullname;

      fullname = g_strconcat (prefix, ".", *action, NULL);
      g_array_append_val (actions, fullname);
    }

  g_strfreev (group_actions);
}

126
static gchar **
127
gtk_action_muxer_list_actions (GActionGroup *action_group)
128
{
129
  GtkActionMuxer *muxer = GTK_ACTION_MUXER (action_group);
130
  GArray *actions;
131

132
  actions = g_array_new (TRUE, FALSE, sizeof (gchar *));
133

134 135 136
  for ( ; muxer != NULL; muxer = muxer->parent)
    {
      g_hash_table_foreach (muxer->groups,
137
                            gtk_action_muxer_append_group_actions,
138 139
                            actions);
    }
140

141
  return (gchar **)(void *) g_array_free (actions, FALSE);
142 143 144
}

static Group *
145 146 147
gtk_action_muxer_find_group (GtkActionMuxer  *muxer,
                             const gchar     *full_name,
                             const gchar    **action_name)
148 149 150 151 152
{
  const gchar *dot;
  gchar *prefix;
  Group *group;

153
  dot = strchr (full_name, '.');
154 155 156 157

  if (!dot)
    return NULL;

158
  prefix = g_strndup (full_name, dot - full_name);
159 160 161
  group = g_hash_table_lookup (muxer->groups, prefix);
  g_free (prefix);

162 163
  if (action_name)
    *action_name = dot + 1;
164 165 166 167

  return group;
}

168
static void
169 170 171
gtk_action_muxer_action_enabled_changed (GtkActionMuxer *muxer,
                                         const gchar    *action_name,
                                         gboolean        enabled)
172 173
{
  Action *action;
174
  GSList *node;
175

176 177
  action = g_hash_table_lookup (muxer->observed_actions, action_name);
  for (node = action ? action->watchers : NULL; node; node = node->next)
178
    gtk_action_observer_action_enabled_changed (node->data, GTK_ACTION_OBSERVABLE (muxer), action_name, enabled);
179
  g_action_group_action_enabled_changed (G_ACTION_GROUP (muxer), action_name, enabled);
180 181 182
}

static void
183 184 185 186
gtk_action_muxer_group_action_enabled_changed (GActionGroup *action_group,
                                               const gchar  *action_name,
                                               gboolean      enabled,
                                               gpointer      user_data)
187 188 189 190
{
  Group *group = user_data;
  gchar *fullname;

191
  fullname = g_strconcat (group->prefix, ".", action_name, NULL);
192
  gtk_action_muxer_action_enabled_changed (group->muxer, fullname, enabled);
193

194 195 196 197
  g_free (fullname);
}

static void
198 199 200 201
gtk_action_muxer_parent_action_enabled_changed (GActionGroup *action_group,
                                                const gchar  *action_name,
                                                gboolean      enabled,
                                                gpointer      user_data)
202
{
203
  GtkActionMuxer *muxer = user_data;
204

205
  gtk_action_muxer_action_enabled_changed (muxer, action_name, enabled);
206 207 208
}

static void
209 210 211
gtk_action_muxer_action_state_changed (GtkActionMuxer *muxer,
                                       const gchar    *action_name,
                                       GVariant       *state)
212 213 214 215
{
  Action *action;
  GSList *node;

216
  action = g_hash_table_lookup (muxer->observed_actions, action_name);
217
  for (node = action ? action->watchers : NULL; node; node = node->next)
218
    gtk_action_observer_action_state_changed (node->data, GTK_ACTION_OBSERVABLE (muxer), action_name, state);
219 220 221 222
  g_action_group_action_state_changed (G_ACTION_GROUP (muxer), action_name, state);
}

static void
223 224 225 226
gtk_action_muxer_group_action_state_changed (GActionGroup *action_group,
                                             const gchar  *action_name,
                                             GVariant     *state,
                                             gpointer      user_data)
227 228 229 230 231
{
  Group *group = user_data;
  gchar *fullname;

  fullname = g_strconcat (group->prefix, ".", action_name, NULL);
232
  gtk_action_muxer_action_state_changed (group->muxer, fullname, state);
233

234 235 236 237
  g_free (fullname);
}

static void
238 239 240 241
gtk_action_muxer_parent_action_state_changed (GActionGroup *action_group,
                                              const gchar  *action_name,
                                              GVariant     *state,
                                              gpointer      user_data)
242
{
243
  GtkActionMuxer *muxer = user_data;
244

245
  gtk_action_muxer_action_state_changed (muxer, action_name, state);
246 247 248
}

static void
249 250 251 252
gtk_action_muxer_action_added (GtkActionMuxer *muxer,
                               const gchar    *action_name,
                               GActionGroup   *original_group,
                               const gchar    *orignal_action_name)
253 254 255 256
{
  const GVariantType *parameter_type;
  gboolean enabled;
  GVariant *state;
257
  Action *action;
258

259 260 261 262 263
  action = g_hash_table_lookup (muxer->observed_actions, action_name);

  if (action && action->watchers &&
      g_action_group_query_action (original_group, orignal_action_name,
                                   &enabled, &parameter_type, NULL, NULL, &state))
264 265 266
    {
      GSList *node;

267
      for (node = action->watchers; node; node = node->next)
268 269
        gtk_action_observer_action_added (node->data,
                                        GTK_ACTION_OBSERVABLE (muxer),
270
                                        action_name, parameter_type, enabled, state);
271 272 273 274

      if (state)
        g_variant_unref (state);
    }
275 276

  g_action_group_action_added (G_ACTION_GROUP (muxer), action_name);
277 278 279
}

static void
280 281 282
gtk_action_muxer_action_added_to_group (GActionGroup *action_group,
                                        const gchar  *action_name,
                                        gpointer      user_data)
283 284 285
{
  Group *group = user_data;
  gchar *fullname;
286 287

  fullname = g_strconcat (group->prefix, ".", action_name, NULL);
288
  gtk_action_muxer_action_added (group->muxer, fullname, action_group, action_name);
289 290 291 292 293

  g_free (fullname);
}

static void
294 295 296
gtk_action_muxer_action_added_to_parent (GActionGroup *action_group,
                                         const gchar  *action_name,
                                         gpointer      user_data)
297
{
298
  GtkActionMuxer *muxer = user_data;
299

300
  gtk_action_muxer_action_added (muxer, action_name, action_group, action_name);
301 302 303
}

static void
304 305
gtk_action_muxer_action_removed (GtkActionMuxer *muxer,
                                 const gchar    *action_name)
306
{
307 308 309
  Action *action;
  GSList *node;

310
  action = g_hash_table_lookup (muxer->observed_actions, action_name);
311
  for (node = action ? action->watchers : NULL; node; node = node->next)
312
    gtk_action_observer_action_removed (node->data, GTK_ACTION_OBSERVABLE (muxer), action_name);
313 314 315 316
  g_action_group_action_removed (G_ACTION_GROUP (muxer), action_name);
}

static void
317 318 319
gtk_action_muxer_action_removed_from_group (GActionGroup *action_group,
                                            const gchar  *action_name,
                                            gpointer      user_data)
320 321 322 323 324
{
  Group *group = user_data;
  gchar *fullname;

  fullname = g_strconcat (group->prefix, ".", action_name, NULL);
325
  gtk_action_muxer_action_removed (group->muxer, fullname);
326

327 328 329
  g_free (fullname);
}

330
static void
331 332 333
gtk_action_muxer_action_removed_from_parent (GActionGroup *action_group,
                                             const gchar  *action_name,
                                             gpointer      user_data)
334
{
335
  GtkActionMuxer *muxer = user_data;
336

337
  gtk_action_muxer_action_removed (muxer, action_name);
338 339
}

340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372
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);
}

373
static gboolean
374 375 376 377 378 379 380 381 382
gtk_action_muxer_query_action (GActionGroup        *action_group,
                               const gchar         *action_name,
                               gboolean            *enabled,
                               const GVariantType **parameter_type,
                               const GVariantType **state_type,
                               GVariant           **state_hint,
                               GVariant           **state)
{
  GtkActionMuxer *muxer = GTK_ACTION_MUXER (action_group);
383
  Group *group;
384
  const gchar *unprefixed_name;
385

386
  group = gtk_action_muxer_find_group (muxer, action_name, &unprefixed_name);
387

388 389 390
  if (group)
    return g_action_group_query_action (group->group, unprefixed_name, enabled,
                                        parameter_type, state_type, state_hint, state);
391

392 393 394 395 396 397
  if (muxer->parent)
    return g_action_group_query_action (G_ACTION_GROUP (muxer->parent), action_name,
                                        enabled, parameter_type,
                                        state_type, state_hint, state);

  return FALSE;
398 399 400
}

static void
401 402 403
gtk_action_muxer_activate_action (GActionGroup *action_group,
                                  const gchar  *action_name,
                                  GVariant     *parameter)
404
{
405
  GtkActionMuxer *muxer = GTK_ACTION_MUXER (action_group);
406
  Group *group;
407
  const gchar *unprefixed_name;
408

409
  group = gtk_action_muxer_find_group (muxer, action_name, &unprefixed_name);
410 411

  if (group)
412 413 414
    g_action_group_activate_action (group->group, unprefixed_name, parameter);
  else if (muxer->parent)
    g_action_group_activate_action (G_ACTION_GROUP (muxer->parent), action_name, parameter);
415 416 417
}

static void
418 419 420
gtk_action_muxer_change_action_state (GActionGroup *action_group,
                                      const gchar  *action_name,
                                      GVariant     *state)
421
{
422
  GtkActionMuxer *muxer = GTK_ACTION_MUXER (action_group);
423
  Group *group;
424
  const gchar *unprefixed_name;
425

426
  group = gtk_action_muxer_find_group (muxer, action_name, &unprefixed_name);
427 428

  if (group)
429 430 431
    g_action_group_change_action_state (group->group, unprefixed_name, state);
  else if (muxer->parent)
    g_action_group_change_action_state (G_ACTION_GROUP (muxer->parent), action_name, state);
432 433 434
}

static void
435 436
gtk_action_muxer_unregister_internal (Action   *action,
                                      gpointer  observer)
437
{
438
  GtkActionMuxer *muxer = action->muxer;
439 440 441 442 443 444 445 446
  GSList **ptr;

  for (ptr = &action->watchers; *ptr; ptr = &(*ptr)->next)
    if ((*ptr)->data == observer)
      {
        *ptr = g_slist_remove (*ptr, observer);

        if (action->watchers == NULL)
447
            g_hash_table_remove (muxer->observed_actions, action->fullname);
448 449 450 451 452 453

        break;
      }
}

static void
454 455
gtk_action_muxer_weak_notify (gpointer  data,
                              GObject  *where_the_object_was)
456 457 458
{
  Action *action = data;

459
  gtk_action_muxer_unregister_internal (action, where_the_object_was);
460 461 462
}

static void
463 464 465
gtk_action_muxer_register_observer (GtkActionObservable *observable,
                                    const gchar         *name,
                                    GtkActionObserver   *observer)
466
{
467
  GtkActionMuxer *muxer = GTK_ACTION_MUXER (observable);
468 469
  Action *action;

470
  action = g_hash_table_lookup (muxer->observed_actions, name);
471 472 473 474

  if (action == NULL)
    {
      action = g_slice_new (Action);
475
      action->muxer = muxer;
476 477 478
      action->fullname = g_strdup (name);
      action->watchers = NULL;

479
      g_hash_table_insert (muxer->observed_actions, action->fullname, action);
480 481 482
    }

  action->watchers = g_slist_prepend (action->watchers, observer);
483
  g_object_weak_ref (G_OBJECT (observer), gtk_action_muxer_weak_notify, action);
484 485 486
}

static void
487 488 489
gtk_action_muxer_unregister_observer (GtkActionObservable *observable,
                                      const gchar         *name,
                                      GtkActionObserver   *observer)
490
{
491
  GtkActionMuxer *muxer = GTK_ACTION_MUXER (observable);
492 493
  Action *action;

494
  action = g_hash_table_lookup (muxer->observed_actions, name);
495 496
  g_object_weak_unref (G_OBJECT (observer), gtk_action_muxer_weak_notify, action);
  gtk_action_muxer_unregister_internal (action, observer);
497 498 499
}

static void
500
gtk_action_muxer_free_group (gpointer data)
501 502
{
  Group *group = data;
503 504 505 506 507
  gint i;

  /* 'for loop' or 'four loop'? */
  for (i = 0; i < 4; i++)
    g_signal_handler_disconnect (group->group, group->handler_ids[i]);
508 509 510 511 512 513 514

  g_object_unref (group->group);
  g_free (group->prefix);

  g_slice_free (Group, group);
}

515
static void
516
gtk_action_muxer_free_action (gpointer data)
517 518 519 520 521
{
  Action *action = data;
  GSList *it;

  for (it = action->watchers; it; it = it->next)
522
    g_object_weak_unref (G_OBJECT (it->data), gtk_action_muxer_weak_notify, action);
523 524 525 526 527 528 529

  g_slist_free (action->watchers);
  g_free (action->fullname);

  g_slice_free (Action, action);
}

530
static void
531
gtk_action_muxer_finalize (GObject *object)
532
{
533
  GtkActionMuxer *muxer = GTK_ACTION_MUXER (object);
534

535 536
  g_assert_cmpint (g_hash_table_size (muxer->observed_actions), ==, 0);
  g_hash_table_unref (muxer->observed_actions);
537
  g_hash_table_unref (muxer->groups);
538 539
  if (muxer->primary_accels)
    g_hash_table_unref (muxer->primary_accels);
540

541
  G_OBJECT_CLASS (gtk_action_muxer_parent_class)
542 543 544
    ->finalize (object);
}

545
static void
546
gtk_action_muxer_dispose (GObject *object)
547
{
548
  GtkActionMuxer *muxer = GTK_ACTION_MUXER (object);
549 550 551

  if (muxer->parent)
  {
552 553 554 555
    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);
556
    g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_parent_primary_accel_changed, muxer);
557 558 559 560 561 562

    g_clear_object (&muxer->parent);
  }

  g_hash_table_remove_all (muxer->observed_actions);

563
  G_OBJECT_CLASS (gtk_action_muxer_parent_class)
564 565 566 567
    ->dispose (object);
}

static void
568 569 570 571
gtk_action_muxer_get_property (GObject    *object,
                               guint       property_id,
                               GValue     *value,
                               GParamSpec *pspec)
572
{
573
  GtkActionMuxer *muxer = GTK_ACTION_MUXER (object);
574 575 576 577

  switch (property_id)
    {
    case PROP_PARENT:
578
      g_value_set_object (value, gtk_action_muxer_get_parent (muxer));
579 580 581 582 583 584 585 586
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    }
}

static void
587 588 589 590
gtk_action_muxer_set_property (GObject      *object,
                               guint         property_id,
                               const GValue *value,
                               GParamSpec   *pspec)
591
{
592
  GtkActionMuxer *muxer = GTK_ACTION_MUXER (object);
593 594 595 596

  switch (property_id)
    {
    case PROP_PARENT:
597
      gtk_action_muxer_set_parent (muxer, g_value_get_object (value));
598 599 600 601 602 603 604
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    }
}

605
static void
606
gtk_action_muxer_init (GtkActionMuxer *muxer)
607
{
608 609
  muxer->observed_actions = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, gtk_action_muxer_free_action);
  muxer->groups = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, gtk_action_muxer_free_group);
610 611 612
}

static void
613
gtk_action_muxer_observable_iface_init (GtkActionObservableInterface *iface)
614
{
615 616
  iface->register_observer = gtk_action_muxer_register_observer;
  iface->unregister_observer = gtk_action_muxer_unregister_observer;
617 618 619
}

static void
620
gtk_action_muxer_group_iface_init (GActionGroupInterface *iface)
621
{
622 623 624 625
  iface->list_actions = gtk_action_muxer_list_actions;
  iface->query_action = gtk_action_muxer_query_action;
  iface->activate_action = gtk_action_muxer_activate_action;
  iface->change_action_state = gtk_action_muxer_change_action_state;
626 627 628
}

static void
629
gtk_action_muxer_class_init (GObjectClass *class)
630
{
631 632 633 634
  class->get_property = gtk_action_muxer_get_property;
  class->set_property = gtk_action_muxer_set_property;
  class->finalize = gtk_action_muxer_finalize;
  class->dispose = gtk_action_muxer_dispose;
635

636
  accel_signal = g_signal_new (I_("primary-accel-changed"), GTK_TYPE_ACTION_MUXER, G_SIGNAL_RUN_LAST,
637 638
                               0, NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);

639 640
  properties[PROP_PARENT] = g_param_spec_object ("parent", "Parent",
                                                 "The parent muxer",
641
                                                 GTK_TYPE_ACTION_MUXER,
642 643 644 645
                                                 G_PARAM_READWRITE |
                                                 G_PARAM_STATIC_STRINGS);

  g_object_class_install_properties (class, NUM_PROPERTIES, properties);
646 647
}

648
/*< private >
649 650
 * gtk_action_muxer_insert:
 * @muxer: a #GtkActionMuxer
651 652 653 654 655
 * @prefix: the prefix string for the action group
 * @action_group: a #GActionGroup
 *
 * Adds the actions in @action_group to the list of actions provided by
 * @muxer.  @prefix is prefixed to each action name, such that for each
656 657
 * action `x` in @action_group, there is an equivalent
 * action @prefix`.x` in @muxer.
658
 *
659 660 661
 * For example, if @prefix is “`app`” and @action_group
 * contains an action called “`quit`”, then @muxer will
 * now contain an action called “`app.quit`”.
662
 *
663
 * If any #GtkActionObservers are registered for actions in the group,
664
 * “action_added” notifications will be emitted, as appropriate.
665 666
 *
 * @prefix must not contain a dot ('.').
667
 */
668
void
669 670 671
gtk_action_muxer_insert (GtkActionMuxer *muxer,
                         const gchar    *prefix,
                         GActionGroup   *action_group)
672 673 674 675 676 677
{
  gchar **actions;
  Group *group;
  gint i;

  /* TODO: diff instead of ripout and replace */
678
  gtk_action_muxer_remove (muxer, prefix);
679 680 681 682 683 684 685 686 687 688

  group = g_slice_new (Group);
  group->muxer = muxer;
  group->group = g_object_ref (action_group);
  group->prefix = g_strdup (prefix);

  g_hash_table_insert (muxer->groups, group->prefix, group);

  actions = g_action_group_list_actions (group->group);
  for (i = 0; actions[i]; i++)
689
    gtk_action_muxer_action_added_to_group (group->group, actions[i], group);
690 691 692
  g_strfreev (actions);

  group->handler_ids[0] = g_signal_connect (group->group, "action-added",
693
                                            G_CALLBACK (gtk_action_muxer_action_added_to_group), group);
694
  group->handler_ids[1] = g_signal_connect (group->group, "action-removed",
695
                                            G_CALLBACK (gtk_action_muxer_action_removed_from_group), group);
696
  group->handler_ids[2] = g_signal_connect (group->group, "action-enabled-changed",
697
                                            G_CALLBACK (gtk_action_muxer_group_action_enabled_changed), group);
698
  group->handler_ids[3] = g_signal_connect (group->group, "action-state-changed",
699
                                            G_CALLBACK (gtk_action_muxer_group_action_state_changed), group);
700 701
}

702
/*< private >
703 704
 * gtk_action_muxer_remove:
 * @muxer: a #GtkActionMuxer
705 706
 * @prefix: the prefix of the action group to remove
 *
707
 * Removes a #GActionGroup from the #GtkActionMuxer.
708
 *
709
 * If any #GtkActionObservers are registered for actions in the group,
710
 * “action_removed” notifications will be emitted, as appropriate.
711
 */
712
void
713 714
gtk_action_muxer_remove (GtkActionMuxer *muxer,
                         const gchar    *prefix)
715 716 717 718 719 720 721 722 723 724 725 726 727 728
{
  Group *group;

  group = g_hash_table_lookup (muxer->groups, prefix);

  if (group != NULL)
    {
      gchar **actions;
      gint i;

      g_hash_table_steal (muxer->groups, prefix);

      actions = g_action_group_list_actions (group->group);
      for (i = 0; actions[i]; i++)
729
        gtk_action_muxer_action_removed_from_group (group->group, actions[i], group);
730 731
      g_strfreev (actions);

732
      gtk_action_muxer_free_group (group);
733 734 735
    }
}

736
const gchar **
737 738
gtk_action_muxer_list_prefixes (GtkActionMuxer *muxer)
{
739
  return (const gchar **) g_hash_table_get_keys_as_array (muxer->groups, NULL);
740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755
}

GActionGroup *
gtk_action_muxer_lookup (GtkActionMuxer *muxer,
                         const gchar    *prefix)
{
  Group *group;

  group = g_hash_table_lookup (muxer->groups, prefix);

  if (group != NULL)
    return group->group;

  return NULL;
}

756
/*< private >
757
 * gtk_action_muxer_new:
758
 *
759
 * Creates a new #GtkActionMuxer.
760
 */
761 762
GtkActionMuxer *
gtk_action_muxer_new (void)
763
{
764
  return g_object_new (GTK_TYPE_ACTION_MUXER, NULL);
765
}
766

767
/*< private >
768
 * gtk_action_muxer_get_parent:
769
 * @muxer: a #GtkActionMuxer
770
 *
771
 * Returns: (transfer none): the parent of @muxer, or NULL.
772
 */
773 774
GtkActionMuxer *
gtk_action_muxer_get_parent (GtkActionMuxer *muxer)
775
{
776
  g_return_val_if_fail (GTK_IS_ACTION_MUXER (muxer), NULL);
777 778 779 780

  return muxer->parent;
}

781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800
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;
    }
}

801
/*< private >
802
 * gtk_action_muxer_set_parent:
803 804
 * @muxer: a #GtkActionMuxer
 * @parent: (allow-none): the new parent #GtkActionMuxer
805 806 807 808
 *
 * Sets the parent of @muxer to @parent.
 */
void
809 810
gtk_action_muxer_set_parent (GtkActionMuxer *muxer,
                             GtkActionMuxer *parent)
811
{
812 813
  g_return_if_fail (GTK_IS_ACTION_MUXER (muxer));
  g_return_if_fail (parent == NULL || GTK_IS_ACTION_MUXER (parent));
814 815 816 817 818 819 820 821 822 823 824

  if (muxer->parent == parent)
    return;

  if (muxer->parent != NULL)
    {
      gchar **actions;
      gchar **it;

      actions = g_action_group_list_actions (G_ACTION_GROUP (muxer->parent));
      for (it = actions; *it; it++)
825
        gtk_action_muxer_action_removed (muxer, *it);
826 827
      g_strfreev (actions);

828 829
      emit_changed_accels (muxer, muxer->parent);

830 831 832 833
      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);
834
      g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_parent_primary_accel_changed, muxer);
835 836 837 838 839 840 841 842 843 844 845 846 847 848 849

      g_object_unref (muxer->parent);
    }

  muxer->parent = parent;

  if (muxer->parent != NULL)
    {
      gchar **actions;
      gchar **it;

      g_object_ref (muxer->parent);

      actions = g_action_group_list_actions (G_ACTION_GROUP (muxer->parent));
      for (it = actions; *it; it++)
850
        gtk_action_muxer_action_added (muxer, *it, G_ACTION_GROUP (muxer->parent), *it);
851 852
      g_strfreev (actions);

853 854
      emit_changed_accels (muxer, muxer->parent);

855
      g_signal_connect (muxer->parent, "action-added",
856
                        G_CALLBACK (gtk_action_muxer_action_added_to_parent), muxer);
857
      g_signal_connect (muxer->parent, "action-removed",
858
                        G_CALLBACK (gtk_action_muxer_action_removed_from_parent), muxer);
859
      g_signal_connect (muxer->parent, "action-enabled-changed",
860
                        G_CALLBACK (gtk_action_muxer_parent_action_enabled_changed), muxer);
861
      g_signal_connect (muxer->parent, "action-state-changed",
862
                        G_CALLBACK (gtk_action_muxer_parent_action_state_changed), muxer);
863 864
      g_signal_connect (muxer->parent, "primary-accel-changed",
                        G_CALLBACK (gtk_action_muxer_parent_primary_accel_changed), muxer);
865 866 867 868
    }

  g_object_notify_by_pspec (G_OBJECT (muxer), properties[PROP_PARENT]);
}
869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912

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;

913 914
  g_return_val_if_fail (strchr (action_name, '|') == NULL, NULL);
  g_return_val_if_fail (action_namespace == NULL || strchr (action_namespace, '|') == NULL, NULL);
915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931

  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);
}
932

933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952
gchar *
gtk_normalise_detailed_action_name (const gchar *detailed_action_name)
{
  GError *error = NULL;
  gchar *action_and_target;
  gchar *action_name;
  GVariant *target;

  g_action_parse_detailed_name (detailed_action_name, &action_name, &target, &error);
  g_assert_no_error (error);

  action_and_target = gtk_print_action_and_target (NULL, action_name, target);

  if (target)
    g_variant_unref (target);

  g_free (action_name);

  return action_and_target;
}