1 /* gtkatspiselection.c: AT-SPI Selection implementation
2  *
3  * Copyright 2020 Red Hat, Inc.
4  *
5  * SPDX-License-Identifier: LGPL-2.1-or-later
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include "config.h"
22 
23 #include "gtkatspiselectionprivate.h"
24 
25 #include "a11y/atspi/atspi-selection.h"
26 
27 #include "gtkatcontextprivate.h"
28 #include "gtkatspicontextprivate.h"
29 #include "gtkaccessibleprivate.h"
30 #include "gtkdebug.h"
31 #include "gtklistbase.h"
32 #include "gtklistbox.h"
33 #include "gtkflowbox.h"
34 #include "gtkcombobox.h"
35 #include "gtkstackswitcher.h"
36 #include "gtknotebook.h"
37 #include "gtklistview.h"
38 #include "gtkgridview.h"
39 #include "gtklistitem.h"
40 #include "gtkbitset.h"
41 #include "gtklistbaseprivate.h"
42 #include "gtklistitemwidgetprivate.h"
43 
44 #include <gio/gio.h>
45 
46 typedef struct {
47   int n;
48   GtkWidget *child;
49 } Counter;
50 
51 static void
find_nth(GtkWidget * box,GtkWidget * child,gpointer data)52 find_nth (GtkWidget *box,
53           GtkWidget *child,
54           gpointer   data)
55 {
56   Counter *counter = data;
57 
58   if (counter->n == 0)
59     counter->child = child;
60 
61   counter->n--;
62 }
63 
64 /* {{{ GtkListbox */
65 
66 static void
listbox_handle_method(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * method_name,GVariant * parameters,GDBusMethodInvocation * invocation,gpointer user_data)67 listbox_handle_method (GDBusConnection       *connection,
68                        const gchar           *sender,
69                        const gchar           *object_path,
70                        const gchar           *interface_name,
71                        const gchar           *method_name,
72                        GVariant              *parameters,
73                        GDBusMethodInvocation *invocation,
74                        gpointer               user_data)
75 {
76   GtkATContext *self = user_data;
77   GtkAccessible *accessible = gtk_at_context_get_accessible (self);
78   GtkWidget *widget = GTK_WIDGET (accessible);
79 
80   if (g_strcmp0 (method_name, "GetSelectedChild") == 0)
81     {
82       Counter counter;
83       int idx;
84 
85       g_variant_get (parameters, "(i)", &idx);
86 
87       counter.n = idx;
88       counter.child = NULL;
89 
90       gtk_list_box_selected_foreach (GTK_LIST_BOX (widget), (GtkListBoxForeachFunc)find_nth, &counter);
91 
92       if (counter.child == NULL)
93         g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No selected child for %d", idx);
94       else
95         {
96           GtkATContext *ctx = gtk_accessible_get_at_context (GTK_ACCESSIBLE (counter.child));
97           g_dbus_method_invocation_return_value (invocation, g_variant_new ("(@(so))", gtk_at_spi_context_to_ref (GTK_AT_SPI_CONTEXT (ctx))));
98         }
99     }
100   else if (g_strcmp0 (method_name, "SelectChild") == 0)
101     {
102       int idx;
103       GtkListBoxRow *row;
104 
105       g_variant_get (parameters, "(i)", &idx);
106 
107       row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (widget), idx);
108       if (!row)
109         g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No child at position %d", idx);
110       else
111         {
112           gboolean ret;
113 
114           gtk_list_box_select_row (GTK_LIST_BOX (widget), row);
115           ret = gtk_list_box_row_is_selected (row);
116           g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
117         }
118     }
119   else if (g_strcmp0 (method_name, "DeselectChild") == 0)
120     {
121       int idx;
122       GtkListBoxRow *row;
123 
124       g_variant_get (parameters, "(i)", &idx);
125 
126       row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (widget), idx);
127       if (!row)
128         g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No child at position %d", idx);
129       else
130         {
131           gboolean ret;
132 
133           gtk_list_box_unselect_row (GTK_LIST_BOX (widget), row);
134           ret = !gtk_list_box_row_is_selected (row);
135           g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
136         }
137     }
138   else if (g_strcmp0 (method_name, "DeselectSelectedChild") == 0)
139     {
140       Counter counter;
141       int idx;
142 
143       g_variant_get (parameters, "(i)", &idx);
144 
145       counter.n = idx;
146       counter.child = NULL;
147 
148       gtk_list_box_selected_foreach (GTK_LIST_BOX (widget), (GtkListBoxForeachFunc)find_nth, &counter);
149 
150       if (counter.child == NULL)
151         g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No selected child for %d", idx);
152       else
153         {
154           gboolean ret;
155 
156           gtk_list_box_unselect_row (GTK_LIST_BOX (widget), GTK_LIST_BOX_ROW (counter.child));
157           ret = !gtk_list_box_row_is_selected (GTK_LIST_BOX_ROW (counter.child));
158           g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
159         }
160     }
161   else if (g_strcmp0 (method_name, "IsChildSelected") == 0)
162     {
163       int idx;
164       GtkListBoxRow *row;
165 
166       g_variant_get (parameters, "(i)", &idx);
167 
168       row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (widget), idx);
169       if (!row)
170         g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No child at position %d", idx);
171       else
172         g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", gtk_list_box_row_is_selected (row)));
173     }
174   else if (g_strcmp0 (method_name, "SelectAll") == 0)
175     {
176       gtk_list_box_select_all (GTK_LIST_BOX (widget));
177       g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", TRUE));
178     }
179   else if (g_strcmp0 (method_name, "ClearSelection") == 0)
180     {
181       gtk_list_box_unselect_all (GTK_LIST_BOX (widget));
182       g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", TRUE));
183     }
184 }
185 
186 static void
count_selected(GtkWidget * box,GtkWidget * child,gpointer data)187 count_selected (GtkWidget *box,
188                 GtkWidget *child,
189                 gpointer   data)
190 {
191   *(int *)data += 1;
192 }
193 
194 static GVariant *
listbox_get_property(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * property_name,GError ** error,gpointer user_data)195 listbox_get_property (GDBusConnection  *connection,
196                       const gchar      *sender,
197                       const gchar      *object_path,
198                       const gchar      *interface_name,
199                       const gchar      *property_name,
200                       GError          **error,
201                       gpointer          user_data)
202 {
203   GtkATContext *self = GTK_AT_CONTEXT (user_data);
204   GtkAccessible *accessible = gtk_at_context_get_accessible (self);
205   GtkWidget *widget = GTK_WIDGET (accessible);
206 
207   if (g_strcmp0 (property_name, "NSelectedChildren") == 0)
208     {
209       int count = 0;
210 
211       gtk_list_box_selected_foreach (GTK_LIST_BOX (widget), (GtkListBoxForeachFunc)count_selected, &count);
212 
213       return g_variant_new_int32 (count);
214     }
215 
216   return NULL;
217 }
218 
219 static const GDBusInterfaceVTable listbox_vtable = {
220   listbox_handle_method,
221   listbox_get_property,
222   NULL
223 };
224 
225 /* }}} */
226 /* {{{ GtkListView */
227 
228 static void
listview_handle_method(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * method_name,GVariant * parameters,GDBusMethodInvocation * invocation,gpointer user_data)229 listview_handle_method (GDBusConnection       *connection,
230                         const gchar           *sender,
231                         const gchar           *object_path,
232                         const gchar           *interface_name,
233                         const gchar           *method_name,
234                         GVariant              *parameters,
235                         GDBusMethodInvocation *invocation,
236                         gpointer               user_data)
237 {
238   GtkATContext *self = user_data;
239   GtkAccessible *accessible = gtk_at_context_get_accessible (self);
240   GtkWidget *widget = GTK_WIDGET (accessible);
241   GtkSelectionModel *model = gtk_list_base_get_model (GTK_LIST_BASE (widget));
242 
243   if (g_strcmp0 (method_name, "GetSelectedChild") == 0)
244     {
245       int idx;
246       GtkWidget *child;
247 
248       g_variant_get (parameters, "(i)", &idx);
249 
250       /* We are asked for the idx-the selected child *among the
251        * current children*
252        */
253       for (child = gtk_widget_get_first_child (widget);
254            child;
255            child = gtk_widget_get_next_sibling (child))
256         {
257           if (gtk_list_item_widget_get_selected (GTK_LIST_ITEM_WIDGET (child)))
258             {
259               if (idx == 0)
260                 break;
261               idx--;
262             }
263         }
264 
265       if (child == NULL)
266         g_dbus_method_invocation_return_error (invocation,
267                                                G_DBUS_ERROR,
268                                                G_DBUS_ERROR_INVALID_ARGS,
269                                                "No selected child for %d", idx);
270       else
271         {
272           GtkATContext *ctx = gtk_accessible_get_at_context (GTK_ACCESSIBLE (child));
273           g_dbus_method_invocation_return_value (invocation,
274               g_variant_new ("(@(so))", gtk_at_spi_context_to_ref (GTK_AT_SPI_CONTEXT (ctx))));
275         }
276     }
277   else if (g_strcmp0 (method_name, "SelectChild") == 0)
278     {
279       int idx;
280       GtkWidget *child;
281 
282       g_variant_get (parameters, "(i)", &idx);
283 
284       for (child = gtk_widget_get_first_child (widget);
285            child;
286            child = gtk_widget_get_next_sibling (child))
287         {
288           if (idx == 0)
289             break;
290           idx--;
291         }
292 
293       if (child == NULL)
294         g_dbus_method_invocation_return_error (invocation,
295                                                G_DBUS_ERROR,
296                                                G_DBUS_ERROR_INVALID_ARGS,
297                                                "No child for %d", idx);
298       else
299         {
300           guint pos;
301           gboolean ret;
302 
303           pos = gtk_list_item_widget_get_position (GTK_LIST_ITEM_WIDGET (child));
304           ret = gtk_selection_model_select_item (model, pos, FALSE);
305 
306           g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
307         }
308     }
309   else if (g_strcmp0 (method_name, "DeselectChild") == 0)
310     {
311       int idx;
312       GtkWidget *child;
313 
314       g_variant_get (parameters, "(i)", &idx);
315 
316       for (child = gtk_widget_get_first_child (widget);
317            child;
318            child = gtk_widget_get_next_sibling (child))
319         {
320           if (idx == 0)
321             break;
322           idx--;
323         }
324 
325       if (child == NULL)
326         g_dbus_method_invocation_return_error (invocation,
327                                                G_DBUS_ERROR,
328                                                G_DBUS_ERROR_INVALID_ARGS,
329                                                "No child for %d", idx);
330       else
331         {
332           guint pos;
333           gboolean ret;
334 
335           pos = gtk_list_item_widget_get_position (GTK_LIST_ITEM_WIDGET (child));
336           ret = gtk_selection_model_unselect_item (model, pos);
337 
338           g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
339         }
340     }
341   else if (g_strcmp0 (method_name, "DeselectSelectedChild") == 0)
342     {
343       int idx;
344       GtkWidget *child;
345 
346       g_variant_get (parameters, "(i)", &idx);
347 
348       /* We are asked for the n-th selected child *among the current children* */
349       for (child = gtk_widget_get_first_child (widget);
350            child;
351            child = gtk_widget_get_next_sibling (child))
352         {
353           if (gtk_list_item_widget_get_selected (GTK_LIST_ITEM_WIDGET (child)))
354             {
355               if (idx == 0)
356                 break;
357               idx--;
358             }
359         }
360 
361       if (child == NULL)
362         g_dbus_method_invocation_return_error (invocation,
363                                                G_DBUS_ERROR,
364                                                G_DBUS_ERROR_INVALID_ARGS,
365                                                "No selected child for %d", idx);
366       else
367         {
368           guint pos;
369           gboolean ret;
370 
371           pos = gtk_list_item_widget_get_position (GTK_LIST_ITEM_WIDGET (child));
372           ret = gtk_selection_model_unselect_item (model, pos);
373 
374           g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
375         }
376     }
377   else if (g_strcmp0 (method_name, "IsChildSelected") == 0)
378     {
379       int idx;
380       GtkWidget *child;
381 
382       g_variant_get (parameters, "(i)", &idx);
383 
384       for (child = gtk_widget_get_first_child (widget);
385            child;
386            child = gtk_widget_get_next_sibling (child))
387         {
388           if (idx == 0)
389             break;
390           idx--;
391         }
392 
393       if (child == NULL)
394         g_dbus_method_invocation_return_error (invocation,
395                                                G_DBUS_ERROR,
396                                                G_DBUS_ERROR_INVALID_ARGS,
397                                                "No child for %d", idx);
398       else
399         {
400           gboolean ret;
401 
402           ret = gtk_list_item_widget_get_selected (GTK_LIST_ITEM_WIDGET (child));
403 
404           g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
405         }
406     }
407   else if (g_strcmp0 (method_name, "SelectAll") == 0)
408     {
409       gboolean ret;
410 
411       /* This is a bit inconsistent - the Selection interface is defined in terms
412        * of the current children, but this selects all items in the model, whether
413        * they are currently represented or not.
414        */
415       ret = gtk_selection_model_select_all (model);
416 
417       g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
418     }
419   else if (g_strcmp0 (method_name, "ClearSelection") == 0)
420     {
421       gboolean ret;
422 
423       ret = gtk_selection_model_unselect_all (model);
424 
425       g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
426     }
427 }
428 
429 static GVariant *
listview_get_property(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * property_name,GError ** error,gpointer user_data)430 listview_get_property (GDBusConnection  *connection,
431                        const gchar      *sender,
432                        const gchar      *object_path,
433                        const gchar      *interface_name,
434                        const gchar      *property_name,
435                        GError          **error,
436                        gpointer          user_data)
437 {
438   GtkATContext *self = GTK_AT_CONTEXT (user_data);
439   GtkAccessible *accessible = gtk_at_context_get_accessible (self);
440   GtkWidget *widget = GTK_WIDGET (accessible);
441   GtkSelectionModel *model = gtk_list_base_get_model (GTK_LIST_BASE (widget));
442 
443   if (g_strcmp0 (property_name, "NSelectedChildren") == 0)
444     {
445       int count = 0;
446       GtkBitset *set;
447 
448       set = gtk_selection_model_get_selection (model);
449       count = gtk_bitset_get_size (set);
450       gtk_bitset_unref (set);
451 
452       return g_variant_new_int32 (count);
453     }
454 
455   return NULL;
456 }
457 
458 static const GDBusInterfaceVTable listview_vtable = {
459   listview_handle_method,
460   listview_get_property,
461   NULL
462 };
463 
464 /* }}} */
465 /* {{{ GtkFlowBox */
466 
467 static void
flowbox_handle_method(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * method_name,GVariant * parameters,GDBusMethodInvocation * invocation,gpointer user_data)468 flowbox_handle_method (GDBusConnection       *connection,
469                        const gchar           *sender,
470                        const gchar           *object_path,
471                        const gchar           *interface_name,
472                        const gchar           *method_name,
473                        GVariant              *parameters,
474                        GDBusMethodInvocation *invocation,
475                        gpointer               user_data)
476 {
477   GtkATContext *self = user_data;
478   GtkAccessible *accessible = gtk_at_context_get_accessible (self);
479   GtkWidget *widget = GTK_WIDGET (accessible);
480 
481   if (g_strcmp0 (method_name, "GetSelectedChild") == 0)
482     {
483       Counter counter;
484       int idx;
485 
486       g_variant_get (parameters, "(i)", &idx);
487 
488       counter.n = idx;
489       counter.child = NULL;
490 
491       gtk_flow_box_selected_foreach (GTK_FLOW_BOX (widget), (GtkFlowBoxForeachFunc)find_nth, &counter);
492 
493       if (counter.child == NULL)
494         g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No selected child for %d", idx);
495       else
496         {
497           GtkATContext *ctx = gtk_accessible_get_at_context (GTK_ACCESSIBLE (counter.child));
498           g_dbus_method_invocation_return_value (invocation, g_variant_new ("(@(so))", gtk_at_spi_context_to_ref (GTK_AT_SPI_CONTEXT (ctx))));
499         }
500     }
501   else if (g_strcmp0 (method_name, "SelectChild") == 0)
502     {
503       int idx;
504       GtkFlowBoxChild *child;
505 
506       g_variant_get (parameters, "(i)", &idx);
507 
508       child = gtk_flow_box_get_child_at_index (GTK_FLOW_BOX (widget), idx);
509       if (!child)
510         g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No child at position %d", idx);
511       else
512         {
513           gboolean ret;
514 
515           gtk_flow_box_select_child (GTK_FLOW_BOX (widget), child);
516           ret = gtk_flow_box_child_is_selected (child);
517           g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
518         }
519     }
520   else if (g_strcmp0 (method_name, "DeselectChild") == 0)
521     {
522       int idx;
523       GtkFlowBoxChild *child;
524 
525       g_variant_get (parameters, "(i)", &idx);
526 
527       child = gtk_flow_box_get_child_at_index (GTK_FLOW_BOX (widget), idx);
528       if (!child)
529         g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No child at position %d", idx);
530       else
531         {
532           gboolean ret;
533 
534           gtk_flow_box_unselect_child (GTK_FLOW_BOX (widget), child);
535           ret = !gtk_flow_box_child_is_selected (child);
536           g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
537         }
538     }
539   else if (g_strcmp0 (method_name, "DeselectSelectedChild") == 0)
540     {
541       Counter counter;
542       int idx;
543 
544       g_variant_get (parameters, "(i)", &idx);
545 
546       counter.n = idx;
547       counter.child = NULL;
548 
549       gtk_flow_box_selected_foreach (GTK_FLOW_BOX (widget), (GtkFlowBoxForeachFunc)find_nth, &counter);
550 
551       if (counter.child == NULL)
552         g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No selected child for %d", idx);
553       else
554         {
555           gboolean ret;
556 
557           gtk_flow_box_unselect_child (GTK_FLOW_BOX (widget), GTK_FLOW_BOX_CHILD (counter.child));
558           ret = !gtk_flow_box_child_is_selected (GTK_FLOW_BOX_CHILD (counter.child));
559           g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", ret));
560         }
561     }
562   else if (g_strcmp0 (method_name, "IsChildSelected") == 0)
563     {
564       int idx;
565       GtkFlowBoxChild *child;
566 
567       g_variant_get (parameters, "(i)", &idx);
568 
569       child = gtk_flow_box_get_child_at_index (GTK_FLOW_BOX (widget), idx);
570       if (!child)
571         g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No child at position %d", idx);
572       else
573         g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", gtk_flow_box_child_is_selected (child)));
574     }
575   else if (g_strcmp0 (method_name, "SelectAll") == 0)
576     {
577       gtk_flow_box_select_all (GTK_FLOW_BOX (widget));
578       g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", TRUE));
579     }
580   else if (g_strcmp0 (method_name, "ClearSelection") == 0)
581     {
582       gtk_flow_box_unselect_all (GTK_FLOW_BOX (widget));
583       g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", TRUE));
584     }
585 }
586 
587 static GVariant *
flowbox_get_property(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * property_name,GError ** error,gpointer user_data)588 flowbox_get_property (GDBusConnection  *connection,
589                       const gchar      *sender,
590                       const gchar      *object_path,
591                       const gchar      *interface_name,
592                       const gchar      *property_name,
593                       GError          **error,
594                       gpointer          user_data)
595 {
596   GtkATContext *self = GTK_AT_CONTEXT (user_data);
597   GtkAccessible *accessible = gtk_at_context_get_accessible (self);
598   GtkWidget *widget = GTK_WIDGET (accessible);
599 
600   if (g_strcmp0 (property_name, "NSelectedChildren") == 0)
601     {
602       int count = 0;
603 
604       gtk_flow_box_selected_foreach (GTK_FLOW_BOX (widget), (GtkFlowBoxForeachFunc)count_selected, &count);
605 
606       return g_variant_new_int32 (count);
607     }
608 
609   return NULL;
610 }
611 
612 static const GDBusInterfaceVTable flowbox_vtable = {
613   flowbox_handle_method,
614   flowbox_get_property,
615   NULL
616 };
617 
618 /* }}} */
619 /* {{{ GtkComboBox */
620 
621 static void
combobox_handle_method(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * method_name,GVariant * parameters,GDBusMethodInvocation * invocation,gpointer user_data)622 combobox_handle_method (GDBusConnection       *connection,
623                         const gchar           *sender,
624                         const gchar           *object_path,
625                         const gchar           *interface_name,
626                         const gchar           *method_name,
627                         GVariant              *parameters,
628                         GDBusMethodInvocation *invocation,
629                         gpointer               user_data)
630 {
631   GtkATContext *self = user_data;
632   GtkAccessible *accessible = gtk_at_context_get_accessible (self);
633   GtkWidget *widget = GTK_WIDGET (accessible);
634 
635   if (g_strcmp0 (method_name, "GetSelectedChild") == 0)
636     {
637       /* Need to figure out what to do here */
638       g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, "");
639     }
640   else if (g_strcmp0 (method_name, "SelectChild") == 0)
641     {
642       int idx;
643 
644       g_variant_get (parameters, "(i)", &idx);
645       gtk_combo_box_set_active (GTK_COMBO_BOX (widget), idx);
646       g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", TRUE));
647     }
648   else if (g_strcmp0 (method_name, "DeselectChild") == 0)
649     {
650       int idx;
651 
652       g_variant_get (parameters, "(i)", &idx);
653 
654       gtk_combo_box_set_active (GTK_COMBO_BOX (widget), -1);
655       g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", TRUE));
656     }
657   else if (g_strcmp0 (method_name, "DeselectSelectedChild") == 0)
658     {
659       int idx;
660 
661       g_variant_get (parameters, "(i)", &idx);
662       if (idx == 0)
663         gtk_combo_box_set_active (GTK_COMBO_BOX (widget), -1);
664       g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", idx == 0));
665     }
666   else if (g_strcmp0 (method_name, "IsChildSelected") == 0)
667     {
668       int idx;
669       gboolean active;
670 
671       g_variant_get (parameters, "(i)", &idx);
672       active = idx = gtk_combo_box_get_active (GTK_COMBO_BOX (widget));
673       g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", active));
674     }
675   else if (g_strcmp0 (method_name, "SelectAll") == 0)
676     {
677       g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", FALSE));
678     }
679   else if (g_strcmp0 (method_name, "ClearSelection") == 0)
680     {
681       gtk_combo_box_set_active (GTK_COMBO_BOX (widget), -1);
682       g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", TRUE));
683     }
684 }
685 
686 static GVariant *
combobox_get_property(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * property_name,GError ** error,gpointer user_data)687 combobox_get_property (GDBusConnection  *connection,
688                        const gchar      *sender,
689                        const gchar      *object_path,
690                        const gchar      *interface_name,
691                        const gchar      *property_name,
692                        GError          **error,
693                        gpointer          user_data)
694 {
695   GtkATContext *self = GTK_AT_CONTEXT (user_data);
696   GtkAccessible *accessible = gtk_at_context_get_accessible (self);
697   GtkWidget *widget = GTK_WIDGET (accessible);
698 
699   if (g_strcmp0 (property_name, "NSelectedChildren") == 0)
700     {
701       if (gtk_combo_box_get_active (GTK_COMBO_BOX (widget)))
702         return g_variant_new_int32 (1);
703       else
704         return g_variant_new_int32 (0);
705     }
706 
707   return NULL;
708 }
709 
710 static const GDBusInterfaceVTable combobox_vtable = {
711   combobox_handle_method,
712   combobox_get_property,
713   NULL
714 };
715 
716 /* }}} */
717 /* {{{ GtkStackSwitcher */
718 
719 static void
stackswitcher_handle_method(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * method_name,GVariant * parameters,GDBusMethodInvocation * invocation,gpointer user_data)720 stackswitcher_handle_method (GDBusConnection       *connection,
721                              const gchar           *sender,
722                              const gchar           *object_path,
723                              const gchar           *interface_name,
724                              const gchar           *method_name,
725                              GVariant              *parameters,
726                              GDBusMethodInvocation *invocation,
727                              gpointer               user_data)
728 {
729   GtkATContext *self = user_data;
730   GtkAccessible *accessible = gtk_at_context_get_accessible (self);
731   GtkWidget *widget = GTK_WIDGET (accessible);
732   GtkStack *stack = gtk_stack_switcher_get_stack (GTK_STACK_SWITCHER (widget));
733 
734   if (g_strcmp0 (method_name, "GetSelectedChild") == 0)
735     {
736       guint i, n;
737       GtkSelectionModel *pages;
738       GtkWidget *child;
739 
740       pages = gtk_stack_get_pages (stack);
741       n = g_list_model_get_n_items (G_LIST_MODEL (pages));
742       for (i = 0, child = gtk_widget_get_first_child (widget);
743            i < n && child;
744            i++, child = gtk_widget_get_next_sibling (child))
745         {
746           if (gtk_selection_model_is_selected (pages, i))
747             break;
748         }
749       g_object_unref (pages);
750 
751       if (child == NULL)
752         g_dbus_method_invocation_return_error_literal (invocation,
753                                                        G_DBUS_ERROR,
754                                                        G_DBUS_ERROR_INVALID_ARGS,
755                                                        "No selected child");
756       else
757         {
758           GtkATContext *ctx = gtk_accessible_get_at_context (GTK_ACCESSIBLE (child));
759           g_dbus_method_invocation_return_value (invocation,
760                g_variant_new ("(@(so))", gtk_at_spi_context_to_ref (GTK_AT_SPI_CONTEXT (ctx))));
761         }
762     }
763   else if (g_strcmp0 (method_name, "SelectChild") == 0)
764     {
765       int idx;
766       GtkSelectionModel *pages;
767 
768       g_variant_get (parameters, "(i)", &idx);
769 
770       pages = gtk_stack_get_pages (stack);
771       gtk_selection_model_select_item (pages, idx, TRUE);
772       g_object_unref (pages);
773 
774       g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", TRUE));
775     }
776   else if (g_strcmp0 (method_name, "DeselectChild") == 0)
777     {
778       g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", FALSE));
779     }
780   else if (g_strcmp0 (method_name, "DeselectSelectedChild") == 0)
781     {
782       g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", FALSE));
783     }
784   else if (g_strcmp0 (method_name, "IsChildSelected") == 0)
785     {
786       int idx;
787       GtkSelectionModel *pages;
788       gboolean active;
789 
790       g_variant_get (parameters, "(i)", &idx);
791 
792       pages = gtk_stack_get_pages (stack);
793       active = gtk_selection_model_is_selected (pages, idx);
794       g_object_unref (pages);
795 
796       g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", active));
797     }
798   else if (g_strcmp0 (method_name, "SelectAll") == 0)
799     {
800       g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", FALSE));
801     }
802   else if (g_strcmp0 (method_name, "ClearSelection") == 0)
803     {
804       g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", FALSE));
805     }
806 }
807 
808 static GVariant *
stackswitcher_get_property(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * property_name,GError ** error,gpointer user_data)809 stackswitcher_get_property (GDBusConnection  *connection,
810                             const gchar      *sender,
811                             const gchar      *object_path,
812                             const gchar      *interface_name,
813                             const gchar      *property_name,
814                             GError          **error,
815                             gpointer          user_data)
816 {
817   GtkATContext *self = GTK_AT_CONTEXT (user_data);
818   GtkAccessible *accessible = gtk_at_context_get_accessible (self);
819   GtkWidget *widget = GTK_WIDGET (accessible);
820 
821   if (g_strcmp0 (property_name, "NSelectedChildren") == 0)
822     {
823       GtkStack *stack = gtk_stack_switcher_get_stack (GTK_STACK_SWITCHER (widget));
824 
825       if (stack == NULL || gtk_stack_get_visible_child (stack) == NULL)
826         return g_variant_new_int32 (0);
827       else
828         return g_variant_new_int32 (1);
829     }
830 
831   return NULL;
832 }
833 
834 static const GDBusInterfaceVTable stackswitcher_vtable = {
835   stackswitcher_handle_method,
836   stackswitcher_get_property,
837   NULL
838 };
839 
840 /* }}} */
841 /* {{{ GtkNotebook */
842 
843 static void
notebook_handle_method(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * method_name,GVariant * parameters,GDBusMethodInvocation * invocation,gpointer user_data)844 notebook_handle_method (GDBusConnection       *connection,
845                         const gchar           *sender,
846                         const gchar           *object_path,
847                         const gchar           *interface_name,
848                         const gchar           *method_name,
849                         GVariant              *parameters,
850                         GDBusMethodInvocation *invocation,
851                         gpointer               user_data)
852 {
853   GtkATContext *self = user_data;
854   GtkAccessible *accessible = gtk_at_context_get_accessible (self);
855   GtkWidget *widget = GTK_WIDGET (accessible);
856   GtkWidget *notebook = gtk_widget_get_parent (gtk_widget_get_parent (widget));
857 
858   if (g_strcmp0 (method_name, "GetSelectedChild") == 0)
859     {
860       int i;
861       GtkWidget *child;
862 
863       i = gtk_notebook_get_current_page (GTK_NOTEBOOK (notebook));
864 
865       for (child = gtk_widget_get_first_child (widget);
866            child;
867            child = gtk_widget_get_next_sibling (widget))
868         {
869           /* skip actions */
870           if (gtk_accessible_get_accessible_role (GTK_ACCESSIBLE (child)) != GTK_ACCESSIBLE_ROLE_TAB)
871             continue;
872 
873           if (i == 0)
874             break;
875 
876           i--;
877         }
878 
879       if (child == NULL)
880         {
881           g_dbus_method_invocation_return_error_literal (invocation,
882                                                          G_DBUS_ERROR,
883                                                          G_DBUS_ERROR_INVALID_ARGS,
884                                                          "No selected child");
885         }
886       else
887         {
888           GtkATContext *ctx = gtk_accessible_get_at_context (GTK_ACCESSIBLE (child));
889           g_dbus_method_invocation_return_value (invocation,
890                g_variant_new ("(@(so))", gtk_at_spi_context_to_ref (GTK_AT_SPI_CONTEXT (ctx))));
891         }
892     }
893   else if (g_strcmp0 (method_name, "SelectChild") == 0)
894     {
895       int i;
896       GtkWidget *child;
897 
898       g_variant_get (parameters, "(i)", &i);
899 
900       /* skip an action widget */
901       child = gtk_widget_get_first_child (widget);
902       if (gtk_accessible_get_accessible_role (GTK_ACCESSIBLE (child)) != GTK_ACCESSIBLE_ROLE_TAB)
903         i--;
904 
905       gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), i);
906 
907       g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", TRUE));
908     }
909   else if (g_strcmp0 (method_name, "DeselectChild") == 0)
910     {
911       g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", FALSE));
912     }
913   else if (g_strcmp0 (method_name, "DeselectSelectedChild") == 0)
914     {
915       g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", FALSE));
916     }
917   else if (g_strcmp0 (method_name, "IsChildSelected") == 0)
918     {
919       int i;
920       gboolean active;
921       GtkWidget *child;
922 
923       g_variant_get (parameters, "(i)", &i);
924 
925       /* skip an action widget */
926       child = gtk_widget_get_first_child (widget);
927       if (gtk_accessible_get_accessible_role (GTK_ACCESSIBLE (child)) != GTK_ACCESSIBLE_ROLE_TAB)
928         i--;
929 
930       active = i == gtk_notebook_get_current_page (GTK_NOTEBOOK (notebook));
931 
932       g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", active));
933     }
934   else if (g_strcmp0 (method_name, "SelectAll") == 0)
935     {
936       g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", FALSE));
937     }
938   else if (g_strcmp0 (method_name, "ClearSelection") == 0)
939     {
940       g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", FALSE));
941     }
942 }
943 
944 static GVariant *
notebook_get_property(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * property_name,GError ** error,gpointer user_data)945 notebook_get_property (GDBusConnection  *connection,
946                        const gchar      *sender,
947                        const gchar      *object_path,
948                        const gchar      *interface_name,
949                        const gchar      *property_name,
950                        GError          **error,
951                        gpointer          user_data)
952 {
953   if (g_strcmp0 (property_name, "NSelectedChildren") == 0)
954     {
955       return g_variant_new_int32 (1);
956     }
957 
958   return NULL;
959 }
960 
961 static const GDBusInterfaceVTable notebook_vtable = {
962   notebook_handle_method,
963   notebook_get_property,
964   NULL
965 };
966 
967 /* }}} */
968 
969 #define IS_NOTEBOOK_TAB_LIST(s,r) \
970   ((r == GTK_ACCESSIBLE_ROLE_TAB_LIST) && \
971    (gtk_widget_get_parent (GTK_WIDGET (s)) != NULL) && \
972    GTK_IS_NOTEBOOK (gtk_widget_get_parent (gtk_widget_get_parent (GTK_WIDGET (s)))))
973 
974 const GDBusInterfaceVTable *
gtk_atspi_get_selection_vtable(GtkAccessible * accessible,GtkAccessibleRole role)975 gtk_atspi_get_selection_vtable (GtkAccessible     *accessible,
976                                 GtkAccessibleRole  role)
977 {
978   if (GTK_IS_LIST_BOX (accessible))
979     return &listbox_vtable;
980   else if (GTK_IS_LIST_VIEW (accessible) ||
981            GTK_IS_GRID_VIEW (accessible))
982     return &listview_vtable;
983   else if (GTK_IS_FLOW_BOX (accessible))
984     return &flowbox_vtable;
985   else if (GTK_IS_COMBO_BOX (accessible))
986     return &combobox_vtable;
987   else if (GTK_IS_STACK_SWITCHER (accessible))
988     return &stackswitcher_vtable;
989   else if (IS_NOTEBOOK_TAB_LIST (accessible, role))
990     return &notebook_vtable;
991 
992   return NULL;
993 }
994 
995 /* {{{ GtkListView notification */
996 
997 typedef struct {
998   GtkAtspiSelectionCallback *changed;
999   gpointer data;
1000 } SelectionChanged;
1001 
1002 typedef struct {
1003   GtkSelectionModel *model;
1004   GtkAtspiSelectionCallback *changed;
1005   gpointer data;
1006 } ListViewData;
1007 
1008 static void
update_model(ListViewData * data,GtkSelectionModel * model)1009 update_model (ListViewData      *data,
1010               GtkSelectionModel *model)
1011 {
1012   if (data->model)
1013     g_signal_handlers_disconnect_by_func (data->model, data->changed, data->data);
1014 
1015   g_set_object (&data->model, model);
1016 
1017   if (data->model)
1018     g_signal_connect_swapped (data->model, "selection-changed", G_CALLBACK (data->changed), data->data);
1019 }
1020 
1021 static void
list_view_data_free(gpointer user_data)1022 list_view_data_free (gpointer user_data)
1023 {
1024   ListViewData *data = user_data;
1025   update_model (data, NULL);
1026   g_free (data);
1027 }
1028 
1029 static void
model_changed(GtkListBase * list,GParamSpec * pspec,gpointer unused)1030 model_changed (GtkListBase  *list,
1031                GParamSpec   *pspec,
1032                gpointer      unused)
1033 {
1034   ListViewData *data;
1035 
1036   data = (ListViewData *)g_object_get_data (G_OBJECT (list), "accessible-selection-data");
1037   update_model (data, gtk_list_base_get_model (list));
1038 }
1039 
1040 /* }}} */
1041 
1042 void
gtk_atspi_connect_selection_signals(GtkAccessible * accessible,GtkAtspiSelectionCallback selection_changed,gpointer data)1043 gtk_atspi_connect_selection_signals (GtkAccessible *accessible,
1044                                      GtkAtspiSelectionCallback selection_changed,
1045                                      gpointer   data)
1046 {
1047   if (GTK_IS_LIST_BOX (accessible))
1048     {
1049       SelectionChanged *changed;
1050 
1051       changed = g_new (SelectionChanged, 1);
1052       changed->changed = selection_changed;
1053       changed->data = data;
1054 
1055       g_object_set_data_full (G_OBJECT (accessible), "accessible-selection-data", changed, g_free);
1056 
1057       g_signal_connect_swapped (accessible, "selected-rows-changed", G_CALLBACK (selection_changed), data);
1058     }
1059   else if (GTK_IS_FLOW_BOX (accessible))
1060     {
1061       SelectionChanged *changed;
1062 
1063       changed = g_new (SelectionChanged, 1);
1064       changed->changed = selection_changed;
1065       changed->data = data;
1066 
1067       g_object_set_data_full (G_OBJECT (accessible), "accessible-selection-data", changed, g_free);
1068 
1069       g_signal_connect_swapped (accessible, "selected-children-changed", G_CALLBACK (selection_changed), data);
1070     }
1071   else if (GTK_IS_COMBO_BOX (accessible))
1072     {
1073       SelectionChanged *changed;
1074 
1075       changed = g_new (SelectionChanged, 1);
1076       changed->changed = selection_changed;
1077       changed->data = data;
1078 
1079       g_object_set_data_full (G_OBJECT (accessible), "accessible-selection-data", changed, g_free);
1080 
1081       g_signal_connect_swapped (accessible, "changed", G_CALLBACK (selection_changed), data);
1082     }
1083   else if (GTK_IS_STACK_SWITCHER (accessible))
1084     {
1085       SelectionChanged *changed;
1086 
1087       changed = g_new (SelectionChanged, 1);
1088       changed->changed = selection_changed;
1089       changed->data = data;
1090 
1091       g_object_set_data_full (G_OBJECT (accessible), "accessible-selection-data", changed, g_free);
1092 
1093       g_signal_connect_swapped (accessible, "notify::visible-child", G_CALLBACK (selection_changed), data);
1094     }
1095   else if (IS_NOTEBOOK_TAB_LIST (accessible, GTK_AT_CONTEXT (data)->accessible_role))
1096     {
1097       GtkWidget *notebook = gtk_widget_get_parent (gtk_widget_get_parent (GTK_WIDGET (accessible)));
1098       SelectionChanged *changed;
1099 
1100       changed = g_new (SelectionChanged, 1);
1101       changed->changed = selection_changed;
1102       changed->data = data;
1103 
1104       g_object_set_data_full (G_OBJECT (accessible), "accessible-selection-data", changed, g_free);
1105 
1106       g_signal_connect_swapped (notebook, "notify::page", G_CALLBACK (selection_changed), data);
1107     }
1108   else if (GTK_IS_LIST_VIEW (accessible) ||
1109            GTK_IS_GRID_VIEW (accessible))
1110     {
1111       ListViewData *changed;
1112 
1113       changed = g_new0 (ListViewData, 1);
1114       changed->changed = selection_changed;
1115       changed->data = data;
1116 
1117       g_object_set_data_full (G_OBJECT (accessible), "accessible-selection-data", changed, list_view_data_free);
1118 
1119       g_signal_connect (accessible, "notify::model", G_CALLBACK (model_changed), NULL);
1120       model_changed (GTK_LIST_BASE (accessible), NULL, changed);
1121     }
1122 }
1123 
1124 void
gtk_atspi_disconnect_selection_signals(GtkAccessible * accessible)1125 gtk_atspi_disconnect_selection_signals (GtkAccessible *accessible)
1126 {
1127   if (GTK_IS_LIST_BOX (accessible) ||
1128       GTK_IS_FLOW_BOX (accessible) ||
1129       GTK_IS_COMBO_BOX (accessible) ||
1130       GTK_IS_STACK_SWITCHER (accessible))
1131     {
1132       SelectionChanged *changed;
1133 
1134       changed = g_object_get_data (G_OBJECT (accessible), "accessible-selection-data");
1135       if (changed == NULL)
1136         return;
1137 
1138       g_signal_handlers_disconnect_by_func (accessible, changed->changed, changed->data);
1139 
1140       g_object_set_data (G_OBJECT (accessible), "accessible-selection-data", NULL);
1141     }
1142   else if (IS_NOTEBOOK_TAB_LIST (accessible, gtk_accessible_get_accessible_role (accessible)))
1143     {
1144       GtkWidget *notebook = gtk_widget_get_parent (gtk_widget_get_parent (GTK_WIDGET (accessible)));
1145       SelectionChanged *changed;
1146 
1147       changed = g_object_get_data (G_OBJECT (accessible), "accessible-selection-data");
1148       if (changed == NULL)
1149         return;
1150 
1151       g_signal_handlers_disconnect_by_func (notebook, changed->changed, changed->data);
1152 
1153       g_object_set_data (G_OBJECT (accessible), "accessible-selection-data", NULL);
1154     }
1155   else if (GTK_IS_LIST_VIEW (accessible) ||
1156            GTK_IS_GRID_VIEW (accessible))
1157     {
1158       g_signal_handlers_disconnect_by_func (accessible, model_changed, NULL);
1159 
1160       g_object_set_data (G_OBJECT (accessible), "accessible-selection-data", NULL);
1161     }
1162 }
1163 
1164 /* vim:set foldmethod=marker expandtab: */
1165 
1166