From 2cb9ded6eca80aa0852ddfefaf6899ccb913c556 Mon Sep 17 00:00:00 2001 From: Jan-Arve Saether Date: Wed, 8 Aug 2012 16:06:33 +0200 Subject: [PATCH] Implemented QAccessibleTextWidget 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 --- .../accessible/widgets/qaccessiblewidgets.cpp | 742 ++++++++++++--------- .../accessible/widgets/qaccessiblewidgets.h | 59 +- .../other/qaccessibility/tst_qaccessibility.cpp | 112 +++- 3 files changed, 534 insertions(+), 379 deletions(-) diff --git a/src/plugins/accessible/widgets/qaccessiblewidgets.cpp b/src/plugins/accessible/widgets/qaccessiblewidgets.cpp index f6ec940..c8f68e1 100644 --- a/src/plugins/accessible/widgets/qaccessiblewidgets.cpp +++ b/src/plugins/accessible/widgets/qaccessiblewidgets.cpp @@ -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 @@ -106,12 +107,12 @@ QList childWidgets(const QWidget *widget, bool includeTopLevel) */ /*! - \fn QAccessibleTextEdit::QAccessibleTextEdit(QWidget* widget) + \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(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 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; - QWidget* widget = qobject_cast(child->object()); + QWidget *widget = qobject_cast(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 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(characterCount(), characterCount()); + if (offset < 0) + return QPair(0, 0); + + QTextCursor cursor = textCursor(); + QPair 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 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 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 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) { } diff --git a/src/plugins/accessible/widgets/qaccessiblewidgets.h b/src/plugins/accessible/widgets/qaccessiblewidgets.h index dba6455..e403465 100644 --- a/src/plugins/accessible/widgets/qaccessiblewidgets.h +++ b/src/plugins/accessible/widgets/qaccessiblewidgets.h @@ -48,6 +48,7 @@ #ifndef QT_NO_ACCESSIBILITY #include +#include 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 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; }; diff --git a/tests/auto/other/qaccessibility/tst_qaccessibility.cpp b/tests/auto/other/qaccessibility/tst_qaccessibility.cpp index 90dc405..3e937fe 100644 --- a/tests/auto/other/qaccessibility/tst_qaccessibility.cpp +++ b/tests/auto/other/qaccessibility/tst_qaccessibility.cpp @@ -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 = "

hello world.
How are you today?

I'm fine, thanks

"; + 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() -- 2.7.4