Home | History | Annotate | Download | only in gtk
      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