1/*
2 * Copyright (c) 2020 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 "a11y.h"
22#include "window.h"
23
24#include "gtktypebuiltins.h"
25#include "gtklabel.h"
26#include "gtkstack.h"
27#include "gtkbinlayout.h"
28#include "gtkaccessibleprivate.h"
29#include "gtkaccessiblevalueprivate.h"
30#include "gtkatcontextprivate.h"
31#include "gtkcolumnview.h"
32#include "gtksignallistitemfactory.h"
33#include "gtklistitem.h"
34#include "gtknoselection.h"
35#include "gtkfilterlistmodel.h"
36#include "gtkboolfilter.h"
37#ifdef G_OS_UNIX
38#include "a11y/gtkatspicontextprivate.h"
39#endif
40
41typedef enum {
42 STATE,
43 PROPERTY,
44 RELATION
45} AttributeKind;
46
47typedef struct _AccessibleAttribute AccessibleAttribute;
48typedef struct _AccessibleAttributeClass AccessibleAttributeClass;
49
50struct _AccessibleAttribute
51{
52 GObject parent_instance;
53 AttributeKind kind;
54 int attribute;
55 char *name;
56 gboolean is_default;
57 GtkAccessibleValue *value;
58};
59
60struct _AccessibleAttributeClass
61{
62 GObjectClass parent_class;
63};
64
65enum {
66 PROP_KIND = 1,
67 PROP_ATTRIBUTE,
68 PROP_NAME,
69 PROP_IS_DEFAULT,
70 PROP_VALUE
71};
72
73static GType accessible_attribute_get_type (void);
74
75G_DEFINE_TYPE (AccessibleAttribute, accessible_attribute, G_TYPE_OBJECT);
76
77static void
78accessible_attribute_init (AccessibleAttribute *object)
79{
80}
81
82static void
83accessible_attribute_finalize (GObject *object)
84{
85 AccessibleAttribute *self = (AccessibleAttribute *)object;
86
87 g_free (mem: self->name);
88 gtk_accessible_value_unref (self: self->value);
89
90 G_OBJECT_CLASS (accessible_attribute_parent_class)->finalize (object);
91}
92
93static void
94accessible_attribute_set_property (GObject *object,
95 guint prop_id,
96 const GValue *value,
97 GParamSpec *pspec)
98{
99 AccessibleAttribute *self = (AccessibleAttribute *)object;
100
101 switch (prop_id)
102 {
103 case PROP_KIND:
104 self->kind = g_value_get_uint (value);
105 break;
106
107 case PROP_ATTRIBUTE:
108 self->attribute = g_value_get_uint (value);
109 break;
110
111 case PROP_NAME:
112 g_clear_pointer (&self->name, g_free);
113 self->name = g_value_dup_string (value);
114 break;
115
116 case PROP_IS_DEFAULT:
117 self->is_default = g_value_get_boolean (value);
118 break;
119
120 case PROP_VALUE:
121 g_clear_pointer (&self->value, gtk_accessible_value_unref);
122 self->value = g_value_dup_boxed (value);
123 break;
124
125 default:
126 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
127 }
128}
129
130static void
131accessible_attribute_get_property (GObject *object,
132 guint prop_id,
133 GValue *value,
134 GParamSpec *pspec)
135{
136 AccessibleAttribute *self = (AccessibleAttribute *)object;
137
138 switch (prop_id)
139 {
140 case PROP_KIND:
141 g_value_set_uint (value, v_uint: self->kind);
142 break;
143
144 case PROP_ATTRIBUTE:
145 g_value_set_uint (value, v_uint: self->attribute);
146 break;
147
148 case PROP_NAME:
149 g_value_set_string (value, v_string: self->name);
150 break;
151
152 case PROP_IS_DEFAULT:
153 g_value_set_boolean (value, v_boolean: self->is_default);
154 break;
155
156 case PROP_VALUE:
157 g_value_set_boxed (value, v_boxed: self->value);
158 break;
159
160 default:
161 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
162 }
163}
164
165static void
166accessible_attribute_class_init (AccessibleAttributeClass *class)
167{
168 GObjectClass *object_class = G_OBJECT_CLASS (class);
169
170 object_class->finalize = accessible_attribute_finalize;
171 object_class->set_property = accessible_attribute_set_property;
172 object_class->get_property = accessible_attribute_get_property;
173
174 g_object_class_install_property (oclass: object_class, property_id: PROP_KIND,
175 pspec: g_param_spec_uint (name: "kind", nick: "kind", blurb: "kind",
176 minimum: 0, maximum: 2, default_value: 0,
177 flags: G_PARAM_READWRITE));
178 g_object_class_install_property (oclass: object_class, property_id: PROP_ATTRIBUTE,
179 pspec: g_param_spec_uint (name: "attribute", nick: "attribute", blurb: "attribute",
180 minimum: 0, G_MAXUINT, default_value: 0,
181 flags: G_PARAM_READWRITE));
182 g_object_class_install_property (oclass: object_class, property_id: PROP_NAME,
183 pspec: g_param_spec_string (name: "name", nick: "name", blurb: "name",
184 NULL,
185 flags: G_PARAM_READWRITE));
186 g_object_class_install_property (oclass: object_class, property_id: PROP_IS_DEFAULT,
187 pspec: g_param_spec_boolean (name: "is-default", nick: "is-default", blurb: "is-default",
188 FALSE,
189 flags: G_PARAM_READWRITE));
190 g_object_class_install_property (oclass: object_class, property_id: PROP_VALUE,
191 pspec: g_param_spec_boxed (name: "value", nick: "value", blurb: "value",
192 GTK_TYPE_ACCESSIBLE_VALUE,
193 flags: G_PARAM_READWRITE));
194}
195
196struct _GtkInspectorA11y
197{
198 GtkWidget parent;
199
200 GObject *object;
201
202 GtkWidget *box;
203 GtkWidget *role;
204 GtkWidget *path;
205 GtkWidget *attributes;
206};
207
208typedef struct _GtkInspectorA11yClass
209{
210 GtkWidgetClass parent_class;
211} GtkInspectorA11yClass;
212
213G_DEFINE_TYPE (GtkInspectorA11y, gtk_inspector_a11y, GTK_TYPE_WIDGET)
214
215static void
216update_role (GtkInspectorA11y *sl)
217{
218 GtkAccessibleRole role;
219 GEnumClass *eclass;
220 GEnumValue *value;
221
222 role = gtk_accessible_get_accessible_role (self: GTK_ACCESSIBLE (ptr: sl->object));
223
224 eclass = g_type_class_ref (type: GTK_TYPE_ACCESSIBLE_ROLE);
225 value = g_enum_get_value (enum_class: eclass, value: role);
226 gtk_label_set_label (GTK_LABEL (sl->role), str: value->value_nick);
227 g_type_class_unref (g_class: eclass);
228}
229
230static void
231update_path (GtkInspectorA11y *sl)
232{
233 const char *path = "—";
234#ifdef G_OS_UNIX
235 GtkATContext *context;
236
237 context = gtk_accessible_get_at_context (self: GTK_ACCESSIBLE (ptr: sl->object));
238 if (GTK_IS_AT_SPI_CONTEXT (ptr: context))
239 path = gtk_at_spi_context_get_context_path (self: GTK_AT_SPI_CONTEXT (ptr: context));
240#endif
241
242 gtk_label_set_label (GTK_LABEL (sl->path), str: path);
243}
244
245extern GType gtk_string_pair_get_type (void);
246
247static void
248update_attributes (GtkInspectorA11y *sl)
249{
250 GtkATContext *context;
251 GListStore *store;
252 GtkBoolFilter *filter;
253 GtkFilterListModel *filter_model;
254 GtkNoSelection *selection;
255 GObject *obj;
256 GEnumClass *eclass;
257 guint i;
258 const char *name;
259 GtkAccessibleState state;
260 GtkAccessibleProperty prop;
261 GtkAccessibleRelation rel;
262 GtkAccessibleValue *value;
263 gboolean has_value;
264
265 context = gtk_accessible_get_at_context (self: GTK_ACCESSIBLE (ptr: sl->object));
266 if (!context)
267 return;
268
269 store = g_list_store_new (G_TYPE_OBJECT);
270
271 eclass = g_type_class_ref (type: GTK_TYPE_ACCESSIBLE_STATE);
272
273 for (i = 0; i < eclass->n_values; i++)
274 {
275 state = eclass->values[i].value;
276 name = eclass->values[i].value_nick;
277 has_value = gtk_at_context_has_accessible_state (self: context, state);
278 value = gtk_at_context_get_accessible_state (self: context, state);
279 obj = g_object_new (object_type: accessible_attribute_get_type (),
280 first_property_name: "kind", STATE,
281 "attribute", state,
282 "name", name,
283 "is-default", !has_value,
284 "value", value,
285 NULL);
286 g_list_store_append (store, item: obj);
287 g_object_unref (object: obj);
288 }
289
290 g_type_class_unref (g_class: eclass);
291
292 eclass = g_type_class_ref (type: GTK_TYPE_ACCESSIBLE_PROPERTY);
293
294 for (i = 0; i < eclass->n_values; i++)
295 {
296 prop = eclass->values[i].value;
297 name = eclass->values[i].value_nick;
298 has_value = gtk_at_context_has_accessible_property (self: context, property: prop);
299 value = gtk_at_context_get_accessible_property (self: context, property: prop);
300 obj = g_object_new (object_type: accessible_attribute_get_type (),
301 first_property_name: "kind", PROPERTY,
302 "attribute", prop,
303 "name", name,
304 "is-default", !has_value,
305 "value", value,
306 NULL);
307 g_list_store_append (store, item: obj);
308 g_object_unref (object: obj);
309 }
310
311 g_type_class_unref (g_class: eclass);
312
313 eclass = g_type_class_ref (type: GTK_TYPE_ACCESSIBLE_RELATION);
314
315 for (i = 0; i < eclass->n_values; i++)
316 {
317 rel = eclass->values[i].value;
318 name = eclass->values[i].value_nick;
319 has_value = gtk_at_context_has_accessible_relation (self: context, relation: rel);
320 value = gtk_at_context_get_accessible_relation (self: context, relation: rel);
321 obj = g_object_new (object_type: accessible_attribute_get_type (),
322 first_property_name: "kind", RELATION,
323 "attribute", rel,
324 "name", name,
325 "is-default", !has_value,
326 "value", value,
327 NULL);
328 g_list_store_append (store, item: obj);
329 g_object_unref (object: obj);
330 }
331
332 g_type_class_unref (g_class: eclass);
333
334 filter = gtk_bool_filter_new (expression: gtk_property_expression_new (this_type: accessible_attribute_get_type (), NULL, property_name: "is-default"));
335 gtk_bool_filter_set_invert (self: filter, TRUE);
336
337 filter_model = gtk_filter_list_model_new (model: G_LIST_MODEL (ptr: store), filter: GTK_FILTER (ptr: filter));
338 selection = gtk_no_selection_new (model: G_LIST_MODEL (ptr: filter_model));
339 gtk_column_view_set_model (GTK_COLUMN_VIEW (sl->attributes), model: GTK_SELECTION_MODEL (ptr: selection));
340 g_object_unref (object: selection);
341
342 if (g_list_model_get_n_items (list: G_LIST_MODEL (ptr: filter_model)) > 0)
343 gtk_widget_show (widget: sl->attributes);
344 else
345 gtk_widget_hide (widget: sl->attributes);
346}
347
348static void
349setup_cell_cb (GtkSignalListItemFactory *factory,
350 GtkListItem *list_item)
351{
352 GtkWidget *label;
353
354 label = gtk_label_new (NULL);
355 gtk_label_set_xalign (GTK_LABEL (label), xalign: 0.0);
356 gtk_widget_set_margin_start (widget: label, margin: 6);
357 gtk_widget_set_margin_end (widget: label, margin: 6);
358 gtk_list_item_set_child (self: list_item, child: label);
359}
360
361static void
362bind_name_cb (GtkSignalListItemFactory *factory,
363 GtkListItem *list_item)
364{
365 AccessibleAttribute *item;
366 GtkWidget *label;
367
368 item = gtk_list_item_get_item (self: list_item);
369 label = gtk_list_item_get_child (self: list_item);
370
371 if (item->is_default)
372 gtk_widget_add_css_class (widget: label, css_class: "dim-label");
373 else
374 gtk_widget_remove_css_class (widget: label, css_class: "dim-label");
375
376 gtk_label_set_label (GTK_LABEL (label), str: item->name);
377}
378
379static void
380bind_value_cb (GtkSignalListItemFactory *factory,
381 GtkListItem *list_item)
382{
383 AccessibleAttribute *item;
384 GtkWidget *label;
385 char *string;
386
387 item = gtk_list_item_get_item (self: list_item);
388 label = gtk_list_item_get_child (self: list_item);
389
390 if (item->is_default)
391 gtk_widget_add_css_class (widget: label, css_class: "dim-label");
392 else
393 gtk_widget_remove_css_class (widget: label, css_class: "dim-label");
394
395 string = gtk_accessible_value_to_string (self: item->value);
396 gtk_label_set_label (GTK_LABEL (label), str: string);
397 g_free (mem: string);
398}
399
400static void
401refresh_all (GtkInspectorA11y *sl)
402{
403 update_role (sl);
404 update_path (sl);
405 update_attributes (sl);
406}
407
408void
409gtk_inspector_a11y_set_object (GtkInspectorA11y *sl,
410 GObject *object)
411{
412 GtkWidget *stack;
413 GtkStackPage *page;
414 GtkATContext *context;
415
416 if (sl->object && GTK_IS_ACCESSIBLE (ptr: sl->object))
417 {
418 context = gtk_accessible_get_at_context (self: GTK_ACCESSIBLE (ptr: sl->object));
419 if (context)
420 g_signal_handlers_disconnect_by_func (context, refresh_all, sl);
421 }
422
423 g_set_object (&sl->object, object);
424
425 stack = gtk_widget_get_parent (GTK_WIDGET (sl));
426 page = gtk_stack_get_page (GTK_STACK (stack), GTK_WIDGET (sl));
427
428 if (GTK_IS_ACCESSIBLE (ptr: object))
429 {
430 context = gtk_accessible_get_at_context (self: GTK_ACCESSIBLE (ptr: sl->object));
431 if (context)
432 g_signal_connect_swapped (context, "state-change", G_CALLBACK (refresh_all), sl);
433 gtk_stack_page_set_visible (self: page, TRUE);
434 update_role (sl);
435 update_path (sl);
436 update_attributes (sl);
437 }
438 else
439 {
440 gtk_stack_page_set_visible (self: page, FALSE);
441 }
442}
443
444static void
445gtk_inspector_a11y_init (GtkInspectorA11y *sl)
446{
447 gtk_widget_init_template (GTK_WIDGET (sl));
448}
449
450static void
451dispose (GObject *o)
452{
453 GtkInspectorA11y *sl = GTK_INSPECTOR_A11Y (o);
454
455 if (sl->object && GTK_IS_ACCESSIBLE (ptr: sl->object))
456 {
457 GtkATContext *context;
458
459 context = gtk_accessible_get_at_context (self: GTK_ACCESSIBLE (ptr: sl->object));
460 g_signal_handlers_disconnect_by_func (context, refresh_all, sl);
461 }
462
463 g_clear_object (&sl->object);
464
465 g_clear_pointer (&sl->box, gtk_widget_unparent);
466
467 G_OBJECT_CLASS (gtk_inspector_a11y_parent_class)->dispose (o);
468}
469
470static void
471gtk_inspector_a11y_class_init (GtkInspectorA11yClass *klass)
472{
473 GObjectClass *object_class = G_OBJECT_CLASS (klass);
474 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
475
476 object_class->dispose = dispose;
477
478 gtk_widget_class_set_template_from_resource (widget_class, resource_name: "/org/gtk/libgtk/inspector/a11y.ui");
479 gtk_widget_class_bind_template_child (widget_class, GtkInspectorA11y, box);
480 gtk_widget_class_bind_template_child (widget_class, GtkInspectorA11y, role);
481 gtk_widget_class_bind_template_child (widget_class, GtkInspectorA11y, path);
482 gtk_widget_class_bind_template_child (widget_class, GtkInspectorA11y, attributes);
483
484 gtk_widget_class_bind_template_callback (widget_class, setup_cell_cb);
485 gtk_widget_class_bind_template_callback (widget_class, bind_name_cb);
486 gtk_widget_class_bind_template_callback (widget_class, bind_value_cb);
487
488 gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
489}
490
491// vim: set et sw=2 ts=2:
492

source code of gtk/gtk/inspector/a11y.c