JAWS unable to read text through IAccessible2 with Qt

Hello.

I am trying to make an Interactive Fiction interpreter (you could call it an "engine" if you will) accessible with JAWS. The application uses Qt 5. For the curious, the application in question is QTads (http://qtads.sf.net).

Accessibility in Qt is provided though the IAccessible2 interface, wrapped in Qt-specific classes, and I have implemented the required callbacks in a way that allows me to see what the screen reader is calling. When running NVDA, it works fine. NVDA queries the role of the output window, for which I specify QAccessible::EditableText, and it then requests a QAccessible::TextInterface, for which I return a QAccessibleTextInterface object (which translates into an IAccessibleText object). NVDA will then call characterCount() on the returned object (this is the wrapper for nCharacters() in IAccessible2) and then the various text extraction methods, like text(), textAtOffset(), offsetAtPoint() (when moving the mouse over the text), etc. It then speaks the text I return in those methods.

JAWS, however, doesn't want to play along. After it queries the object role, it performs a few calls to characterCount(), selection() and attributes(), but calls to text extraction methods never happen. QAccessibleTextInterface::text(), textAtOffset(), etc, are never called and thus the text is never spoken by JAWS. When the window is activated, JAWS speaks this out:

"Alt tab, game text, edit read only, insert f1 help, type and text." (Not sure about the "type and text"; it's kind of hard to tell what it says exactly.)

("game text" is the text I return when the QAccessible::Name text is requested though the QAccessibleInterface::text() function.)

I do not know where to go from here. Is this a Qt issue? Something I'm doing wrong? I can't tell.

My implementation looks like this:

I have a QWidget subclass, called DispWidget, that paints the text on screen, driven by the game engine's text layout engine. To make DispWidget accessible, I implement an AccessibleDispWidget class that inherits from QAccessibleWidget, QAccessibleTextInterface and QAccessibleEditableTextInterface:

class AccessibleDispWidget: public QAccessibleWidget,
                            public QAccessibleTextInterface,
                            public QAccessibleEditableTextInterface
{
public:
    explicit AccessibleDispWidget(QWidget* w)
        : QAccessibleWidget(w, QAccessible::EditableText)
    { /* ... */ }

    // ...
    // Overrides of the virtuals here -- see below.
    // ...
}

I then implement or override the appropriate virtuals:

// =========================================================
// QAccessibleInterface
// =========================================================
QString
text(QAccessible::Text txt) const override
{
    switch (txt) {
    case QAccessible::Name:
        return "Game text.";
    case QAccessible::Description:
        return "";
    case QAccessible::Value:
        return "";
    case QAccessible::Help:
        return "Help.";
    case QAccessible::Accelerator:
        return "";
    default:
        return QAccessibleWidget::text(txt);
    }
}

void*
interface_cast(QAccessible::InterfaceType t) override
{
    if (t == QAccessible::TextInterface) {
        return static_cast<QAccessibleTextInterface*>(this);
    }
    if (t == QAccessible::EditableTextInterface) {
        return static_cast<QAccessibleEditableTextInterface*>(this);
    }
    return QAccessibleWidget::interface_cast(t);
}

QAccessible::State
state() const override
{
    auto s = QAccessibleWidget::state();
    s.multiLine = true;
    s.readOnly = true;
    s.selectableText = false;
    s.editable = false;
    return s;
}

// =========================================================
// QAccessibleTextInterface
// =========================================================
// never called
void
addSelection(int startOffset, int endOffset) override;

QString
attributes(int offset, int* startOffset, int* endOffset) const override
{
    // Simple implementation for now as to not complicate things;
    // all text uses the same attributes.
    *startOffset = 0;
    *endOffset = characterCount();
    return QLatin1String("font-family:\"Open Sans\";font-size:14pt;font-style:normal;font-weight:normal;language:en-US;");
}

int
characterCount() const override
{ return length_of_text; }

// never called
QRect
characterRect(int offset) const override;

// never called
int
cursorPosition() const override;

// never called
int
offsetAtPoint(const QPoint& point) const override;

// never called
void
removeSelection(int selectionIndex) override;

// never called
void
scrollToSubstring(int startIndex, int endIndex) override;

void
selection(int selectionIndex, int* startOffset, int* endOffset) const override
{ *startOffset = *endOffset = 0; }

// never called
int
selectionCount() const override;

// never called
void
setCursorPosition(int position) override;

// never called
void
setSelection(int selectionIndex, int startOffset, int endOffset) override;

// never called
QString
text(int startOffset, int endOffset) const override;

// never called
QString
textAfterOffset(int offset, QAccessible::TextBoundaryType boundaryType, int* startOffset,
                int* endOffset) const override;

// never called
QString
textAtOffset(int offset, QAccessible::TextBoundaryType boundaryType, int* startOffset,
             int* endOffset) const override;

// never called
QString
textBeforeOffset(int offset, QAccessible::TextBoundaryType boundaryType, int* startOffset,
                 int* endOffset) const override;

// =========================================================
// QAccessibleEditableTextInterface
// =========================================================
// never called
void
deleteText(int startOffset, int endOffset) override;

// never called
void
insertText(int offset, const QString& text) override;

// never called
void
replaceText(int startOffset, int endOffset, const QString& text) override;

So all appropriate functions are implemented, and as mentioned previously, NVDA makes use of them and extracts the text just fine. Functions that are never called by JAWS are marked as such with  "// never called", so at this point their implementations are irrelevant and omitted to keep the above code short.

And this is where I'm at, with no idea on why JAWS isn't using the provided interface functions or how to proceed from here.

_______________________________________________
Audiogames-reflector mailing list
Audiogames-reflector@sabahattin-gucukoglu.com
https://sabahattin-gucukoglu.com/cgi-bin/mailman/listinfo/audiogames-reflector
  • ... AudioGames . net Forum — Developers room : realnc via Audiogames-reflector
    • ... AudioGames . net Forum — Developers room : realnc via Audiogames-reflector
    • ... AudioGames . net Forum — Developers room : Ethin via Audiogames-reflector
    • ... AudioGames . net Forum — Developers room : realnc via Audiogames-reflector
    • ... AudioGames . net Forum — Developers room : Ethin via Audiogames-reflector
    • ... AudioGames . net Forum — Developers room : Ethin via Audiogames-reflector
    • ... AudioGames . net Forum — Developers room : pitermach via Audiogames-reflector
    • ... AudioGames . net Forum — Developers room : realnc via Audiogames-reflector

Reply via email to