1/*
2 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
3 * Copyright (C) 2009 Jan Michael Alonzo
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#include "AccessibilityUIElement.h"
29
30#include "GOwnPtr.h"
31#include "GRefPtr.h"
32#include "WebCoreSupport/DumpRenderTreeSupportGtk.h"
33#include <JavaScriptCore/JSStringRef.h>
34#include <atk/atk.h>
35#include <gtk/gtk.h>
36#include <wtf/Assertions.h>
37
38AccessibilityUIElement::AccessibilityUIElement(PlatformUIElement element)
39    : m_element(element)
40{
41}
42
43AccessibilityUIElement::AccessibilityUIElement(const AccessibilityUIElement& other)
44    : m_element(other.m_element)
45{
46}
47
48AccessibilityUIElement::~AccessibilityUIElement()
49{
50}
51
52void AccessibilityUIElement::getLinkedUIElements(Vector<AccessibilityUIElement>& elements)
53{
54    // FIXME: implement
55}
56
57void AccessibilityUIElement::getDocumentLinks(Vector<AccessibilityUIElement>&)
58{
59    // FIXME: implement
60}
61
62void AccessibilityUIElement::getChildren(Vector<AccessibilityUIElement>& children)
63{
64    int count = childrenCount();
65    for (int i = 0; i < count; i++) {
66        AtkObject* child = atk_object_ref_accessible_child(ATK_OBJECT(m_element), i);
67        children.append(AccessibilityUIElement(child));
68    }
69}
70
71void AccessibilityUIElement::getChildrenWithRange(Vector<AccessibilityUIElement>& elementVector, unsigned start, unsigned end)
72{
73    for (unsigned i = start; i < end; i++) {
74        AtkObject* child = atk_object_ref_accessible_child(ATK_OBJECT(m_element), i);
75        elementVector.append(AccessibilityUIElement(child));
76    }
77}
78
79int AccessibilityUIElement::rowCount()
80{
81    if (!m_element)
82        return 0;
83
84    ASSERT(ATK_IS_TABLE(m_element));
85
86    return atk_table_get_n_rows(ATK_TABLE(m_element));
87}
88
89int AccessibilityUIElement::columnCount()
90{
91    if (!m_element)
92        return 0;
93
94    ASSERT(ATK_IS_TABLE(m_element));
95
96    return atk_table_get_n_columns(ATK_TABLE(m_element));
97}
98
99int AccessibilityUIElement::childrenCount()
100{
101    if (!m_element)
102        return 0;
103
104    ASSERT(ATK_IS_OBJECT(m_element));
105
106    return atk_object_get_n_accessible_children(ATK_OBJECT(m_element));
107}
108
109AccessibilityUIElement AccessibilityUIElement::elementAtPoint(int x, int y)
110{
111    // FIXME: implement
112    return 0;
113}
114
115AccessibilityUIElement AccessibilityUIElement::linkedUIElementAtIndex(unsigned index)
116{
117    // FIXME: implement
118    return 0;
119}
120
121AccessibilityUIElement AccessibilityUIElement::getChildAtIndex(unsigned index)
122{
123    Vector<AccessibilityUIElement> children;
124    getChildrenWithRange(children, index, index + 1);
125
126    if (children.size() == 1)
127        return children.at(0);
128
129    return 0;
130}
131
132unsigned AccessibilityUIElement::indexOfChild(AccessibilityUIElement* element)
133{
134    // FIXME: implement
135    return 0;
136}
137
138gchar* attributeSetToString(AtkAttributeSet* attributeSet)
139{
140    GString* str = g_string_new(0);
141    for (GSList* attributes = attributeSet; attributes; attributes = attributes->next) {
142        AtkAttribute* attribute = static_cast<AtkAttribute*>(attributes->data);
143        g_string_append(str, g_strconcat(attribute->name, ":", attribute->value, NULL));
144        if (attributes->next)
145            g_string_append(str, ", ");
146    }
147
148    return g_string_free(str, FALSE);
149}
150
151JSStringRef AccessibilityUIElement::allAttributes()
152{
153    if (!m_element)
154        return JSStringCreateWithCharacters(0, 0);
155
156    ASSERT(ATK_IS_OBJECT(m_element));
157    return JSStringCreateWithUTF8CString(attributeSetToString(atk_object_get_attributes(ATK_OBJECT(m_element))));
158}
159
160JSStringRef AccessibilityUIElement::attributesOfLinkedUIElements()
161{
162    // FIXME: implement
163    return JSStringCreateWithCharacters(0, 0);
164}
165
166JSStringRef AccessibilityUIElement::attributesOfDocumentLinks()
167{
168    // FIXME: implement
169    return JSStringCreateWithCharacters(0, 0);
170}
171
172AccessibilityUIElement AccessibilityUIElement::titleUIElement()
173{
174    // FIXME: implement
175    return 0;
176}
177
178AccessibilityUIElement AccessibilityUIElement::parentElement()
179{
180    if (!m_element)
181        return 0;
182
183    ASSERT(ATK_IS_OBJECT(m_element));
184
185    AtkObject* parent =  atk_object_get_parent(ATK_OBJECT(m_element));
186    return parent ? AccessibilityUIElement(parent) : 0;
187}
188
189JSStringRef AccessibilityUIElement::attributesOfChildren()
190{
191    // FIXME: implement
192    return JSStringCreateWithCharacters(0, 0);
193}
194
195JSStringRef AccessibilityUIElement::parameterizedAttributeNames()
196{
197    // FIXME: implement
198    return JSStringCreateWithCharacters(0, 0);
199}
200
201JSStringRef AccessibilityUIElement::role()
202{
203    AtkRole role = atk_object_get_role(ATK_OBJECT(m_element));
204
205    if (!role)
206        return JSStringCreateWithCharacters(0, 0);
207
208    const gchar* roleName = atk_role_get_name(role);
209    GOwnPtr<gchar> axRole(g_strdup_printf("AXRole: %s", roleName));
210
211    return JSStringCreateWithUTF8CString(axRole.get());
212}
213
214JSStringRef AccessibilityUIElement::subrole()
215{
216    return 0;
217}
218
219JSStringRef AccessibilityUIElement::roleDescription()
220{
221    return 0;
222}
223
224JSStringRef AccessibilityUIElement::title()
225{
226    const gchar* name = atk_object_get_name(ATK_OBJECT(m_element));
227
228    if (!name)
229        return JSStringCreateWithCharacters(0, 0);
230
231    GOwnPtr<gchar> axTitle(g_strdup_printf("AXTitle: %s", name));
232
233    return JSStringCreateWithUTF8CString(axTitle.get());
234}
235
236JSStringRef AccessibilityUIElement::description()
237{
238    const gchar* description = atk_object_get_description(ATK_OBJECT(m_element));
239
240    if (!description)
241        return JSStringCreateWithCharacters(0, 0);
242
243    GOwnPtr<gchar> axDesc(g_strdup_printf("AXDescription: %s", description));
244
245    return JSStringCreateWithUTF8CString(axDesc.get());
246}
247
248JSStringRef AccessibilityUIElement::stringValue()
249{
250    // FIXME: implement
251    return JSStringCreateWithCharacters(0, 0);
252}
253
254JSStringRef AccessibilityUIElement::language()
255{
256    // FIXME: implement
257    return JSStringCreateWithCharacters(0, 0);
258}
259
260JSStringRef AccessibilityUIElement::helpText() const
261{
262    return 0;
263}
264
265double AccessibilityUIElement::x()
266{
267    int x, y;
268
269    atk_component_get_position(ATK_COMPONENT(m_element), &x, &y, ATK_XY_SCREEN);
270
271    return x;
272}
273
274double AccessibilityUIElement::y()
275{
276    int x, y;
277
278    atk_component_get_position(ATK_COMPONENT(m_element), &x, &y, ATK_XY_SCREEN);
279
280    return y;
281}
282
283double AccessibilityUIElement::width()
284{
285    int width, height;
286
287    atk_component_get_size(ATK_COMPONENT(m_element), &width, &height);
288
289    return width;
290}
291
292double AccessibilityUIElement::height()
293{
294    int width, height;
295
296    atk_component_get_size(ATK_COMPONENT(m_element), &width, &height);
297
298    return height;
299}
300
301double AccessibilityUIElement::clickPointX()
302{
303    return 0.f;
304}
305
306double AccessibilityUIElement::clickPointY()
307{
308    return 0.f;
309}
310
311JSStringRef AccessibilityUIElement::orientation() const
312{
313    return 0;
314}
315
316double AccessibilityUIElement::intValue() const
317{
318    GValue value = { 0, { { 0 } } };
319
320    if (!ATK_IS_VALUE(m_element))
321        return 0.0f;
322
323    atk_value_get_current_value(ATK_VALUE(m_element), &value);
324
325    if (G_VALUE_HOLDS_DOUBLE(&value))
326        return g_value_get_double(&value);
327    else if (G_VALUE_HOLDS_INT(&value))
328        return static_cast<double>(g_value_get_int(&value));
329    else
330        return 0.0f;
331}
332
333double AccessibilityUIElement::minValue()
334{
335    GValue value = { 0, { { 0 } } };
336
337    if (!ATK_IS_VALUE(m_element))
338        return 0.0f;
339
340    atk_value_get_minimum_value(ATK_VALUE(m_element), &value);
341
342    if (G_VALUE_HOLDS_DOUBLE(&value))
343        return g_value_get_double(&value);
344    else if (G_VALUE_HOLDS_INT(&value))
345        return static_cast<double>(g_value_get_int(&value));
346    else
347        return 0.0f;
348}
349
350double AccessibilityUIElement::maxValue()
351{
352    GValue value = { 0, { { 0 } } };
353
354    if (!ATK_IS_VALUE(m_element))
355        return 0.0f;
356
357    atk_value_get_maximum_value(ATK_VALUE(m_element), &value);
358
359    if (G_VALUE_HOLDS_DOUBLE(&value))
360        return g_value_get_double(&value);
361    else if (G_VALUE_HOLDS_INT(&value))
362        return static_cast<double>(g_value_get_int(&value));
363    else
364        return 0.0f;
365}
366
367JSStringRef AccessibilityUIElement::valueDescription()
368{
369    // FIXME: implement
370    return JSStringCreateWithCharacters(0, 0);
371}
372
373static bool checkElementState(PlatformUIElement element, AtkStateType stateType)
374{
375    if (!ATK_IS_OBJECT(element))
376         return false;
377
378    GRefPtr<AtkStateSet> stateSet = adoptGRef(atk_object_ref_state_set(ATK_OBJECT(element)));
379    return atk_state_set_contains_state(stateSet.get(), stateType);
380}
381
382bool AccessibilityUIElement::isEnabled()
383{
384    return checkElementState(m_element, ATK_STATE_ENABLED);
385}
386
387int AccessibilityUIElement::insertionPointLineNumber()
388{
389    // FIXME: implement
390    return 0;
391}
392
393bool AccessibilityUIElement::isActionSupported(JSStringRef action)
394{
395    // FIXME: implement
396    return false;
397}
398
399bool AccessibilityUIElement::isRequired() const
400{
401    // FIXME: implement
402    return false;
403}
404
405bool AccessibilityUIElement::isFocused() const
406{
407    if (!ATK_IS_OBJECT(m_element))
408        return false;
409
410    GRefPtr<AtkStateSet> stateSet = adoptGRef(atk_object_ref_state_set(ATK_OBJECT(m_element)));
411    gboolean isFocused = atk_state_set_contains_state(stateSet.get(), ATK_STATE_FOCUSED);
412
413    return isFocused;
414}
415
416bool AccessibilityUIElement::isSelected() const
417{
418    return checkElementState(m_element, ATK_STATE_SELECTED);
419}
420
421int AccessibilityUIElement::hierarchicalLevel() const
422{
423    // FIXME: implement
424    return 0;
425}
426
427bool AccessibilityUIElement::ariaIsGrabbed() const
428{
429    return false;
430}
431
432JSStringRef AccessibilityUIElement::ariaDropEffects() const
433{
434    return 0;
435}
436
437bool AccessibilityUIElement::isExpanded() const
438{
439    // FIXME: implement
440    return false;
441}
442
443bool AccessibilityUIElement::isChecked() const
444{
445    return intValue();
446}
447
448JSStringRef AccessibilityUIElement::attributesOfColumnHeaders()
449{
450    // FIXME: implement
451    return JSStringCreateWithCharacters(0, 0);
452}
453
454JSStringRef AccessibilityUIElement::attributesOfRowHeaders()
455{
456    // FIXME: implement
457    return JSStringCreateWithCharacters(0, 0);
458}
459
460JSStringRef AccessibilityUIElement::attributesOfColumns()
461{
462    // FIXME: implement
463    return JSStringCreateWithCharacters(0, 0);
464}
465
466JSStringRef AccessibilityUIElement::attributesOfRows()
467{
468    // FIXME: implement
469    return JSStringCreateWithCharacters(0, 0);
470}
471
472JSStringRef AccessibilityUIElement::attributesOfVisibleCells()
473{
474    // FIXME: implement
475    return JSStringCreateWithCharacters(0, 0);
476}
477
478JSStringRef AccessibilityUIElement::attributesOfHeader()
479{
480    // FIXME: implement
481    return JSStringCreateWithCharacters(0, 0);
482}
483
484int AccessibilityUIElement::indexInTable()
485{
486    // FIXME: implement
487    return 0;
488}
489
490static JSStringRef indexRangeInTable(PlatformUIElement element, bool isRowRange)
491{
492    GOwnPtr<gchar> rangeString(g_strdup("{0, 0}"));
493
494    if (!element)
495        return JSStringCreateWithUTF8CString(rangeString.get());
496
497    ASSERT(ATK_IS_OBJECT(element));
498
499    AtkObject* axTable = atk_object_get_parent(ATK_OBJECT(element));
500    if (!axTable || !ATK_IS_TABLE(axTable))
501        return JSStringCreateWithUTF8CString(rangeString.get());
502
503    // Look for the cell in the table.
504    gint indexInParent = atk_object_get_index_in_parent(ATK_OBJECT(element));
505    if (indexInParent == -1)
506        return JSStringCreateWithUTF8CString(rangeString.get());
507
508    int row = -1;
509    int column = -1;
510    row = atk_table_get_row_at_index(ATK_TABLE(axTable), indexInParent);
511    column = atk_table_get_column_at_index(ATK_TABLE(axTable), indexInParent);
512
513    // Get the actual values, if row and columns are valid values.
514    if (row != -1 && column != -1) {
515        int base = 0;
516        int length = 0;
517        if (isRowRange) {
518            base = row;
519            length = atk_table_get_row_extent_at(ATK_TABLE(axTable), row, column);
520        } else {
521            base = column;
522            length = atk_table_get_column_extent_at(ATK_TABLE(axTable), row, column);
523        }
524        rangeString.set(g_strdup_printf("{%d, %d}", base, length));
525    }
526
527    return JSStringCreateWithUTF8CString(rangeString.get());
528}
529
530JSStringRef AccessibilityUIElement::rowIndexRange()
531{
532    // Range in table for rows.
533    return indexRangeInTable(m_element, true);
534}
535
536JSStringRef AccessibilityUIElement::columnIndexRange()
537{
538    // Range in table for columns.
539    return indexRangeInTable(m_element, false);
540}
541
542int AccessibilityUIElement::lineForIndex(int)
543{
544    // FIXME: implement
545    return 0;
546}
547
548JSStringRef AccessibilityUIElement::boundsForRange(unsigned location, unsigned length)
549{
550    // FIXME: implement
551    return JSStringCreateWithCharacters(0, 0);
552}
553
554JSStringRef AccessibilityUIElement::stringForRange(unsigned, unsigned)
555{
556    // FIXME: implement
557    return JSStringCreateWithCharacters(0, 0);
558}
559
560JSStringRef AccessibilityUIElement::attributedStringForRange(unsigned, unsigned)
561{
562    // FIXME: implement
563    return JSStringCreateWithCharacters(0, 0);
564}
565
566bool AccessibilityUIElement::attributedStringRangeIsMisspelled(unsigned location, unsigned length)
567{
568    // FIXME: implement
569    return false;
570}
571
572AccessibilityUIElement AccessibilityUIElement::cellForColumnAndRow(unsigned column, unsigned row)
573{
574    if (!m_element)
575        return 0;
576
577    ASSERT(ATK_IS_TABLE(m_element));
578
579    AtkObject* foundCell = atk_table_ref_at(ATK_TABLE(m_element), row, column);
580    return foundCell ? AccessibilityUIElement(foundCell) : 0;
581}
582
583JSStringRef AccessibilityUIElement::selectedTextRange()
584{
585    // FIXME: implement
586    return JSStringCreateWithCharacters(0, 0);
587}
588
589void AccessibilityUIElement::setSelectedTextRange(unsigned location, unsigned length)
590{
591    // FIXME: implement
592}
593
594JSStringRef AccessibilityUIElement::stringAttributeValue(JSStringRef attribute)
595{
596    // FIXME: implement
597    return JSStringCreateWithCharacters(0, 0);
598}
599
600bool AccessibilityUIElement::boolAttributeValue(JSStringRef attribute)
601{
602    // FIXME: implement
603    return false;
604}
605
606bool AccessibilityUIElement::isAttributeSettable(JSStringRef attribute)
607{
608    // FIXME: implement
609    return false;
610}
611
612bool AccessibilityUIElement::isAttributeSupported(JSStringRef attribute)
613{
614    return false;
615}
616
617void AccessibilityUIElement::increment()
618{
619    if (!m_element)
620        return;
621
622    ASSERT(ATK_IS_OBJECT(m_element));
623    DumpRenderTreeSupportGtk::incrementAccessibilityValue(ATK_OBJECT(m_element));
624}
625
626void AccessibilityUIElement::decrement()
627{
628    if (!m_element)
629        return;
630
631    ASSERT(ATK_IS_OBJECT(m_element));
632    DumpRenderTreeSupportGtk::decrementAccessibilityValue(ATK_OBJECT(m_element));
633}
634
635void AccessibilityUIElement::press()
636{
637    if (!m_element)
638        return;
639
640    ASSERT(ATK_IS_OBJECT(m_element));
641
642    if (!ATK_IS_ACTION(m_element))
643        return;
644
645    // Only one action per object is supported so far.
646    atk_action_do_action(ATK_ACTION(m_element), 0);
647}
648
649void AccessibilityUIElement::showMenu()
650{
651    // FIXME: implement
652}
653
654AccessibilityUIElement AccessibilityUIElement::disclosedRowAtIndex(unsigned index)
655{
656    return 0;
657}
658
659AccessibilityUIElement AccessibilityUIElement::ariaOwnsElementAtIndex(unsigned index)
660{
661    return 0;
662}
663
664AccessibilityUIElement AccessibilityUIElement::ariaFlowToElementAtIndex(unsigned index)
665{
666    return 0;
667}
668
669AccessibilityUIElement AccessibilityUIElement::selectedRowAtIndex(unsigned index)
670{
671    return 0;
672}
673
674AccessibilityUIElement AccessibilityUIElement::disclosedByRow()
675{
676    return 0;
677}
678
679JSStringRef AccessibilityUIElement::accessibilityValue() const
680{
681    // FIXME: implement
682    return JSStringCreateWithCharacters(0, 0);
683}
684
685JSStringRef AccessibilityUIElement::documentEncoding()
686{
687    AtkRole role = atk_object_get_role(ATK_OBJECT(m_element));
688    if (role != ATK_ROLE_DOCUMENT_FRAME)
689        return JSStringCreateWithCharacters(0, 0);
690
691    return JSStringCreateWithUTF8CString(atk_document_get_attribute_value(ATK_DOCUMENT(m_element), "Encoding"));
692}
693
694JSStringRef AccessibilityUIElement::documentURI()
695{
696    AtkRole role = atk_object_get_role(ATK_OBJECT(m_element));
697    if (role != ATK_ROLE_DOCUMENT_FRAME)
698        return JSStringCreateWithCharacters(0, 0);
699
700    return JSStringCreateWithUTF8CString(atk_document_get_attribute_value(ATK_DOCUMENT(m_element), "URI"));
701}
702
703JSStringRef AccessibilityUIElement::url()
704{
705    // FIXME: implement
706    return JSStringCreateWithCharacters(0, 0);
707}
708
709bool AccessibilityUIElement::addNotificationListener(JSObjectRef functionCallback)
710{
711    // FIXME: implement
712    return false;
713}
714
715void AccessibilityUIElement::removeNotificationListener()
716{
717    // FIXME: implement
718}
719
720bool AccessibilityUIElement::isFocusable() const
721{
722    if (!ATK_IS_OBJECT(m_element))
723        return false;
724
725    GRefPtr<AtkStateSet> stateSet = adoptGRef(atk_object_ref_state_set(ATK_OBJECT(m_element)));
726    gboolean isFocusable = atk_state_set_contains_state(stateSet.get(), ATK_STATE_FOCUSABLE);
727
728    return isFocusable;
729}
730
731bool AccessibilityUIElement::isSelectable() const
732{
733    // FIXME: implement
734    return false;
735}
736
737bool AccessibilityUIElement::isMultiSelectable() const
738{
739    // FIXME: implement
740    return false;
741}
742
743bool AccessibilityUIElement::isVisible() const
744{
745    // FIXME: implement
746    return false;
747}
748
749bool AccessibilityUIElement::isOffScreen() const
750{
751    // FIXME: implement
752    return false;
753}
754
755bool AccessibilityUIElement::isCollapsed() const
756{
757    // FIXME: implement
758    return false;
759}
760
761bool AccessibilityUIElement::isIgnored() const
762{
763    // FIXME: implement
764    return false;
765}
766
767bool AccessibilityUIElement::hasPopup() const
768{
769    // FIXME: implement
770    return false;
771}
772
773void AccessibilityUIElement::takeFocus()
774{
775    // FIXME: implement
776}
777
778void AccessibilityUIElement::takeSelection()
779{
780    // FIXME: implement
781}
782
783void AccessibilityUIElement::addSelection()
784{
785    // FIXME: implement
786}
787
788void AccessibilityUIElement::removeSelection()
789{
790    // FIXME: implement
791}
792