+2009-05-19 Xan Lopez <xlopez@igalia.com>
+
+ Reviewed by Jan Alonzo and Gustavo Noronha.
+
+ https://bugs.webkit.org/show_bug.cgi?id=25415
+ [GTK][ATK] Please implement support for get_text_at_offset
+
+ Add new test file for ATK.
+
+ * GNUmakefile.am:
+
2009-05-28 Gustavo Noronha Silva <gustavo.noronha@collabora.co.uk>
Rubber-stamped by Xan Lopez.
TEST_PROGS += Programs/unittests/testwebframe \
Programs/unittests/testwebbackforwardlist \
Programs/unittests/testwebhistoryitem \
- Programs/unittests/testdownload
+ Programs/unittests/testdownload \
+ Programs/unittests/testatk
# Add additional tests here
Programs_unittests_testwebframe_SOURCES = WebKit/gtk/tests/testwebframe.c
Programs_unittests_testdownload_LDADD = $(webkit_tests_ldadd)
Programs_unittests_testdownload_LDFLAGS = $(webkit_tests_ldflags)
+Programs_unittests_testatk_SOURCES = WebKit/gtk/tests/testatk.c
+Programs_unittests_testatk_CFLAGS = $(webkit_tests_cflags)
+Programs_unittests_testatk_LDADD = $(webkit_tests_ldadd)
+Programs_unittests_testatk_LDFLAGS = $(webkit_tests_ldflags)
+
# Autogenerated sources
BUILT_SOURCES := \
$(javascriptcore_built_sources) \
+2009-05-19 Xan Lopez <xlopez@igalia.com>
+
+ Reviewed by Jan Alonzo and Gustavo Noronha.
+
+ https://bugs.webkit.org/show_bug.cgi?id=25415
+ [GTK][ATK] Please implement support for get_text_at_offset
+
+ Implement atk_text_get_text_{at,after,before}_offset.
+
+ * accessibility/gtk/AccessibilityObjectWrapperAtk.cpp:
+
2009-05-28 Nikolas Zimmermann <nikolas.zimmermann@torchmobile.com>
Rubber-stamped by Darin Adler.
#include <atk/atk.h>
#include <glib.h>
#include <glib/gprintf.h>
+#include <pango/pango.h>
using namespace WebCore;
return g_strdup(ret.utf8().data());
}
-static gchar* webkit_accessible_text_get_text_after_offset(AtkText* text, gint offset, AtkTextBoundary boundary_type, gint* start_offset, gint* end_offset)
+enum GetTextFunctionType {
+ AfterOffset,
+ AtOffset,
+ BeforeOffset
+};
+
+typedef bool (*isCharacterAttribute) (PangoLogAttr* attr);
+
+static inline bool isWordStart(PangoLogAttr* attr)
{
- notImplemented();
- return NULL;
+ return attr->is_word_start;
}
-static gchar* webkit_accessible_text_get_text_at_offset(AtkText* text, gint offset, AtkTextBoundary boundary_type, gint* start_offset, gint* end_offset)
+static inline bool isWordEnd(PangoLogAttr* attr)
{
- notImplemented();
- return NULL;
+ return attr->is_word_end;
}
-static gunichar webkit_accessible_text_get_character_at_offset(AtkText* text, gint offset)
+static inline bool isSentenceStart(PangoLogAttr* attr)
{
- notImplemented();
- return 0;
+ return attr->is_sentence_start;
+}
+
+static inline bool isSentenceEnd(PangoLogAttr* attr)
+{
+ return attr->is_sentence_end;
+}
+
+enum Direction {
+ DirectionForward,
+ DirectionBackwards
+};
+
+static bool findCharacterAttribute(isCharacterAttribute predicateFunction, PangoLogAttr* attributes, Direction direction, int startOffset, int attrsLength, int* resultOffset)
+{
+ int advanceBy = direction == DirectionForward ? 1 : -1;
+
+ *resultOffset = -1;
+
+ for (int i = startOffset; i >= 0 && i < attrsLength; i += advanceBy) {
+ if (predicateFunction(attributes + i)) {
+ *resultOffset = i;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool findCharacterAttributeSkip(isCharacterAttribute predicateFunction, unsigned skip, PangoLogAttr* attributes, Direction direction, int startOffset, int attrsLength, int* resultOffset)
+{
+ int tmpOffset;
+
+ bool retValue = findCharacterAttribute(predicateFunction, attributes, direction, startOffset, attrsLength, &tmpOffset);
+ if (skip == 0) {
+ *resultOffset = tmpOffset;
+ return retValue;
+ }
+
+ if (direction == DirectionForward)
+ tmpOffset++;
+ else
+ tmpOffset--;
+
+ return findCharacterAttributeSkip(predicateFunction, skip - 1, attributes, direction, tmpOffset, attrsLength, resultOffset);
+}
+
+static isCharacterAttribute oppositePredicate(isCharacterAttribute predicate)
+{
+ if (predicate == isWordStart)
+ return isWordEnd;
+ if (predicate == isWordEnd)
+ return isWordStart;
+ if (predicate == isSentenceStart)
+ return isSentenceEnd;
+ if (predicate == isSentenceEnd)
+ return isSentenceStart;
+
+ g_assert_not_reached();
}
-static gchar* webkit_accessible_text_get_text_before_offset(AtkText* text, gint offset, AtkTextBoundary boundary_type, gint* start_offset, gint* end_offset)
+static gchar* getTextHelper(GetTextFunctionType getTextFunctionType, AtkText* textObject, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset)
+{
+ AccessibilityObject* coreObject = core(textObject);
+ String text;
+
+ *startOffset = *endOffset = -1;
+
+ if (coreObject->isTextControl())
+ text = coreObject->text();
+ else
+ text = coreObject->textUnderElement();
+
+ char* cText = g_strdup(text.utf8().data());
+ glong textLength = g_utf8_strlen(cText, -1);
+
+ if (boundaryType == ATK_TEXT_BOUNDARY_CHAR) {
+ int effectiveOffset;
+
+ switch (getTextFunctionType) {
+ case AfterOffset:
+ effectiveOffset = offset + 1;
+ break;
+ case BeforeOffset:
+ effectiveOffset = offset - 1;
+ break;
+ case AtOffset:
+ effectiveOffset = offset;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+
+ *startOffset = effectiveOffset;
+ *endOffset = effectiveOffset + 1;
+ } else {
+ PangoLogAttr* attrs = g_new(PangoLogAttr, textLength + 1);
+ PangoLanguage* language = pango_language_get_default();
+ pango_get_log_attrs(cText, -1, -1, language, attrs, textLength + 1);
+
+ isCharacterAttribute predicate;
+
+ if (boundaryType == ATK_TEXT_BOUNDARY_WORD_START)
+ predicate = isWordStart;
+ else if (boundaryType == ATK_TEXT_BOUNDARY_WORD_END)
+ predicate = isWordEnd;
+ else if (boundaryType == ATK_TEXT_BOUNDARY_SENTENCE_START)
+ predicate = isSentenceStart;
+ else if (boundaryType == ATK_TEXT_BOUNDARY_SENTENCE_END)
+ predicate = isSentenceEnd;
+ else
+ // FIXME: bail out for now, since we are missing the LINE
+ // boundary implementations
+ goto out;
+
+ switch (boundaryType) {
+ case ATK_TEXT_BOUNDARY_WORD_START:
+ case ATK_TEXT_BOUNDARY_SENTENCE_START:
+ if (getTextFunctionType == AfterOffset) {
+ // Take the item after the current one in any case
+ findCharacterAttribute(predicate, attrs, DirectionForward, offset + 1, textLength + 1, startOffset);
+ findCharacterAttributeSkip(predicate, 1, attrs, DirectionForward, offset + 1, textLength + 1, endOffset);
+ } else if (getTextFunctionType == AtOffset) {
+ // Take the item at point if the offset is in an item or
+ // the item before otherwise
+ findCharacterAttribute(predicate, attrs, DirectionBackwards, offset, textLength + 1, startOffset);
+ if (!findCharacterAttribute(predicate, attrs, DirectionForward, offset + 1, textLength + 1, endOffset)) {
+ findCharacterAttribute(oppositePredicate(predicate), attrs, DirectionForward, offset + 1, textLength + 1, endOffset);
+ // We want to include the actual end boundary
+ // here, since *_START would have done so. Advance
+ // until the end of the string if possible
+ if (*endOffset != -1 && *endOffset < textLength)
+ *endOffset = textLength;
+ }
+ } else {
+ // Take the item before the point if the offset is in an
+ // item, or the the item before that one otherwise
+ findCharacterAttributeSkip(predicate, 1, attrs, DirectionBackwards, offset, textLength + 1, startOffset);
+ findCharacterAttribute(predicate, attrs, DirectionBackwards, offset, textLength + 1, endOffset);
+ }
+ break;
+ case ATK_TEXT_BOUNDARY_WORD_END:
+ case ATK_TEXT_BOUNDARY_SENTENCE_END:
+ if (getTextFunctionType == AfterOffset) {
+ // Take the item after the current item if the offset is
+ // in a item, or the item after that otherwise
+ findCharacterAttribute(predicate, attrs, DirectionForward, offset, textLength + 1, startOffset);
+ findCharacterAttributeSkip(predicate, 1, attrs, DirectionForward, offset, textLength + 1, endOffset);
+ } else if (getTextFunctionType == AtOffset) {
+ // Take the item at point if the offset is in a item or
+ // the item after otherwise
+ if (!findCharacterAttribute(predicate, attrs, DirectionBackwards, offset, textLength + 1, startOffset))
+ // No match before offset, take the first opposite match at or before the offset
+ findCharacterAttribute(oppositePredicate(predicate), attrs, DirectionBackwards, offset, textLength + 1, startOffset);
+ findCharacterAttribute(predicate, attrs, DirectionForward, offset + 1, textLength + 1, endOffset);
+ } else {
+ // Take the item before the point in any case
+ if (!findCharacterAttributeSkip(predicate, 1, attrs, DirectionBackwards, offset, textLength + 1, startOffset)) {
+ int tmpOffset;
+ // No match before offset, take the first opposite match at or before the offset
+ findCharacterAttribute(predicate, attrs, DirectionBackwards, offset, textLength + 1, &tmpOffset);
+ findCharacterAttribute(oppositePredicate(predicate), attrs, DirectionBackwards, tmpOffset - 1, textLength + 1, startOffset);
+ }
+ findCharacterAttribute(predicate, attrs, DirectionBackwards, offset, textLength + 1, endOffset);
+ }
+ break;
+ default:
+ g_assert_not_reached();
+ }
+
+ g_free(attrs);
+ }
+
+ out:
+ if (*startOffset < 0 || *endOffset < 0) {
+ *startOffset = *endOffset = 0;
+ return g_strdup("");
+ }
+
+ char* start = g_utf8_offset_to_pointer(cText, (glong)*startOffset);
+ char* end = g_utf8_offset_to_pointer(cText, (glong)*endOffset);
+ char* resultText = g_strndup(start, end - start);
+ g_free(cText);
+ return resultText;
+}
+
+static gchar* webkit_accessible_text_get_text_after_offset(AtkText* text, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset)
+{
+ return getTextHelper(AfterOffset, text, offset, boundaryType, startOffset, endOffset);
+}
+
+static gchar* webkit_accessible_text_get_text_at_offset(AtkText* text, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset)
+{
+ return getTextHelper(AtOffset, text, offset, boundaryType, startOffset, endOffset);
+}
+
+static gchar* webkit_accessible_text_get_text_before_offset(AtkText* text, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset)
+{
+ return getTextHelper(BeforeOffset, text, offset, boundaryType, startOffset, endOffset);
+}
+
+static gunichar webkit_accessible_text_get_character_at_offset(AtkText* text, gint offset)
{
notImplemented();
return NULL;
+2009-05-19 Xan Lopez <xlopez@igalia.com>
+
+ Reviewed by Jan Alonzo and Gustavo Noronha.
+
+ https://bugs.webkit.org/show_bug.cgi?id=25415
+ [GTK][ATK] Please implement support for get_text_at_offset
+
+ New test file for ATK functionality.
+
+ * tests/testatk.c: Added.
+ (bail_out):
+ (test_get_text_function):
+ (test_webkit_atk_get_text_at_offset):
+ (main):
+
2009-05-28 Gustavo Noronha Silva <gustavo.noronha@collabora.co.uk>
Reviewed by Xan Lopez.
--- /dev/null
+/*
+ * Copyright (C) 2009 Igalia S.L.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <errno.h>
+#include <unistd.h>
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
+#include <webkit/webkit.h>
+
+#if GLIB_CHECK_VERSION(2, 16, 0) && GTK_CHECK_VERSION(2, 14, 0)
+
+static const char* contents = "<html><body><p>This is a test. This is the second sentence. And this the third.</p></body></html>";
+
+static gboolean bail_out(GMainLoop* loop)
+{
+ if (g_main_loop_is_running(loop))
+ g_main_loop_quit(loop);
+
+ return FALSE;
+}
+
+typedef gchar* (*AtkGetTextFunction) (AtkText*, gint, AtkTextBoundary, gint*, gint*);
+
+static void test_get_text_function(AtkText* text_obj, AtkGetTextFunction fn, AtkTextBoundary boundary, gint offset, const char* text_result, gint start_offset_result, gint end_offset_result)
+{
+ gint start_offset, end_offset;
+ char* text;
+
+ text = fn(text_obj, offset, boundary, &start_offset, &end_offset);
+ g_assert_cmpstr(text, ==, text_result);
+ g_assert_cmpint(start_offset, ==, start_offset_result);
+ g_assert_cmpint(end_offset, ==, end_offset_result);
+ g_free(text);
+}
+
+static void test_webkit_atk_get_text_at_offset(void)
+{
+ WebKitWebView* webView;
+ AtkObject *obj;
+ GMainLoop* loop;
+ AtkText* text_obj;
+ char* text;
+
+ webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
+ g_object_ref_sink(webView);
+ webkit_web_view_load_string(webView, contents, NULL, NULL, NULL);
+ loop = g_main_loop_new(NULL, TRUE);
+
+ g_timeout_add(100, (GSourceFunc)bail_out, loop);
+ g_main_loop_run(loop);
+
+ /* Get to the inner AtkText object */
+ obj = gtk_widget_get_accessible(GTK_WIDGET(webView));
+ g_assert(obj);
+ obj = atk_object_ref_accessible_child(obj, 0);
+ g_assert(obj);
+ obj = atk_object_ref_accessible_child(obj, 0);
+ g_assert(obj);
+
+ text_obj = ATK_TEXT(obj);
+ g_assert(ATK_IS_TEXT(text_obj));
+
+ text = atk_text_get_text(text_obj, 0, -1);
+ g_assert_cmpstr(text, ==, "This is a test. This is the second sentence. And this the third.");
+ g_free(text);
+
+ /* ATK_TEXT_BOUNDARY_CHAR */
+ test_get_text_function(text_obj, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_CHAR,
+ 0, "T", 0, 1);
+
+ test_get_text_function(text_obj, atk_text_get_text_after_offset, ATK_TEXT_BOUNDARY_CHAR,
+ 0, "h", 1, 2);
+
+ test_get_text_function(text_obj, atk_text_get_text_before_offset, ATK_TEXT_BOUNDARY_CHAR,
+ 0, "", 0, 0);
+
+ test_get_text_function(text_obj, atk_text_get_text_before_offset, ATK_TEXT_BOUNDARY_CHAR,
+ 1, "T", 0, 1);
+
+ /* ATK_TEXT_BOUNDARY_WORD_START */
+ test_get_text_function(text_obj, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_WORD_START,
+ 0, "This ", 0, 5);
+
+ test_get_text_function(text_obj, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_WORD_START,
+ 4, "This ", 0, 5);
+
+ test_get_text_function(text_obj, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_WORD_START,
+ 10, "test. ", 10, 16);
+
+ test_get_text_function(text_obj, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_WORD_START,
+ 58, "third.", 58, 64);
+
+ test_get_text_function(text_obj, atk_text_get_text_before_offset, ATK_TEXT_BOUNDARY_WORD_START,
+ 5, "This ", 0, 5);
+
+ test_get_text_function(text_obj, atk_text_get_text_before_offset, ATK_TEXT_BOUNDARY_WORD_START,
+ 7, "This ", 0, 5);
+
+ test_get_text_function(text_obj, atk_text_get_text_after_offset, ATK_TEXT_BOUNDARY_WORD_START,
+ 0, "is ", 5, 8);
+
+ test_get_text_function(text_obj, atk_text_get_text_after_offset, ATK_TEXT_BOUNDARY_WORD_START,
+ 4, "is ", 5, 8);
+
+ test_get_text_function(text_obj, atk_text_get_text_after_offset, ATK_TEXT_BOUNDARY_WORD_START,
+ 3, "is ", 5, 8);
+
+ /* ATK_TEXT_BOUNDARY_WORD_END */
+ test_get_text_function(text_obj, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_WORD_END,
+ 0, "This", 0, 4);
+
+ test_get_text_function(text_obj, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_WORD_END,
+ 4, " is", 4, 7);
+
+ test_get_text_function(text_obj, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_WORD_END,
+ 5, " is", 4, 7);
+
+ test_get_text_function(text_obj, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_WORD_END,
+ 9, " test", 9, 14);
+
+ test_get_text_function(text_obj, atk_text_get_text_before_offset, ATK_TEXT_BOUNDARY_WORD_END,
+ 5, "This", 0, 4);
+
+ test_get_text_function(text_obj, atk_text_get_text_before_offset, ATK_TEXT_BOUNDARY_WORD_END,
+ 4, "This", 0, 4);
+
+ test_get_text_function(text_obj, atk_text_get_text_before_offset, ATK_TEXT_BOUNDARY_WORD_END,
+ 7, " is", 4, 7);
+
+ test_get_text_function(text_obj, atk_text_get_text_after_offset, ATK_TEXT_BOUNDARY_WORD_END,
+ 5, " a", 7, 9);
+
+ test_get_text_function(text_obj, atk_text_get_text_after_offset, ATK_TEXT_BOUNDARY_WORD_END,
+ 4, " is", 4, 7);
+
+ test_get_text_function(text_obj, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_WORD_END,
+ 58, " third", 57, 63);
+
+ /* ATK_TEXT_BOUNDARY_SENTENCE_START */
+ test_get_text_function(text_obj, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_SENTENCE_START,
+ 0, "This is a test. ", 0, 16);
+
+ test_get_text_function(text_obj, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_SENTENCE_START,
+ 15, "This is a test. ", 0, 16);
+
+ test_get_text_function(text_obj, atk_text_get_text_after_offset, ATK_TEXT_BOUNDARY_SENTENCE_START,
+ 0, "This is the second sentence. ", 16, 45);
+
+ test_get_text_function(text_obj, atk_text_get_text_after_offset, ATK_TEXT_BOUNDARY_SENTENCE_START,
+ 15, "This is the second sentence. ", 16, 45);
+
+ test_get_text_function(text_obj, atk_text_get_text_before_offset, ATK_TEXT_BOUNDARY_SENTENCE_START,
+ 16, "This is a test. ", 0, 16);
+
+ test_get_text_function(text_obj, atk_text_get_text_before_offset, ATK_TEXT_BOUNDARY_SENTENCE_START,
+ 44, "This is a test. ", 0, 16);
+
+ test_get_text_function(text_obj, atk_text_get_text_before_offset, ATK_TEXT_BOUNDARY_SENTENCE_START,
+ 15, "", 0, 0);
+
+ /* ATK_TEXT_BOUNDARY_SENTENCE_END */
+ test_get_text_function(text_obj, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_SENTENCE_END,
+ 0, "This is a test.", 0, 15);
+
+ test_get_text_function(text_obj, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_SENTENCE_END,
+ 15, " This is the second sentence.", 15, 44);
+
+ test_get_text_function(text_obj, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_SENTENCE_END,
+ 16, " This is the second sentence.", 15, 44);
+
+ test_get_text_function(text_obj, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_SENTENCE_END,
+ 17, " This is the second sentence.", 15, 44);
+
+ test_get_text_function(text_obj, atk_text_get_text_after_offset, ATK_TEXT_BOUNDARY_SENTENCE_END,
+ 0, " This is the second sentence.", 15, 44);
+
+ test_get_text_function(text_obj, atk_text_get_text_after_offset, ATK_TEXT_BOUNDARY_SENTENCE_END,
+ 15, " This is the second sentence.", 15, 44);
+
+ test_get_text_function(text_obj, atk_text_get_text_before_offset, ATK_TEXT_BOUNDARY_SENTENCE_END,
+ 16, "This is a test.", 0, 15);
+
+ test_get_text_function(text_obj, atk_text_get_text_before_offset, ATK_TEXT_BOUNDARY_SENTENCE_END,
+ 15, "This is a test.", 0, 15);
+
+ test_get_text_function(text_obj, atk_text_get_text_before_offset, ATK_TEXT_BOUNDARY_SENTENCE_END,
+ 14, "", 0, 0);
+
+ test_get_text_function(text_obj, atk_text_get_text_before_offset, ATK_TEXT_BOUNDARY_SENTENCE_END,
+ 44, " This is the second sentence.", 15, 44);
+
+ /* ATK_TEXT_BOUNDARY_LINE_START */
+ /* TODO */
+ test_get_text_function(text_obj, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_LINE_START,
+ 0, "", 0, 0);
+
+ /* ATK_TEXT_BOUNDARY_LINE_END */
+ /* TODO */
+ test_get_text_function(text_obj, atk_text_get_text_at_offset, ATK_TEXT_BOUNDARY_LINE_END,
+ 0, "", 0, 0);
+
+ g_object_unref(webView);
+}
+
+int main(int argc, char** argv)
+{
+ g_thread_init(NULL);
+ gtk_test_init(&argc, &argv, NULL);
+
+ g_test_bug_base("https://bugs.webkit.org/");
+ g_test_add_func("/webkit/atk/get_text_at_offset", test_webkit_atk_get_text_at_offset);
+ return g_test_run ();
+}
+
+#else
+int main(int argc, char** argv)
+{
+ g_critical("You will need at least glib-2.16.0 and gtk-2.14.0 to run the unit tests. Doing nothing now.");
+ return 0;
+}
+
+#endif