1/*
2 * Copyright © 2011 Canonical Limited
3 *
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.
8 *
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.
13 *
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/>.
16 *
17 * Author: Ryan Lortie <desrt@desrt.ca>
18 */
19
20#include "config.h"
21
22#include "gtkactionmuxerprivate.h"
23
24#include "gtkactionobservableprivate.h"
25#include "gtkactionobserverprivate.h"
26#include "gtkbitmaskprivate.h"
27#include "gtkintl.h"
28#include "gtkmarshalers.h"
29#include "gtkwidgetprivate.h"
30#include "gsettings-mapping.h"
31
32#include <string.h>
33
34typedef struct
35{
36 char *action_and_target;
37 char *accel;
38} GtkAccel;
39
40static void
41gtk_accel_clear (GtkAccel *accel)
42{
43 g_free (mem: accel->action_and_target);
44 g_free (mem: accel->accel);
45}
46
47#define GDK_ARRAY_NAME gtk_accels
48#define GDK_ARRAY_TYPE_NAME GtkAccels
49#define GDK_ARRAY_ELEMENT_TYPE GtkAccel
50#define GDK_ARRAY_FREE_FUNC gtk_accel_clear
51#define GDK_ARRAY_BY_VALUE 1
52#define GDK_ARRAY_PREALLOC 2
53#include "gdk/gdkarrayimpl.c"
54
55static guint
56gtk_accels_find (GtkAccels *accels,
57 const char *action_and_target)
58{
59 guint i;
60
61 for (i = 0; i < gtk_accels_get_size (self: accels); i++)
62 {
63 GtkAccel *accel = gtk_accels_index (self: accels, pos: i);
64 if (strcmp (s1: accel->action_and_target, s2: action_and_target) == 0)
65 return i;
66 }
67
68 return G_MAXUINT;
69}
70
71static void
72gtk_accels_replace (GtkAccels *accels,
73 const char *action_and_target,
74 const char *primary_accel)
75{
76 guint position;
77
78 position = gtk_accels_find (accels, action_and_target);
79 if (position < gtk_accels_get_size (self: accels))
80 {
81 GtkAccel *accel = gtk_accels_index (self: accels, pos: position);
82 g_free (mem: accel->accel);
83 accel->accel = g_strdup (str: primary_accel);
84 }
85 else
86 {
87 GtkAccel accel;
88
89 accel.action_and_target = g_strdup (str: action_and_target);
90 accel.accel = g_strdup (str: primary_accel);
91 gtk_accels_append (self: accels, value: &accel);
92 }
93}
94
95static void
96gtk_accels_remove (GtkAccels *accels,
97 const char *action_and_target)
98{
99 guint position;
100
101 position = gtk_accels_find (accels, action_and_target);
102 if (position < gtk_accels_get_size (self: accels))
103 gtk_accels_splice (self: accels, pos: position, removed: 1, FALSE, NULL, added: 0);
104}
105
106/*< private >
107 * GtkActionMuxer:
108 *
109 * GtkActionMuxer aggregates and monitors actions from multiple sources.
110 *
111 * `GtkActionMuxer` is `GtkActionObservable` and `GtkActionObserver` that
112 * offers a `GActionGroup`-like api and is capable of containing other
113 * `GActionGroup` instances. `GtkActionMuxer` does not implement the
114 * `GActionGroup` interface because it requires excessive signal emissions
115 * and has poor scalability. We use the `GtkActionObserver` machinery
116 * instead to propagate changes between action muxer instances and
117 * to other users.
118 *
119 * Beyond action groups, `GtkActionMuxer` can incorporate actions that
120 * are associated with widget classes (*class actions*) and actions
121 * that are associated with the parent widget, allowing for recursive
122 * lookup.
123 *
124 * In addition to the action attributes provided by `GActionGroup`,
125 * `GtkActionMuxer` maintains a *primary accelerator* string for
126 * actions that can be shown in menuitems.
127 *
128 * The typical use is aggregating all of the actions applicable to a
129 * particular context into a single action group, with namespacing.
130 *
131 * Consider the case of two action groups -- one containing actions
132 * applicable to an entire application (such as “quit”) and one
133 * containing actions applicable to a particular window in the
134 * application (such as “fullscreen”).
135 *
136 * In this case, each of these action groups could be added to a
137 * `GtkActionMuxer` with the prefixes “app” and “win”, respectively.
138 * This would expose the actions as “app.quit” and “win.fullscreen”
139 * on the `GActionGroup`-like interface presented by the `GtkActionMuxer`.
140 *
141 * Activations and state change requests on the `GtkActionMuxer` are
142 * wired through to the underlying actions in the expected way.
143 *
144 * This class is typically only used at the site of “consumption” of
145 * actions (eg: when displaying a menu that contains many actions on
146 * different objects).
147 */
148
149static void gtk_action_muxer_observable_iface_init (GtkActionObservableInterface *iface);
150static void gtk_action_muxer_observer_iface_init (GtkActionObserverInterface *iface);
151
152typedef GObjectClass GtkActionMuxerClass;
153
154struct _GtkActionMuxer
155{
156 GObject parent_instance;
157 GtkActionMuxer *parent;
158 GtkWidget *widget;
159
160 GHashTable *observed_actions;
161 GHashTable *groups;
162 GtkAccels primary_accels;
163
164 GtkBitmask *widget_actions_disabled;
165};
166
167G_DEFINE_TYPE_WITH_CODE (GtkActionMuxer, gtk_action_muxer, G_TYPE_OBJECT,
168 G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTION_OBSERVER, gtk_action_muxer_observer_iface_init)
169 G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTION_OBSERVABLE, gtk_action_muxer_observable_iface_init))
170
171enum
172{
173 PROP_0,
174 PROP_PARENT,
175 PROP_WIDGET,
176 NUM_PROPERTIES
177};
178
179static GParamSpec *properties[NUM_PROPERTIES];
180
181typedef struct
182{
183 GtkActionMuxer *muxer;
184 GSList *watchers;
185 char *fullname;
186} Action;
187
188typedef struct
189{
190 GtkActionMuxer *muxer;
191 GActionGroup *group;
192 char *prefix;
193 gulong handler_ids[4];
194} Group;
195
196static inline guint
197get_action_position (GtkWidgetAction *action)
198{
199 guint slot;
200 /* We use the length of @action to the end of the chain as the slot so that
201 * we have stable positions for any class or it's subclasses. Doing so helps
202 * us avoid having mutable arrays in the class data as we will not have
203 * access to the ClassPrivate data during instance _init() functions.
204 */
205 for (slot = 0; action->next != NULL; slot++, action = action->next) {}
206 return slot;
207}
208
209static void
210gtk_action_muxer_append_group_actions (const char *prefix,
211 Group *group,
212 GHashTable *actions)
213{
214 char **group_actions;
215 char **action;
216
217 group_actions = g_action_group_list_actions (action_group: group->group);
218 for (action = group_actions; *action; action++)
219 {
220 char *name = g_strconcat (string1: prefix, ".", *action, NULL);
221 g_hash_table_add (hash_table: actions, key: name);
222 }
223
224 g_strfreev (str_array: group_actions);
225}
226
227char **
228gtk_action_muxer_list_actions (GtkActionMuxer *muxer,
229 gboolean local_only)
230{
231 GHashTable *actions;
232 char **keys;
233
234 actions = g_hash_table_new_full (hash_func: g_str_hash, key_equal_func: g_str_equal, key_destroy_func: g_free, NULL);
235
236 for ( ; muxer != NULL; muxer = muxer->parent)
237 {
238 GHashTableIter iter;
239 const char *prefix;
240 Group *group;
241
242 if (muxer->widget)
243 {
244 GtkWidgetClass *klass = GTK_WIDGET_GET_CLASS (muxer->widget);
245 GtkWidgetClassPrivate *priv = klass->priv;
246 GtkWidgetAction *action;
247
248 for (action = priv->actions; action; action = action->next)
249 g_hash_table_add (hash_table: actions, key: g_strdup (str: action->name));
250 }
251
252 if (muxer->groups)
253 {
254 g_hash_table_iter_init (iter: &iter, hash_table: muxer->groups);
255 while (g_hash_table_iter_next (iter: &iter, key: (gpointer *)&prefix, value: (gpointer *)&group))
256 gtk_action_muxer_append_group_actions (prefix, group, actions);
257 }
258
259 if (local_only)
260 break;
261 }
262
263 keys = (char **)g_hash_table_get_keys_as_array (hash_table: actions, NULL);
264
265 g_hash_table_steal_all (hash_table: actions);
266 g_hash_table_unref (hash_table: actions);
267
268 return (char **)keys;
269}
270
271static Group *
272gtk_action_muxer_find_group (GtkActionMuxer *muxer,
273 const char *full_name,
274 const char **action_name)
275{
276 const char *dot;
277 char *prefix;
278 const char *name;
279 Group *group;
280
281 if (!muxer->groups)
282 return NULL;
283
284 dot = strchr (s: full_name, c: '.');
285
286 if (!dot)
287 return NULL;
288
289 name = dot + 1;
290
291 prefix = g_strndup (str: full_name, n: dot - full_name);
292 group = g_hash_table_lookup (hash_table: muxer->groups, key: prefix);
293 g_free (mem: prefix);
294
295 if (action_name)
296 *action_name = name;
297
298 if (group &&
299 g_action_group_has_action (action_group: group->group, action_name: name))
300 return group;
301
302 return NULL;
303}
304
305GActionGroup *
306gtk_action_muxer_find (GtkActionMuxer *muxer,
307 const char *action_name,
308 const char **unprefixed_name)
309{
310 Group *group;
311
312 group = gtk_action_muxer_find_group (muxer, full_name: action_name, action_name: unprefixed_name);
313 if (group)
314 return group->group;
315
316 return NULL;
317}
318
319GActionGroup *
320gtk_action_muxer_get_group (GtkActionMuxer *muxer,
321 const char *group_name)
322{
323 Group *group;
324
325 group = g_hash_table_lookup (hash_table: muxer->groups, key: group_name);
326 if (group)
327 return group->group;
328
329 return NULL;
330}
331
332static inline Action *
333find_observers (GtkActionMuxer *muxer,
334 const char *action_name)
335{
336 if (muxer->observed_actions)
337 return g_hash_table_lookup (hash_table: muxer->observed_actions, key: action_name);
338
339 return NULL;
340}
341
342void
343gtk_action_muxer_action_enabled_changed (GtkActionMuxer *muxer,
344 const char *action_name,
345 gboolean enabled)
346{
347 GtkWidgetAction *iter;
348 Action *action;
349 GSList *node;
350
351 if (muxer->widget)
352 {
353 GtkWidgetClass *klass = GTK_WIDGET_GET_CLASS (muxer->widget);
354 GtkWidgetClassPrivate *priv = klass->priv;
355
356 for (iter = priv->actions; iter; iter = iter->next)
357 {
358 if (strcmp (s1: action_name, s2: iter->name) == 0)
359 {
360 guint position = get_action_position (action: iter);
361 muxer->widget_actions_disabled =
362 _gtk_bitmask_set (mask: muxer->widget_actions_disabled, index_: position, value: !enabled);
363 break;
364 }
365 }
366 }
367
368 action = find_observers (muxer, action_name);
369
370 for (node = action ? action->watchers : NULL; node; node = node->next)
371 gtk_action_observer_action_enabled_changed (observer: node->data, GTK_ACTION_OBSERVABLE (muxer), action_name, enabled);
372}
373
374static void
375gtk_action_muxer_group_action_enabled_changed (GActionGroup *action_group,
376 const char *action_name,
377 gboolean enabled,
378 gpointer user_data)
379{
380 Group *group = user_data;
381 char *fullname;
382
383 fullname = g_strconcat (string1: group->prefix, ".", action_name, NULL);
384 gtk_action_muxer_action_enabled_changed (muxer: group->muxer, action_name: fullname, enabled);
385 g_free (mem: fullname);
386}
387
388void
389gtk_action_muxer_action_state_changed (GtkActionMuxer *muxer,
390 const char *action_name,
391 GVariant *state)
392{
393 Action *action;
394 GSList *node;
395
396 action = find_observers (muxer, action_name);
397 for (node = action ? action->watchers : NULL; node; node = node->next)
398 gtk_action_observer_action_state_changed (observer: node->data, GTK_ACTION_OBSERVABLE (muxer), action_name, state);
399}
400
401static void
402gtk_action_muxer_group_action_state_changed (GActionGroup *action_group,
403 const char *action_name,
404 GVariant *state,
405 gpointer user_data)
406{
407 Group *group = user_data;
408 char *fullname;
409
410 fullname = g_strconcat (string1: group->prefix, ".", action_name, NULL);
411 gtk_action_muxer_action_state_changed (muxer: group->muxer, action_name: fullname, state);
412 g_free (mem: fullname);
413}
414
415static gboolean action_muxer_query_action (GtkActionMuxer *muxer,
416 const char *action_name,
417 gboolean *enabled,
418 const GVariantType **parameter_type,
419 const GVariantType **state_type,
420 GVariant **state_hint,
421 GVariant **state,
422 gboolean recurse);
423
424static void
425notify_observers_added (GtkActionMuxer *muxer,
426 GtkActionMuxer *parent)
427{
428 GHashTableIter iter;
429 const char *action_name;
430 Action *action;
431
432 if (!muxer->observed_actions)
433 return;
434
435 g_hash_table_iter_init (iter: &iter, hash_table: muxer->observed_actions);
436 while (g_hash_table_iter_next (iter: &iter, key: (gpointer *)&action_name, value: (gpointer *)&action))
437 {
438 const GVariantType *parameter_type;
439 gboolean enabled;
440 GVariant *state;
441 GSList *node;
442
443 if (!action->watchers)
444 continue;
445
446 for (node = action ? action->watchers : NULL; node; node = node->next)
447 gtk_action_observer_primary_accel_changed (observer: node->data, GTK_ACTION_OBSERVABLE (muxer),
448 action_name, NULL);
449
450 gtk_action_observable_register_observer (GTK_ACTION_OBSERVABLE (parent), action_name, GTK_ACTION_OBSERVER (muxer));
451
452 if (!action_muxer_query_action (muxer: parent, action_name,
453 enabled: &enabled, parameter_type: &parameter_type,
454 NULL, NULL, state: &state,
455 TRUE))
456 continue;
457
458 for (node = action->watchers; node; node = node->next)
459 {
460 gtk_action_observer_action_added (observer: node->data,
461 GTK_ACTION_OBSERVABLE (muxer),
462 action_name, parameter_type, enabled, state);
463 }
464
465 if (state)
466 g_variant_unref (value: state);
467 }
468}
469
470static void
471notify_observers_removed (GtkActionMuxer *muxer,
472 GtkActionMuxer *parent)
473{
474 GHashTableIter iter;
475 const char *action_name;
476 Action *action;
477
478 if (!muxer->observed_actions)
479 return;
480
481 g_hash_table_iter_init (iter: &iter, hash_table: muxer->observed_actions);
482 while (g_hash_table_iter_next (iter: &iter, key: (gpointer *)&action_name, value: (gpointer *)&action))
483 {
484 GSList *node;
485
486 gtk_action_observable_unregister_observer (GTK_ACTION_OBSERVABLE (parent), action_name, GTK_ACTION_OBSERVER (muxer));
487
488 for (node = action->watchers; node; node = node->next)
489 {
490 gtk_action_observer_action_removed (observer: node->data,
491 GTK_ACTION_OBSERVABLE (muxer),
492 action_name);
493 }
494 }
495}
496
497static void
498gtk_action_muxer_action_added (GtkActionMuxer *muxer,
499 const char *action_name,
500 const GVariantType *parameter_type,
501 gboolean enabled,
502 GVariant *state)
503{
504 Action *action;
505 GSList *node;
506
507 action = find_observers (muxer, action_name);
508 for (node = action ? action->watchers : NULL; node; node = node->next)
509 gtk_action_observer_action_added (observer: node->data,
510 GTK_ACTION_OBSERVABLE (muxer),
511 action_name, parameter_type, enabled, state);
512}
513
514static void
515gtk_action_muxer_action_added_to_group (GActionGroup *action_group,
516 const char *action_name,
517 gpointer user_data)
518{
519 Group *group = user_data;
520 GtkActionMuxer *muxer = group->muxer;
521 Action *action;
522 const GVariantType *parameter_type;
523 gboolean enabled;
524 GVariant *state;
525 char *fullname;
526
527 fullname = g_strconcat (string1: group->prefix, ".", action_name, NULL);
528
529 if (muxer->parent)
530 gtk_action_observable_unregister_observer (GTK_ACTION_OBSERVABLE (muxer->parent),
531 action_name: fullname,
532 GTK_ACTION_OBSERVER (muxer));
533
534 action = find_observers (muxer, action_name: fullname);
535
536 if (action && action->watchers &&
537 g_action_group_query_action (action_group, action_name,
538 enabled: &enabled, parameter_type: &parameter_type, NULL, NULL, state: &state))
539 {
540 gtk_action_muxer_action_added (muxer, action_name: fullname, parameter_type, enabled, state);
541
542 if (state)
543 g_variant_unref (value: state);
544 }
545
546 g_free (mem: fullname);
547}
548
549static void
550gtk_action_muxer_action_removed (GtkActionMuxer *muxer,
551 const char *action_name)
552{
553 Action *action;
554 GSList *node;
555
556 action = find_observers (muxer, action_name);
557 for (node = action ? action->watchers : NULL; node; node = node->next)
558 gtk_action_observer_action_removed (observer: node->data, GTK_ACTION_OBSERVABLE (muxer), action_name);
559}
560
561static void
562gtk_action_muxer_action_removed_from_group (GActionGroup *action_group,
563 const char *action_name,
564 gpointer user_data)
565{
566 Group *group = user_data;
567 GtkActionMuxer *muxer = group->muxer;
568 char *fullname;
569 Action *action;
570
571 fullname = g_strconcat (string1: group->prefix, ".", action_name, NULL);
572 gtk_action_muxer_action_removed (muxer, action_name: fullname);
573 g_free (mem: fullname);
574
575 action = find_observers (muxer, action_name);
576
577 if (action && action->watchers &&
578 !action_muxer_query_action (muxer, action_name,
579 NULL, NULL, NULL, NULL, NULL, FALSE))
580 {
581 if (muxer->parent)
582 gtk_action_observable_register_observer (GTK_ACTION_OBSERVABLE (muxer->parent),
583 action_name,
584 GTK_ACTION_OBSERVER (muxer));
585 }
586}
587
588static void
589gtk_action_muxer_primary_accel_changed (GtkActionMuxer *muxer,
590 const char *action_name,
591 const char *action_and_target)
592{
593 Action *action;
594 GSList *node;
595
596 if (!action_name)
597 action_name = strrchr (s: action_and_target, c: '|') + 1;
598
599 action = find_observers (muxer, action_name);
600 for (node = action ? action->watchers : NULL; node; node = node->next)
601 gtk_action_observer_primary_accel_changed (observer: node->data, GTK_ACTION_OBSERVABLE (muxer),
602 action_name, action_and_target);
603}
604
605static GVariant *
606prop_action_get_state (GtkWidget *widget,
607 GtkWidgetAction *action)
608{
609 GValue value = G_VALUE_INIT;
610 GVariant *result;
611
612 g_value_init (value: &value, g_type: action->pspec->value_type);
613 g_object_get_property (G_OBJECT (widget), property_name: action->pspec->name, value: &value);
614
615 result = g_settings_set_mapping (value: &value, expected_type: action->state_type, NULL);
616 g_value_unset (value: &value);
617
618 return g_variant_ref_sink (value: result);
619}
620
621static GVariant *
622prop_action_get_state_hint (GtkWidget *widget,
623 GtkWidgetAction *action)
624{
625 if (action->pspec->value_type == G_TYPE_INT)
626 {
627 GParamSpecInt *pspec = (GParamSpecInt *)action->pspec;
628 return g_variant_new (format_string: "(ii)", pspec->minimum, pspec->maximum);
629 }
630 else if (action->pspec->value_type == G_TYPE_UINT)
631 {
632 GParamSpecUInt *pspec = (GParamSpecUInt *)action->pspec;
633 return g_variant_new (format_string: "(uu)", pspec->minimum, pspec->maximum);
634 }
635 else if (action->pspec->value_type == G_TYPE_FLOAT)
636 {
637 GParamSpecFloat *pspec = (GParamSpecFloat *)action->pspec;
638 return g_variant_new (format_string: "(dd)", (double)pspec->minimum, (double)pspec->maximum);
639 }
640 else if (action->pspec->value_type == G_TYPE_DOUBLE)
641 {
642 GParamSpecDouble *pspec = (GParamSpecDouble *)action->pspec;
643 return g_variant_new (format_string: "(dd)", pspec->minimum, pspec->maximum);
644 }
645
646 return NULL;
647}
648
649static void
650prop_action_set_state (GtkWidget *widget,
651 GtkWidgetAction *action,
652 GVariant *state)
653{
654 GValue value = G_VALUE_INIT;
655
656 g_value_init (value: &value, g_type: action->pspec->value_type);
657 g_settings_get_mapping (value: &value, variant: state, NULL);
658
659 g_object_set_property (G_OBJECT (widget), property_name: action->pspec->name, value: &value);
660 g_value_unset (value: &value);
661}
662
663static void
664prop_action_activate (GtkWidget *widget,
665 GtkWidgetAction *action,
666 GVariant *parameter)
667{
668 if (action->pspec->value_type == G_TYPE_BOOLEAN)
669 {
670 gboolean value;
671
672 g_return_if_fail (parameter == NULL);
673
674 g_object_get (G_OBJECT (widget), first_property_name: action->pspec->name, &value, NULL);
675 value = !value;
676 g_object_set (G_OBJECT (widget), first_property_name: action->pspec->name, value, NULL);
677 }
678 else
679 {
680 g_return_if_fail (parameter != NULL && g_variant_is_of_type (parameter, action->state_type));
681
682 prop_action_set_state (widget, action, state: parameter);
683 }
684}
685
686static void
687prop_action_notify (GObject *object,
688 GParamSpec *pspec,
689 gpointer user_data)
690{
691 GtkWidget *widget = GTK_WIDGET (object);
692 GtkWidgetAction *action = user_data;
693 GtkActionMuxer *muxer = _gtk_widget_get_action_muxer (widget, TRUE);
694 GVariant *state;
695
696 g_assert (muxer->widget == widget);
697 g_assert (action->pspec == pspec);
698
699 state = prop_action_get_state (widget, action);
700 gtk_action_muxer_action_state_changed (muxer, action_name: action->name, state);
701 g_variant_unref (value: state);
702}
703
704static void
705prop_actions_connect (GtkActionMuxer *muxer)
706{
707 GtkWidgetClassPrivate *priv;
708 GtkWidgetAction *action;
709 GtkWidgetClass *klass;
710 guint signal_id;
711
712 if (!muxer->widget)
713 return;
714
715 klass = GTK_WIDGET_GET_CLASS (muxer->widget);
716 priv = klass->priv;
717 if (!priv->actions)
718 return;
719
720 signal_id = g_signal_lookup (name: "notify", G_TYPE_OBJECT);
721
722 for (action = priv->actions; action; action = action->next)
723 {
724 if (!action->pspec)
725 continue;
726
727 g_signal_connect_closure_by_id (instance: muxer->widget,
728 signal_id,
729 detail: g_param_spec_get_name_quark (pspec: action->pspec),
730 closure: g_cclosure_new (G_CALLBACK (prop_action_notify),
731 user_data: action,
732 NULL),
733 FALSE);
734 }
735}
736
737static gboolean
738action_muxer_query_action (GtkActionMuxer *muxer,
739 const char *action_name,
740 gboolean *enabled,
741 const GVariantType **parameter_type,
742 const GVariantType **state_type,
743 GVariant **state_hint,
744 GVariant **state,
745 gboolean recurse)
746{
747 GtkWidgetAction *action;
748 Group *group;
749 const char *unprefixed_name;
750
751 if (muxer->widget)
752 {
753 GtkWidgetClass *klass = GTK_WIDGET_GET_CLASS (muxer->widget);
754 GtkWidgetClassPrivate *priv = klass->priv;
755
756 for (action = priv->actions; action; action = action->next)
757 {
758 guint position;
759
760 if (strcmp (s1: action->name, s2: action_name) != 0)
761 continue;
762
763 position = get_action_position (action);
764
765 if (enabled)
766 *enabled = !_gtk_bitmask_get (mask: muxer->widget_actions_disabled, index_: position);
767 if (parameter_type)
768 *parameter_type = action->parameter_type;
769 if (state_type)
770 *state_type = action->state_type;
771
772 if (state_hint)
773 *state_hint = NULL;
774 if (state)
775 *state = NULL;
776
777 if (action->pspec)
778 {
779 if (state)
780 *state = prop_action_get_state (widget: muxer->widget, action);
781 if (state_hint)
782 *state_hint = prop_action_get_state_hint (widget: muxer->widget, action);
783 }
784
785 return TRUE;
786 }
787 }
788
789 group = gtk_action_muxer_find_group (muxer, full_name: action_name, action_name: &unprefixed_name);
790
791 if (group)
792 return g_action_group_query_action (action_group: group->group, action_name: unprefixed_name, enabled,
793 parameter_type, state_type, state_hint, state);
794
795 if (muxer->parent && recurse)
796 return gtk_action_muxer_query_action (muxer: muxer->parent, action_name,
797 enabled, parameter_type,
798 state_type, state_hint, state);
799
800 return FALSE;
801}
802
803gboolean
804gtk_action_muxer_query_action (GtkActionMuxer *muxer,
805 const char *action_name,
806 gboolean *enabled,
807 const GVariantType **parameter_type,
808 const GVariantType **state_type,
809 GVariant **state_hint,
810 GVariant **state)
811{
812 return action_muxer_query_action (muxer, action_name,
813 enabled, parameter_type,
814 state_type, state_hint, state,
815 TRUE);
816}
817
818gboolean
819gtk_action_muxer_has_action (GtkActionMuxer *muxer,
820 const char *action_name)
821{
822 return action_muxer_query_action (muxer, action_name,
823 NULL, NULL, NULL, NULL, NULL,
824 TRUE);
825}
826
827void
828gtk_action_muxer_activate_action (GtkActionMuxer *muxer,
829 const char *action_name,
830 GVariant *parameter)
831{
832 const char *unprefixed_name;
833 Group *group;
834
835 if (muxer->widget)
836 {
837 GtkWidgetClass *klass = GTK_WIDGET_GET_CLASS (muxer->widget);
838 GtkWidgetClassPrivate *priv = klass->priv;
839 GtkWidgetAction *action;
840
841 for (action = priv->actions; action; action = action->next)
842 {
843 if (strcmp (s1: action->name, s2: action_name) == 0)
844 {
845 guint position = get_action_position (action);
846
847 if (!_gtk_bitmask_get (mask: muxer->widget_actions_disabled, index_: position))
848 {
849 if (action->activate)
850 action->activate (muxer->widget, action->name, parameter);
851 else if (action->pspec)
852 prop_action_activate (widget: muxer->widget, action, parameter);
853 }
854
855 return;
856 }
857 }
858 }
859
860 group = gtk_action_muxer_find_group (muxer, full_name: action_name, action_name: &unprefixed_name);
861
862 if (group)
863 g_action_group_activate_action (action_group: group->group, action_name: unprefixed_name, parameter);
864 else if (muxer->parent)
865 gtk_action_muxer_activate_action (muxer: muxer->parent, action_name, parameter);
866}
867
868void
869gtk_action_muxer_change_action_state (GtkActionMuxer *muxer,
870 const char *action_name,
871 GVariant *state)
872{
873 GtkWidgetAction *action;
874 const char *unprefixed_name;
875 Group *group;
876
877 if (muxer->widget)
878 {
879 GtkWidgetClass *klass = GTK_WIDGET_GET_CLASS (muxer->widget);
880 GtkWidgetClassPrivate *priv = klass->priv;
881
882 for (action = priv->actions; action; action = action->next)
883 {
884 if (strcmp (s1: action->name, s2: action_name) == 0)
885 {
886 if (action->pspec)
887 prop_action_set_state (widget: muxer->widget, action, state);
888
889 return;
890 }
891 }
892 }
893
894 group = gtk_action_muxer_find_group (muxer, full_name: action_name, action_name: &unprefixed_name);
895
896 if (group)
897 g_action_group_change_action_state (action_group: group->group, action_name: unprefixed_name, value: state);
898 else if (muxer->parent)
899 gtk_action_muxer_change_action_state (muxer: muxer->parent, action_name, state);
900}
901
902static void
903gtk_action_muxer_unregister_internal (Action *action,
904 gpointer observer)
905{
906 GtkActionMuxer *muxer = action->muxer;
907 GSList **ptr;
908
909 for (ptr = &action->watchers; *ptr; ptr = &(*ptr)->next)
910 if ((*ptr)->data == observer)
911 {
912 *ptr = g_slist_remove (list: *ptr, data: observer);
913
914 if (action->watchers == NULL)
915 {
916 if (muxer->parent)
917 gtk_action_observable_unregister_observer (GTK_ACTION_OBSERVABLE (muxer->parent),
918 action_name: action->fullname,
919 GTK_ACTION_OBSERVER (muxer));
920 g_hash_table_remove (hash_table: muxer->observed_actions, key: action->fullname);
921 }
922
923 break;
924 }
925}
926
927static void
928gtk_action_muxer_weak_notify (gpointer data,
929 GObject *where_the_object_was)
930{
931 Action *action = data;
932
933 gtk_action_muxer_unregister_internal (action, observer: where_the_object_was);
934}
935
936static void gtk_action_muxer_free_action (gpointer data);
937
938static void
939gtk_action_muxer_register_observer (GtkActionObservable *observable,
940 const char *name,
941 GtkActionObserver *observer)
942{
943 GtkActionMuxer *muxer = GTK_ACTION_MUXER (observable);
944 Action *action;
945 gboolean enabled;
946 const GVariantType *parameter_type;
947 GVariant *state;
948 gboolean is_duplicate;
949
950 if (!muxer->observed_actions)
951 muxer->observed_actions = g_hash_table_new_full (hash_func: g_str_hash, key_equal_func: g_str_equal, NULL, value_destroy_func: gtk_action_muxer_free_action);
952
953 action = g_hash_table_lookup (hash_table: muxer->observed_actions, key: name);
954
955 if (action == NULL)
956 {
957 action = g_slice_new (Action);
958 action->muxer = muxer;
959 action->fullname = g_strdup (str: name);
960 action->watchers = NULL;
961
962 g_hash_table_insert (hash_table: muxer->observed_actions, key: action->fullname, value: action);
963 }
964
965 is_duplicate = g_slist_find (list: action->watchers, data: observer) != NULL;
966 action->watchers = g_slist_prepend (list: action->watchers, data: observer);
967 g_object_weak_ref (G_OBJECT (observer), notify: gtk_action_muxer_weak_notify, data: action);
968
969 if (!is_duplicate)
970 {
971 if (action_muxer_query_action (muxer, action_name: name,
972 enabled: &enabled, parameter_type: &parameter_type,
973 NULL, NULL, state: &state, TRUE))
974 {
975 gtk_action_muxer_action_added (muxer, action_name: name, parameter_type, enabled, state);
976 g_clear_pointer (&state, g_variant_unref);
977 }
978
979 if (muxer->parent)
980 {
981 gtk_action_observable_register_observer (GTK_ACTION_OBSERVABLE (muxer->parent),
982 action_name: name,
983 GTK_ACTION_OBSERVER (muxer));
984 }
985 }
986}
987
988static void
989gtk_action_muxer_unregister_observer (GtkActionObservable *observable,
990 const char *name,
991 GtkActionObserver *observer)
992{
993 GtkActionMuxer *muxer = GTK_ACTION_MUXER (observable);
994 Action *action = find_observers (muxer, action_name: name);
995
996 if (action)
997 {
998 if (g_slist_find (list: action->watchers, data: observer) != NULL)
999 {
1000 g_object_weak_unref (G_OBJECT (observer), notify: gtk_action_muxer_weak_notify, data: action);
1001 gtk_action_muxer_unregister_internal (action, observer);
1002 }
1003 }
1004}
1005
1006static void
1007gtk_action_muxer_free_group (gpointer data)
1008{
1009 Group *group = data;
1010 int i;
1011
1012 /* 'for loop' or 'four loop'? */
1013 for (i = 0; i < 4; i++)
1014 g_signal_handler_disconnect (instance: group->group, handler_id: group->handler_ids[i]);
1015
1016 g_object_unref (object: group->group);
1017 g_free (mem: group->prefix);
1018
1019 g_slice_free (Group, group);
1020}
1021
1022static void
1023gtk_action_muxer_free_action (gpointer data)
1024{
1025 Action *action = data;
1026 GSList *it;
1027
1028 for (it = action->watchers; it; it = it->next)
1029 g_object_weak_unref (G_OBJECT (it->data), notify: gtk_action_muxer_weak_notify, data: action);
1030
1031 g_slist_free (list: action->watchers);
1032 g_free (mem: action->fullname);
1033
1034 g_slice_free (Action, action);
1035}
1036
1037static void
1038gtk_action_muxer_finalize (GObject *object)
1039{
1040 GtkActionMuxer *muxer = GTK_ACTION_MUXER (object);
1041
1042 if (muxer->observed_actions)
1043 {
1044 g_assert_cmpint (g_hash_table_size (muxer->observed_actions), ==, 0);
1045 g_hash_table_unref (hash_table: muxer->observed_actions);
1046 }
1047 if (muxer->groups)
1048 g_hash_table_unref (hash_table: muxer->groups);
1049
1050 gtk_accels_clear (self: &muxer->primary_accels);
1051
1052 _gtk_bitmask_free (mask: muxer->widget_actions_disabled);
1053
1054 G_OBJECT_CLASS (gtk_action_muxer_parent_class)->finalize (object);
1055}
1056
1057static void
1058gtk_action_muxer_dispose (GObject *object)
1059{
1060 GtkActionMuxer *muxer = GTK_ACTION_MUXER (object);
1061
1062 g_clear_object (&muxer->parent);
1063 if (muxer->observed_actions)
1064 g_hash_table_remove_all (hash_table: muxer->observed_actions);
1065
1066 muxer->widget = NULL;
1067
1068 G_OBJECT_CLASS (gtk_action_muxer_parent_class)->dispose (object);
1069}
1070
1071void
1072gtk_action_muxer_connect_class_actions (GtkActionMuxer *muxer)
1073{
1074 prop_actions_connect (muxer);
1075}
1076
1077static void
1078gtk_action_muxer_get_property (GObject *object,
1079 guint property_id,
1080 GValue *value,
1081 GParamSpec *pspec)
1082{
1083 GtkActionMuxer *muxer = GTK_ACTION_MUXER (object);
1084
1085 switch (property_id)
1086 {
1087 case PROP_PARENT:
1088 g_value_set_object (value, v_object: gtk_action_muxer_get_parent (muxer));
1089 break;
1090
1091 case PROP_WIDGET:
1092 g_value_set_object (value, v_object: muxer->widget);
1093 break;
1094
1095 default:
1096 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1097 }
1098}
1099
1100static void
1101gtk_action_muxer_set_property (GObject *object,
1102 guint property_id,
1103 const GValue *value,
1104 GParamSpec *pspec)
1105{
1106 GtkActionMuxer *muxer = GTK_ACTION_MUXER (object);
1107
1108 switch (property_id)
1109 {
1110 case PROP_PARENT:
1111 gtk_action_muxer_set_parent (muxer, parent: g_value_get_object (value));
1112 break;
1113
1114 case PROP_WIDGET:
1115 muxer->widget = g_value_get_object (value);
1116 break;
1117
1118 default:
1119 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1120 }
1121}
1122
1123static void
1124gtk_action_muxer_init (GtkActionMuxer *muxer)
1125{
1126 muxer->widget_actions_disabled = _gtk_bitmask_new ();
1127}
1128
1129static void
1130gtk_action_muxer_observable_iface_init (GtkActionObservableInterface *iface)
1131{
1132 iface->register_observer = gtk_action_muxer_register_observer;
1133 iface->unregister_observer = gtk_action_muxer_unregister_observer;
1134}
1135
1136
1137static void
1138gtk_action_muxer_observer_action_added (GtkActionObserver *observer,
1139 GtkActionObservable *observable,
1140 const char *action_name,
1141 const GVariantType *parameter_type,
1142 gboolean enabled,
1143 GVariant *state)
1144{
1145 if (action_muxer_query_action (GTK_ACTION_MUXER (observer), action_name,
1146 NULL, NULL, NULL, NULL, NULL, FALSE))
1147 return;
1148
1149 gtk_action_muxer_action_added (GTK_ACTION_MUXER (observer),
1150 action_name,
1151 parameter_type,
1152 enabled,
1153 state);
1154}
1155
1156static void
1157gtk_action_muxer_observer_action_removed (GtkActionObserver *observer,
1158 GtkActionObservable *observable,
1159 const char *action_name)
1160{
1161 if (action_muxer_query_action (GTK_ACTION_MUXER (observer), action_name,
1162 NULL, NULL, NULL, NULL, NULL, FALSE))
1163 return;
1164
1165 gtk_action_muxer_action_removed (GTK_ACTION_MUXER (observer), action_name);
1166}
1167
1168static void
1169gtk_action_muxer_observer_action_enabled_changed (GtkActionObserver *observer,
1170 GtkActionObservable *observable,
1171 const char *action_name,
1172 gboolean enabled)
1173{
1174 if (action_muxer_query_action (GTK_ACTION_MUXER (observer), action_name,
1175 NULL, NULL, NULL, NULL, NULL, FALSE))
1176 return;
1177
1178 gtk_action_muxer_action_enabled_changed (GTK_ACTION_MUXER (observer), action_name, enabled);
1179}
1180
1181static void
1182gtk_action_muxer_observer_action_state_changed (GtkActionObserver *observer,
1183 GtkActionObservable *observable,
1184 const char *action_name,
1185 GVariant *state)
1186{
1187 if (action_muxer_query_action (GTK_ACTION_MUXER (observer), action_name,
1188 NULL, NULL, NULL, NULL, NULL, FALSE))
1189 return;
1190
1191 gtk_action_muxer_action_state_changed (GTK_ACTION_MUXER (observer), action_name, state);
1192}
1193
1194static void
1195gtk_action_muxer_observer_primary_accel_changed (GtkActionObserver *observer,
1196 GtkActionObservable *observable,
1197 const char *action_name,
1198 const char *action_and_target)
1199{
1200 gtk_action_muxer_primary_accel_changed (GTK_ACTION_MUXER (observer),
1201 action_name,
1202 action_and_target);
1203}
1204
1205static void
1206gtk_action_muxer_observer_iface_init (GtkActionObserverInterface *iface)
1207{
1208 iface->action_added = gtk_action_muxer_observer_action_added;
1209 iface->action_removed = gtk_action_muxer_observer_action_removed;
1210 iface->action_enabled_changed = gtk_action_muxer_observer_action_enabled_changed;
1211 iface->action_state_changed = gtk_action_muxer_observer_action_state_changed;
1212 iface->primary_accel_changed = gtk_action_muxer_observer_primary_accel_changed;
1213}
1214
1215static void
1216gtk_action_muxer_class_init (GObjectClass *class)
1217{
1218 class->get_property = gtk_action_muxer_get_property;
1219 class->set_property = gtk_action_muxer_set_property;
1220 class->finalize = gtk_action_muxer_finalize;
1221 class->dispose = gtk_action_muxer_dispose;
1222
1223 properties[PROP_PARENT] = g_param_spec_object (name: "parent", nick: "Parent",
1224 blurb: "The parent muxer",
1225 GTK_TYPE_ACTION_MUXER,
1226 flags: G_PARAM_READWRITE |
1227 G_PARAM_STATIC_STRINGS);
1228
1229 properties[PROP_WIDGET] = g_param_spec_object (name: "widget", nick: "Widget",
1230 blurb: "The widget that owns the muxer",
1231 GTK_TYPE_WIDGET,
1232 flags: G_PARAM_READWRITE |
1233 G_PARAM_CONSTRUCT_ONLY |
1234 G_PARAM_STATIC_STRINGS);
1235
1236 g_object_class_install_properties (oclass: class, n_pspecs: NUM_PROPERTIES, pspecs: properties);
1237}
1238
1239/*< private >
1240 * gtk_action_muxer_insert:
1241 * @muxer: a `GtkActionMuxer`
1242 * @prefix: the prefix string for the action group
1243 * @action_group: a `GActionGroup`
1244 *
1245 * Adds the actions in @action_group to the list of actions provided by
1246 * @muxer. @prefix is prefixed to each action name, such that for each
1247 * action `x` in @action_group, there is an equivalent
1248 * action @prefix`.x` in @muxer.
1249 *
1250 * For example, if @prefix is “`app`” and @action_group
1251 * contains an action called “`quit`”, then @muxer will
1252 * now contain an action called “`app.quit`”.
1253 *
1254 * If any `GtkActionObserver`s are registered for actions in the group,
1255 * “action_added” notifications will be emitted, as appropriate.
1256 *
1257 * @prefix must not contain a dot ('.').
1258 */
1259void
1260gtk_action_muxer_insert (GtkActionMuxer *muxer,
1261 const char *prefix,
1262 GActionGroup *action_group)
1263{
1264 char **actions;
1265 Group *group;
1266 int i;
1267
1268 /* TODO: diff instead of ripout and replace */
1269 gtk_action_muxer_remove (muxer, prefix);
1270
1271 if (!muxer->groups)
1272 muxer->groups = g_hash_table_new_full (hash_func: g_str_hash, key_equal_func: g_str_equal, NULL, value_destroy_func: gtk_action_muxer_free_group);
1273
1274 group = g_slice_new0 (Group);
1275 group->muxer = muxer;
1276 group->group = g_object_ref (action_group);
1277 group->prefix = g_strdup (str: prefix);
1278
1279 g_hash_table_insert (hash_table: muxer->groups, key: group->prefix, value: group);
1280
1281 actions = g_action_group_list_actions (action_group: group->group);
1282 for (i = 0; actions[i]; i++)
1283 gtk_action_muxer_action_added_to_group (action_group: group->group, action_name: actions[i], user_data: group);
1284 g_strfreev (str_array: actions);
1285
1286 group->handler_ids[0] = g_signal_connect (group->group, "action-added",
1287 G_CALLBACK (gtk_action_muxer_action_added_to_group), group);
1288 group->handler_ids[1] = g_signal_connect (group->group, "action-removed",
1289 G_CALLBACK (gtk_action_muxer_action_removed_from_group), group);
1290 group->handler_ids[2] = g_signal_connect (group->group, "action-enabled-changed",
1291 G_CALLBACK (gtk_action_muxer_group_action_enabled_changed), group);
1292 group->handler_ids[3] = g_signal_connect (group->group, "action-state-changed",
1293 G_CALLBACK (gtk_action_muxer_group_action_state_changed), group);
1294}
1295
1296/*< private >
1297 * gtk_action_muxer_remove:
1298 * @muxer: a `GtkActionMuxer`
1299 * @prefix: the prefix of the action group to remove
1300 *
1301 * Removes a `GActionGroup` from the `GtkActionMuxer`.
1302 *
1303 * If any `GtkActionObserver`s are registered for actions in the group,
1304 * “action_removed” notifications will be emitted, as appropriate.
1305 */
1306void
1307gtk_action_muxer_remove (GtkActionMuxer *muxer,
1308 const char *prefix)
1309{
1310 Group *group;
1311
1312 if (!muxer->groups)
1313 return;
1314
1315 group = g_hash_table_lookup (hash_table: muxer->groups, key: prefix);
1316
1317 if (group != NULL)
1318 {
1319 char **actions;
1320 int i;
1321
1322 g_hash_table_steal (hash_table: muxer->groups, key: prefix);
1323
1324 actions = g_action_group_list_actions (action_group: group->group);
1325 for (i = 0; actions[i]; i++)
1326 gtk_action_muxer_action_removed_from_group (action_group: group->group, action_name: actions[i], user_data: group);
1327 g_strfreev (str_array: actions);
1328
1329 gtk_action_muxer_free_group (data: group);
1330 }
1331}
1332
1333/*< private >
1334 * gtk_action_muxer_new:
1335 * @widget: the widget to which the muxer belongs
1336 *
1337 * Creates a new `GtkActionMuxer`.
1338 */
1339GtkActionMuxer *
1340gtk_action_muxer_new (GtkWidget *widget)
1341{
1342 return g_object_new (GTK_TYPE_ACTION_MUXER,
1343 first_property_name: "widget", widget,
1344 NULL);
1345}
1346
1347/*< private >
1348 * gtk_action_muxer_get_parent:
1349 * @muxer: a `GtkActionMuxer`
1350 *
1351 * Returns: (transfer none): the parent of @muxer, or NULL.
1352 */
1353GtkActionMuxer *
1354gtk_action_muxer_get_parent (GtkActionMuxer *muxer)
1355{
1356 g_return_val_if_fail (GTK_IS_ACTION_MUXER (muxer), NULL);
1357
1358 return muxer->parent;
1359}
1360
1361/*< private >
1362 * gtk_action_muxer_set_parent:
1363 * @muxer: a `GtkActionMuxer`
1364 * @parent: (nullable): the new parent `GtkActionMuxer`
1365 *
1366 * Sets the parent of @muxer to @parent.
1367 */
1368void
1369gtk_action_muxer_set_parent (GtkActionMuxer *muxer,
1370 GtkActionMuxer *parent)
1371{
1372 g_return_if_fail (GTK_IS_ACTION_MUXER (muxer));
1373 g_return_if_fail (parent == NULL || GTK_IS_ACTION_MUXER (parent));
1374
1375 if (muxer->parent == parent)
1376 return;
1377
1378 if (muxer->parent != NULL)
1379 {
1380 notify_observers_removed (muxer, parent: muxer->parent);
1381 g_object_unref (object: muxer->parent);
1382 }
1383
1384 muxer->parent = parent;
1385
1386 if (muxer->parent != NULL)
1387 {
1388 g_object_ref (muxer->parent);
1389 notify_observers_added (muxer, parent: muxer->parent);
1390 }
1391
1392 g_object_notify_by_pspec (G_OBJECT (muxer), pspec: properties[PROP_PARENT]);
1393}
1394
1395void
1396gtk_action_muxer_set_primary_accel (GtkActionMuxer *muxer,
1397 const char *action_and_target,
1398 const char *primary_accel)
1399{
1400 if (primary_accel)
1401 gtk_accels_replace (accels: &muxer->primary_accels, action_and_target, primary_accel);
1402 else
1403 gtk_accels_remove (accels: &muxer->primary_accels, action_and_target);
1404
1405 gtk_action_muxer_primary_accel_changed (muxer, NULL, action_and_target);
1406}
1407
1408const char *
1409gtk_action_muxer_get_primary_accel (GtkActionMuxer *muxer,
1410 const char *action_and_target)
1411{
1412 guint position;
1413
1414 position = gtk_accels_find (accels: &muxer->primary_accels, action_and_target);
1415 if (position < G_MAXUINT)
1416 return gtk_accels_index (self: &muxer->primary_accels, pos: position)->accel;
1417
1418 if (!muxer->parent)
1419 return NULL;
1420
1421 return gtk_action_muxer_get_primary_accel (muxer: muxer->parent, action_and_target);
1422}
1423
1424char *
1425gtk_print_action_and_target (const char *action_namespace,
1426 const char *action_name,
1427 GVariant *target)
1428{
1429 GString *result;
1430
1431 g_return_val_if_fail (strchr (action_name, '|') == NULL, NULL);
1432 g_return_val_if_fail (action_namespace == NULL || strchr (action_namespace, '|') == NULL, NULL);
1433
1434 result = g_string_new (NULL);
1435
1436 if (target)
1437 g_variant_print_string (value: target, string: result, TRUE);
1438 g_string_append_c (result, '|');
1439
1440 if (action_namespace)
1441 {
1442 g_string_append (string: result, val: action_namespace);
1443 g_string_append_c (result, '.');
1444 }
1445
1446 g_string_append (string: result, val: action_name);
1447
1448 return g_string_free (string: result, FALSE);
1449}
1450
1451char *
1452gtk_normalise_detailed_action_name (const char *detailed_action_name)
1453{
1454 GError *error = NULL;
1455 char *action_and_target;
1456 char *action_name;
1457 GVariant *target;
1458
1459 g_action_parse_detailed_name (detailed_name: detailed_action_name, action_name: &action_name, target_value: &target, error: &error);
1460 g_assert_no_error (error);
1461
1462 action_and_target = gtk_print_action_and_target (NULL, action_name, target);
1463
1464 if (target)
1465 g_variant_unref (value: target);
1466
1467 g_free (mem: action_name);
1468
1469 return action_and_target;
1470}
1471
1472

source code of gtk/gtk/gtkactionmuxer.c