1/*
2 * Copyright (c) 2014 Red Hat, Inc.
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 License, 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
18#include "config.h"
19#include <glib/gi18n-lib.h>
20
21#include "prop-editor.h"
22
23#include "strv-editor.h"
24#include "prop-list.h"
25
26#include "gtkactionable.h"
27#include "gtkadjustment.h"
28#include "gtkapplicationwindow.h"
29#include "gtkcelllayout.h"
30#include "gtkcellrenderertext.h"
31#include "gtkcolorbutton.h"
32#include "gtkcolorchooser.h"
33#include "gtkcombobox.h"
34#include "gtkfontbutton.h"
35#include "gtkfontchooser.h"
36#include "gtkiconview.h"
37#include "gtklabel.h"
38#include "gtkpopover.h"
39#include "gtkscrolledwindow.h"
40#include "gtkspinbutton.h"
41#include "gtksettingsprivate.h"
42#include "gtktogglebutton.h"
43#include "gtkviewport.h"
44#include "gtkwidgetprivate.h"
45#include "gtkcssnodeprivate.h"
46#include "gtklistbox.h"
47#include "gtkmenubutton.h"
48
49struct _GtkInspectorPropEditor
50{
51 GtkBox parent_instance;
52
53 GObject *object;
54 char *name;
55 GtkWidget *self;
56 GtkSizeGroup *size_group;
57};
58
59enum
60{
61 PROP_0,
62 PROP_OBJECT,
63 PROP_NAME,
64 PROP_SIZE_GROUP
65};
66
67enum
68{
69 SHOW_OBJECT,
70 N_SIGNALS
71};
72
73static guint signals[N_SIGNALS] = { 0 };
74
75G_DEFINE_TYPE (GtkInspectorPropEditor, gtk_inspector_prop_editor, GTK_TYPE_BOX);
76
77static GParamSpec *
78find_property (GtkInspectorPropEditor *self)
79{
80 return g_object_class_find_property (G_OBJECT_GET_CLASS (self->object), property_name: self->name);
81}
82
83typedef struct
84{
85 gpointer instance;
86 GObject *alive_object;
87 gulong id;
88} DisconnectData;
89
90static void
91disconnect_func (gpointer data)
92{
93 DisconnectData *dd = data;
94
95 g_signal_handler_disconnect (instance: dd->instance, handler_id: dd->id);
96}
97
98static void
99signal_removed (gpointer data,
100 GClosure *closure)
101{
102 DisconnectData *dd = data;
103
104 g_object_steal_data (object: dd->alive_object, key: "alive-object-data");
105 g_free (mem: dd);
106}
107
108static void
109g_object_connect_property (GObject *object,
110 GParamSpec *spec,
111 GCallback func,
112 gpointer data,
113 GObject *alive_object)
114{
115 GClosure *closure;
116 char *with_detail;
117 DisconnectData *dd;
118
119 with_detail = g_strconcat (string1: "notify::", spec->name, NULL);
120
121 dd = g_new (DisconnectData, 1);
122
123 closure = g_cclosure_new (callback_func: func, user_data: data, NULL);
124 g_closure_add_invalidate_notifier (closure, notify_data: dd, notify_func: signal_removed);
125 dd->id = g_signal_connect_closure (instance: object, detailed_signal: with_detail, closure, FALSE);
126 dd->instance = object;
127 dd->alive_object = alive_object;
128
129 g_object_set_data_full (G_OBJECT (alive_object), key: "alive-object-data",
130 data: dd, destroy: disconnect_func);
131
132 g_free (mem: with_detail);
133}
134
135static void
136block_notify (GObject *self)
137{
138 DisconnectData *dd = (DisconnectData *)g_object_get_data (object: self, key: "alive-object-data");
139
140 if (dd)
141 g_signal_handler_block (instance: dd->instance, handler_id: dd->id);
142}
143
144static void
145unblock_notify (GObject *self)
146{
147 DisconnectData *dd = (DisconnectData *)g_object_get_data (object: self, key: "alive-object-data");
148
149 if (dd)
150 g_signal_handler_unblock (instance: dd->instance, handler_id: dd->id);
151}
152
153typedef struct
154{
155 GObject *obj;
156 GParamSpec *spec;
157 gulong modified_id;
158} ObjectProperty;
159
160static void
161free_object_property (ObjectProperty *p)
162{
163 g_free (mem: p);
164}
165
166static void
167connect_controller (GObject *controller,
168 const char *signal,
169 GObject *model,
170 GParamSpec *spec,
171 GCallback func)
172{
173 ObjectProperty *p;
174
175 p = g_new (ObjectProperty, 1);
176 p->obj = model;
177 p->spec = spec;
178
179 p->modified_id = g_signal_connect_data (instance: controller, detailed_signal: signal, c_handler: func, data: p,
180 destroy_data: (GClosureNotify)free_object_property, connect_flags: 0);
181 g_object_set_data (object: controller, key: "object-property", data: p);
182}
183
184static void
185block_controller (GObject *controller)
186{
187 ObjectProperty *p = g_object_get_data (object: controller, key: "object-property");
188
189 if (p)
190 g_signal_handler_block (instance: controller, handler_id: p->modified_id);
191}
192
193static void
194unblock_controller (GObject *controller)
195{
196 ObjectProperty *p = g_object_get_data (object: controller, key: "object-property");
197
198 if (p)
199 g_signal_handler_unblock (instance: controller, handler_id: p->modified_id);
200}
201
202static void
203get_property_value (GObject *object, GParamSpec *pspec, GValue *value)
204{
205 g_object_get_property (object, property_name: pspec->name, value);
206}
207
208static void
209set_property_value (GObject *object, GParamSpec *pspec, GValue *value)
210{
211 g_object_set_property (object, property_name: pspec->name, value);
212}
213
214static void
215notify_property (GObject *object, GParamSpec *pspec)
216{
217 g_object_notify (object, property_name: pspec->name);
218}
219
220static void
221int_modified (GtkAdjustment *adj, ObjectProperty *p)
222{
223 GValue val = G_VALUE_INIT;
224
225 g_value_init (value: &val, G_TYPE_INT);
226 g_value_set_int (value: &val, v_int: (int) gtk_adjustment_get_value (adjustment: adj));
227 set_property_value (object: p->obj, pspec: p->spec, value: &val);
228 g_value_unset (value: &val);
229}
230
231static void
232int_changed (GObject *object, GParamSpec *pspec, gpointer data)
233{
234 GtkAdjustment *adj = GTK_ADJUSTMENT (data);
235 GValue val = G_VALUE_INIT;
236
237 g_value_init (value: &val, G_TYPE_INT);
238 get_property_value (object, pspec, value: &val);
239
240 if (g_value_get_int (value: &val) != (int)gtk_adjustment_get_value (adjustment: adj))
241 {
242 block_controller (G_OBJECT (adj));
243 gtk_adjustment_set_value (adjustment: adj, value: g_value_get_int (value: &val));
244 unblock_controller (G_OBJECT (adj));
245 }
246
247 g_value_unset (value: &val);
248}
249static void
250uint_modified (GtkAdjustment *adj, ObjectProperty *p)
251{
252 GValue val = G_VALUE_INIT;
253
254 g_value_init (value: &val, G_TYPE_UINT);
255 g_value_set_uint (value: &val, v_uint: (guint) gtk_adjustment_get_value (adjustment: adj));
256 set_property_value (object: p->obj, pspec: p->spec, value: &val);
257 g_value_unset (value: &val);
258}
259
260static void
261uint_changed (GObject *object, GParamSpec *pspec, gpointer data)
262{
263 GtkAdjustment *adj = GTK_ADJUSTMENT (data);
264 GValue val = G_VALUE_INIT;
265
266 g_value_init (value: &val, G_TYPE_UINT);
267 get_property_value (object, pspec, value: &val);
268
269 if (g_value_get_uint (value: &val) != (guint)gtk_adjustment_get_value (adjustment: adj))
270 {
271 block_controller (G_OBJECT (adj));
272 gtk_adjustment_set_value (adjustment: adj, value: g_value_get_uint (value: &val));
273 unblock_controller (G_OBJECT (adj));
274 }
275
276 g_value_unset (value: &val);
277}
278
279static void
280float_modified (GtkAdjustment *adj, ObjectProperty *p)
281{
282 GValue val = G_VALUE_INIT;
283
284 g_value_init (value: &val, G_TYPE_FLOAT);
285 g_value_set_float (value: &val, v_float: (float) gtk_adjustment_get_value (adjustment: adj));
286 set_property_value (object: p->obj, pspec: p->spec, value: &val);
287 g_value_unset (value: &val);
288}
289
290static void
291float_changed (GObject *object, GParamSpec *pspec, gpointer data)
292{
293 GtkAdjustment *adj = GTK_ADJUSTMENT (data);
294 GValue val = G_VALUE_INIT;
295
296 g_value_init (value: &val, G_TYPE_FLOAT);
297 get_property_value (object, pspec, value: &val);
298
299 if (g_value_get_float (value: &val) != (float) gtk_adjustment_get_value (adjustment: adj))
300 {
301 block_controller (G_OBJECT (adj));
302 gtk_adjustment_set_value (adjustment: adj, value: g_value_get_float (value: &val));
303 unblock_controller (G_OBJECT (adj));
304 }
305
306 g_value_unset (value: &val);
307}
308
309static void
310double_modified (GtkAdjustment *adj, ObjectProperty *p)
311{
312 GValue val = G_VALUE_INIT;
313
314 g_value_init (value: &val, G_TYPE_DOUBLE);
315 g_value_set_double (value: &val, v_double: gtk_adjustment_get_value (adjustment: adj));
316 set_property_value (object: p->obj, pspec: p->spec, value: &val);
317 g_value_unset (value: &val);
318}
319
320static void
321double_changed (GObject *object, GParamSpec *pspec, gpointer data)
322{
323 GtkAdjustment *adj = GTK_ADJUSTMENT (data);
324 GValue val = G_VALUE_INIT;
325
326 g_value_init (value: &val, G_TYPE_DOUBLE);
327 get_property_value (object, pspec, value: &val);
328
329 if (g_value_get_double (value: &val) != gtk_adjustment_get_value (adjustment: adj))
330 {
331 block_controller (G_OBJECT (adj));
332 gtk_adjustment_set_value (adjustment: adj, value: g_value_get_double (value: &val));
333 unblock_controller (G_OBJECT (adj));
334 }
335
336 g_value_unset (value: &val);
337}
338
339static void
340string_modified (GtkEntry *entry, ObjectProperty *p)
341{
342 GValue val = G_VALUE_INIT;
343
344 g_value_init (value: &val, G_TYPE_STRING);
345 g_value_set_static_string (value: &val, v_string: gtk_editable_get_text (GTK_EDITABLE (entry)));
346 set_property_value (object: p->obj, pspec: p->spec, value: &val);
347 g_value_unset (value: &val);
348}
349
350static void
351intern_string_modified (GtkEntry *entry, ObjectProperty *p)
352{
353 const char *s;
354
355 s = gtk_editable_get_text (GTK_EDITABLE (entry));
356 if (g_str_equal (v1: p->spec->name, v2: "id"))
357 gtk_css_node_set_id (GTK_CSS_NODE (p->obj), id: g_quark_from_string (string: s));
358 else if (g_str_equal (v1: p->spec->name, v2: "name"))
359 gtk_css_node_set_name (GTK_CSS_NODE (p->obj), name: g_quark_from_string (string: s));
360}
361
362static void
363attr_list_modified (GtkEntry *entry, ObjectProperty *p)
364{
365 GValue val = G_VALUE_INIT;
366 PangoAttrList *attrs;
367
368 attrs = pango_attr_list_from_string (text: gtk_editable_get_text (GTK_EDITABLE (entry)));
369 if (!attrs)
370 return;
371
372 g_value_init (value: &val, PANGO_TYPE_ATTR_LIST);
373 g_value_take_boxed (value: &val, v_boxed: attrs);
374 set_property_value (object: p->obj, pspec: p->spec, value: &val);
375 g_value_unset (value: &val);
376}
377
378static void
379string_changed (GObject *object, GParamSpec *pspec, gpointer data)
380{
381 GtkEntry *entry = GTK_ENTRY (data);
382 GValue val = G_VALUE_INIT;
383 const char *str;
384 const char *text;
385
386 g_value_init (value: &val, G_TYPE_STRING);
387 get_property_value (object, pspec, value: &val);
388
389 str = g_value_get_string (value: &val);
390 if (str == NULL)
391 str = "";
392 text = gtk_editable_get_text (GTK_EDITABLE (entry));
393 if (g_strcmp0 (str1: str, str2: text) != 0)
394 {
395 block_controller (G_OBJECT (entry));
396 gtk_editable_set_text (GTK_EDITABLE (entry), text: str);
397 unblock_controller (G_OBJECT (entry));
398 }
399
400 g_value_unset (value: &val);
401}
402
403static void
404attr_list_changed (GObject *object, GParamSpec *pspec, gpointer data)
405{
406 GtkEntry *entry = GTK_ENTRY (data);
407 GValue val = G_VALUE_INIT;
408 char *str = NULL;
409 const char *text;
410 PangoAttrList *attrs;
411
412 g_value_init (value: &val, PANGO_TYPE_ATTR_LIST);
413 get_property_value (object, pspec, value: &val);
414
415 attrs = g_value_get_boxed (value: &val);
416 if (attrs)
417 str = pango_attr_list_to_string (list: attrs);
418 if (str == NULL)
419 str = g_strdup (str: "");
420 text = gtk_editable_get_text (GTK_EDITABLE (entry));
421 if (g_strcmp0 (str1: str, str2: text) != 0)
422 {
423 block_controller (G_OBJECT (entry));
424 gtk_editable_set_text (GTK_EDITABLE (entry), text: str);
425 unblock_controller (G_OBJECT (entry));
426 }
427
428 g_free (mem: str);
429
430 g_value_unset (value: &val);
431}
432
433static void
434strv_modified (GtkInspectorStrvEditor *self, ObjectProperty *p)
435{
436 GValue val = G_VALUE_INIT;
437 char **strv;
438
439 g_value_init (value: &val, G_TYPE_STRV);
440 strv = gtk_inspector_strv_editor_get_strv (editor: self);
441 g_value_take_boxed (value: &val, v_boxed: strv);
442 block_notify (G_OBJECT (self));
443 set_property_value (object: p->obj, pspec: p->spec, value: &val);
444 unblock_notify (G_OBJECT (self));
445 g_value_unset (value: &val);
446}
447
448static void
449strv_changed (GObject *object, GParamSpec *pspec, gpointer data)
450{
451 GtkInspectorStrvEditor *self = data;
452 GValue val = G_VALUE_INIT;
453 char **strv;
454
455 g_value_init (value: &val, G_TYPE_STRV);
456 get_property_value (object, pspec, value: &val);
457
458 strv = g_value_get_boxed (value: &val);
459 block_controller (G_OBJECT (self));
460 gtk_inspector_strv_editor_set_strv (editor: self, strv);
461 unblock_controller (G_OBJECT (self));
462
463 g_value_unset (value: &val);
464}
465
466static void
467bool_modified (GtkCheckButton *cb,
468 ObjectProperty *p)
469{
470 GValue val = G_VALUE_INIT;
471
472 g_value_init (value: &val, G_TYPE_BOOLEAN);
473 g_value_set_boolean (value: &val, v_boolean: gtk_check_button_get_active (self: cb));
474 set_property_value (object: p->obj, pspec: p->spec, value: &val);
475 g_value_unset (value: &val);
476}
477
478static void
479bool_changed (GObject *object, GParamSpec *pspec, gpointer data)
480{
481 GtkCheckButton *cb = GTK_CHECK_BUTTON (data);
482 GValue val = G_VALUE_INIT;
483
484 g_value_init (value: &val, G_TYPE_BOOLEAN);
485 get_property_value (object, pspec, value: &val);
486
487 if (g_value_get_boolean (value: &val) != gtk_check_button_get_active (self: cb))
488 {
489 block_controller (G_OBJECT (cb));
490 gtk_check_button_set_active (self: cb, setting: g_value_get_boolean (value: &val));
491 unblock_controller (G_OBJECT (cb));
492 }
493
494 g_value_unset (value: &val);
495}
496
497static void
498enum_modified (GtkDropDown *dropdown, GParamSpec *pspec, ObjectProperty *p)
499{
500 int i = gtk_drop_down_get_selected (self: dropdown);
501 GEnumClass *eclass;
502 GValue val = G_VALUE_INIT;
503
504 eclass = G_ENUM_CLASS (g_type_class_peek (p->spec->value_type));
505
506 g_value_init (value: &val, g_type: p->spec->value_type);
507 g_value_set_enum (value: &val, v_enum: eclass->values[i].value);
508 set_property_value (object: p->obj, pspec: p->spec, value: &val);
509 g_value_unset (value: &val);
510}
511
512static void
513enum_changed (GObject *object, GParamSpec *pspec, gpointer data)
514{
515 GValue val = G_VALUE_INIT;
516 GEnumClass *eclass;
517 int i;
518
519 eclass = G_ENUM_CLASS (g_type_class_peek (pspec->value_type));
520
521 g_value_init (value: &val, g_type: pspec->value_type);
522 get_property_value (object, pspec, value: &val);
523
524 i = 0;
525 while (i < eclass->n_values)
526 {
527 if (eclass->values[i].value == g_value_get_enum (value: &val))
528 break;
529 ++i;
530 }
531 g_value_unset (value: &val);
532
533 block_controller (G_OBJECT (data));
534 gtk_drop_down_set_selected (self: GTK_DROP_DOWN (ptr: data), position: i);
535 unblock_controller (G_OBJECT (data));
536}
537
538static void
539flags_modified (GtkCheckButton *button, ObjectProperty *p)
540{
541 gboolean active;
542 GFlagsClass *fclass;
543 guint flags;
544 int i;
545 GValue val = G_VALUE_INIT;
546
547 active = gtk_check_button_get_active (self: button);
548 i = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), "index"));
549 fclass = G_FLAGS_CLASS (g_type_class_peek (p->spec->value_type));
550
551 g_value_init (value: &val, g_type: p->spec->value_type);
552 get_property_value (object: p->obj, pspec: p->spec, value: &val);
553 flags = g_value_get_flags (value: &val);
554 if (active)
555 flags |= fclass->values[i].value;
556 else
557 flags &= ~fclass->values[i].value;
558 g_value_set_flags (value: &val, v_flags: flags);
559 set_property_value (object: p->obj, pspec: p->spec, value: &val);
560 g_value_unset (value: &val);
561}
562
563static char *
564flags_to_string (GFlagsClass *flags_class,
565 guint value)
566{
567 GString *str;
568 GFlagsValue *flags_value;
569
570 str = g_string_new (NULL);
571
572 while ((str->len == 0 || value != 0) &&
573 (flags_value = g_flags_get_first_value (flags_class, value)) != NULL)
574 {
575 if (str->len > 0)
576 g_string_append (string: str, val: " | ");
577
578 g_string_append (string: str, val: flags_value->value_nick);
579
580 value &= ~flags_value->value;
581 }
582
583 /* Show the extra bits */
584 if (value != 0 || str->len == 0)
585 {
586 if (str->len > 0)
587 g_string_append (string: str, val: " | ");
588
589 g_string_append_printf (string: str, format: "0x%x", value);
590 }
591
592 return g_string_free (string: str, FALSE);
593}
594
595static void
596flags_changed (GObject *object, GParamSpec *pspec, gpointer data)
597{
598 GValue val = G_VALUE_INIT;
599 GFlagsClass *fclass;
600 guint flags;
601 int i;
602 GtkPopover *popover;
603 GtkWidget *sw;
604 GtkWidget *viewport;
605 GtkWidget *box;
606 char *str;
607 GtkWidget *child;
608
609 fclass = G_FLAGS_CLASS (g_type_class_peek (pspec->value_type));
610
611 g_value_init (value: &val, g_type: pspec->value_type);
612 get_property_value (object, pspec, value: &val);
613 flags = g_value_get_flags (value: &val);
614 g_value_unset (value: &val);
615
616 str = flags_to_string (flags_class: fclass, value: flags);
617 gtk_menu_button_set_label (GTK_MENU_BUTTON (data), label: str);
618 g_free (mem: str);
619
620 popover = gtk_menu_button_get_popover (GTK_MENU_BUTTON (data));
621 sw = gtk_popover_get_child (GTK_POPOVER (popover));
622 viewport = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (sw));
623 box = gtk_viewport_get_child (GTK_VIEWPORT (viewport));
624 for (child = gtk_widget_get_first_child (widget: box);
625 child != NULL;
626 child = gtk_widget_get_next_sibling (widget: child))
627 block_controller (G_OBJECT (child));
628
629 for (child = gtk_widget_get_first_child (widget: box), i = 0;
630 child != NULL;
631 child = gtk_widget_get_next_sibling (widget: child), i++)
632 gtk_check_button_set_active (GTK_CHECK_BUTTON (child),
633 setting: (fclass->values[i].value & flags) != 0);
634
635 for (child = gtk_widget_get_first_child (widget: box);
636 child != NULL;
637 child = gtk_widget_get_next_sibling (widget: child))
638 unblock_controller (G_OBJECT (child));
639}
640
641static gunichar
642unichar_get_value (GtkEntry *entry)
643{
644 const char *text = gtk_editable_get_text (GTK_EDITABLE (entry));
645
646 if (text[0])
647 return g_utf8_get_char (p: text);
648 else
649 return 0;
650}
651
652static void
653unichar_modified (GtkEntry *entry, ObjectProperty *p)
654{
655 gunichar u = unichar_get_value (entry);
656 GValue val = G_VALUE_INIT;
657
658 g_value_init (value: &val, g_type: p->spec->value_type);
659 g_value_set_uint (value: &val, v_uint: u);
660 set_property_value (object: p->obj, pspec: p->spec, value: &val);
661 g_value_unset (value: &val);
662}
663static void
664unichar_changed (GObject *object, GParamSpec *pspec, gpointer data)
665{
666 GtkEntry *entry = GTK_ENTRY (data);
667 gunichar new_val;
668 gunichar old_val = unichar_get_value (entry);
669 GValue val = G_VALUE_INIT;
670 char buf[7];
671 int len;
672
673 g_value_init (value: &val, g_type: pspec->value_type);
674 get_property_value (object, pspec, value: &val);
675 new_val = (gunichar)g_value_get_uint (value: &val);
676 g_value_unset (value: &val);
677
678 if (new_val != old_val)
679 {
680 if (!new_val)
681 len = 0;
682 else
683 len = g_unichar_to_utf8 (c: new_val, outbuf: buf);
684
685 buf[len] = '\0';
686
687 block_controller (G_OBJECT (entry));
688 gtk_editable_set_text (GTK_EDITABLE (entry), text: buf);
689 unblock_controller (G_OBJECT (entry));
690 }
691}
692
693static void
694pointer_changed (GObject *object, GParamSpec *pspec, gpointer data)
695{
696 GtkLabel *label = GTK_LABEL (data);
697 char *str;
698 gpointer ptr;
699
700 g_object_get (object, first_property_name: pspec->name, &ptr, NULL);
701
702 str = g_strdup_printf (_("Pointer: %p"), ptr);
703 gtk_label_set_text (self: label, str);
704 g_free (mem: str);
705}
706
707static char *
708object_label (GObject *obj, GParamSpec *pspec)
709{
710 return g_strdup_printf (format: "%p", obj);
711}
712
713static void
714object_changed (GObject *object, GParamSpec *pspec, gpointer data)
715{
716 GtkWidget *label, *button;
717 char *str;
718 GObject *obj;
719
720 label = gtk_widget_get_first_child (GTK_WIDGET (data));
721 button = gtk_widget_get_next_sibling (widget: label);
722 g_object_get (object, first_property_name: pspec->name, &obj, NULL);
723
724 str = object_label (obj, pspec);
725
726 gtk_label_set_text (GTK_LABEL (label), str);
727 gtk_widget_set_sensitive (widget: button, G_IS_OBJECT (obj));
728
729 if (obj)
730 g_object_unref (object: obj);
731
732 g_free (mem: str);
733}
734
735static void
736object_properties (GtkInspectorPropEditor *self)
737{
738 GObject *obj;
739
740 g_object_get (object: self->object, first_property_name: self->name, &obj, NULL);
741 if (G_IS_OBJECT (obj))
742 g_signal_emit (instance: self, signal_id: signals[SHOW_OBJECT], detail: 0, obj, self->name, "properties");
743}
744
745static void
746rgba_modified (GtkColorButton *cb, GParamSpec *ignored, ObjectProperty *p)
747{
748 GValue val = G_VALUE_INIT;
749
750 g_value_init (value: &val, g_type: p->spec->value_type);
751 g_object_get_property (G_OBJECT (cb), property_name: "rgba", value: &val);
752 set_property_value (object: p->obj, pspec: p->spec, value: &val);
753 g_value_unset (value: &val);
754}
755
756static void
757rgba_changed (GObject *object, GParamSpec *pspec, gpointer data)
758{
759 GtkColorChooser *cb = GTK_COLOR_CHOOSER (data);
760 GValue val = G_VALUE_INIT;
761 GdkRGBA *color;
762 GdkRGBA cb_color;
763
764 g_value_init (value: &val, GDK_TYPE_RGBA);
765 get_property_value (object, pspec, value: &val);
766
767 color = g_value_get_boxed (value: &val);
768 gtk_color_chooser_get_rgba (GTK_COLOR_CHOOSER (cb), color: &cb_color);
769
770 if (color != NULL && !gdk_rgba_equal (p1: color, p2: &cb_color))
771 {
772 block_controller (G_OBJECT (cb));
773 gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (cb), color);
774 unblock_controller (G_OBJECT (cb));
775 }
776 g_value_unset (value: &val);
777}
778
779static void
780font_modified (GtkFontChooser *fb, GParamSpec *pspec, ObjectProperty *p)
781{
782 GValue val = G_VALUE_INIT;
783
784 g_value_init (value: &val, PANGO_TYPE_FONT_DESCRIPTION);
785 g_object_get_property (G_OBJECT (fb), property_name: "font-desc", value: &val);
786 set_property_value (object: p->obj, pspec: p->spec, value: &val);
787 g_value_unset (value: &val);
788}
789
790static void
791font_changed (GObject *object, GParamSpec *pspec, gpointer data)
792{
793 GtkFontChooser *fb = GTK_FONT_CHOOSER (data);
794 GValue val = G_VALUE_INIT;
795 const PangoFontDescription *font_desc;
796 PangoFontDescription *fb_font_desc;
797
798 g_value_init (value: &val, PANGO_TYPE_FONT_DESCRIPTION);
799 get_property_value (object, pspec, value: &val);
800
801 font_desc = g_value_get_boxed (value: &val);
802 fb_font_desc = gtk_font_chooser_get_font_desc (fontchooser: fb);
803
804 if (font_desc == NULL ||
805 (fb_font_desc != NULL &&
806 !pango_font_description_equal (desc1: fb_font_desc, desc2: font_desc)))
807 {
808 block_controller (G_OBJECT (fb));
809 gtk_font_chooser_set_font_desc (fontchooser: fb, font_desc);
810 unblock_controller (G_OBJECT (fb));
811 }
812
813 g_value_unset (value: &val);
814 pango_font_description_free (desc: fb_font_desc);
815}
816
817static char *
818describe_expression (GtkExpression *expression)
819{
820 if (expression == NULL)
821 return NULL;
822
823 if (G_TYPE_CHECK_INSTANCE_TYPE (expression, GTK_TYPE_CONSTANT_EXPRESSION))
824 {
825 const GValue *value = gtk_constant_expression_get_value (expression);
826 GValue dest = G_VALUE_INIT;
827
828 g_value_init (value: &dest, G_TYPE_STRING);
829 if (g_value_transform (src_value: value, dest_value: &dest))
830 {
831 /* Translators: %s is a type name, for example
832 * GtkPropertyExpression with value \"2.5\"
833 */
834 char *res = g_strdup_printf (_("%s with value \"%s\""),
835 g_type_name (G_TYPE_FROM_INSTANCE (expression)),
836 g_value_get_string (value: &dest));
837 g_value_unset (value: &dest);
838 return res;
839 }
840 else
841 {
842 /* Translators: Both %s are type names, for example
843 * GtkPropertyExpression with type GObject
844 */
845 return g_strdup_printf (_("%s with type %s"),
846 g_type_name (G_TYPE_FROM_INSTANCE (expression)),
847 g_type_name (G_VALUE_TYPE (value)));
848 }
849 }
850 else if (G_TYPE_CHECK_INSTANCE_TYPE (expression, GTK_TYPE_OBJECT_EXPRESSION))
851 {
852 gpointer obj = gtk_object_expression_get_object (expression);
853
854 if (obj)
855 /* Translators: Both %s are type names, for example
856 * GtkObjectExpression for GtkStringObject 0x23456789
857 */
858 return g_strdup_printf (_("%s for %s %p"),
859 g_type_name (G_TYPE_FROM_INSTANCE (expression)),
860 G_OBJECT_TYPE_NAME (obj), obj);
861 else
862 return g_strdup (str: g_type_name (G_TYPE_FROM_INSTANCE (expression)));
863 }
864 else if (G_TYPE_CHECK_INSTANCE_TYPE (expression, GTK_TYPE_PROPERTY_EXPRESSION))
865 {
866 GParamSpec *pspec = gtk_property_expression_get_pspec (expression);
867 GtkExpression *expr = gtk_property_expression_get_expression (expression);
868 char *str;
869 char *res;
870
871 str = describe_expression (expression: expr);
872 /* Translators: The first %s is a type name, %s:%s is a qualified
873 * property name, and is a value, for example
874 * GtkPropertyExpression for property GtkLabellabel on: GObjectExpression ...
875 */
876 res = g_strdup_printf (format: "%s for property %s:%s on: %s",
877 g_type_name (G_TYPE_FROM_INSTANCE (expression)),
878 g_type_name (type: pspec->owner_type),
879 pspec->name,
880 str);
881 g_free (mem: str);
882 return res;
883 }
884 else
885 /* Translators: Both %s are type names, for example
886 * GtkPropertyExpression with value type: gchararray
887 */
888 return g_strdup_printf (_("%s with value type %s"),
889 g_type_name (G_TYPE_FROM_INSTANCE (expression)),
890 g_type_name (type: gtk_expression_get_value_type (self: expression)));
891}
892
893static void
894toggle_unicode (GtkToggleButton *button,
895 GParamSpec *pspec,
896 GtkWidget *stack)
897{
898 GtkWidget *entry;
899 GtkWidget *unicode;
900
901 entry = gtk_stack_get_child_by_name (GTK_STACK (stack), name: "entry");
902 unicode = gtk_stack_get_child_by_name (GTK_STACK (stack), name: "unicode");
903 if (gtk_toggle_button_get_active (toggle_button: button))
904 {
905 const char *text;
906 const char *p;
907 GString *s;
908
909 text = gtk_editable_get_text (GTK_EDITABLE (entry));
910 s = g_string_sized_new (dfl_size: 6 * strlen (s: text));
911 for (p = text; *p; p = g_utf8_next_char (p))
912 {
913 gunichar ch = g_utf8_get_char (p);
914 if (s->len > 0)
915 g_string_append_c (s, ' ');
916 g_string_append_printf (string: s, format: "U+%04X", ch);
917 }
918 gtk_editable_set_text (GTK_EDITABLE (unicode), text: s->str);
919 g_string_free (string: s, TRUE);
920
921 gtk_stack_set_visible_child_name (GTK_STACK (stack), name: "unicode");
922 }
923 else
924 {
925 gtk_editable_set_text (GTK_EDITABLE (unicode), text: "");
926 gtk_stack_set_visible_child_name (GTK_STACK (stack), name: "entry");
927 }
928}
929
930static GtkWidget *
931property_editor (GObject *object,
932 GParamSpec *spec,
933 GtkInspectorPropEditor *self)
934{
935 GtkWidget *prop_edit;
936 GtkAdjustment *adj;
937 char *msg;
938 GType type = G_PARAM_SPEC_TYPE (spec);
939
940 if (type == G_TYPE_PARAM_INT)
941 {
942 adj = gtk_adjustment_new (G_PARAM_SPEC_INT (spec)->default_value,
943 G_PARAM_SPEC_INT (spec)->minimum,
944 G_PARAM_SPEC_INT (spec)->maximum,
945 step_increment: 1,
946 MAX ((G_PARAM_SPEC_INT (spec)->maximum - G_PARAM_SPEC_INT (spec)->minimum) / 10, 1),
947 page_size: 0.0);
948
949 prop_edit = gtk_spin_button_new (adjustment: adj, climb_rate: 1.0, digits: 0);
950
951 g_object_connect_property (object, spec, G_CALLBACK (int_changed), data: adj, G_OBJECT (adj));
952
953 connect_controller (G_OBJECT (adj), signal: "value_changed",
954 model: object, spec, G_CALLBACK (int_modified));
955 }
956 else if (type == G_TYPE_PARAM_UINT)
957 {
958 adj = gtk_adjustment_new (G_PARAM_SPEC_UINT (spec)->default_value,
959 G_PARAM_SPEC_UINT (spec)->minimum,
960 G_PARAM_SPEC_UINT (spec)->maximum,
961 step_increment: 1,
962 MAX ((G_PARAM_SPEC_UINT (spec)->maximum - G_PARAM_SPEC_UINT (spec)->minimum) / 10, 1),
963 page_size: 0.0);
964
965 prop_edit = gtk_spin_button_new (adjustment: adj, climb_rate: 1.0, digits: 0);
966
967 g_object_connect_property (object, spec,
968 G_CALLBACK (uint_changed),
969 data: adj, G_OBJECT (adj));
970
971 connect_controller (G_OBJECT (adj), signal: "value_changed",
972 model: object, spec, G_CALLBACK (uint_modified));
973 }
974 else if (type == G_TYPE_PARAM_FLOAT)
975 {
976 adj = gtk_adjustment_new (G_PARAM_SPEC_FLOAT (spec)->default_value,
977 G_PARAM_SPEC_FLOAT (spec)->minimum,
978 G_PARAM_SPEC_FLOAT (spec)->maximum,
979 step_increment: 0.1,
980 MAX ((G_PARAM_SPEC_FLOAT (spec)->maximum - G_PARAM_SPEC_FLOAT (spec)->minimum) / 10, 0.1),
981 page_size: 0.0);
982
983 prop_edit = gtk_spin_button_new (adjustment: adj, climb_rate: 0.1, digits: 2);
984
985 g_object_connect_property (object, spec,
986 G_CALLBACK (float_changed),
987 data: adj, G_OBJECT (adj));
988
989 connect_controller (G_OBJECT (adj), signal: "value_changed",
990 model: object, spec, G_CALLBACK (float_modified));
991 }
992 else if (type == G_TYPE_PARAM_DOUBLE)
993 {
994 adj = gtk_adjustment_new (G_PARAM_SPEC_DOUBLE (spec)->default_value,
995 G_PARAM_SPEC_DOUBLE (spec)->minimum,
996 G_PARAM_SPEC_DOUBLE (spec)->maximum,
997 step_increment: 0.1,
998 page_increment: 1.0,
999 page_size: 0.0);
1000
1001 prop_edit = gtk_spin_button_new (adjustment: adj, climb_rate: 0.1, digits: 2);
1002
1003 g_object_connect_property (object, spec,
1004 G_CALLBACK (double_changed),
1005 data: adj, G_OBJECT (adj));
1006
1007 connect_controller (G_OBJECT (adj), signal: "value_changed",
1008 model: object, spec, G_CALLBACK (double_modified));
1009 }
1010 else if (type == G_TYPE_PARAM_STRING)
1011 {
1012 GtkWidget *entry;
1013 GtkWidget *button;
1014 GtkWidget *stack;
1015 GtkWidget *unicode;
1016
1017 entry = gtk_entry_new ();
1018
1019 g_object_connect_property (object, spec,
1020 G_CALLBACK (string_changed),
1021 data: entry, G_OBJECT (entry));
1022
1023 if (GTK_IS_CSS_NODE (object))
1024 connect_controller (G_OBJECT (entry), signal: "changed",
1025 model: object, spec, G_CALLBACK (intern_string_modified));
1026 else
1027 connect_controller (G_OBJECT (entry), signal: "changed",
1028 model: object, spec, G_CALLBACK (string_modified));
1029
1030 unicode = gtk_entry_new ();
1031 gtk_editable_set_editable (GTK_EDITABLE (unicode), FALSE);
1032
1033 stack = gtk_stack_new ();
1034 gtk_stack_add_named (GTK_STACK (stack), child: entry, name: "entry");
1035 gtk_stack_add_named (GTK_STACK (stack), child: unicode, name: "unicode");
1036
1037 prop_edit = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 10);
1038 gtk_box_append (GTK_BOX (prop_edit), child: stack);
1039
1040 button = gtk_toggle_button_new_with_label (label: "Unicode");
1041 gtk_box_append (GTK_BOX (prop_edit), child: button);
1042
1043 g_signal_connect (button, "notify::active", G_CALLBACK (toggle_unicode), stack);
1044 }
1045 else if (type == G_TYPE_PARAM_BOOLEAN)
1046 {
1047 prop_edit = gtk_check_button_new_with_label (label: "");
1048
1049 g_object_connect_property (object, spec,
1050 G_CALLBACK (bool_changed),
1051 data: prop_edit, G_OBJECT (prop_edit));
1052
1053 connect_controller (G_OBJECT (prop_edit), signal: "toggled",
1054 model: object, spec, G_CALLBACK (bool_modified));
1055 }
1056 else if (type == G_TYPE_PARAM_ENUM)
1057 {
1058 {
1059 GEnumClass *eclass;
1060 GtkStringList *names;
1061 int j;
1062
1063 eclass = G_ENUM_CLASS (g_type_class_ref (spec->value_type));
1064
1065 names = gtk_string_list_new (NULL);
1066 for (j = 0; j < eclass->n_values; j++)
1067 gtk_string_list_append (self: names, string: eclass->values[j].value_name);
1068
1069 prop_edit = gtk_drop_down_new (model: G_LIST_MODEL (ptr: names), NULL);
1070
1071 connect_controller (G_OBJECT (prop_edit), signal: "notify::selected",
1072 model: object, spec, G_CALLBACK (enum_modified));
1073
1074 g_type_class_unref (g_class: eclass);
1075
1076 g_object_connect_property (object, spec,
1077 G_CALLBACK (enum_changed),
1078 data: prop_edit, G_OBJECT (prop_edit));
1079 }
1080 }
1081 else if (type == G_TYPE_PARAM_FLAGS)
1082 {
1083 {
1084 GtkWidget *box;
1085 GtkWidget *sw;
1086 GtkWidget *popover;
1087 GFlagsClass *fclass;
1088 int j;
1089
1090 popover = gtk_popover_new ();
1091 prop_edit = gtk_menu_button_new ();
1092 gtk_menu_button_set_popover (GTK_MENU_BUTTON (prop_edit), popover);
1093
1094 sw = gtk_scrolled_window_new ();
1095 gtk_popover_set_child (GTK_POPOVER (popover), child: sw);
1096 g_object_set (object: sw,
1097 first_property_name: "hexpand", TRUE,
1098 "vexpand", TRUE,
1099 "hscrollbar-policy", GTK_POLICY_NEVER,
1100 "vscrollbar-policy", GTK_POLICY_NEVER,
1101 NULL);
1102 box = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 0);
1103 gtk_widget_show (widget: box);
1104 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), child: box);
1105
1106 fclass = G_FLAGS_CLASS (g_type_class_ref (spec->value_type));
1107
1108 for (j = 0; j < fclass->n_values; j++)
1109 {
1110 GtkWidget *b;
1111
1112 b = gtk_check_button_new_with_label (label: fclass->values[j].value_nick);
1113 g_object_set_data (G_OBJECT (b), key: "index", GINT_TO_POINTER (j));
1114 gtk_widget_show (widget: b);
1115 gtk_box_append (GTK_BOX (box), child: b);
1116 connect_controller (G_OBJECT (b), signal: "toggled",
1117 model: object, spec, G_CALLBACK (flags_modified));
1118 }
1119
1120 if (j >= 10)
1121 {
1122 g_object_set (object: sw,
1123 first_property_name: "vscrollbar-policy", GTK_POLICY_AUTOMATIC,
1124 "min-content-height", 250,
1125 NULL);
1126 }
1127
1128 g_type_class_unref (g_class: fclass);
1129
1130 g_object_connect_property (object, spec,
1131 G_CALLBACK (flags_changed),
1132 data: prop_edit, G_OBJECT (prop_edit));
1133 }
1134 }
1135 else if (type == G_TYPE_PARAM_UNICHAR)
1136 {
1137 prop_edit = gtk_entry_new ();
1138 gtk_entry_set_max_length (GTK_ENTRY (prop_edit), max: 1);
1139
1140 g_object_connect_property (object, spec,
1141 G_CALLBACK (unichar_changed),
1142 data: prop_edit, G_OBJECT (prop_edit));
1143
1144 connect_controller (G_OBJECT (prop_edit), signal: "changed",
1145 model: object, spec, G_CALLBACK (unichar_modified));
1146 }
1147 else if (type == G_TYPE_PARAM_POINTER)
1148 {
1149 prop_edit = gtk_label_new (str: "");
1150
1151 g_object_connect_property (object, spec,
1152 G_CALLBACK (pointer_changed),
1153 data: prop_edit, G_OBJECT (prop_edit));
1154 }
1155 else if (type == G_TYPE_PARAM_OBJECT)
1156 {
1157 GtkWidget *label, *button;
1158
1159 prop_edit = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 5);
1160
1161 label = gtk_label_new (str: "");
1162 button = gtk_button_new_with_label (_("Properties"));
1163 g_signal_connect_swapped (button, "clicked",
1164 G_CALLBACK (object_properties),
1165 self);
1166 gtk_box_append (GTK_BOX (prop_edit), child: label);
1167 gtk_box_append (GTK_BOX (prop_edit), child: button);
1168 gtk_widget_show (widget: label);
1169 gtk_widget_show (widget: button);
1170
1171 g_object_connect_property (object, spec,
1172 G_CALLBACK (object_changed),
1173 data: prop_edit, G_OBJECT (label));
1174 }
1175 else if (type == G_TYPE_PARAM_BOXED &&
1176 G_PARAM_SPEC_VALUE_TYPE (spec) == GDK_TYPE_RGBA)
1177 {
1178 prop_edit = gtk_color_button_new ();
1179 gtk_color_chooser_set_use_alpha (GTK_COLOR_CHOOSER (prop_edit), TRUE);
1180
1181 g_object_connect_property (object, spec,
1182 G_CALLBACK (rgba_changed),
1183 data: prop_edit, G_OBJECT (prop_edit));
1184
1185 connect_controller (G_OBJECT (prop_edit), signal: "notify::rgba",
1186 model: object, spec, G_CALLBACK (rgba_modified));
1187 }
1188 else if (type == G_TYPE_PARAM_BOXED &&
1189 G_PARAM_SPEC_VALUE_TYPE (spec) == PANGO_TYPE_FONT_DESCRIPTION)
1190 {
1191 prop_edit = gtk_font_button_new ();
1192
1193 g_object_connect_property (object, spec,
1194 G_CALLBACK (font_changed),
1195 data: prop_edit, G_OBJECT (prop_edit));
1196
1197 connect_controller (G_OBJECT (prop_edit), signal: "notify::font-desc",
1198 model: object, spec, G_CALLBACK (font_modified));
1199 }
1200 else if (type == G_TYPE_PARAM_BOXED &&
1201 G_PARAM_SPEC_VALUE_TYPE (spec) == G_TYPE_STRV)
1202 {
1203 prop_edit = g_object_new (object_type: gtk_inspector_strv_editor_get_type (),
1204 first_property_name: "visible", TRUE,
1205 NULL);
1206
1207 g_object_connect_property (object, spec,
1208 G_CALLBACK (strv_changed),
1209 data: prop_edit, G_OBJECT (prop_edit));
1210
1211 connect_controller (G_OBJECT (prop_edit), signal: "changed",
1212 model: object, spec, G_CALLBACK (strv_modified));
1213
1214 gtk_widget_set_halign (widget: prop_edit, align: GTK_ALIGN_START);
1215 gtk_widget_set_valign (widget: prop_edit, align: GTK_ALIGN_CENTER);
1216 }
1217 else if (type == G_TYPE_PARAM_BOXED &&
1218 G_PARAM_SPEC_VALUE_TYPE (spec) == PANGO_TYPE_ATTR_LIST)
1219 {
1220 prop_edit = gtk_entry_new ();
1221
1222 g_object_connect_property (object, spec,
1223 G_CALLBACK (attr_list_changed),
1224 data: prop_edit, G_OBJECT (prop_edit));
1225
1226 connect_controller (G_OBJECT (prop_edit), signal: "changed",
1227 model: object, spec, G_CALLBACK (attr_list_modified));
1228 }
1229 else if (type == GTK_TYPE_PARAM_SPEC_EXPRESSION)
1230 {
1231 GtkExpression *expression;
1232 g_object_get (object, first_property_name: spec->name, &expression, NULL);
1233 msg = describe_expression (expression);
1234 prop_edit = gtk_label_new (str: msg);
1235 g_free (mem: msg);
1236 g_clear_pointer (&expression, gtk_expression_unref);
1237 gtk_widget_set_halign (widget: prop_edit, align: GTK_ALIGN_START);
1238 gtk_widget_set_valign (widget: prop_edit, align: GTK_ALIGN_CENTER);
1239 }
1240 else
1241 {
1242 msg = g_strdup_printf (_("Uneditable property type: %s"),
1243 g_type_name (G_PARAM_SPEC_TYPE (spec)));
1244 prop_edit = gtk_label_new (str: msg);
1245 g_free (mem: msg);
1246 gtk_widget_set_halign (widget: prop_edit, align: GTK_ALIGN_START);
1247 gtk_widget_set_valign (widget: prop_edit, align: GTK_ALIGN_CENTER);
1248 }
1249
1250 if (g_param_spec_get_blurb (pspec: spec))
1251 gtk_widget_set_tooltip_text (widget: prop_edit, text: g_param_spec_get_blurb (pspec: spec));
1252
1253 notify_property (object, pspec: spec);
1254
1255 return prop_edit;
1256}
1257
1258static void
1259gtk_inspector_prop_editor_init (GtkInspectorPropEditor *self)
1260{
1261 g_object_set (object: self,
1262 first_property_name: "orientation", GTK_ORIENTATION_HORIZONTAL,
1263 "spacing", 10,
1264 NULL);
1265}
1266
1267static GtkTreeModel *
1268gtk_cell_layout_get_model (GtkCellLayout *layout)
1269{
1270 if (GTK_IS_TREE_VIEW_COLUMN (layout))
1271 return gtk_tree_view_get_model (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (GTK_TREE_VIEW_COLUMN (layout))));
1272 else if (GTK_IS_ICON_VIEW (layout))
1273 return gtk_icon_view_get_model (GTK_ICON_VIEW (layout));
1274 else if (GTK_IS_COMBO_BOX (layout))
1275 return gtk_combo_box_get_model (GTK_COMBO_BOX (layout));
1276 else
1277 return NULL;
1278}
1279
1280static GtkWidget *
1281gtk_cell_layout_get_widget (GtkCellLayout *layout)
1282{
1283 if (GTK_IS_TREE_VIEW_COLUMN (layout))
1284 return gtk_tree_view_column_get_tree_view (GTK_TREE_VIEW_COLUMN (layout));
1285 else if (GTK_IS_WIDGET (layout))
1286 return GTK_WIDGET (layout);
1287 else
1288 return NULL;
1289}
1290
1291static void
1292model_properties (GtkButton *button,
1293 GtkInspectorPropEditor *self)
1294{
1295 GObject *model;
1296
1297 model = g_object_get_data (G_OBJECT (button), key: "model");
1298 g_signal_emit (instance: self, signal_id: signals[SHOW_OBJECT], detail: 0, model, "model", "data");
1299}
1300
1301static void
1302attribute_mapping_changed (GtkDropDown *dropdown,
1303 GParamSpec *pspec,
1304 GtkInspectorPropEditor *self)
1305{
1306 int col;
1307 gpointer layout;
1308 GtkCellRenderer *cell;
1309 GtkCellArea *area;
1310
1311 col = gtk_drop_down_get_selected (self: dropdown) - 1;
1312 layout = g_object_get_data (object: self->object, key: "gtk-inspector-cell-layout");
1313 if (GTK_IS_CELL_LAYOUT (layout))
1314 {
1315 cell = GTK_CELL_RENDERER (self->object);
1316 area = gtk_cell_layout_get_area (GTK_CELL_LAYOUT (layout));
1317 gtk_cell_area_attribute_disconnect (area, renderer: cell, attribute: self->name);
1318 if (col != -1)
1319 gtk_cell_area_attribute_connect (area, renderer: cell, attribute: self->name, column: col);
1320 gtk_widget_set_sensitive (widget: self->self, sensitive: col == -1);
1321 notify_property (object: self->object, pspec: find_property (self));
1322 gtk_widget_queue_draw (widget: gtk_cell_layout_get_widget (GTK_CELL_LAYOUT (layout)));
1323 }
1324}
1325
1326#define ATTRIBUTE_TYPE_HOLDER (attribute_holder_get_type ())
1327G_DECLARE_FINAL_TYPE (AttributeHolder, attribute_holder, ATTRIBUTE, HOLDER, GObject)
1328
1329struct _AttributeHolder {
1330 GObject parent_instance;
1331 int column;
1332 gboolean sensitive;
1333};
1334
1335G_DEFINE_TYPE (AttributeHolder, attribute_holder, G_TYPE_OBJECT);
1336
1337static void
1338attribute_holder_init (AttributeHolder *holder)
1339{
1340}
1341
1342static void
1343attribute_holder_class_init (AttributeHolderClass *class)
1344{
1345}
1346
1347static AttributeHolder *
1348attribute_holder_new (int column,
1349 gboolean sensitive)
1350{
1351 AttributeHolder *holder = g_object_new (ATTRIBUTE_TYPE_HOLDER, NULL);
1352 holder->column = column;
1353 holder->sensitive = sensitive;
1354 return holder;
1355}
1356
1357static void
1358attribute_setup_item (GtkSignalListItemFactory *factory,
1359 GtkListItem *item)
1360{
1361 GtkWidget *label;
1362
1363 label = gtk_label_new (str: "");
1364 gtk_label_set_xalign (GTK_LABEL (label), xalign: 0.0);
1365
1366 gtk_list_item_set_child (self: item, child: label);
1367}
1368
1369static void
1370attribute_bind_item (GtkSignalListItemFactory *factory,
1371 GtkListItem *item)
1372{
1373 GtkWidget *label;
1374 AttributeHolder *holder;
1375
1376 holder = gtk_list_item_get_item (self: item);
1377 label = gtk_list_item_get_child (self: item);
1378
1379 if (holder->column >= 0)
1380 {
1381 char *text = g_strdup_printf (format: "%d", holder->column);
1382 gtk_label_set_label (GTK_LABEL (label), str: text);
1383 g_free (mem: text);
1384 }
1385 else
1386 gtk_label_set_label (GTK_LABEL (label), C_("column number", "None"));
1387
1388 gtk_list_item_set_selectable (self: item, selectable: holder->sensitive);
1389 gtk_widget_set_sensitive (widget: label, sensitive: holder->sensitive);
1390}
1391
1392static GtkWidget *
1393attribute_editor (GObject *object,
1394 GParamSpec *spec,
1395 GtkInspectorPropEditor *self)
1396{
1397 gpointer layout;
1398 GtkCellArea *area;
1399 GtkTreeModel *model = NULL;
1400 int col = -1;
1401 GtkWidget *label;
1402 GtkWidget *button;
1403 GtkWidget *box;
1404 GtkWidget *dropdown;
1405 GListStore *store;
1406 GtkListItemFactory *factory;
1407 int i;
1408 AttributeHolder *holder;
1409 gboolean sensitive;
1410
1411 layout = g_object_get_data (object, key: "gtk-inspector-cell-layout");
1412 if (GTK_IS_CELL_LAYOUT (layout))
1413 {
1414 area = gtk_cell_layout_get_area (GTK_CELL_LAYOUT (layout));
1415 col = gtk_cell_area_attribute_get_column (area,
1416 GTK_CELL_RENDERER (object),
1417 attribute: self->name);
1418 model = gtk_cell_layout_get_model (GTK_CELL_LAYOUT (layout));
1419 }
1420
1421 box = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 10);
1422
1423 label = gtk_label_new (_("Attribute:"));
1424 gtk_box_append (GTK_BOX (box), child: label);
1425
1426 button = gtk_button_new_with_label (_("Model"));
1427 g_object_set_data (G_OBJECT (button), key: "model", data: model);
1428 g_signal_connect (button, "clicked", G_CALLBACK (model_properties), self);
1429 gtk_box_append (GTK_BOX (box), child: button);
1430
1431 gtk_box_append (GTK_BOX (box), child: gtk_label_new (_("Column:")));
1432 dropdown = gtk_drop_down_new (NULL, NULL);
1433
1434 store = g_list_store_new (ATTRIBUTE_TYPE_HOLDER);
1435 holder = attribute_holder_new (column: -1, TRUE);
1436 g_list_store_append (store, item: holder);
1437 g_object_unref (object: holder);
1438
1439 for (i = 0; i < gtk_tree_model_get_n_columns (tree_model: model); i++)
1440 {
1441 sensitive = g_value_type_transformable (src_type: gtk_tree_model_get_column_type (tree_model: model, index_: i),
1442 dest_type: spec->value_type);
1443 holder = attribute_holder_new (column: i, sensitive);
1444 g_list_store_append (store, item: holder);
1445 g_object_unref (object: holder);
1446 }
1447 gtk_drop_down_set_model (self: GTK_DROP_DOWN (ptr: dropdown), model: G_LIST_MODEL (ptr: store));
1448 g_object_unref (object: store);
1449
1450 factory = gtk_signal_list_item_factory_new ();
1451 g_signal_connect (factory, "setup", G_CALLBACK (attribute_setup_item), NULL);
1452 g_signal_connect (factory, "bind", G_CALLBACK (attribute_bind_item), NULL);
1453 gtk_drop_down_set_factory (self: GTK_DROP_DOWN (ptr: dropdown), factory);
1454 g_object_unref (object: factory);
1455
1456 gtk_drop_down_set_selected (self: GTK_DROP_DOWN (ptr: dropdown), position: col + 1);
1457 attribute_mapping_changed (dropdown: GTK_DROP_DOWN (ptr: dropdown), NULL, self);
1458 g_signal_connect (dropdown, "notify::selected",
1459 G_CALLBACK (attribute_mapping_changed), self);
1460
1461 gtk_box_append (GTK_BOX (box), child: dropdown);
1462
1463 return box;
1464}
1465
1466static GObject *
1467find_action_owner (GtkActionable *actionable)
1468{
1469 GtkWidget *widget = GTK_WIDGET (actionable);
1470 const char *full_name;
1471 GtkWidget *win;
1472
1473 full_name = gtk_actionable_get_action_name (actionable);
1474 if (!full_name)
1475 return NULL;
1476
1477 win = gtk_widget_get_ancestor (widget, GTK_TYPE_APPLICATION_WINDOW);
1478 if (g_str_has_prefix (str: full_name, prefix: "win.") == 0)
1479 {
1480 if (G_IS_OBJECT (win))
1481 return (GObject *)win;
1482 }
1483 else if (g_str_has_prefix (str: full_name, prefix: "app.") == 0)
1484 {
1485 if (GTK_IS_WINDOW (win))
1486 return (GObject *)gtk_window_get_application (GTK_WINDOW (win));
1487 }
1488
1489 while (widget != NULL)
1490 {
1491 GtkActionMuxer *muxer;
1492
1493 muxer = _gtk_widget_get_action_muxer (widget, FALSE);
1494 if (muxer && gtk_action_muxer_find (muxer, action_name: full_name, NULL))
1495 return (GObject *)widget;
1496
1497 widget = gtk_widget_get_parent (widget);
1498 }
1499
1500 return NULL;
1501}
1502
1503static void
1504show_action_owner (GtkButton *button,
1505 GtkInspectorPropEditor *self)
1506{
1507 GObject *owner;
1508
1509 owner = g_object_get_data (G_OBJECT (button), key: "owner");
1510 g_signal_emit (instance: self, signal_id: signals[SHOW_OBJECT], detail: 0, owner, NULL, "actions");
1511}
1512
1513static GtkWidget *
1514action_editor (GObject *object,
1515 GtkInspectorPropEditor *self)
1516{
1517 GtkWidget *box;
1518 GtkWidget *button;
1519 GObject *owner;
1520 char *text;
1521
1522 owner = find_action_owner (GTK_ACTIONABLE (object));
1523
1524 box = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 10);
1525 if (owner)
1526 {
1527 /* Translators: %s is a type name, for example
1528 * Action from 0x2345678 (GtkApplicationWindow)
1529 */
1530 text = g_strdup_printf (_("Action from: %p (%s)"),
1531 owner, g_type_name_from_instance (instance: (GTypeInstance *)owner));
1532 gtk_box_append (GTK_BOX (box), child: gtk_label_new (str: text));
1533 g_free (mem: text);
1534 button = gtk_button_new_with_label (_("Properties"));
1535 g_object_set_data (G_OBJECT (button), key: "owner", data: owner);
1536 g_signal_connect (button, "clicked",
1537 G_CALLBACK (show_action_owner), self);
1538 gtk_box_append (GTK_BOX (box), child: button);
1539 }
1540
1541 return box;
1542}
1543
1544static void
1545add_attribute_info (GtkInspectorPropEditor *self,
1546 GParamSpec *spec)
1547{
1548 if (GTK_IS_CELL_RENDERER (self->object))
1549 gtk_box_append (GTK_BOX (self),
1550 child: attribute_editor (object: self->object, spec, self));
1551}
1552
1553static void
1554add_actionable_info (GtkInspectorPropEditor *self)
1555{
1556 if (GTK_IS_ACTIONABLE (self->object) &&
1557 g_strcmp0 (str1: self->name, str2: "action-name") == 0)
1558 gtk_box_append (GTK_BOX (self),
1559 child: action_editor (object: self->object, self));
1560}
1561
1562static void
1563reset_setting (GtkInspectorPropEditor *self)
1564{
1565 gtk_settings_reset_property (GTK_SETTINGS (self->object), name: self->name);
1566}
1567
1568static void
1569add_gtk_settings_info (GtkInspectorPropEditor *self)
1570{
1571 GObject *object;
1572 const char *name;
1573 GtkWidget *row;
1574 const char *source;
1575 GtkWidget *button;
1576
1577 object = self->object;
1578 name = self->name;
1579
1580 if (!GTK_IS_SETTINGS (object))
1581 return;
1582
1583 row = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 10);
1584
1585 button = gtk_button_new_with_label (_("Reset"));
1586 gtk_box_append (GTK_BOX (row), child: button);
1587 gtk_widget_set_sensitive (widget: button, FALSE);
1588 g_signal_connect_swapped (button, "clicked", G_CALLBACK (reset_setting), self);
1589
1590 switch (_gtk_settings_get_setting_source (GTK_SETTINGS (object), name))
1591 {
1592 case GTK_SETTINGS_SOURCE_DEFAULT:
1593 source = C_("GtkSettings source", "Default");
1594 break;
1595 case GTK_SETTINGS_SOURCE_THEME:
1596 source = C_("GtkSettings source", "Theme");
1597 break;
1598 case GTK_SETTINGS_SOURCE_XSETTING:
1599 source = C_("GtkSettings source", "XSettings");
1600 break;
1601 case GTK_SETTINGS_SOURCE_APPLICATION:
1602 gtk_widget_set_sensitive (widget: button, TRUE);
1603 source = C_("GtkSettings source", "Application");
1604 break;
1605 default:
1606 source = C_("GtkSettings source", "Unknown");
1607 break;
1608 }
1609 gtk_box_append (GTK_BOX (row), child: gtk_label_new (_("Source:")));
1610 gtk_box_append (GTK_BOX (row), child: gtk_label_new (str: source));
1611
1612 gtk_box_append (GTK_BOX (self), child: row);
1613}
1614
1615static void
1616readonly_changed (GObject *object,
1617 GParamSpec *spec,
1618 gpointer data)
1619{
1620 GValue gvalue = {0};
1621 char *value;
1622 char *type;
1623
1624 g_value_init (value: &gvalue, g_type: spec->value_type);
1625 g_object_get_property (object, property_name: spec->name, value: &gvalue);
1626 strdup_value_contents (value: &gvalue, contents: &value, type: &type);
1627
1628 gtk_label_set_label (GTK_LABEL (data), str: value);
1629
1630 g_value_unset (value: &gvalue);
1631 g_free (mem: value);
1632 g_free (mem: type);
1633}
1634
1635static void
1636constructed (GObject *object)
1637{
1638 GtkInspectorPropEditor *self = GTK_INSPECTOR_PROP_EDITOR (ptr: object);
1639 GParamSpec *spec;
1640 GtkWidget *label;
1641 gboolean can_modify;
1642 GtkWidget *box;
1643
1644 spec = find_property (self);
1645
1646 can_modify = ((spec->flags & G_PARAM_WRITABLE) != 0 &&
1647 (spec->flags & G_PARAM_CONSTRUCT_ONLY) == 0);
1648
1649 box = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 10);
1650
1651 if ((spec->flags & G_PARAM_CONSTRUCT_ONLY) != 0)
1652 label = gtk_label_new (str: "(construct-only)");
1653 else if ((spec->flags & G_PARAM_WRITABLE) == 0)
1654 label = gtk_label_new (str: "(not writable)");
1655 else
1656 label = NULL;
1657
1658 if (label)
1659 {
1660 gtk_widget_add_css_class (widget: label, css_class: "dim-label");
1661 gtk_box_append (GTK_BOX (box), child: label);
1662 }
1663
1664 /* By reaching this, we already know the property is readable.
1665 * Since all we can do for a GObject is dive down into it's properties
1666 * and inspect bindings and such, pretend to be mutable.
1667 */
1668 if (g_type_is_a (type: spec->value_type, G_TYPE_OBJECT))
1669 can_modify = TRUE;
1670
1671 if (!can_modify)
1672 {
1673 label = gtk_label_new (str: "");
1674 gtk_label_set_ellipsize (GTK_LABEL (label), mode: PANGO_ELLIPSIZE_END);
1675 gtk_label_set_max_width_chars (GTK_LABEL (label), n_chars: 20);
1676 gtk_label_set_xalign (GTK_LABEL (label), xalign: 0.0);
1677 gtk_widget_set_hexpand (widget: label, TRUE);
1678 gtk_widget_set_halign (widget: label, align: GTK_ALIGN_FILL);
1679 gtk_widget_add_css_class (widget: label, css_class: "dim-label");
1680 gtk_box_append (GTK_BOX (box), child: label);
1681
1682 readonly_changed (object: self->object, spec, data: label);
1683 g_object_connect_property (object: self->object, spec,
1684 G_CALLBACK (readonly_changed),
1685 data: label, G_OBJECT (label));
1686
1687 if (self->size_group)
1688 gtk_size_group_add_widget (size_group: self->size_group, widget: box);
1689 gtk_box_append (GTK_BOX (self), child: box);
1690 return;
1691 }
1692
1693 self->self = property_editor (object: self->object, spec, self);
1694 gtk_box_append (GTK_BOX (box), child: self->self);
1695 if (self->size_group)
1696 gtk_size_group_add_widget (size_group: self->size_group, widget: box);
1697 gtk_box_append (GTK_BOX (self), child: box);
1698
1699 add_attribute_info (self, spec);
1700 add_actionable_info (self);
1701 add_gtk_settings_info (self);
1702}
1703
1704static void
1705finalize (GObject *object)
1706{
1707 GtkInspectorPropEditor *self = GTK_INSPECTOR_PROP_EDITOR (ptr: object);
1708
1709 g_free (mem: self->name);
1710
1711 G_OBJECT_CLASS (gtk_inspector_prop_editor_parent_class)->finalize (object);
1712}
1713
1714static void
1715get_property (GObject *object,
1716 guint param_id,
1717 GValue *value,
1718 GParamSpec *pspec)
1719{
1720 GtkInspectorPropEditor *self = GTK_INSPECTOR_PROP_EDITOR (ptr: object);
1721
1722 switch (param_id)
1723 {
1724 case PROP_OBJECT:
1725 g_value_set_object (value, v_object: self->object);
1726 break;
1727
1728 case PROP_NAME:
1729 g_value_set_string (value, v_string: self->name);
1730 break;
1731
1732 case PROP_SIZE_GROUP:
1733 g_value_set_object (value, v_object: self->size_group);
1734 break;
1735
1736 default:
1737 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1738 break;
1739 }
1740}
1741
1742static void
1743set_property (GObject *object,
1744 guint param_id,
1745 const GValue *value,
1746 GParamSpec *pspec)
1747{
1748 GtkInspectorPropEditor *self = GTK_INSPECTOR_PROP_EDITOR (ptr: object);
1749
1750 switch (param_id)
1751 {
1752 case PROP_OBJECT:
1753 self->object = g_value_get_object (value);
1754 break;
1755
1756 case PROP_NAME:
1757 g_free (mem: self->name);
1758 self->name = g_value_dup_string (value);
1759 break;
1760
1761 case PROP_SIZE_GROUP:
1762 self->size_group = g_value_get_object (value);
1763 break;
1764
1765 default:
1766 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
1767 break;
1768 }
1769}
1770
1771static void
1772gtk_inspector_prop_editor_class_init (GtkInspectorPropEditorClass *klass)
1773{
1774 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1775 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1776
1777 object_class->constructed = constructed;
1778 object_class->finalize = finalize;
1779 object_class->get_property = get_property;
1780 object_class->set_property = set_property;
1781
1782 widget_class->focus = gtk_widget_focus_child;
1783 widget_class->grab_focus = gtk_widget_grab_focus_child;
1784
1785 signals[SHOW_OBJECT] =
1786 g_signal_new (signal_name: "show-object",
1787 G_TYPE_FROM_CLASS (object_class),
1788 signal_flags: G_SIGNAL_RUN_LAST,
1789 class_offset: 0,
1790 NULL, NULL, NULL,
1791 G_TYPE_NONE, n_params: 3, G_TYPE_OBJECT, G_TYPE_STRING, G_TYPE_STRING);
1792
1793 g_object_class_install_property (oclass: object_class, property_id: PROP_OBJECT,
1794 pspec: g_param_spec_object (name: "object", nick: "Object", blurb: "The object owning the property",
1795 G_TYPE_OBJECT, flags: G_PARAM_READWRITE|G_PARAM_CONSTRUCT));
1796
1797 g_object_class_install_property (oclass: object_class, property_id: PROP_NAME,
1798 pspec: g_param_spec_string (name: "name", nick: "Name", blurb: "The property name",
1799 NULL, flags: G_PARAM_READWRITE|G_PARAM_CONSTRUCT));
1800
1801 g_object_class_install_property (oclass: object_class, property_id: PROP_SIZE_GROUP,
1802 pspec: g_param_spec_object (name: "size-group", nick: "Size group", blurb: "The size group for the value part",
1803 GTK_TYPE_SIZE_GROUP, flags: G_PARAM_READWRITE|G_PARAM_CONSTRUCT));
1804}
1805
1806GtkWidget *
1807gtk_inspector_prop_editor_new (GObject *object,
1808 const char *name,
1809 GtkSizeGroup *values)
1810{
1811 return g_object_new (GTK_TYPE_INSPECTOR_PROP_EDITOR,
1812 first_property_name: "object", object,
1813 "name", name,
1814 "size-group", values,
1815 NULL);
1816}
1817
1818gboolean
1819gtk_inspector_prop_editor_should_expand (GtkInspectorPropEditor *self)
1820{
1821 if (GTK_IS_SCROLLED_WINDOW (self->self))
1822 {
1823 GtkPolicyType policy;
1824
1825 g_object_get (object: self->self, first_property_name: "vscrollbar-policy", &policy, NULL);
1826 if (policy != GTK_POLICY_NEVER)
1827 return TRUE;
1828 }
1829
1830 return FALSE;
1831}
1832
1833
1834// vim: set et:
1835

source code of gtk/gtk/inspector/prop-editor.c