gkdbus: Fix underflow and unreachable code bug
[platform/upstream/glib.git] / gio / gpropertyaction.c
1 /*
2  * Copyright © 2013 Canonical Limited
3  *
4  * SPDX-License-Identifier: LGPL-2.1-or-later
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General
17  * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
18  *
19  * Authors: Ryan Lortie <desrt@desrt.ca>
20  */
21
22 #include "config.h"
23
24 #include "gpropertyaction.h"
25
26 #include "gsettings-mapping.h"
27 #include "gaction.h"
28 #include "glibintl.h"
29
30 /**
31  * SECTION:gpropertyaction
32  * @title: GPropertyAction
33  * @short_description: A GAction reflecting a GObject property
34  * @include: gio/gio.h
35  *
36  * A #GPropertyAction is a way to get a #GAction with a state value
37  * reflecting and controlling the value of a #GObject property.
38  *
39  * The state of the action will correspond to the value of the property.
40  * Changing it will change the property (assuming the requested value
41  * matches the requirements as specified in the #GParamSpec).
42  *
43  * Only the most common types are presently supported.  Booleans are
44  * mapped to booleans, strings to strings, signed/unsigned integers to
45  * int32/uint32 and floats and doubles to doubles.
46  *
47  * If the property is an enum then the state will be string-typed and
48  * conversion will automatically be performed between the enum value and
49  * "nick" string as per the #GEnumValue table.
50  *
51  * Flags types are not currently supported.
52  *
53  * Properties of object types, boxed types and pointer types are not
54  * supported and probably never will be.
55  *
56  * Properties of #GVariant types are not currently supported.
57  *
58  * If the property is boolean-valued then the action will have a NULL
59  * parameter type, and activating the action (with no parameter) will
60  * toggle the value of the property.
61  *
62  * In all other cases, the parameter type will correspond to the type of
63  * the property.
64  *
65  * The general idea here is to reduce the number of locations where a
66  * particular piece of state is kept (and therefore has to be synchronised
67  * between). #GPropertyAction does not have a separate state that is kept
68  * in sync with the property value -- its state is the property value.
69  *
70  * For example, it might be useful to create a #GAction corresponding to
71  * the "visible-child-name" property of a #GtkStack so that the current
72  * page can be switched from a menu.  The active radio indication in the
73  * menu is then directly determined from the active page of the
74  * #GtkStack.
75  *
76  * An anti-example would be binding the "active-id" property on a
77  * #GtkComboBox.  This is because the state of the combobox itself is
78  * probably uninteresting and is actually being used to control
79  * something else.
80  *
81  * Another anti-example would be to bind to the "visible-child-name"
82  * property of a #GtkStack if this value is actually stored in
83  * #GSettings.  In that case, the real source of the value is
84  * #GSettings.  If you want a #GAction to control a setting stored in
85  * #GSettings, see g_settings_create_action() instead, and possibly
86  * combine its use with g_settings_bind().
87  *
88  * Since: 2.38
89  **/
90 struct _GPropertyAction
91 {
92   GObject     parent_instance;
93
94   gchar              *name;
95   gpointer            object;
96   GParamSpec         *pspec;
97   const GVariantType *state_type;
98   gboolean            invert_boolean;
99 };
100
101 /**
102  * GPropertyAction:
103  *
104  * This type is opaque.
105  *
106  * Since: 2.38
107  **/
108
109 typedef GObjectClass GPropertyActionClass;
110
111 static void g_property_action_iface_init (GActionInterface *iface);
112 G_DEFINE_TYPE_WITH_CODE (GPropertyAction, g_property_action, G_TYPE_OBJECT,
113   G_IMPLEMENT_INTERFACE (G_TYPE_ACTION, g_property_action_iface_init))
114
115 enum
116 {
117   PROP_NONE,
118   PROP_NAME,
119   PROP_PARAMETER_TYPE,
120   PROP_ENABLED,
121   PROP_STATE_TYPE,
122   PROP_STATE,
123   PROP_OBJECT,
124   PROP_PROPERTY_NAME,
125   PROP_INVERT_BOOLEAN
126 };
127
128 static gboolean
129 g_property_action_get_invert_boolean (GAction *action)
130 {
131   GPropertyAction *paction = G_PROPERTY_ACTION (action);
132
133   return paction->invert_boolean;
134 }
135
136 static const gchar *
137 g_property_action_get_name (GAction *action)
138 {
139   GPropertyAction *paction = G_PROPERTY_ACTION (action);
140
141   return paction->name;
142 }
143
144 static const GVariantType *
145 g_property_action_get_parameter_type (GAction *action)
146 {
147   GPropertyAction *paction = G_PROPERTY_ACTION (action);
148
149   return paction->pspec->value_type == G_TYPE_BOOLEAN ? NULL : paction->state_type;
150 }
151
152 static const GVariantType *
153 g_property_action_get_state_type (GAction *action)
154 {
155   GPropertyAction *paction = G_PROPERTY_ACTION (action);
156
157   return paction->state_type;
158 }
159
160 static GVariant *
161 g_property_action_get_state_hint (GAction *action)
162 {
163   GPropertyAction *paction = G_PROPERTY_ACTION (action);
164
165   if (paction->pspec->value_type == G_TYPE_INT)
166     {
167       GParamSpecInt *pspec = (GParamSpecInt *)paction->pspec;
168       return g_variant_new ("(ii)", pspec->minimum, pspec->maximum);
169     }
170   else if (paction->pspec->value_type == G_TYPE_UINT)
171     {
172       GParamSpecUInt *pspec = (GParamSpecUInt *)paction->pspec;
173       return g_variant_new ("(uu)", pspec->minimum, pspec->maximum);
174     }
175   else if (paction->pspec->value_type == G_TYPE_FLOAT)
176     {
177       GParamSpecFloat *pspec = (GParamSpecFloat *)paction->pspec;
178       return g_variant_new ("(dd)", (double)pspec->minimum, (double)pspec->maximum);
179     }
180   else if (paction->pspec->value_type == G_TYPE_DOUBLE)
181     {
182       GParamSpecDouble *pspec = (GParamSpecDouble *)paction->pspec;
183       return g_variant_new ("(dd)", pspec->minimum, pspec->maximum);
184     }
185
186   return NULL;
187 }
188
189 static gboolean
190 g_property_action_get_enabled (GAction *action)
191 {
192   return TRUE;
193 }
194
195 static void
196 g_property_action_set_state (GPropertyAction *paction,
197                              GVariant        *variant)
198 {
199   GValue value = G_VALUE_INIT;
200
201   g_value_init (&value, paction->pspec->value_type);
202   g_settings_get_mapping (&value, variant, NULL);
203
204   if (paction->pspec->value_type == G_TYPE_BOOLEAN && paction->invert_boolean)
205     g_value_set_boolean (&value, !g_value_get_boolean (&value));
206
207   g_object_set_property (paction->object, paction->pspec->name, &value);
208   g_value_unset (&value);
209 }
210
211 static void
212 g_property_action_change_state (GAction  *action,
213                                 GVariant *value)
214 {
215   GPropertyAction *paction = G_PROPERTY_ACTION (action);
216
217   g_return_if_fail (g_variant_is_of_type (value, paction->state_type));
218
219   g_property_action_set_state (paction, value);
220 }
221
222 static GVariant *
223 g_property_action_get_state (GAction *action)
224 {
225   GPropertyAction *paction = G_PROPERTY_ACTION (action);
226   GValue value = G_VALUE_INIT;
227   GVariant *result;
228
229   g_value_init (&value, paction->pspec->value_type);
230   g_object_get_property (paction->object, paction->pspec->name, &value);
231
232   if (paction->pspec->value_type == G_TYPE_BOOLEAN && paction->invert_boolean)
233     g_value_set_boolean (&value, !g_value_get_boolean (&value));
234
235   result = g_settings_set_mapping (&value, paction->state_type, NULL);
236   g_value_unset (&value);
237
238   return g_variant_ref_sink (result);
239 }
240
241 static void
242 g_property_action_activate (GAction  *action,
243                             GVariant *parameter)
244 {
245   GPropertyAction *paction = G_PROPERTY_ACTION (action);
246
247   if (paction->pspec->value_type == G_TYPE_BOOLEAN)
248     {
249       gboolean value;
250
251       g_return_if_fail (paction->pspec->value_type == G_TYPE_BOOLEAN && parameter == NULL);
252
253       g_object_get (paction->object, paction->pspec->name, &value, NULL);
254       value = !value;
255       g_object_set (paction->object, paction->pspec->name, value, NULL);
256     }
257   else
258     {
259       g_return_if_fail (parameter != NULL && g_variant_is_of_type (parameter, paction->state_type));
260
261       g_property_action_set_state (paction, parameter);
262     }
263 }
264
265 static const GVariantType *
266 g_property_action_determine_type (GParamSpec *pspec)
267 {
268   if (G_TYPE_IS_ENUM (pspec->value_type))
269     return G_VARIANT_TYPE_STRING;
270
271   switch (pspec->value_type)
272     {
273     case G_TYPE_BOOLEAN:
274       return G_VARIANT_TYPE_BOOLEAN;
275
276     case G_TYPE_INT:
277       return G_VARIANT_TYPE_INT32;
278
279     case G_TYPE_UINT:
280       return G_VARIANT_TYPE_UINT32;
281
282     case G_TYPE_DOUBLE:
283     case G_TYPE_FLOAT:
284       return G_VARIANT_TYPE_DOUBLE;
285
286     case G_TYPE_STRING:
287       return G_VARIANT_TYPE_STRING;
288
289     default:
290       g_critical ("Unable to use GPropertyAction with property '%s::%s' of type '%s'",
291                   g_type_name (pspec->owner_type), pspec->name, g_type_name (pspec->value_type));
292       return NULL;
293     }
294 }
295
296 static void
297 g_property_action_notify (GObject    *object,
298                           GParamSpec *pspec,
299                           gpointer    user_data)
300 {
301   GPropertyAction *paction = user_data;
302
303   g_assert (object == paction->object);
304   g_assert (pspec == paction->pspec);
305
306   g_object_notify (G_OBJECT (paction), "state");
307 }
308
309 static void
310 g_property_action_set_property_name (GPropertyAction *paction,
311                                      const gchar     *property_name)
312 {
313   GParamSpec *pspec;
314   gchar *detailed;
315
316   /* In case somebody is constructing GPropertyAction without passing
317    * a property name
318    */
319   if (G_UNLIKELY (property_name == NULL || property_name[0] == '\0'))
320     {
321       g_critical ("Attempted to use an empty property name for GPropertyAction");
322       return;
323     }
324
325   pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (paction->object), property_name);
326
327   if (pspec == NULL)
328     {
329       g_critical ("Attempted to use non-existent property '%s::%s' for GPropertyAction",
330                   G_OBJECT_TYPE_NAME (paction->object), property_name);
331       return;
332     }
333
334   if (~pspec->flags & G_PARAM_READABLE || ~pspec->flags & G_PARAM_WRITABLE || pspec->flags & G_PARAM_CONSTRUCT_ONLY)
335     {
336       g_critical ("Property '%s::%s' used with GPropertyAction must be readable, writable, and not construct-only",
337                   G_OBJECT_TYPE_NAME (paction->object), property_name);
338       return;
339     }
340
341   paction->pspec = pspec;
342
343   detailed = g_strconcat ("notify::", paction->pspec->name, NULL);
344   paction->state_type = g_property_action_determine_type (paction->pspec);
345   g_signal_connect (paction->object, detailed, G_CALLBACK (g_property_action_notify), paction);
346   g_free (detailed);
347 }
348
349 static void
350 g_property_action_set_property (GObject      *object,
351                                 guint         prop_id,
352                                 const GValue *value,
353                                 GParamSpec   *pspec)
354 {
355   GPropertyAction *paction = G_PROPERTY_ACTION (object);
356
357   switch (prop_id)
358     {
359     case PROP_NAME:
360       paction->name = g_value_dup_string (value);
361       break;
362
363     case PROP_OBJECT:
364       paction->object = g_value_dup_object (value);
365       break;
366
367     case PROP_PROPERTY_NAME:
368       g_property_action_set_property_name (paction, g_value_get_string (value));
369       break;
370
371     case PROP_INVERT_BOOLEAN:
372       paction->invert_boolean = g_value_get_boolean (value);
373       break;
374
375     default:
376       g_assert_not_reached ();
377     }
378 }
379
380 static void
381 g_property_action_get_property (GObject    *object,
382                                 guint       prop_id,
383                                 GValue     *value,
384                                 GParamSpec *pspec)
385 {
386   GAction *action = G_ACTION (object);
387
388   switch (prop_id)
389     {
390     case PROP_NAME:
391       g_value_set_string (value, g_property_action_get_name (action));
392       break;
393
394     case PROP_PARAMETER_TYPE:
395       g_value_set_boxed (value, g_property_action_get_parameter_type (action));
396       break;
397
398     case PROP_ENABLED:
399       g_value_set_boolean (value, g_property_action_get_enabled (action));
400       break;
401
402     case PROP_STATE_TYPE:
403       g_value_set_boxed (value, g_property_action_get_state_type (action));
404       break;
405
406     case PROP_STATE:
407       g_value_take_variant (value, g_property_action_get_state (action));
408       break;
409
410     case PROP_INVERT_BOOLEAN:
411       g_value_set_boolean (value, g_property_action_get_invert_boolean (action));
412       break;
413
414     default:
415       g_assert_not_reached ();
416     }
417 }
418
419 static void
420 g_property_action_dispose (GObject *object)
421 {
422   GPropertyAction *paction = G_PROPERTY_ACTION (object);
423
424   if (paction->object != NULL)
425     {
426       g_signal_handlers_disconnect_by_func (paction->object, g_property_action_notify, paction);
427       g_clear_object (&paction->object);
428     }
429
430   G_OBJECT_CLASS (g_property_action_parent_class)->dispose (object);
431 }
432
433 static void
434 g_property_action_finalize (GObject *object)
435 {
436   GPropertyAction *paction = G_PROPERTY_ACTION (object);
437
438   g_free (paction->name);
439
440   G_OBJECT_CLASS (g_property_action_parent_class)
441     ->finalize (object);
442 }
443
444 void
445 g_property_action_init (GPropertyAction *property)
446 {
447 }
448
449 void
450 g_property_action_iface_init (GActionInterface *iface)
451 {
452   iface->get_name = g_property_action_get_name;
453   iface->get_parameter_type = g_property_action_get_parameter_type;
454   iface->get_state_type = g_property_action_get_state_type;
455   iface->get_state_hint = g_property_action_get_state_hint;
456   iface->get_enabled = g_property_action_get_enabled;
457   iface->get_state = g_property_action_get_state;
458   iface->change_state = g_property_action_change_state;
459   iface->activate = g_property_action_activate;
460 }
461
462 void
463 g_property_action_class_init (GPropertyActionClass *class)
464 {
465   GObjectClass *object_class = G_OBJECT_CLASS (class);
466
467   object_class->set_property = g_property_action_set_property;
468   object_class->get_property = g_property_action_get_property;
469   object_class->dispose = g_property_action_dispose;
470   object_class->finalize = g_property_action_finalize;
471
472   /**
473    * GPropertyAction:name:
474    *
475    * The name of the action.  This is mostly meaningful for identifying
476    * the action once it has been added to a #GActionMap.
477    *
478    * Since: 2.38
479    **/
480   g_object_class_install_property (object_class, PROP_NAME,
481                                    g_param_spec_string ("name",
482                                                         P_("Action Name"),
483                                                         P_("The name used to invoke the action"),
484                                                         NULL,
485                                                         G_PARAM_READWRITE |
486                                                         G_PARAM_CONSTRUCT_ONLY |
487                                                         G_PARAM_STATIC_STRINGS));
488
489   /**
490    * GPropertyAction:parameter-type:
491    *
492    * The type of the parameter that must be given when activating the
493    * action.
494    *
495    * Since: 2.38
496    **/
497   g_object_class_install_property (object_class, PROP_PARAMETER_TYPE,
498                                    g_param_spec_boxed ("parameter-type",
499                                                        P_("Parameter Type"),
500                                                        P_("The type of GVariant passed to activate()"),
501                                                        G_TYPE_VARIANT_TYPE,
502                                                        G_PARAM_READABLE |
503                                                        G_PARAM_STATIC_STRINGS));
504
505   /**
506    * GPropertyAction:enabled:
507    *
508    * If @action is currently enabled.
509    *
510    * If the action is disabled then calls to g_action_activate() and
511    * g_action_change_state() have no effect.
512    *
513    * Since: 2.38
514    **/
515   g_object_class_install_property (object_class, PROP_ENABLED,
516                                    g_param_spec_boolean ("enabled",
517                                                          P_("Enabled"),
518                                                          P_("If the action can be activated"),
519                                                          TRUE,
520                                                          G_PARAM_READABLE |
521                                                          G_PARAM_STATIC_STRINGS));
522
523   /**
524    * GPropertyAction:state-type:
525    *
526    * The #GVariantType of the state that the action has, or %NULL if the
527    * action is stateless.
528    *
529    * Since: 2.38
530    **/
531   g_object_class_install_property (object_class, PROP_STATE_TYPE,
532                                    g_param_spec_boxed ("state-type",
533                                                        P_("State Type"),
534                                                        P_("The type of the state kept by the action"),
535                                                        G_TYPE_VARIANT_TYPE,
536                                                        G_PARAM_READABLE |
537                                                        G_PARAM_STATIC_STRINGS));
538
539   /**
540    * GPropertyAction:state:
541    *
542    * The state of the action, or %NULL if the action is stateless.
543    *
544    * Since: 2.38
545    **/
546   g_object_class_install_property (object_class, PROP_STATE,
547                                    g_param_spec_variant ("state",
548                                                          P_("State"),
549                                                          P_("The state the action is in"),
550                                                          G_VARIANT_TYPE_ANY,
551                                                          NULL,
552                                                          G_PARAM_READABLE |
553                                                          G_PARAM_STATIC_STRINGS));
554
555   /**
556    * GPropertyAction:object:
557    *
558    * The object to wrap a property on.
559    *
560    * The object must be a non-%NULL #GObject with properties.
561    *
562    * Since: 2.38
563    **/
564   g_object_class_install_property (object_class, PROP_OBJECT,
565                                    g_param_spec_object ("object",
566                                                         P_("Object"),
567                                                         P_("The object with the property to wrap"),
568                                                         G_TYPE_OBJECT,
569                                                         G_PARAM_WRITABLE |
570                                                         G_PARAM_CONSTRUCT_ONLY |
571                                                         G_PARAM_STATIC_STRINGS));
572
573   /**
574    * GPropertyAction:property-name:
575    *
576    * The name of the property to wrap on the object.
577    *
578    * The property must exist on the passed-in object and it must be
579    * readable and writable (and not construct-only).
580    *
581    * Since: 2.38
582    **/
583   g_object_class_install_property (object_class, PROP_PROPERTY_NAME,
584                                    g_param_spec_string ("property-name",
585                                                         P_("Property name"),
586                                                         P_("The name of the property to wrap"),
587                                                         NULL,
588                                                         G_PARAM_WRITABLE |
589                                                         G_PARAM_CONSTRUCT_ONLY |
590                                                         G_PARAM_STATIC_STRINGS));
591
592   /**
593    * GPropertyAction:invert-boolean:
594    *
595    * If %TRUE, the state of the action will be the negation of the
596    * property value, provided the property is boolean.
597    *
598    * Since: 2.46
599    */
600   g_object_class_install_property (object_class, PROP_INVERT_BOOLEAN,
601                                    g_param_spec_boolean ("invert-boolean",
602                                                          P_("Invert boolean"),
603                                                          P_("Whether to invert the value of a boolean property"),
604                                                          FALSE,
605                                                          G_PARAM_READWRITE |
606                                                          G_PARAM_CONSTRUCT_ONLY |
607                                                          G_PARAM_STATIC_STRINGS));
608 }
609
610 /**
611  * g_property_action_new:
612  * @name: the name of the action to create
613  * @object: (type GObject.Object): the object that has the property
614  *   to wrap
615  * @property_name: the name of the property
616  *
617  * Creates a #GAction corresponding to the value of property
618  * @property_name on @object.
619  *
620  * The property must be existent and readable and writable (and not
621  * construct-only).
622  *
623  * This function takes a reference on @object and doesn't release it
624  * until the action is destroyed.
625  *
626  * Returns: a new #GPropertyAction
627  *
628  * Since: 2.38
629  **/
630 GPropertyAction *
631 g_property_action_new (const gchar *name,
632                        gpointer     object,
633                        const gchar *property_name)
634 {
635   g_return_val_if_fail (name != NULL, NULL);
636   g_return_val_if_fail (G_IS_OBJECT (object), NULL);
637   g_return_val_if_fail (property_name != NULL, NULL);
638
639   return g_object_new (G_TYPE_PROPERTY_ACTION,
640                        "name", name,
641                        "object", object,
642                        "property-name", property_name,
643                        NULL);
644 }