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 | |
41 | typedef enum { |
42 | STATE, |
43 | PROPERTY, |
44 | RELATION |
45 | } AttributeKind; |
46 | |
47 | typedef struct _AccessibleAttribute AccessibleAttribute; |
48 | typedef struct _AccessibleAttributeClass AccessibleAttributeClass; |
49 | |
50 | struct _AccessibleAttribute |
51 | { |
52 | GObject parent_instance; |
53 | AttributeKind kind; |
54 | int attribute; |
55 | char *name; |
56 | gboolean is_default; |
57 | GtkAccessibleValue *value; |
58 | }; |
59 | |
60 | struct _AccessibleAttributeClass |
61 | { |
62 | GObjectClass parent_class; |
63 | }; |
64 | |
65 | enum { |
66 | PROP_KIND = 1, |
67 | PROP_ATTRIBUTE, |
68 | PROP_NAME, |
69 | PROP_IS_DEFAULT, |
70 | PROP_VALUE |
71 | }; |
72 | |
73 | static GType accessible_attribute_get_type (void); |
74 | |
75 | G_DEFINE_TYPE (AccessibleAttribute, accessible_attribute, G_TYPE_OBJECT); |
76 | |
77 | static void |
78 | accessible_attribute_init (AccessibleAttribute *object) |
79 | { |
80 | } |
81 | |
82 | static void |
83 | accessible_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 | |
93 | static void |
94 | accessible_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 | |
130 | static void |
131 | accessible_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 | |
165 | static void |
166 | accessible_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 | |
196 | struct _GtkInspectorA11y |
197 | { |
198 | GtkWidget parent; |
199 | |
200 | GObject *object; |
201 | |
202 | GtkWidget *box; |
203 | GtkWidget *role; |
204 | GtkWidget *path; |
205 | GtkWidget *attributes; |
206 | }; |
207 | |
208 | typedef struct _GtkInspectorA11yClass |
209 | { |
210 | GtkWidgetClass parent_class; |
211 | } GtkInspectorA11yClass; |
212 | |
213 | G_DEFINE_TYPE (GtkInspectorA11y, gtk_inspector_a11y, GTK_TYPE_WIDGET) |
214 | |
215 | static void |
216 | update_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 | |
230 | static void |
231 | update_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 | |
245 | extern GType gtk_string_pair_get_type (void); |
246 | |
247 | static void |
248 | update_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 | |
348 | static void |
349 | setup_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 | |
361 | static void |
362 | bind_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 | |
379 | static void |
380 | bind_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 | |
400 | static void |
401 | refresh_all (GtkInspectorA11y *sl) |
402 | { |
403 | update_role (sl); |
404 | update_path (sl); |
405 | update_attributes (sl); |
406 | } |
407 | |
408 | void |
409 | gtk_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 | |
444 | static void |
445 | gtk_inspector_a11y_init (GtkInspectorA11y *sl) |
446 | { |
447 | gtk_widget_init_template (GTK_WIDGET (sl)); |
448 | } |
449 | |
450 | static void |
451 | dispose (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 | |
470 | static void |
471 | gtk_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 | |