1/*
2 * Copyright (C) 2008 Nuanti Ltd.
3 * Copyright (C) 2009 Jan Alonzo
4 * Copyright (C) 2010, 2011, 2012 Igalia S.L.
5 *
6 * Portions from Mozilla a11y, copyright as follows:
7 *
8 * The Original Code is mozilla.org code.
9 *
10 * The Initial Developer of the Original Code is
11 * Sun Microsystems, Inc.
12 * Portions created by the Initial Developer are Copyright (C) 2002
13 * the Initial Developer. All Rights Reserved.
14 *
15 * This library is free software; you can redistribute it and/or
16 * modify it under the terms of the GNU Library General Public
17 * License as published by the Free Software Foundation; either
18 * version 2 of the License, or (at your option) any later version.
19 *
20 * This library is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
23 * Library General Public License for more details.
24 *
25 * You should have received a copy of the GNU Library General Public License
26 * along with this library; see the file COPYING.LIB.  If not, write to
27 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
28 * Boston, MA 02110-1301, USA.
29 */
30
31#include "config.h"
32#include "WebKitAccessibleInterfaceSelection.h"
33
34#if HAVE(ACCESSIBILITY)
35
36#include "AccessibilityListBox.h"
37#include "AccessibilityObject.h"
38#include "HTMLSelectElement.h"
39#include "RenderObject.h"
40#include "WebKitAccessibleUtil.h"
41#include "WebKitAccessibleWrapperAtk.h"
42
43using namespace WebCore;
44
45static AccessibilityObject* core(AtkSelection* selection)
46{
47    if (!WEBKIT_IS_ACCESSIBLE(selection))
48        return nullptr;
49
50    return webkitAccessibleGetAccessibilityObject(WEBKIT_ACCESSIBLE(selection));
51}
52
53static AccessibilityObject* listObjectForSelection(AtkSelection* selection)
54{
55    AccessibilityObject* coreSelection = core(selection);
56
57    // Only list boxes and menu lists supported so far.
58    if (!coreSelection->isListBox() && !coreSelection->isMenuList())
59        return nullptr;
60
61    // For list boxes the list object is just itself.
62    if (coreSelection->isListBox())
63        return coreSelection;
64
65    // For menu lists we need to return the first accessible child,
66    // with role MenuListPopupRole, since that's the one holding the list
67    // of items with role MenuListOptionRole.
68    const AccessibilityObject::AccessibilityChildrenVector& children = coreSelection->children();
69    if (!children.size())
70        return nullptr;
71
72    AccessibilityObject* listObject = children.at(0).get();
73    if (!listObject->isMenuListPopup())
74        return nullptr;
75
76    return listObject;
77}
78
79static AccessibilityObject* optionFromList(AtkSelection* selection, gint index)
80{
81    AccessibilityObject* coreSelection = core(selection);
82    if (!coreSelection || index < 0)
83        return nullptr;
84
85    // Need to select the proper list object depending on the type.
86    AccessibilityObject* listObject = listObjectForSelection(selection);
87    if (!listObject)
88        return nullptr;
89
90    const AccessibilityObject::AccessibilityChildrenVector& options = listObject->children();
91    if (index < static_cast<gint>(options.size()))
92        return options.at(index).get();
93
94    return nullptr;
95}
96
97static AccessibilityObject* optionFromSelection(AtkSelection* selection, gint index)
98{
99    // i is the ith selection as opposed to the ith child.
100
101    AccessibilityObject* coreSelection = core(selection);
102    if (!coreSelection || !coreSelection->isAccessibilityRenderObject() || index < 0)
103        return nullptr;
104
105    int selectedIndex = index;
106    if (coreSelection->isMenuList()) {
107        RenderObject* renderer = coreSelection->renderer();
108        if (!renderer)
109            return nullptr;
110
111        HTMLSelectElement* selectNode = toHTMLSelectElement(renderer->node());
112        if (!selectNode)
113            return nullptr;
114
115        selectedIndex = selectNode->selectedIndex();
116        const auto& listItems = selectNode->listItems();
117
118        if (selectedIndex < 0 || selectedIndex >= static_cast<int>(listItems.size()))
119            return nullptr;
120    }
121
122    return optionFromList(selection, selectedIndex);
123}
124
125static gboolean webkitAccessibleSelectionAddSelection(AtkSelection* selection, gint index)
126{
127    g_return_val_if_fail(ATK_SELECTION(selection), FALSE);
128    returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(selection), FALSE);
129
130    AccessibilityObject* coreSelection = core(selection);
131    if (!coreSelection)
132        return FALSE;
133
134    AccessibilityObject* option = optionFromList(selection, index);
135    if (option && (coreSelection->isListBox() || coreSelection->isMenuList())) {
136        option->setSelected(true);
137        return option->isSelected();
138    }
139
140    return FALSE;
141}
142
143static gboolean webkitAccessibleSelectionClearSelection(AtkSelection* selection)
144{
145    g_return_val_if_fail(ATK_SELECTION(selection), FALSE);
146    returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(selection), FALSE);
147
148    AccessibilityObject* coreSelection = core(selection);
149    if (!coreSelection)
150        return FALSE;
151
152    AccessibilityObject::AccessibilityChildrenVector selectedItems;
153    if (coreSelection->isListBox() || coreSelection->isMenuList()) {
154        // Set the list of selected items to an empty list; then verify that it worked.
155        AccessibilityListBox* listBox = toAccessibilityListBox(coreSelection);
156        listBox->setSelectedChildren(selectedItems);
157        listBox->selectedChildren(selectedItems);
158        return !selectedItems.size();
159    }
160    return FALSE;
161}
162
163static AtkObject* webkitAccessibleSelectionRefSelection(AtkSelection* selection, gint index)
164{
165    g_return_val_if_fail(ATK_SELECTION(selection), nullptr);
166    returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(selection), nullptr);
167
168    AccessibilityObject* option = optionFromSelection(selection, index);
169    if (option) {
170        AtkObject* child = option->wrapper();
171        g_object_ref(child);
172        return child;
173    }
174
175    return nullptr;
176}
177
178static gint webkitAccessibleSelectionGetSelectionCount(AtkSelection* selection)
179{
180    g_return_val_if_fail(ATK_SELECTION(selection), 0);
181    returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(selection), 0);
182
183    AccessibilityObject* coreSelection = core(selection);
184    if (!coreSelection || !coreSelection->isAccessibilityRenderObject())
185        return 0;
186
187    if (coreSelection->isListBox()) {
188        AccessibilityObject::AccessibilityChildrenVector selectedItems;
189        coreSelection->selectedChildren(selectedItems);
190        return static_cast<gint>(selectedItems.size());
191    }
192
193    if (coreSelection->isMenuList()) {
194        RenderObject* renderer = coreSelection->renderer();
195        if (!renderer)
196            return 0;
197
198        int selectedIndex = toHTMLSelectElement(renderer->node())->selectedIndex();
199        return selectedIndex >= 0 && selectedIndex < static_cast<int>(toHTMLSelectElement(renderer->node())->listItems().size());
200    }
201
202    return 0;
203}
204
205static gboolean webkitAccessibleSelectionIsChildSelected(AtkSelection* selection, gint index)
206{
207    g_return_val_if_fail(ATK_SELECTION(selection), FALSE);
208    returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(selection), FALSE);
209
210    AccessibilityObject* coreSelection = core(selection);
211    if (!coreSelection)
212        return FALSE;
213
214    AccessibilityObject* option = optionFromList(selection, index);
215    if (option && (coreSelection->isListBox() || coreSelection->isMenuList()))
216        return option->isSelected();
217
218    return FALSE;
219}
220
221static gboolean webkitAccessibleSelectionRemoveSelection(AtkSelection* selection, gint index)
222{
223    g_return_val_if_fail(ATK_SELECTION(selection), FALSE);
224    returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(selection), FALSE);
225
226    AccessibilityObject* coreSelection = core(selection);
227    if (!coreSelection)
228        return FALSE;
229
230    // TODO: This is only getting called if i == 0. What is preventing the rest?
231    AccessibilityObject* option = optionFromSelection(selection, index);
232    if (option && (coreSelection->isListBox() || coreSelection->isMenuList())) {
233        option->setSelected(false);
234        return !option->isSelected();
235    }
236
237    return FALSE;
238}
239
240static gboolean webkitAccessibleSelectionSelectAllSelection(AtkSelection* selection)
241{
242    g_return_val_if_fail(ATK_SELECTION(selection), FALSE);
243    returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(selection), FALSE);
244
245    AccessibilityObject* coreSelection = core(selection);
246    if (!coreSelection || !coreSelection->isMultiSelectable())
247        return FALSE;
248
249    if (coreSelection->isListBox()) {
250        const AccessibilityObject::AccessibilityChildrenVector& children = coreSelection->children();
251        AccessibilityListBox* listBox = toAccessibilityListBox(coreSelection);
252        listBox->setSelectedChildren(children);
253        AccessibilityObject::AccessibilityChildrenVector selectedItems;
254        listBox->selectedChildren(selectedItems);
255        return selectedItems.size() == children.size();
256    }
257
258    return FALSE;
259}
260
261void webkitAccessibleSelectionInterfaceInit(AtkSelectionIface* iface)
262{
263    iface->add_selection = webkitAccessibleSelectionAddSelection;
264    iface->clear_selection = webkitAccessibleSelectionClearSelection;
265    iface->ref_selection = webkitAccessibleSelectionRefSelection;
266    iface->get_selection_count = webkitAccessibleSelectionGetSelectionCount;
267    iface->is_child_selected = webkitAccessibleSelectionIsChildSelected;
268    iface->remove_selection = webkitAccessibleSelectionRemoveSelection;
269    iface->select_all_selection = webkitAccessibleSelectionSelectAllSelection;
270}
271
272#endif
273