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