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
30 namespace WebCore {
31
detachWrapper(AccessibilityObject * obj)32 void AXObjectCache::detachWrapper(AccessibilityObject* obj)
33 {
34 webkit_accessible_detach(WEBKIT_ACCESSIBLE(obj->wrapper()));
35 }
36
attachWrapper(AccessibilityObject * obj)37 void 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
getListObject(AccessibilityObject * object)44 static 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
notifyChildrenSelectionChange(AccessibilityObject * object)68 static 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
postPlatformNotification(AccessibilityObject * coreObject,AXNotification notification)129 void 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
emitTextChanged(AccessibilityObject * object,AXObjectCache::AXTextChange textChange,unsigned offset,unsigned count)159 static 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
nodeTextChangePlatformNotification(AccessibilityObject * object,AXTextChange textChange,unsigned offset,unsigned count)181 void 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
handleFocusedUIElementChanged(RenderObject * oldFocusedRender,RenderObject * newFocusedRender)192 void 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
handleScrolledToAnchor(const Node *)206 void AXObjectCache::handleScrolledToAnchor(const Node*)
207 {
208 }
209
210 } // namespace WebCore
211