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 32 void AXObjectCache::detachWrapper(AccessibilityObject* obj) 33 { 34 webkit_accessible_detach(WEBKIT_ACCESSIBLE(obj->wrapper())); 35 } 36 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 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 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 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 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 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 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 206 void AXObjectCache::handleScrolledToAnchor(const Node*) 207 { 208 } 209 210 } // namespace WebCore 211