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