From 068545f2cd5b257c1691a28f814ff149d167ceec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Arve=20S=C3=A6ther?= Date: Wed, 6 Apr 2011 10:47:28 +0200 Subject: [PATCH 1/6] Make accessibility work on Windows with alien This means that there will be no implicit conversion to windows handles anymore! Enabler for making QML accessible on windows. (cherry picked from commit a3ac7deb5dfe48c5fdd0e170c20b6852c3bb41de) Reviewed-by: Frederik Gladhorn (cherry picked from commit d289e54f2d2aa066cb383d8c8249bd7594bdf7b0) --- src/gui/accessible/qaccessible_win.cpp | 126 ++++++++++++++++++++++--- 1 file changed, 113 insertions(+), 13 deletions(-) diff --git a/src/gui/accessible/qaccessible_win.cpp b/src/gui/accessible/qaccessible_win.cpp index caa21043d8..0e69a5eea1 100644 --- a/src/gui/accessible/qaccessible_win.cpp +++ b/src/gui/accessible/qaccessible_win.cpp @@ -47,6 +47,8 @@ #include "qt_windows.h" #include "qwidget.h" #include "qsettings.h" +#include +#include #include #if !defined(WINABLEAPI) @@ -159,6 +161,12 @@ void showDebug(const char* funcName, const QAccessibleInterface *iface) # define showDebug(f, iface) #endif +// This stuff is used for widgets/items with no window handle: +typedef QMap > NotifyMap; +Q_GLOBAL_STATIC(NotifyMap, qAccessibleRecentSentEvents) +static int eventNum = 0; + + void QAccessible::initialize() { @@ -251,17 +259,15 @@ void QAccessible::updateAccessibility(QObject *o, int who, Event reason) // An event has to be associated with a window, // so find the first parent that is a widget. QWidget *w = 0; - if (o->isWidgetType()) { - w = (QWidget*)o; - } else { - QObject *p = o; - while ((p = p->parent()) != 0) { - if (p->isWidgetType()) { - w = (QWidget*)p; + QObject *p = o; + do { + if (p->isWidgetType()) { + w = static_cast(p); + if (w->internalWinId()) break; - } } - } + p = p->parent(); + } while (p); if (!w) { if (reason != QAccessible::ContextHelpStart && @@ -282,12 +288,81 @@ void QAccessible::updateAccessibility(QObject *o, int who, Event reason) } } + WId wid = w->internalWinId(); if (reason != MenuCommand) { // MenuCommand is faked - ptrNotifyWinEvent(reason, w->winId(), OBJID_CLIENT, who); + if (w != o) { + // See comment "SENDING EVENTS TO OBJECTS WITH NO WINDOW HANDLE" + eventNum %= 50; //[0..49] + int eventId = - eventNum - 1; + + qAccessibleRecentSentEvents()->insert(eventId, qMakePair(o,who)); + ptrNotifyWinEvent(reason, wid, OBJID_CLIENT, eventId ); + + ++eventNum; + } else { + ptrNotifyWinEvent(reason, wid, OBJID_CLIENT, who); + } } #endif // Q_WS_WINCE } +/* == SENDING EVENTS TO OBJECTS WITH NO WINDOW HANDLE == + + If the user requested to send the event to a widget with no window, + we need to send an event to an object with no hwnd. + The way we do that is to send it to the *first* ancestor widget + with a window. + Then we'll need a way of identifying the child: + We'll just keep a list of the most recent events that we have sent, + where each entry in the list is identified by a negative value + between [-50,-1]. This negative value we will pass on to + NotifyWinEvent() as the child id. When the negative value have + reached -50, it will wrap around to -1. This seems to be enough + + Now, when the client receives that event, he will first call + AccessibleObjectFromEvent() where dwChildID is the special + negative value. AccessibleObjectFromEvent does two steps: + 1. It will first sent a WM_GETOBJECT to the server, asking + for the IAccessible interface for the HWND. + 2. With the IAccessible interface it got hold of it will call + acc_getChild where the child id argument is the special + negative identifier. In our reimplementation of get_accChild + we check for this if the child id is negative. If it is, then + we'll look up in our table for the entry that is associated + with that value. + The entry will then contain a pointer to the QObject /QWidget + that we can use to call queryAccessibleInterface() on. + + + The following figure shows how the interaction between server and + client is in the case when the server is sending an event. + +SERVER (Qt) | CLIENT | +--------------------------------------------+---------------------------------------+ + | +acc->updateAccessibility(obj, childIndex) | + | +recentEvents()->insert(- 1 - eventNum, | + qMakePair(obj, childIndex) | +NotifyWinEvent(hwnd, childId) => | + | AccessibleObjectFromEvent(event, hwnd, OBJID_CLIENT, childId ) + | will do: + <=== 1. send WM_GETOBJECT(hwnd, OBJID_CLIENT) +widget ~= hwnd +iface = queryAccessibleInteface(widget) +(create IAccessible interface wrapper for + iface) + return iface ===> IAccessible* iface; (for hwnd) + | + <=== call iface->get_accChild(childId) +get_accChild() { | + if (varChildID.lVal < 0) { + QPair ref = recentEvents().value(varChildID.lVal); + [...] + } +*/ + + void QAccessible::setRootObject(QObject *o) { if (rootObjectHandler) { @@ -418,15 +493,18 @@ public: delete accessible; } + /* IUnknown */ HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID *); ULONG STDMETHODCALLTYPE AddRef(); ULONG STDMETHODCALLTYPE Release(); + /* IDispatch */ HRESULT STDMETHODCALLTYPE GetTypeInfoCount(unsigned int *); HRESULT STDMETHODCALLTYPE GetTypeInfo(unsigned int, unsigned long, ITypeInfo **); HRESULT STDMETHODCALLTYPE GetIDsOfNames(const _GUID &, wchar_t **, unsigned int, unsigned long, long *); HRESULT STDMETHODCALLTYPE Invoke(long, const _GUID &, unsigned long, unsigned short, tagDISPPARAMS *, tagVARIANT *, tagEXCEPINFO *, unsigned int *); + /* IAccessible */ HRESULT STDMETHODCALLTYPE accHitTest(long xLeft, long yTop, VARIANT *pvarID); HRESULT STDMETHODCALLTYPE accLocation(long *pxLeft, long *pyTop, long *pcxWidth, long *pcyHeight, VARIANT varID); HRESULT STDMETHODCALLTYPE accNavigate(long navDir, VARIANT varStart, VARIANT *pvarEnd); @@ -451,6 +529,7 @@ public: HRESULT STDMETHODCALLTYPE get_accFocus(VARIANT *pvarID); HRESULT STDMETHODCALLTYPE get_accSelection(VARIANT *pvarChildren); + /* IOleWindow */ HRESULT STDMETHODCALLTYPE GetWindow(HWND *phwnd); HRESULT STDMETHODCALLTYPE ContextSensitiveHelp(BOOL fEnterMode); @@ -896,9 +975,30 @@ HRESULT STDMETHODCALLTYPE QWindowsAccessible::get_accChild(VARIANT varChildID, I if (varChildID.vt == VT_EMPTY) return E_INVALIDARG; + + int childIndex = varChildID.lVal; QAccessibleInterface *acc = 0; - RelationFlag rel = varChildID.lVal ? Child : Self; - accessible->navigate(rel, varChildID.lVal, &acc); + + if (childIndex < 0) { + const int entry = childIndex; + QPair ref = qAccessibleRecentSentEvents()->value(entry); + if (ref.first) { + acc = queryAccessibleInterface(ref.first); + if (acc && ref.second) { + if (ref.second) { + QAccessibleInterface *res; + int index = acc->navigate(Child, ref.second, &res); + delete acc; + if (index == -1) + return E_INVALIDARG; + acc = res; + } + } + } + } else { + RelationFlag rel = childIndex ? Child : Self; + accessible->navigate(rel, childIndex, &acc); + } if (acc) { QWindowsAccessible* wacc = new QWindowsAccessible(acc); @@ -1203,7 +1303,7 @@ HRESULT STDMETHODCALLTYPE QWindowsAccessible::GetWindow(HWND *phwnd) if (!o || !o->isWidgetType()) return E_FAIL; - *phwnd = static_cast(o)->winId(); + *phwnd = static_cast(o)->effectiveWinId(); return S_OK; } From 5e8b377cb790df011a0bc1ee7214e58c3d11b8a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Arve=20S=C3=A6ther?= Date: Thu, 5 May 2011 16:19:16 +0200 Subject: [PATCH 2/6] Fix updateAccessibility for QGraphicsObjects If updateAccessibility is called on a QGraphicsObject, walk up and find the closest ancestor widget with a HWND. (cherry picked from commit d4291591dfb6a7b1f5c7d00879e8162e84d9ab1b) Reviewed-by: Frederik Gladhorn (cherry picked from commit 96406a7dd609e75340f7b4a05726c60c197786b8) --- src/gui/accessible/qaccessible_win.cpp | 124 ++++++++++++++++++++++++- 1 file changed, 123 insertions(+), 1 deletion(-) diff --git a/src/gui/accessible/qaccessible_win.cpp b/src/gui/accessible/qaccessible_win.cpp index 0e69a5eea1..98db5293d2 100644 --- a/src/gui/accessible/qaccessible_win.cpp +++ b/src/gui/accessible/qaccessible_win.cpp @@ -49,6 +49,9 @@ #include "qsettings.h" #include #include +#include +#include +#include #include #if !defined(WINABLEAPI) @@ -150,6 +153,106 @@ static const char *roleString(QAccessible::Role role) return roles[int(role)]; } +static const char *eventString(QAccessible::Event ev) +{ + static const char *events[] = { + "null", // 0 + "SoundPlayed" /*= 0x0001*/, + "Alert" /*= 0x0002*/, + "ForegroundChanged" /*= 0x0003*/, + "MenuStart" /*= 0x0004*/, + "MenuEnd" /*= 0x0005*/, + "PopupMenuStart" /*= 0x0006*/, + "PopupMenuEnd" /*= 0x0007*/, + "ContextHelpStart" /*= 0x000C*/, // 8 + "ContextHelpEnd" /*= 0x000D*/, + "DragDropStart" /*= 0x000E*/, + "DragDropEnd" /*= 0x000F*/, + "DialogStart" /*= 0x0010*/, + "DialogEnd" /*= 0x0011*/, + "ScrollingStart" /*= 0x0012*/, + "ScrollingEnd" /*= 0x0013*/, + "MenuCommand" /*= 0x0018*/, // 16 + + // Values from IAccessible2 + "ActionChanged" /*= 0x0101*/, // 17 + "ActiveDescendantChanged", + "AttributeChanged", + "DocumentContentChanged", + "DocumentLoadComplete", + "DocumentLoadStopped", + "DocumentReload", + "HyperlinkEndIndexChanged", + "HyperlinkNumberOfAnchorsChanged", + "HyperlinkSelectedLinkChanged", + "HypertextLinkActivated", + "HypertextLinkSelected", + "HyperlinkStartIndexChanged", + "HypertextChanged", + "HypertextNLinksChanged", + "ObjectAttributeChanged", + "PageChanged", + "SectionChanged", + "TableCaptionChanged", + "TableColumnDescriptionChanged", + "TableColumnHeaderChanged", + "TableModelChanged", + "TableRowDescriptionChanged", + "TableRowHeaderChanged", + "TableSummaryChanged", + "TextAttributeChanged", + "TextCaretMoved", + // TextChanged, deprecated, use TextUpdated + //TextColumnChanged = TextCaretMoved + 2, + "TextInserted", + "TextRemoved", + "TextUpdated", + "TextSelectionChanged", + "VisibleDataChanged", /*= 0x0101+32*/ + "ObjectCreated" /*= 0x8000*/, // 49 + "ObjectDestroyed" /*= 0x8001*/, + "ObjectShow" /*= 0x8002*/, + "ObjectHide" /*= 0x8003*/, + "ObjectReorder" /*= 0x8004*/, + "Focus" /*= 0x8005*/, + "Selection" /*= 0x8006*/, + "SelectionAdd" /*= 0x8007*/, + "SelectionRemove" /*= 0x8008*/, + "SelectionWithin" /*= 0x8009*/, + "StateChanged" /*= 0x800A*/, + "LocationChanged" /*= 0x800B*/, + "NameChanged" /*= 0x800C*/, + "DescriptionChanged" /*= 0x800D*/, + "ValueChanged" /*= 0x800E*/, + "ParentChanged" /*= 0x800F*/, + "HelpChanged" /*= 0x80A0*/, + "DefaultActionChanged" /*= 0x80B0*/, + "AcceleratorChanged" /*= 0x80C0*/ + }; + int e = int(ev); + if (e <= 0x80c0) { + const int last = sizeof(events)/sizeof(char*) - 1; + + if (e <= 0x07) + return events[e]; + else if (e <= 0x13) + return events[e - 0x0c + 8]; + else if (e == 0x18) + return events[16]; + else if (e <= 0x0101 + 32) + return events[e - 0x101 + 17]; + else if (e <= 0x800f) + return events[e - 0x8000 + 49]; + else if (e == 0x80a0) + return events[last - 2]; + else if (e == 0x80b0) + return events[last - 1]; + else if (e == 0x80c0) + return events[last]; + } + return "unknown"; +}; + void showDebug(const char* funcName, const QAccessibleInterface *iface) { qDebug() << "Role:" << roleString(iface->role(0)) @@ -266,9 +369,28 @@ void QAccessible::updateAccessibility(QObject *o, int who, Event reason) if (w->internalWinId()) break; } - p = p->parent(); + if (QGraphicsObject *gfxObj = qobject_cast(p)) { + QGraphicsItem *parentItem = gfxObj->parentItem(); + if (parentItem) { + p = parentItem->toGraphicsObject(); + } else { + QGraphicsView *view = 0; + if (QGraphicsScene *scene = gfxObj->scene()) { + QWidget *fw = QApplication::focusWidget(); + const QList views = scene->views(); + for (int i = 0 ; i < views.count() && view != fw; ++i) { + view = views.at(i); + } + } + p = view; + } + } else { + p = p->parent(); + } + } while (p); + //qDebug() << "updateAccessibility(), hwnd:" << w << ", object:" << o << "," << eventString(reason); if (!w) { if (reason != QAccessible::ContextHelpStart && reason != QAccessible::ContextHelpEnd) From 26acac4052b2313d1218eae040889a7686af0752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Arve=20S=C3=A6ther?= Date: Thu, 5 May 2011 16:21:36 +0200 Subject: [PATCH 3/6] Call updateAccessibility on the QGraphicsObject in updateMicroFocus Since QGraphicsObjects now can be in the a11y hierarchy, we should treat them just like we treat widgets. (cherry picked from commit 860745a4713b29857d14571572504da71a2ca077) Reviewed-by: Frederik Gladhorn (cherry picked from commit 4687938fd03ed65306039af6b4e5e192ec8bf491) --- src/gui/graphicsview/qgraphicsitem.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/gui/graphicsview/qgraphicsitem.cpp b/src/gui/graphicsview/qgraphicsitem.cpp index e67fe82045..41ff317676 100644 --- a/src/gui/graphicsview/qgraphicsitem.cpp +++ b/src/gui/graphicsview/qgraphicsitem.cpp @@ -7395,15 +7395,19 @@ void QGraphicsItem::updateMicroFocus() if (QWidget *fw = QApplication::focusWidget()) { if (scene()) { for (int i = 0 ; i < scene()->views().count() ; ++i) { - if (scene()->views().at(i) == fw) - if (QInputContext *inputContext = fw->inputContext()) + if (scene()->views().at(i) == fw) { + if (QInputContext *inputContext = fw->inputContext()) { inputContext->update(); +#ifndef QT_NO_ACCESSIBILITY + // ##### is this correct + if (toGraphicsObject()) + QAccessible::updateAccessibility(toGraphicsObject(), 0, QAccessible::StateChanged); +#endif + break; + } + } } } -#ifndef QT_NO_ACCESSIBILITY - // ##### is this correct - QAccessible::updateAccessibility(fw, 0, QAccessible::StateChanged); -#endif } #endif } From 015434360832572fcd9508f03ff8de0009b88777 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Arve=20S=C3=A6ther?= Date: Thu, 5 May 2011 16:28:58 +0200 Subject: [PATCH 4/6] Notify a11y framework of FocusChanges for QGraphicsObject (cherry picked from commit 1b5cb7865eb8b48a2721f9b9c3ccd2fb25f8175d) Reviewed-by: Frederik Gladhorn (cherry picked from commit 9a02ad74693f1835745ec20798b353f7e62bcd5e) --- src/gui/graphicsview/qgraphicsscene.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/gui/graphicsview/qgraphicsscene.cpp b/src/gui/graphicsview/qgraphicsscene.cpp index 0713d09296..ba4c9148a0 100644 --- a/src/gui/graphicsview/qgraphicsscene.cpp +++ b/src/gui/graphicsview/qgraphicsscene.cpp @@ -245,6 +245,10 @@ #include #include #include +#ifndef QT_NO_ACCESSIBILITY +# include +#endif + #include #include #ifdef Q_WS_X11 @@ -837,6 +841,14 @@ void QGraphicsScenePrivate::setFocusItemHelper(QGraphicsItem *item, if (item) focusItem = item; updateInputMethodSensitivityInViews(); + +#ifndef QT_NO_ACCESSIBILITY + if (focusItem) { + if (QGraphicsObject *focusObj = focusItem->toGraphicsObject()) { + QAccessible::updateAccessibility(focusObj, 0, QAccessible::Focus); + } + } +#endif if (item) { QFocusEvent event(QEvent::FocusIn, focusReason); sendEvent(item, &event); From 97952e918df3d7ace112d805377907927f1c7c2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Arve=20S=C3=A6ther?= Date: Thu, 5 May 2011 16:40:45 +0200 Subject: [PATCH 5/6] Avoided calling updateAccessibility() from updateMicroFocus if the item was hidden This had the following negative effect in qmlviewer: - Every time the qmlviewer logged something to its QPlainTextEdit (regardless of if it was visible or not) it would call updateMicroFocus (because appending to QPlainTextEdit would move the cursor). The result was that the AT client got overloaded with accessibility state changes, and response time got really bad. (cherry picked from commit ad50e45311cce712fbe35641cde973d616ff560d) Reviewed-by: Frederik Gladhorn (cherry picked from commit a825b3a9e6132842090e43fae85d2c6c61b2def6) --- src/gui/kernel/qwidget.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/gui/kernel/qwidget.cpp b/src/gui/kernel/qwidget.cpp index 758cccefdc..b4e1286b9c 100644 --- a/src/gui/kernel/qwidget.cpp +++ b/src/gui/kernel/qwidget.cpp @@ -11342,8 +11342,10 @@ void QWidget::updateMicroFocus() } #endif #ifndef QT_NO_ACCESSIBILITY - // ##### is this correct - QAccessible::updateAccessibility(this, 0, QAccessible::StateChanged); + if (isVisible()) { + // ##### is this correct + QAccessible::updateAccessibility(this, 0, QAccessible::StateChanged); + } #endif } From f0857b766772b2dac0d7ced5cbc2edd66f2cdcca Mon Sep 17 00:00:00 2001 From: Eskil Abrahamsen Blomfeldt Date: Tue, 10 May 2011 13:06:57 +0200 Subject: [PATCH 6/6] Compile on Mac Missing API update in Q_WS_MAC block Reviewed-by: Jiang Jiang (cherry picked from commit 4b95d9939db75d7bd55db4bbbf2d67af459f7eb5) --- src/gui/text/qrawfont.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/text/qrawfont.cpp b/src/gui/text/qrawfont.cpp index 4f2a01e432..29394e9b7a 100644 --- a/src/gui/text/qrawfont.cpp +++ b/src/gui/text/qrawfont.cpp @@ -593,11 +593,11 @@ QRawFont QRawFont::fromFont(const QFont &font, QFontDatabase::WritingSystem writ // if none of them match, just pick the first one for (int i = 0; i < list.size(); i++) { QGlyphRun glyphs = list.at(i); - QRawFont rawfont = glyphs.font(); + QRawFont rawfont = glyphs.rawFont(); if (rawfont.familyName() == font.family()) return rawfont; } - return list.at(0).font(); + return list.at(0).rawFont(); } return QRawFont(); #else