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