AXObjectCacheAtk.cpp revision 2bde8e466a4451c7319e3a072d118917957d6554
1/*
2 * Copyright (C) 2008 Nuanti Ltd.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library 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 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB.  If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20#include "config.h"
21#include "AXObjectCache.h"
22
23#include "AccessibilityObject.h"
24#include "AccessibilityObjectWrapperAtk.h"
25#include "AccessibilityRenderObject.h"
26#include "GOwnPtr.h"
27#include "Range.h"
28#include "SelectElement.h"
29#include "TextIterator.h"
30
31namespace WebCore {
32
33void AXObjectCache::detachWrapper(AccessibilityObject* obj)
34{
35    webkit_accessible_detach(WEBKIT_ACCESSIBLE(obj->wrapper()));
36}
37
38void AXObjectCache::attachWrapper(AccessibilityObject* obj)
39{
40    AtkObject* atkObj = ATK_OBJECT(webkit_accessible_new(obj));
41    obj->setWrapper(atkObj);
42    g_object_unref(atkObj);
43}
44
45static AccessibilityObject* getListObject(AccessibilityObject* object)
46{
47    // Only list boxes and menu lists supported so far.
48    if (!object->isListBox() && !object->isMenuList())
49        return 0;
50
51    // For list boxes the list object is just itself.
52    if (object->isListBox())
53        return object;
54
55    // For menu lists we need to return the first accessible child,
56    // with role MenuListPopupRole, since that's the one holding the list
57    // of items with role MenuListOptionRole.
58    AccessibilityObject::AccessibilityChildrenVector children = object->children();
59    if (!children.size())
60        return 0;
61
62    AccessibilityObject* listObject = children.at(0).get();
63    if (!listObject->isMenuListPopup())
64        return 0;
65
66    return listObject;
67}
68
69static void notifyChildrenSelectionChange(AccessibilityObject* object)
70{
71    // This static variables are needed to keep track of the old
72    // focused object and its associated list object, as per previous
73    // calls to this function, in order to properly decide whether to
74    // emit some signals or not.
75    DEFINE_STATIC_LOCAL(RefPtr<AccessibilityObject>, oldListObject, ());
76    DEFINE_STATIC_LOCAL(RefPtr<AccessibilityObject>, oldFocusedObject, ());
77
78    // Only list boxes and menu lists supported so far.
79    if (!object || !(object->isListBox() || object->isMenuList()))
80        return;
81
82    // Emit signal from the listbox's point of view first.
83    g_signal_emit_by_name(object->wrapper(), "selection-changed");
84
85    // Find the item where the selection change was triggered from.
86    SelectElement* select = toSelectElement(static_cast<Element*>(object->node()));
87    if (!select)
88        return;
89    int changedItemIndex = select->activeSelectionStartListIndex();
90
91    AccessibilityObject* listObject = getListObject(object);
92    if (!listObject) {
93        oldListObject = 0;
94        return;
95    }
96
97    AccessibilityObject::AccessibilityChildrenVector items = listObject->children();
98    if (changedItemIndex < 0 || changedItemIndex >= static_cast<int>(items.size()))
99        return;
100    AccessibilityObject* item = items.at(changedItemIndex).get();
101
102    // Ensure the current list object is the same than the old one so
103    // further comparisons make sense. Otherwise, just reset
104    // oldFocusedObject so it won't be taken into account.
105    if (oldListObject != listObject)
106        oldFocusedObject = 0;
107
108    AtkObject* axItem = item ? item->wrapper() : 0;
109    AtkObject* axOldFocusedObject = oldFocusedObject ? oldFocusedObject->wrapper() : 0;
110
111    // Old focused object just lost focus, so emit the events.
112    if (axOldFocusedObject && axItem != axOldFocusedObject) {
113        g_signal_emit_by_name(axOldFocusedObject, "focus-event", false);
114        g_signal_emit_by_name(axOldFocusedObject, "state-change", "focused", false);
115    }
116
117    // Emit needed events for the currently (un)selected item.
118    if (axItem) {
119        bool isSelected = item->isSelected();
120        g_signal_emit_by_name(axItem, "state-change", "selected", isSelected);
121        g_signal_emit_by_name(axItem, "focus-event", isSelected);
122        g_signal_emit_by_name(axItem, "state-change", "focused", isSelected);
123    }
124
125    // Update pointers to the previously involved objects.
126    oldListObject = listObject;
127    oldFocusedObject = item;
128}
129
130void AXObjectCache::postPlatformNotification(AccessibilityObject* coreObject, AXNotification notification)
131{
132    AtkObject* axObject = coreObject->wrapper();
133    if (!axObject)
134        return;
135
136    if (notification == AXCheckedStateChanged) {
137        if (!coreObject->isCheckboxOrRadio())
138            return;
139        g_signal_emit_by_name(axObject, "state-change", "checked", coreObject->isChecked());
140    } else if (notification == AXSelectedChildrenChanged || notification == AXMenuListValueChanged) {
141        if (notification == AXMenuListValueChanged && coreObject->isMenuList()) {
142            g_signal_emit_by_name(axObject, "focus-event", true);
143            g_signal_emit_by_name(axObject, "state-change", "focused", true);
144        }
145        notifyChildrenSelectionChange(coreObject);
146    } else if (notification == AXValueChanged) {
147        if (!ATK_IS_VALUE(axObject))
148            return;
149
150        AtkPropertyValues propertyValues;
151        propertyValues.property_name = "accessible-value";
152
153        memset(&propertyValues.new_value,  0, sizeof(GValue));
154        atk_value_get_current_value(ATK_VALUE(axObject), &propertyValues.new_value);
155
156        g_signal_emit_by_name(ATK_OBJECT(axObject), "property-change::accessible-value", &propertyValues, NULL);
157    }
158}
159
160static void emitTextChanged(AccessibilityRenderObject* object, AXObjectCache::AXTextChange textChange, unsigned offset, unsigned count)
161{
162    // Get the axObject for the parent object
163    AtkObject* wrapper = object->parentObjectUnignored()->wrapper();
164    if (!wrapper || !ATK_IS_TEXT(wrapper))
165        return;
166
167    // Select the right signal to be emitted
168    CString detail;
169    switch (textChange) {
170    case AXObjectCache::AXTextInserted:
171        detail = "text-changed::insert";
172        break;
173    case AXObjectCache::AXTextDeleted:
174        detail = "text-changed::delete";
175        break;
176    }
177
178    if (!detail.isNull())
179        g_signal_emit_by_name(wrapper, detail.data(), offset, count);
180}
181
182void AXObjectCache::nodeTextChangePlatformNotification(AccessibilityObject* object, AXTextChange textChange, unsigned offset, unsigned count)
183{
184    // Sanity check
185    if (count < 1 || !object || !object->isAccessibilityRenderObject())
186        return;
187
188    Node* node = object->node();
189    RefPtr<Range> range = Range::create(node->document(),  Position(node->parentNode(), 0), Position(node, 0));
190    emitTextChanged(toAccessibilityRenderObject(object), textChange, offset + TextIterator::rangeLength(range.get()), count);
191}
192
193void AXObjectCache::handleFocusedUIElementChanged(RenderObject* oldFocusedRender, RenderObject* newFocusedRender)
194{
195    RefPtr<AccessibilityObject> oldObject = getOrCreate(oldFocusedRender);
196    if (oldObject) {
197        g_signal_emit_by_name(oldObject->wrapper(), "focus-event", false);
198        g_signal_emit_by_name(oldObject->wrapper(), "state-change", "focused", false);
199    }
200    RefPtr<AccessibilityObject> newObject = getOrCreate(newFocusedRender);
201    if (newObject) {
202        g_signal_emit_by_name(newObject->wrapper(), "focus-event", true);
203        g_signal_emit_by_name(newObject->wrapper(), "state-change", "focused", true);
204    }
205}
206
207void AXObjectCache::handleScrolledToAnchor(const Node*)
208{
209}
210
211} // namespace WebCore
212