]> Pileus Git - ~andy/gtk/blob - gtk/gtkactionhelper.c
stylecontext: Do invalidation on first resize container
[~andy/gtk] / gtk / gtkactionhelper.c
1 /*
2  * Copyright © 2012 Canonical Limited
3  *
4  * This library is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU Lesser General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * licence or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful, but
10  * 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  * Authors: Ryan Lortie <desrt@desrt.ca>
18  */
19
20 #include "gtkactionhelper.h"
21 #include "gactionobservable.h"
22
23 #include "gtkwidget.h"
24 #include "gtkwidgetprivate.h"
25
26 #include <string.h>
27
28 typedef struct
29 {
30   GActionGroup *group;
31
32   GHashTable *watchers;
33 } GtkActionHelperGroup;
34
35 static void             gtk_action_helper_action_added                  (GtkActionHelper    *helper,
36                                                                          gboolean            enabled,
37                                                                          const GVariantType *parameter_type,
38                                                                          GVariant           *state,
39                                                                          gboolean            should_emit_signals);
40
41 static void             gtk_action_helper_action_removed                (GtkActionHelper    *helper);
42
43 static void             gtk_action_helper_action_enabled_changed        (GtkActionHelper    *helper,
44                                                                          gboolean            enabled);
45
46 static void             gtk_action_helper_action_state_changed          (GtkActionHelper    *helper,
47                                                                          GVariant           *new_state);
48
49 typedef GObjectClass GtkActionHelperClass;
50
51 struct _GtkActionHelper
52 {
53   GObject parent_instance;
54
55   GtkApplication *application;
56   GtkWidget *widget;
57
58   GtkActionHelperGroup *group;
59
60   GActionMuxer *action_context;
61   gchar *action_name;
62
63   GVariant *target;
64
65   GtkActionHelperRole role;
66   gboolean can_activate;
67   gboolean enabled;
68   gboolean active;
69
70   gint reporting;
71 };
72
73 enum
74 {
75   PROP_0,
76   PROP_ENABLED,
77   PROP_ACTIVE,
78   PROP_ROLE,
79   N_PROPS
80 };
81
82 static GParamSpec *gtk_action_helper_pspecs[N_PROPS];
83
84 static void gtk_action_helper_observer_iface_init (GActionObserverInterface *iface);
85
86 G_DEFINE_TYPE_WITH_CODE (GtkActionHelper, gtk_action_helper, G_TYPE_OBJECT,
87   G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_OBSERVER, gtk_action_helper_observer_iface_init))
88
89 static void
90 gtk_action_helper_report_change (GtkActionHelper *helper,
91                                  guint            prop_id)
92 {
93   helper->reporting++;
94
95   if (!helper->application)
96     {
97       switch (prop_id)
98         {
99         case PROP_ENABLED:
100           gtk_widget_set_sensitive (GTK_WIDGET (helper->widget), helper->enabled);
101           break;
102
103         case PROP_ACTIVE:
104           {
105             GParamSpec *pspec;
106
107             pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (helper->widget), "active");
108
109             if (pspec && G_PARAM_SPEC_VALUE_TYPE (pspec) == G_TYPE_BOOLEAN)
110               g_object_set (G_OBJECT (helper->widget), "active", helper->active, NULL);
111           }
112           break;
113
114         case PROP_ROLE:
115           {
116             GParamSpec *pspec;
117
118             pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (helper->widget), "action-role");
119
120             if (pspec && G_PARAM_SPEC_VALUE_TYPE (pspec) == G_TYPE_UINT)
121               g_object_set (G_OBJECT (helper->widget), "action-role", helper->role, NULL);
122           }
123           break;
124
125         default:
126           g_assert_not_reached ();
127         }
128     }
129
130   g_object_notify_by_pspec (G_OBJECT (helper), gtk_action_helper_pspecs[prop_id]);
131   helper->reporting--;
132 }
133
134 static void
135 gtk_action_helper_action_added (GtkActionHelper    *helper,
136                                 gboolean            enabled,
137                                 const GVariantType *parameter_type,
138                                 GVariant           *state,
139                                 gboolean            should_emit_signals)
140 {
141   /* we can only activate if we have the correct type of parameter */
142   helper->can_activate = (helper->target == NULL && parameter_type == NULL) ||
143                           (helper->target != NULL && parameter_type != NULL &&
144                           g_variant_is_of_type (helper->target, parameter_type));
145
146   if (!helper->can_activate)
147     return;
148
149   helper->enabled = enabled;
150
151   if (helper->target != NULL && state != NULL)
152     {
153       helper->active = g_variant_equal (state, helper->target);
154       helper->role = GTK_ACTION_HELPER_ROLE_RADIO;
155     }
156
157   else if (state != NULL && g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN))
158     {
159       helper->active = g_variant_get_boolean (state);
160       helper->role = GTK_ACTION_HELPER_ROLE_TOGGLE;
161     }
162
163   if (should_emit_signals)
164     {
165       if (helper->enabled)
166         gtk_action_helper_report_change (helper, PROP_ENABLED);
167
168       if (helper->active)
169         gtk_action_helper_report_change (helper, PROP_ACTIVE);
170
171       if (helper->role)
172         gtk_action_helper_report_change (helper, PROP_ROLE);
173     }
174 }
175
176 static void
177 gtk_action_helper_action_removed (GtkActionHelper *helper)
178 {
179   if (!helper->can_activate)
180     return;
181
182   helper->can_activate = FALSE;
183
184   if (helper->enabled)
185     {
186       helper->enabled = FALSE;
187       gtk_action_helper_report_change (helper, PROP_ENABLED);
188     }
189
190   if (helper->active)
191     {
192       helper->active = FALSE;
193       gtk_action_helper_report_change (helper, PROP_ACTIVE);
194     }
195
196   if (helper->role)
197     {
198       helper->role = GTK_ACTION_HELPER_ROLE_NORMAL;
199       gtk_action_helper_report_change (helper, PROP_ROLE);
200     }
201 }
202
203 static void
204 gtk_action_helper_action_enabled_changed (GtkActionHelper *helper,
205                                           gboolean         enabled)
206 {
207   if (!helper->can_activate)
208     return;
209
210   if (helper->enabled == enabled)
211     return;
212
213   helper->enabled = enabled;
214   gtk_action_helper_report_change (helper, PROP_ENABLED);
215 }
216
217 static void
218 gtk_action_helper_action_state_changed (GtkActionHelper *helper,
219                                         GVariant        *new_state)
220 {
221   gboolean was_active;
222
223   if (!helper->can_activate)
224     return;
225
226   was_active = helper->active;
227
228   if (helper->target)
229     helper->active = g_variant_equal (new_state, helper->target);
230
231   else if (g_variant_is_of_type (new_state, G_VARIANT_TYPE_BOOLEAN))
232     helper->active = g_variant_get_boolean (new_state);
233
234   else
235     helper->active = FALSE;
236
237   if (helper->active != was_active)
238     gtk_action_helper_report_change (helper, PROP_ACTIVE);
239 }
240
241 static void
242 gtk_action_helper_get_property (GObject *object, guint prop_id,
243                                 GValue *value, GParamSpec *pspec)
244 {
245   GtkActionHelper *helper = GTK_ACTION_HELPER (object);
246
247   switch (prop_id)
248     {
249     case PROP_ENABLED:
250       g_value_set_boolean (value, helper->enabled);
251       break;
252
253     case PROP_ACTIVE:
254       g_value_set_boolean (value, helper->active);
255       break;
256
257     case PROP_ROLE:
258       g_value_set_uint (value, helper->role);
259       break;
260
261     default:
262       g_assert_not_reached ();
263     }
264 }
265
266 static void
267 gtk_action_helper_finalize (GObject *object)
268 {
269   GtkActionHelper *helper = GTK_ACTION_HELPER (object);
270
271   if (helper->application)
272     {
273       g_signal_handlers_disconnect_by_data (helper->application, helper);
274       g_object_unref (helper->action_context);
275       g_object_unref (helper->application);
276
277       if (helper->widget)
278           g_object_unref (helper->widget);
279     }
280
281   g_free (helper->action_name);
282
283   if (helper->target)
284     g_variant_unref (helper->target);
285
286   G_OBJECT_CLASS (gtk_action_helper_parent_class)
287     ->finalize (object);
288 }
289
290 static void
291 gtk_action_helper_observer_action_added (GActionObserver    *observer,
292                                          GActionObservable  *observable,
293                                          const gchar        *action_name,
294                                          const GVariantType *parameter_type,
295                                          gboolean            enabled,
296                                          GVariant           *state)
297 {
298   gtk_action_helper_action_added (GTK_ACTION_HELPER (observer), enabled, parameter_type, state, TRUE);
299 }
300
301 static void
302 gtk_action_helper_observer_action_enabled_changed (GActionObserver    *observer,
303                                                    GActionObservable  *observable,
304                                                    const gchar        *action_name,
305                                                    gboolean            enabled)
306 {
307   gtk_action_helper_action_enabled_changed (GTK_ACTION_HELPER (observer), enabled);
308 }
309
310 static void
311 gtk_action_helper_observer_action_state_changed (GActionObserver    *observer,
312                                                  GActionObservable  *observable,
313                                                  const gchar        *action_name,
314                                                  GVariant           *state)
315 {
316   gtk_action_helper_action_state_changed (GTK_ACTION_HELPER (observer), state);
317 }
318
319 static void
320 gtk_action_helper_observer_action_removed (GActionObserver    *observer,
321                                            GActionObservable  *observable,
322                                            const gchar        *action_name)
323 {
324   gtk_action_helper_action_removed (GTK_ACTION_HELPER (observer));
325 }
326
327 static void
328 gtk_action_helper_init (GtkActionHelper *helper)
329 {
330 }
331
332 static void
333 gtk_action_helper_class_init (GtkActionHelperClass *class)
334 {
335   class->get_property = gtk_action_helper_get_property;
336   class->finalize = gtk_action_helper_finalize;
337
338   gtk_action_helper_pspecs[PROP_ENABLED] = g_param_spec_boolean ("enabled", "enabled", "enabled", FALSE,
339                                                                  G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
340   gtk_action_helper_pspecs[PROP_ACTIVE] = g_param_spec_boolean ("active", "active", "active", FALSE,
341                                                                 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
342   gtk_action_helper_pspecs[PROP_ROLE] = g_param_spec_uint ("role", "role", "role", 0, 2, 0,
343                                                            G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
344   g_object_class_install_properties (class, N_PROPS, gtk_action_helper_pspecs);
345 }
346
347 static void
348 gtk_action_helper_observer_iface_init (GActionObserverInterface *iface)
349 {
350   iface->action_added = gtk_action_helper_observer_action_added;
351   iface->action_enabled_changed = gtk_action_helper_observer_action_enabled_changed;
352   iface->action_state_changed = gtk_action_helper_observer_action_state_changed;
353   iface->action_removed = gtk_action_helper_observer_action_removed;
354 }
355
356 /*< private >
357  * gtk_action_helper_new:
358  * @widget: a #GtkWidget implementing #GtkActionable
359  *
360  * Creates a helper to track the state of a named action.  This will
361  * usually be used by widgets implementing #GtkActionable.
362  *
363  * This helper class is usually used by @widget itself.  In order to
364  * avoid reference cycles, the helper does not hold a reference on
365  * @widget, but will assume that it continues to exist for the duration
366  * of the life of the helper.  If you are using the helper from outside
367  * of the widget, you should take a ref on @widget for each ref you hold
368  * on the helper.
369  *
370  * Returns: a new #GtkActionHelper
371  */
372 GtkActionHelper *
373 gtk_action_helper_new (GtkActionable *widget)
374 {
375   GtkActionHelper *helper;
376
377   g_return_val_if_fail (GTK_IS_ACTIONABLE (widget), NULL);
378   helper = g_object_new (GTK_TYPE_ACTION_HELPER, NULL);
379
380   helper->widget = GTK_WIDGET (widget);
381
382   if (helper->widget)
383     {
384       GParamSpec *pspec;
385
386       helper->enabled = gtk_widget_get_sensitive (GTK_WIDGET (helper->widget));
387
388       pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (helper->widget), "active");
389       if (pspec && G_PARAM_SPEC_VALUE_TYPE (pspec) == G_TYPE_BOOLEAN)
390         g_object_get (G_OBJECT (helper->widget), "active", &helper->active, NULL);
391     }
392
393   helper->action_context = _gtk_widget_get_action_muxer (GTK_WIDGET (widget));
394
395   return helper;
396 }
397
398 static void
399 gtk_action_helper_active_window_changed (GObject    *object,
400                                          GParamSpec *pspec,
401                                          gpointer    user_data)
402 {
403   GtkActionHelper *helper = user_data;
404   GActionMuxer *parent;
405
406   if (helper->widget)
407     g_object_unref (helper->widget);
408
409   helper->widget = GTK_WIDGET (gtk_application_get_active_window (helper->application));
410
411   if (helper->widget)
412     {
413       parent = g_object_ref (_gtk_widget_get_action_muxer (GTK_WIDGET (helper->widget)));
414       g_object_ref (helper->widget);
415     }
416   else
417     {
418       parent = g_action_muxer_new ();
419       g_action_muxer_insert (parent, "app", G_ACTION_GROUP (helper->application));
420     }
421
422   g_action_muxer_set_parent (helper->action_context, parent);
423   g_object_unref (parent);
424 }
425
426 GtkActionHelper *
427 gtk_action_helper_new_with_application (GtkApplication *application)
428 {
429   GtkActionHelper *helper;
430
431   g_return_val_if_fail (GTK_IS_APPLICATION (application), NULL);
432
433   helper = g_object_new (GTK_TYPE_ACTION_HELPER, NULL);
434   helper->application = g_object_ref (application);
435
436   helper->action_context = g_action_muxer_new ();
437   g_signal_connect (application, "notify::active-window", G_CALLBACK (gtk_action_helper_active_window_changed), helper);
438   gtk_action_helper_active_window_changed (NULL, NULL, helper);
439
440   return helper;
441 }
442
443 void
444 gtk_action_helper_set_action_name (GtkActionHelper *helper,
445                                    const gchar     *action_name)
446 {
447   gboolean was_enabled, was_active;
448   GtkActionHelperRole old_role;
449   const GVariantType *parameter_type;
450   gboolean enabled;
451   GVariant *state;
452
453   if (g_strcmp0 (action_name, helper->action_name) == 0)
454     return;
455
456   if (helper->action_name)
457     {
458       g_action_observable_unregister_observer (G_ACTION_OBSERVABLE (helper->action_context),
459                                                helper->action_name,
460                                                G_ACTION_OBSERVER (helper));
461       g_free (helper->action_name);
462     }
463
464   helper->action_name = g_strdup (action_name);
465
466   g_action_observable_register_observer (G_ACTION_OBSERVABLE (helper->action_context),
467                                          helper->action_name,
468                                          G_ACTION_OBSERVER (helper));
469
470   /* Start by recording the current state of our properties so we know
471    * what notify signals we will need to send.
472    */
473   was_enabled = helper->enabled;
474   was_active = helper->active;
475   old_role = helper->role;
476
477   if (g_action_group_query_action (G_ACTION_GROUP (helper->action_context), helper->action_name,
478                                    &enabled, &parameter_type, NULL, NULL, &state))
479     {
480       gtk_action_helper_action_added (helper, enabled, parameter_type, state, FALSE);
481
482       if (state)
483         g_variant_unref (state);
484     }
485   else
486     {
487       helper->enabled = FALSE;
488     }
489
490   /* Send the notifies for the properties that changed.
491    *
492    * When called during construction, widget is NULL.  We don't need to
493    * report in that case.
494    */
495   if (helper->enabled != was_enabled)
496     gtk_action_helper_report_change (helper, PROP_ENABLED);
497
498   if (helper->active != was_active)
499     gtk_action_helper_report_change (helper, PROP_ACTIVE);
500
501   if (helper->role != old_role)
502     gtk_action_helper_report_change (helper, PROP_ROLE);
503
504   if (!helper->application)
505     g_object_notify (G_OBJECT (helper->widget), "action-name");
506 }
507
508 /*< private >
509  * gtk_action_helper_set_action_target_value:
510  * @helper: a #GtkActionHelper
511  * @target_value: an action target, as per #GtkActionable
512  *
513  * This function consumes @action_target if it is floating.
514  */
515 void
516 gtk_action_helper_set_action_target_value (GtkActionHelper *helper,
517                                            GVariant        *target_value)
518 {
519   gboolean was_enabled;
520   gboolean was_active;
521
522   if (target_value == helper->target)
523     return;
524
525   if (target_value && helper->target && g_variant_equal (target_value, helper->target))
526     {
527       g_variant_unref (g_variant_ref_sink (target_value));
528       return;
529     }
530
531   if (helper->target)
532     {
533       g_variant_unref (helper->target);
534       helper->target = NULL;
535     }
536
537   if (target_value)
538     helper->target = g_variant_ref_sink (target_value);
539
540   /* The action_name has not yet been set.  Don't do anything yet. */
541   if (helper->action_name == NULL)
542     return;
543
544   was_enabled = helper->enabled;
545   was_active = helper->active;
546
547   /* If we are attached to an action group then it is possible that this
548    * change of the target value could impact our properties (including
549    * changes to 'can_activate' and therefore 'enabled', due to resolving
550    * a parameter type mismatch).
551    *
552    * Start over again by pretending the action gets re-added.
553    */
554   helper->can_activate = FALSE;
555   helper->enabled = FALSE;
556   helper->active = FALSE;
557
558   if (helper->action_context)
559     {
560       const GVariantType *parameter_type;
561       gboolean enabled;
562       GVariant *state;
563
564       if (g_action_group_query_action (G_ACTION_GROUP (helper->action_context),
565                                        helper->action_name, &enabled, &parameter_type,
566                                        NULL, NULL, &state))
567         {
568           gtk_action_helper_action_added (helper, enabled, parameter_type, state, FALSE);
569
570           if (state)
571             g_variant_unref (state);
572         }
573     }
574
575   if (helper->enabled != was_enabled)
576     gtk_action_helper_report_change (helper, PROP_ENABLED);
577
578   if (helper->active != was_active)
579     gtk_action_helper_report_change (helper, PROP_ACTIVE);
580
581   if (!helper->application)
582     g_object_notify (G_OBJECT (helper->widget), "action-target");
583 }
584
585 const gchar *
586 gtk_action_helper_get_action_name (GtkActionHelper *helper)
587 {
588   if (helper == NULL)
589     return NULL;
590
591   return helper->action_name;
592 }
593
594 GVariant *
595 gtk_action_helper_get_action_target_value (GtkActionHelper *helper)
596 {
597   if (helper == NULL)
598     return NULL;
599
600   return helper->target;
601 }
602
603 GtkActionHelperRole
604 gtk_action_helper_get_role (GtkActionHelper *helper)
605 {
606   if (helper == NULL)
607     return GTK_ACTION_HELPER_ROLE_NORMAL;
608
609   return helper->role;
610 }
611
612 gboolean
613 gtk_action_helper_get_enabled (GtkActionHelper *helper)
614 {
615   g_return_val_if_fail (GTK_IS_ACTION_HELPER (helper), FALSE);
616
617   return helper->enabled;
618 }
619
620 gboolean
621 gtk_action_helper_get_active (GtkActionHelper *helper)
622 {
623   g_return_val_if_fail (GTK_IS_ACTION_HELPER (helper), FALSE);
624
625   return helper->active;
626 }
627
628 void
629 gtk_action_helper_activate (GtkActionHelper *helper)
630 {
631   g_return_if_fail (GTK_IS_ACTION_HELPER (helper));
632
633   if (!helper->can_activate || helper->reporting)
634     return;
635
636   g_action_group_activate_action (G_ACTION_GROUP (helper->action_context),
637                                   helper->action_name, helper->target);
638 }