Implemented QAccessibleTextWidget
authorJan-Arve Saether <jan-arve.saether@nokia.com>
Wed, 8 Aug 2012 14:06:33 +0000 (16:06 +0200)
committerQt by Nokia <qt-info@nokia.com>
Tue, 14 Aug 2012 01:56:55 +0000 (03:56 +0200)
A new class called QAccessibleTextWidget was added.
This class should implement all methods of QAccessibleTextInterface and
QAccessibleEditableTextInterface which only need a QTextCursor, and it
defines two pure virtual methods, to obtain and set the text cursor, so
accessible implementations of widgets which use a text cursor can implement
these two methods.

QAccessibleTextEdit is now a subclass of QAccessibleTextWidget and most of
its methods were moved to QAccessibleTextWidget.

This is a forward port of ba5d7d608cc31fc63354fd74d85a1bad7780fc45 from
Qt 4.8, and is a prerequisite for forward-porting QPlainTextEdit

Change-Id: I6093c4fa7e0a77b84de779479c6074db006efec1
Reviewed-by: Frederik Gladhorn <frederik.gladhorn@nokia.com>
src/plugins/accessible/widgets/qaccessiblewidgets.cpp
src/plugins/accessible/widgets/qaccessiblewidgets.h
tests/auto/other/qaccessibility/tst_qaccessibility.cpp

index f6ec940..c8f68e1 100644 (file)
@@ -47,6 +47,7 @@
 #include "private/qtextedit_p.h"
 #include "qtextdocument.h"
 #include "qtextobject.h"
+#include "qtextboundaryfinder.h"
 #include "qscrollbar.h"
 #include "qdebug.h"
 #include <QApplication>
@@ -106,12 +107,12 @@ QList<QWidget*> childWidgets(const QWidget *widget, bool includeTopLevel)
 */
 
 /*!
-  \fn QAccessibleTextEdit::QAccessibleTextEdit(QWidgetwidget)
+  \fn QAccessibleTextEdit::QAccessibleTextEdit(QWidget *widget)
 
   Constructs a QAccessibleTextEdit object for a \a widget.
 */
 QAccessibleTextEdit::QAccessibleTextEdit(QWidget *o)
-: QAccessibleWidget(o, QAccessible::EditableText)
+: QAccessibleTextWidget(o, QAccessible::EditableText)
 {
     Q_ASSERT(widget()->inherits("QTextEdit"));
 }
@@ -122,6 +123,34 @@ QTextEdit *QAccessibleTextEdit::textEdit() const
     return static_cast<QTextEdit *>(widget());
 }
 
+QTextCursor QAccessibleTextEdit::textCursor() const
+{
+    return textEdit()->textCursor();
+}
+
+QTextDocument *QAccessibleTextEdit::textDocument() const
+{
+    return textEdit()->document();
+}
+
+void QAccessibleTextEdit::setTextCursor(const QTextCursor &textCursor)
+{
+    textEdit()->setTextCursor(textCursor);
+}
+
+QWidget *QAccessibleTextEdit::viewport() const
+{
+    return textEdit()->viewport();
+}
+
+QPoint QAccessibleTextEdit::scrollBarsCurrentPosition() const
+{
+    QPoint result(0, 0);
+    result.setX(textEdit()->horizontalScrollBar() ? textEdit()->horizontalScrollBar()->sliderPosition() : 0);
+    result.setY(textEdit()->verticalScrollBar() ? textEdit()->verticalScrollBar()->sliderPosition() : 0);
+    return result;
+}
+
 QString QAccessibleTextEdit::text(QAccessible::Text t) const
 {
     if (t == QAccessible::Value)
@@ -159,303 +188,11 @@ void *QAccessibleTextEdit::interface_cast(QAccessible::InterfaceType t)
     return QAccessibleWidget::interface_cast(t);
 }
 
-void QAccessibleTextEdit::addSelection(int startOffset, int endOffset)
-{
-    setSelection(0, startOffset, endOffset);
-}
-
-QString QAccessibleTextEdit::attributes(int offset, int *startOffset, int *endOffset) const
-{
-    /* The list of attributes can be found at:
-     http://linuxfoundation.org/collaborate/workgroups/accessibility/iaccessible2/textattributes
-    */
-
-    if (offset >= characterCount()) {
-        *startOffset = -1;
-        *endOffset = -1;
-        return QString();
-    }
-
-    QMap<QString, QString> attrs;
-
-    QTextCursor cursor = textEdit()->textCursor();
-
-    //cursor.charFormat returns the format of the previous character
-    cursor.setPosition(offset + 1);
-    QTextCharFormat charFormat = cursor.charFormat();
-
-    cursor.setPosition(offset);
-    QTextBlockFormat blockFormat = cursor.blockFormat();
-
-    QTextCharFormat charFormatComp;
-    QTextBlockFormat blockFormatComp;
-
-    *startOffset = offset;
-    cursor.setPosition(*startOffset);
-    while (*startOffset > 0) {
-        charFormatComp = cursor.charFormat();
-        cursor.setPosition(*startOffset - 1);
-        blockFormatComp = cursor.blockFormat();
-        if ((charFormat == charFormatComp) && (blockFormat == blockFormatComp))
-            (*startOffset)--;
-        else
-            break;
-    }
-
-    int limit = characterCount() + 1;
-    *endOffset = offset + 1;
-    cursor.setPosition(*endOffset);
-    while (*endOffset < limit) {
-        blockFormatComp = cursor.blockFormat();
-        cursor.setPosition(*endOffset + 1);
-        charFormatComp = cursor.charFormat();
-        if ((charFormat == charFormatComp) && (cursor.blockFormat() == blockFormatComp))
-            (*endOffset)++;
-        else
-            break;
-    }
-
-    QString family = charFormat.fontFamily();
-    if (!family.isEmpty()) {
-        family = family.replace('\\',"\\\\");
-        family = family.replace(':',"\\:");
-        family = family.replace(',',"\\,");
-        family = family.replace('=',"\\=");
-        family = family.replace(';',"\\;");
-        family = family.replace('\"',"\\\"");
-        attrs["font-family"] = '"'+family+'"';
-    }
-
-    int fontSize = int(charFormat.fontPointSize());
-    if (fontSize)
-        attrs["font-size"] = QString::number(fontSize).append("pt");
-
-    //Different weight values are not handled
-    attrs["font-weight"] = (charFormat.fontWeight() > QFont::Normal) ? "bold" : "normal";
-
-    QFont::Style style = charFormat.font().style();
-    attrs["font-style"] = (style == QFont::StyleItalic) ? "italic" : ((style == QFont::StyleOblique) ? "oblique": "normal");
-
-    attrs["text-underline-style"] = charFormat.font().underline() ? "solid" : "none";
-
-    QTextCharFormat::VerticalAlignment alignment = charFormat.verticalAlignment();
-    attrs["text-position"] = (alignment == QTextCharFormat::AlignSubScript) ? "sub" : ((alignment == QTextCharFormat::AlignSuperScript) ? "super" : "baseline" );
-
-    QBrush background = charFormat.background();
-    if (background.style() == Qt::SolidPattern) {
-        attrs["background-color"] = QString("rgb(%1,%2,%3)").arg(background.color().red()).arg(background.color().green()).arg(background.color().blue());
-    }
-
-    QBrush foreground = charFormat.foreground();
-    if (foreground.style() == Qt::SolidPattern) {
-        attrs["color"] = QString("rgb(%1,%2,%3)").arg(foreground.color().red()).arg(foreground.color().green()).arg(foreground.color().blue());
-    }
-
-    switch (blockFormat.alignment() & (Qt::AlignLeft | Qt::AlignRight | Qt::AlignHCenter | Qt::AlignJustify)) {
-    case Qt::AlignLeft:
-        attrs["text-align"] = "left";
-        break;
-    case Qt::AlignRight:
-        attrs["text-align"] = "right";
-        break;
-    case Qt::AlignHCenter:
-        attrs["text-align"] = "center";
-        break;
-    case Qt::AlignJustify:
-        attrs["text-align"] = "left";
-        break;
-    }
-
-    QString result;
-    foreach (const QString &attributeName, attrs.keys()) {
-        result.append(attributeName).append(':').append(attrs[attributeName]).append(';');
-    }
-
-    return result;
-}
-
-int QAccessibleTextEdit::cursorPosition() const
-{
-    return textEdit()->textCursor().position();
-}
-
-QRect QAccessibleTextEdit::characterRect(int offset) const
-{
-    QTextEdit *edit = textEdit();
-    QTextCursor cursor(edit->document());
-    cursor.setPosition(offset);
-
-    if (cursor.position() != offset)
-        return QRect();
-
-    QRect r = edit->cursorRect(cursor);
-    if (cursor.movePosition(QTextCursor::NextCharacter)) {
-        r.setWidth(edit->cursorRect(cursor).x() - r.x());
-    } else {
-        // we don't know the width of the character - maybe because we're at document end
-        // in that case, IAccessible2 tells us to return the width of a default character
-        int averageCharWidth = QFontMetrics(cursor.charFormat().font()).averageCharWidth();
-        if (edit->layoutDirection() == Qt::RightToLeft)
-            averageCharWidth *= -1;
-        r.setWidth(averageCharWidth);
-    }
-
-    r.moveTo(edit->viewport()->mapToGlobal(r.topLeft()));
-    return r;
-}
-
-int QAccessibleTextEdit::selectionCount() const
-{
-    return textEdit()->textCursor().hasSelection() ? 1 : 0;
-}
-
-int QAccessibleTextEdit::offsetAtPoint(const QPoint &point) const
-{
-    QTextEdit *edit = textEdit();
-
-    QPoint p = edit->viewport()->mapFromGlobal(point);
-    // convert to document coordinates
-    p += QPoint(edit->horizontalScrollBar()->value(), edit->verticalScrollBar()->value());
-
-    return edit->document()->documentLayout()->hitTest(p, Qt::ExactHit);
-}
-
-void QAccessibleTextEdit::selection(int selectionIndex, int *startOffset, int *endOffset) const
-{
-    *startOffset = *endOffset = 0;
-    QTextCursor cursor = textEdit()->textCursor();
-
-    if (selectionIndex != 0 || !cursor.hasSelection())
-        return;
-
-    *startOffset = cursor.selectionStart();
-    *endOffset = cursor.selectionEnd();
-}
-
-QString QAccessibleTextEdit::text(int startOffset, int endOffset) const
-{
-    QTextCursor cursor(textEdit()->document());
-
-    cursor.setPosition(startOffset, QTextCursor::MoveAnchor);
-    cursor.setPosition(endOffset, QTextCursor::KeepAnchor);
-
-    return cursor.selectedText();
-}
-
-QString QAccessibleTextEdit::textBeforeOffset (int offset, BoundaryType boundaryType,
-        int *startOffset, int *endOffset) const
-{
-    // TODO - what exactly is before?
-    Q_UNUSED(offset);
-    Q_UNUSED(boundaryType);
-    Q_UNUSED(startOffset);
-    Q_UNUSED(endOffset);
-    return QString();
-}
-
-QString QAccessibleTextEdit::textAfterOffset(int offset, BoundaryType boundaryType,
-        int *startOffset, int *endOffset) const
-{
-    // TODO - what exactly is after?
-    Q_UNUSED(offset);
-    Q_UNUSED(boundaryType);
-    Q_UNUSED(startOffset);
-    Q_UNUSED(endOffset);
-    return QString();
-}
-
-QString QAccessibleTextEdit::textAtOffset(int offset, BoundaryType boundaryType,
-                                          int *startOffset, int *endOffset) const
-{
-    Q_ASSERT(startOffset);
-    Q_ASSERT(endOffset);
-
-    *startOffset = *endOffset = -1;
-    QTextEdit *edit = textEdit();
-
-    QTextCursor cursor(edit->document());
-    if (offset >= characterCount())
-        return QString();
-
-    cursor.setPosition(offset);
-    switch (boundaryType) {
-    case CharBoundary:
-        *startOffset = cursor.position();
-        cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
-        *endOffset = cursor.position();
-        break;
-    case WordBoundary:
-        cursor.movePosition(QTextCursor::StartOfWord, QTextCursor::MoveAnchor);
-        *startOffset = cursor.position();
-        cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
-        *endOffset = cursor.position();
-        break;
-    case SentenceBoundary:
-        // TODO - what's a sentence?
-        return QString();
-    case LineBoundary:
-        cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
-        *startOffset = cursor.position();
-        cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
-        *endOffset = cursor.position();
-        break;
-    case ParagraphBoundary:
-        cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
-        *startOffset = cursor.position();
-        cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
-        *endOffset = cursor.position();
-        break;
-    case NoBoundary: {
-        *startOffset = 0;
-        const QString txt = edit->toPlainText();
-        *endOffset = txt.count();
-        return txt; }
-    default:
-        qDebug("AccessibleTextAdaptor::textAtOffset: Unknown boundary type %d", boundaryType);
-        return QString();
-    }
-
-    return cursor.selectedText();
-}
-
-void QAccessibleTextEdit::removeSelection(int selectionIndex)
-{
-    if (selectionIndex != 0)
-        return;
-
-    QTextCursor cursor = textEdit()->textCursor();
-    cursor.clearSelection();
-    textEdit()->setTextCursor(cursor);
-}
-
-void QAccessibleTextEdit::setCursorPosition(int position)
-{
-    QTextCursor cursor = textEdit()->textCursor();
-    cursor.setPosition(position);
-    textEdit()->setTextCursor(cursor);
-}
-
-void QAccessibleTextEdit::setSelection(int selectionIndex, int startOffset, int endOffset)
-{
-    if (selectionIndex != 0)
-        return;
-
-    QTextCursor cursor = textEdit()->textCursor();
-    cursor.setPosition(startOffset, QTextCursor::MoveAnchor);
-    cursor.setPosition(endOffset, QTextCursor::KeepAnchor);
-    textEdit()->setTextCursor(cursor);
-}
-
-int QAccessibleTextEdit::characterCount() const
-{
-    return textEdit()->toPlainText().count();
-}
-
 void QAccessibleTextEdit::scrollToSubstring(int startIndex, int endIndex)
 {
     QTextEdit *edit = textEdit();
 
-    QTextCursor cursor(edit->document());
+    QTextCursor cursor = textCursor();
     cursor.setPosition(startIndex);
     QRect r = edit->cursorRect(cursor);
 
@@ -467,39 +204,7 @@ void QAccessibleTextEdit::scrollToSubstring(int startIndex, int endIndex)
 
     // E V I L, but ensureVisible is not public
     if (!QMetaObject::invokeMethod(edit, "_q_ensureVisible", Q_ARG(QRectF, r)))
-        qWarning("AccessibleTextEdit::scrollToSubstring failed!");
-}
-
-static QTextCursor cursorForRange(QTextEdit *textEdit, int startOffset, int endOffset)
-{
-    QTextCursor cursor(textEdit->document());
-    cursor.setPosition(startOffset, QTextCursor::MoveAnchor);
-    cursor.setPosition(endOffset, QTextCursor::KeepAnchor);
-    return cursor;
-}
-
-
-void QAccessibleTextEdit::deleteText(int startOffset, int endOffset)
-{
-    QTextCursor cursor = cursorForRange(textEdit(), startOffset, endOffset);
-
-    cursor.removeSelectedText();
-}
-
-void QAccessibleTextEdit::insertText(int offset, const QString &text)
-{
-    QTextCursor cursor(textEdit()->document());
-    cursor.setPosition(offset);
-
-    cursor.insertText(text);
-}
-
-void QAccessibleTextEdit::replaceText(int startOffset, int endOffset, const QString &text)
-{
-    QTextCursor cursor = cursorForRange(textEdit(), startOffset, endOffset);
-
-    cursor.removeSelectedText();
-    cursor.insertText(text);
+        qWarning("AccessibleTextEdit::scrollToSubstring failed!");
 }
 
 #endif // QT_NO_TEXTEDIT
@@ -535,7 +240,7 @@ int QAccessibleStackedWidget::indexOfChild(const QAccessibleInterface *child) co
     if (!child)
         return -1;
 
-    QWidgetwidget = qobject_cast<QWidget*>(child->object());
+    QWidget *widget = qobject_cast<QWidget*>(child->object());
     return stackedWidget()->indexOf(widget);
 }
 
@@ -1013,6 +718,385 @@ bool QAccessibleTitleBar::isValid() const
 
 #endif // QT_NO_DOCKWIDGET
 
+#ifndef QT_NO_CURSOR
+
+QAccessibleTextWidget::QAccessibleTextWidget(QWidget *o, QAccessible::Role r, const QString &name):
+    QAccessibleWidget(o, r, name)
+{
+
+}
+
+QRect QAccessibleTextWidget::characterRect(int offset) const
+{
+    QTextBlock block = textDocument()->findBlock(offset);
+    if (!block.isValid())
+        return QRect();
+
+    QTextLayout *layout = block.layout();
+    QPointF layoutPosition = layout->position();
+    int relativeOffset = offset - block.position();
+    QTextLine line = layout->lineForTextPosition(relativeOffset);
+
+    QRect r;
+
+    if (line.isValid()) {
+        qreal x = line.cursorToX(relativeOffset);
+        QFontMetrics fm(textCursor().charFormat().font());
+        const QString ch = text(offset, offset + 1);
+        if (!ch.isEmpty()) {
+            int w = fm.width(ch);
+            int h = fm.height();
+            r = QRect(layoutPosition.x() + x, layoutPosition.y() + line.y(),
+                      w, h);
+            r.moveTo(viewport()->mapToGlobal(r.topLeft()));
+        }
+    }
+
+    r.translate(-scrollBarsCurrentPosition());
+
+    return r;
+}
+
+int QAccessibleTextWidget::offsetAtPoint(const QPoint &point) const
+{
+    QPoint p = viewport()->mapFromGlobal(point);
+    // convert to document coordinates
+    p += scrollBarsCurrentPosition();
+    return textDocument()->documentLayout()->hitTest(p, Qt::ExactHit);
+}
+
+int QAccessibleTextWidget::selectionCount() const
+{
+    return textCursor().hasSelection() ? 1 : 0;
+}
+
+QString QAccessibleTextWidget::attributes(int offset, int *startOffset, int *endOffset) const
+{
+    /* The list of attributes can be found at:
+     http://linuxfoundation.org/collaborate/workgroups/accessibility/iaccessible2/textattributes
+    */
+
+    if (offset >= characterCount()) {
+        *startOffset = -1;
+        *endOffset = -1;
+        return QString();
+    }
+
+    QMap<QString, QString> attrs;
+
+    QTextCursor cursor = textCursor();
+
+    //cursor.charFormat returns the format of the previous character
+    cursor.setPosition(offset + 1);
+    QTextCharFormat charFormat = cursor.charFormat();
+
+    cursor.setPosition(offset);
+    QTextBlockFormat blockFormat = cursor.blockFormat();
+
+    QTextCharFormat charFormatComp;
+    QTextBlockFormat blockFormatComp;
+
+    *startOffset = offset;
+    cursor.setPosition(*startOffset);
+    while (*startOffset > 0) {
+        charFormatComp = cursor.charFormat();
+        cursor.setPosition(*startOffset - 1);
+        blockFormatComp = cursor.blockFormat();
+        if ((charFormat == charFormatComp) && (blockFormat == blockFormatComp))
+            (*startOffset)--;
+        else
+            break;
+    }
+
+    int limit = characterCount() + 1;
+    *endOffset = offset + 1;
+    cursor.setPosition(*endOffset);
+    while (*endOffset < limit) {
+        blockFormatComp = cursor.blockFormat();
+        cursor.setPosition(*endOffset + 1);
+        charFormatComp = cursor.charFormat();
+        if ((charFormat == charFormatComp) && (cursor.blockFormat() == blockFormatComp))
+            (*endOffset)++;
+        else
+            break;
+    }
+
+    QString family = charFormat.fontFamily();
+    if (!family.isEmpty()) {
+        family = family.replace('\\',"\\\\");
+        family = family.replace(':',"\\:");
+        family = family.replace(',',"\\,");
+        family = family.replace('=',"\\=");
+        family = family.replace(';',"\\;");
+        family = family.replace('\"',"\\\"");
+        attrs["font-family"] = '"'+family+'"';
+    }
+
+    int fontSize = int(charFormat.fontPointSize());
+    if (fontSize)
+        attrs["font-size"] = QString::number(fontSize).append("pt");
+
+    //Different weight values are not handled
+    attrs["font-weight"] = (charFormat.fontWeight() > QFont::Normal) ? "bold" : "normal";
+
+    QFont::Style style = charFormat.font().style();
+    attrs["font-style"] = (style == QFont::StyleItalic) ? "italic" : ((style == QFont::StyleOblique) ? "oblique": "normal");
+
+    attrs["text-underline-style"] = charFormat.font().underline() ? "solid" : "none";
+
+    QTextCharFormat::VerticalAlignment alignment = charFormat.verticalAlignment();
+    attrs["text-position"] = (alignment == QTextCharFormat::AlignSubScript) ? "sub" : ((alignment == QTextCharFormat::AlignSuperScript) ? "super" : "baseline" );
+
+    QBrush background = charFormat.background();
+    if (background.style() == Qt::SolidPattern) {
+        attrs["background-color"] = QString("rgb(%1,%2,%3)").arg(background.color().red()).arg(background.color().green()).arg(background.color().blue());
+    }
+
+    QBrush foreground = charFormat.foreground();
+    if (foreground.style() == Qt::SolidPattern) {
+        attrs["color"] = QString("rgb(%1,%2,%3)").arg(foreground.color().red()).arg(foreground.color().green()).arg(foreground.color().blue());
+    }
+
+    switch (blockFormat.alignment() & (Qt::AlignLeft | Qt::AlignRight | Qt::AlignHCenter | Qt::AlignJustify)) {
+    case Qt::AlignLeft:
+        attrs["text-align"] = "left";
+        break;
+    case Qt::AlignRight:
+        attrs["text-align"] = "right";
+        break;
+    case Qt::AlignHCenter:
+        attrs["text-align"] = "center";
+        break;
+    case Qt::AlignJustify:
+        attrs["text-align"] = "left";
+        break;
+    }
+
+    QString result;
+    foreach (const QString &attributeName, attrs.keys()) {
+        result.append(attributeName).append(':').append(attrs[attributeName]).append(';');
+    }
+
+    return result;
+}
+
+int QAccessibleTextWidget::cursorPosition() const
+{
+    return textCursor().position();
+}
+
+void QAccessibleTextWidget::selection(int selectionIndex, int *startOffset, int *endOffset) const
+{
+    *startOffset = *endOffset = 0;
+    QTextCursor cursor = textCursor();
+
+    if (selectionIndex != 0 || !cursor.hasSelection())
+        return;
+
+    *startOffset = cursor.selectionStart();
+    *endOffset = cursor.selectionEnd();
+}
+
+QString QAccessibleTextWidget::text(int startOffset, int endOffset) const
+{
+    QTextCursor cursor(textCursor());
+
+    cursor.setPosition(startOffset, QTextCursor::MoveAnchor);
+    cursor.setPosition(endOffset, QTextCursor::KeepAnchor);
+
+    return cursor.selectedText();
+}
+
+QPoint QAccessibleTextWidget::scrollBarsCurrentPosition() const
+{
+    return QPoint(0, 0);
+}
+
+QPair< int, int > QAccessibleTextWidget::getBoundaries(int offset, BoundaryType boundaryType) const
+{
+    if (offset >= characterCount())
+        return QPair<int, int>(characterCount(), characterCount());
+    if (offset < 0)
+        return QPair<int, int>(0, 0);
+
+    QTextCursor cursor = textCursor();
+    QPair<int, int> result;
+
+    cursor.setPosition(offset);
+    switch (boundaryType) {
+    case CharBoundary:
+        result.first = cursor.position();
+        cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
+        result.second = cursor.position();
+        break;
+    case WordBoundary:
+        cursor.movePosition(QTextCursor::StartOfWord, QTextCursor::MoveAnchor);
+        result.first = cursor.position();
+        cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
+        result.second = cursor.position();
+        break;
+    case SentenceBoundary: {
+        // QCursor does not provide functionality to move to next sentence.
+        // We therefore find the current block, then go through the block using
+        // QTextBoundaryFinder and find the sentence the \offset represents
+        cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
+        result.first = cursor.position();
+        cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
+        result.second = cursor.position();
+        QString blockText = cursor.selectedText();
+        const int offsetWithinBlockText = offset - result.first;
+        QTextBoundaryFinder sentenceFinder(QTextBoundaryFinder::Sentence, blockText);
+        sentenceFinder.setPosition(offsetWithinBlockText);
+        int prevBoundary = offsetWithinBlockText;
+        int nextBoundary = offsetWithinBlockText;
+        if (!sentenceFinder.isAtBoundary())
+            prevBoundary = sentenceFinder.toPreviousBoundary();
+        nextBoundary = sentenceFinder.toNextBoundary();
+        if (nextBoundary != -1)
+            result.second = result.first + nextBoundary;
+        if (prevBoundary != -1)
+            result.first += prevBoundary;
+        break; }
+    case LineBoundary:
+        cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
+        result.first = cursor.position();
+        cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
+        result.second = cursor.position();
+        break;
+    case ParagraphBoundary:
+        cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
+        result.first = cursor.position();
+        cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
+        result.second = cursor.position();
+        break;
+    case NoBoundary:
+        result.first = 0;
+        result.second = characterCount();
+        break;
+    default:
+        qDebug("QAccessibleTextWidget::getBoundaries: Unknown boundary type %d", boundaryType);
+        result.first = -1;
+        result.second = -1;
+    }
+    return result;
+}
+
+QString QAccessibleTextWidget::textBeforeOffset(int offset, BoundaryType boundaryType,
+                                                int *startOffset, int *endOffset) const
+{
+    Q_ASSERT(startOffset);
+    Q_ASSERT(endOffset);
+
+    QPair<int, int> boundaries = getBoundaries(offset, boundaryType);
+    boundaries = getBoundaries(boundaries.first - 1, boundaryType);
+
+    *startOffset = boundaries.first;
+    *endOffset = boundaries.second;
+
+    return text(boundaries.first, boundaries.second);
+ }
+
+
+QString QAccessibleTextWidget::textAfterOffset(int offset, BoundaryType boundaryType,
+                                              int *startOffset, int *endOffset) const
+{
+    Q_ASSERT(startOffset);
+    Q_ASSERT(endOffset);
+
+    QPair<int, int> boundaries = getBoundaries(offset, boundaryType);
+    boundaries = getBoundaries(boundaries.second, boundaryType);
+
+    *startOffset = boundaries.first;
+    *endOffset = boundaries.second;
+
+    return text(boundaries.first, boundaries.second);
+}
+
+QString QAccessibleTextWidget::textAtOffset(int offset, BoundaryType boundaryType,
+                                            int *startOffset, int *endOffset) const
+{
+    Q_ASSERT(startOffset);
+    Q_ASSERT(endOffset);
+
+    QPair<int, int> boundaries = getBoundaries(offset, boundaryType);
+
+    *startOffset = boundaries.first;
+    *endOffset = boundaries.second;
+
+    return text(boundaries.first, boundaries.second);
+}
+
+void QAccessibleTextWidget::setCursorPosition(int position)
+{
+    QTextCursor cursor = textCursor();
+    cursor.setPosition(position);
+    setTextCursor(cursor);
+}
+
+void QAccessibleTextWidget::addSelection(int startOffset, int endOffset)
+{
+    setSelection(0, startOffset, endOffset);
+}
+
+void QAccessibleTextWidget::removeSelection(int selectionIndex)
+{
+    if (selectionIndex != 0)
+        return;
+
+    QTextCursor cursor = textCursor();
+    cursor.clearSelection();
+    setTextCursor(cursor);
+}
+
+void QAccessibleTextWidget::setSelection(int selectionIndex, int startOffset, int endOffset)
+{
+    if (selectionIndex != 0)
+        return;
+
+    QTextCursor cursor = textCursor();
+    cursor.setPosition(startOffset, QTextCursor::MoveAnchor);
+    cursor.setPosition(endOffset, QTextCursor::KeepAnchor);
+    setTextCursor(cursor);
+}
+
+int QAccessibleTextWidget::characterCount() const
+{
+    QTextCursor cursor = textCursor();
+    cursor.movePosition(QTextCursor::End);
+    return cursor.position();
+}
+
+QTextCursor QAccessibleTextWidget::textCursorForRange(int startOffset, int endOffset) const
+{
+    QTextCursor cursor = textCursor();
+    cursor.setPosition(startOffset, QTextCursor::MoveAnchor);
+    cursor.setPosition(endOffset, QTextCursor::KeepAnchor);
+
+    return cursor;
+}
+
+void QAccessibleTextWidget::deleteText(int startOffset, int endOffset)
+{
+    QTextCursor cursor = textCursorForRange(startOffset, endOffset);
+    cursor.removeSelectedText();
+}
+
+void QAccessibleTextWidget::insertText(int offset, const QString &text)
+{
+    QTextCursor cursor = textCursor();
+    cursor.setPosition(offset);
+    cursor.insertText(text);
+}
+
+void QAccessibleTextWidget::replaceText(int startOffset, int endOffset, const QString &text)
+{
+    QTextCursor cursor = textCursorForRange(startOffset, endOffset);
+    cursor.removeSelectedText();
+    cursor.insertText(text);
+}
+#endif // QT_NO_CURSOR
+
+
 #ifndef QT_NO_MAINWINDOW
 QAccessibleMainWindow::QAccessibleMainWindow(QWidget *widget)
     : QAccessibleWidget(widget, QAccessible::Window) { }
index dba6455..e403465 100644 (file)
@@ -48,6 +48,7 @@
 #ifndef QT_NO_ACCESSIBILITY
 
 #include <QtCore/QPointer>
+#include <QtCore/QPair>
 
 QT_BEGIN_NAMESPACE
 
@@ -63,19 +64,16 @@ class QAbstractItemView;
 class QDockWidget;
 class QDockWidgetLayout;
 class QMainWindow;
+class QTextCursor;
+class QTextDocument;
 
-#ifndef QT_NO_TEXTEDIT
-class QAccessibleTextEdit : public QAccessibleWidget, public QAccessibleTextInterface,
-                            public QAccessibleEditableTextInterface
+#ifndef QT_NO_CURSOR
+class QAccessibleTextWidget : public QAccessibleWidget,
+                              public QAccessibleTextInterface,
+                              public QAccessibleEditableTextInterface
 {
 public:
-    explicit QAccessibleTextEdit(QWidget *o);
-
-    QString text(QAccessible::Text t) const;
-    void setText(QAccessible::Text t, const QString &text);
-    QAccessible::State state() const;
-
-    void *interface_cast(QAccessible::InterfaceType t);
+    QAccessibleTextWidget(QWidget *o, QAccessible::Role r = QAccessible::EditableText, const QString &name = QString());
 
     // QAccessibleTextInterface
     void addSelection(int startOffset, int endOffset);
@@ -86,17 +84,17 @@ public:
     int offsetAtPoint(const QPoint &point) const;
     void selection(int selectionIndex, int *startOffset, int *endOffset) const;
     QString text(int startOffset, int endOffset) const;
-    QString textBeforeOffset (int offset, QAccessible2::BoundaryType boundaryType,
-            int *startOffset, int *endOffset) const;
+    QString textBeforeOffset(int offset, QAccessible2::BoundaryType boundaryType,
+                             int *startOffset, int *endOffset) const;
     QString textAfterOffset(int offset, QAccessible2::BoundaryType boundaryType,
-            int *startOffset, int *endOffset) const;
+                            int *startOffset, int *endOffset) const;
     QString textAtOffset(int offset, QAccessible2::BoundaryType boundaryType,
-            int *startOffset, int *endOffset) const;
+                         int *startOffset, int *endOffset) const;
     void removeSelection(int selectionIndex);
     void setCursorPosition(int position);
     void setSelection(int selectionIndex, int startOffset, int endOffset);
     int characterCount() const;
-    void scrollToSubstring(int startIndex, int endIndex);
+
 
     // QAccessibleEditableTextInterface
     void deleteText(int startOffset, int endOffset);
@@ -104,8 +102,39 @@ public:
     void replaceText(int startOffset, int endOffset, const QString &text);
 
 protected:
+    QTextCursor textCursorForRange(int startOffset, int endOffset) const;
+    QPair<int, int> getBoundaries(int offset, QAccessible2::BoundaryType boundaryType) const;
+    virtual QPoint scrollBarsCurrentPosition() const;
+    virtual QTextCursor textCursor() const = 0;
+    virtual void setTextCursor(const QTextCursor &) = 0;
+    virtual QTextDocument *textDocument() const = 0;
+    virtual QWidget *viewport() const = 0;
+};
+#endif  //QT_NO_CURSOR
+
+#ifndef QT_NO_TEXTEDIT
+class QAccessibleTextEdit : public QAccessibleTextWidget
+{
+public:
+    explicit QAccessibleTextEdit(QWidget *o);
+
+    QString text(QAccessible::Text t) const;
+    void setText(QAccessible::Text t, const QString &text);
+    QAccessible::State state() const;
+
+    void *interface_cast(QAccessible::InterfaceType t);
+
+    // QAccessibleTextInterface
+    void scrollToSubstring(int startIndex, int endIndex);
+
+protected:
     QTextEdit *textEdit() const;
 
+    QPoint scrollBarsCurrentPosition() const;
+    QTextCursor textCursor() const;
+    void setTextCursor(const QTextCursor &textCursor);
+    QTextDocument *textDocument() const;
+    QWidget *viewport() const;
 private:
     int childOffset;
 };
index 90dc405..3e937fe 100644 (file)
@@ -1544,47 +1544,89 @@ void tst_QAccessibility::doubleSpinBoxTest()
     QTestAccessibility::clearEvents();
 }
 
-void tst_QAccessibility::textEditTest()
+static QRect characterRect(const QTextEdit &edit, int offset)
 {
-    {
-    QTextEdit edit;
-    int startOffset;
-    int endOffset;
-    QString text = "hello world\nhow are you today?\n";
-    edit.setText(text);
-    edit.show();
-
-    QAccessibleInterface *iface = QAccessible::queryAccessibleInterface(&edit);
-    QCOMPARE(iface->text(QAccessible::Value), text);
-    QCOMPARE(iface->textInterface()->textAtOffset(8, QAccessible2::WordBoundary, &startOffset, &endOffset), QString("world"));
-    QCOMPARE(startOffset, 6);
-    QCOMPARE(endOffset, 11);
-    QCOMPARE(iface->textInterface()->textAtOffset(14, QAccessible2::LineBoundary, &startOffset, &endOffset), QString("how are you today?"));
-    QCOMPARE(startOffset, 12);
-    QCOMPARE(endOffset, 30);
-    QCOMPARE(iface->textInterface()->characterCount(), 31);
+    QTextBlock block = edit.document()->findBlock(offset);
+    QTextLayout *layout = block.layout();
+    QPointF layoutPosition = layout->position();
+    int relativeOffset = offset - block.position();
+    QTextLine line = layout->lineForTextPosition(relativeOffset);
     QFontMetrics fm(edit.font());
-    QCOMPARE(iface->textInterface()->characterRect(0).size(), QSize(fm.width("h"), fm.height()));
-    QCOMPARE(iface->textInterface()->characterRect(5).size(), QSize(fm.width(" "), fm.height()));
-    QCOMPARE(iface->textInterface()->characterRect(6).size(), QSize(fm.width("w"), fm.height()));
+    QChar ch = edit.document()->characterAt(offset);
+    int w = fm.width(ch);
+    int h = fm.height();
 
-    QTestAccessibility::clearEvents();
+    qreal x = line.cursorToX(relativeOffset);
+    QRect r(layoutPosition.x() + x, layoutPosition.y() + line.y(), w, h);
+    r.moveTo(edit.viewport()->mapToGlobal(r.topLeft()));
 
-    // select text
-    QTextCursor c = edit.textCursor();
-    c.setPosition(2);
-    c.setPosition(4, QTextCursor::KeepAnchor);
-    edit.setTextCursor(c);
-    QAccessibleTextSelectionEvent sel(&edit, 2, 4);
-    QVERIFY_EVENT(&sel);
+    return r;
+}
 
-    edit.selectAll();
-    int end = edit.textCursor().position();
-    sel.setCursorPosition(end);
-    sel.setSelection(0, end);
-    QVERIFY_EVENT(&sel);
+void tst_QAccessibility::textEditTest()
+{
+    for (int pass = 0; pass < 2; ++pass) {
+        {
+        QTextEdit edit;
+        int startOffset;
+        int endOffset;
+        // create two blocks of text. The first block has two lines.
+        QString text = "<p>hello world.<br/>How are you today?</p><p>I'm fine, thanks</p>";
+        edit.setHtml(text);
+        if (pass == 1) {
+            QFont font("Helvetica");
+            font.setPointSizeF(12.5);
+            font.setWordSpacing(1.1);
+            edit.setFont(font);
+        }
+
+        edit.show();
+        QTest::qWaitForWindowShown(&edit);
+        QAccessibleInterface *iface = QAccessible::queryAccessibleInterface(&edit);
+        QCOMPARE(iface->text(QAccessible::Value), edit.toPlainText());
+        QCOMPARE(iface->textInterface()->textAtOffset(8, QAccessible2::WordBoundary, &startOffset, &endOffset), QString("world"));
+        QCOMPARE(startOffset, 6);
+        QCOMPARE(endOffset, 11);
+        QCOMPARE(iface->textInterface()->textAtOffset(15, QAccessible2::LineBoundary, &startOffset, &endOffset), QString("How are you today?"));
+        QCOMPARE(startOffset, 13);
+        QCOMPARE(endOffset, 31);
+        QCOMPARE(iface->textInterface()->characterCount(), 48);
+        QFontMetrics fm(edit.font());
+        QCOMPARE(iface->textInterface()->characterRect(0).size(), QSize(fm.width("h"), fm.height()));
+        QCOMPARE(iface->textInterface()->characterRect(5).size(), QSize(fm.width(" "), fm.height()));
+        QCOMPARE(iface->textInterface()->characterRect(6).size(), QSize(fm.width("w"), fm.height()));
+
+        int offset = 10;
+        QCOMPARE(iface->textInterface()->text(offset, offset + 1), QStringLiteral("d"));
+        QCOMPARE(iface->textInterface()->characterRect(offset), characterRect(edit, offset));
+        offset = 13;
+        QCOMPARE(iface->textInterface()->text(offset, offset + 1), QStringLiteral("H"));
+        QCOMPARE(iface->textInterface()->characterRect(offset), characterRect(edit, offset));
+        offset = 21;
+        QCOMPARE(iface->textInterface()->text(offset, offset + 1), QStringLiteral("y"));
+        QCOMPARE(iface->textInterface()->characterRect(offset), characterRect(edit, offset));
+        offset = 32;
+        QCOMPARE(iface->textInterface()->text(offset, offset + 1), QStringLiteral("I"));
+        QCOMPARE(iface->textInterface()->characterRect(offset), characterRect(edit, offset));
+
+        QTestAccessibility::clearEvents();
+
+        // select text
+        QTextCursor c = edit.textCursor();
+        c.setPosition(2);
+        c.setPosition(4, QTextCursor::KeepAnchor);
+        edit.setTextCursor(c);
+        QAccessibleTextSelectionEvent sel(&edit, 2, 4);
+        QVERIFY_EVENT(&sel);
+
+        edit.selectAll();
+        int end = edit.textCursor().position();
+        sel.setCursorPosition(end);
+        sel.setSelection(0, end);
+        QVERIFY_EVENT(&sel);
+        }
+        QTestAccessibility::clearEvents();
     }
-    QTestAccessibility::clearEvents();
 }
 
 void tst_QAccessibility::textBrowserTest()