Improve accessibility action support for Qt Quick
authorMorten Johan Sorvig <morten.sorvig@nokia.com>
Tue, 10 Jan 2012 11:12:51 +0000 (12:12 +0100)
committerQt by Nokia <qt-info@nokia.com>
Wed, 7 Mar 2012 19:08:19 +0000 (20:08 +0100)
Add interface_cast for the action interface.
Implement actions for the following roles:
Button                           : Press
CheckBox, RadioButton            : Press, Check, Uncheck
Slider, Spinbox, Dial, ScrollBar : Increment, Decrement

Change-Id: Ic8e0d17c709ba51655f3f4b699092baf603b6f18
Reviewed-by: Frederik Gladhorn <frederik.gladhorn@nokia.com>
examples/declarative/accessibility/widgets/Checkbox.qml [new file with mode: 0644]
examples/declarative/accessibility/widgets/Slider.qml [new file with mode: 0644]
examples/quick/accessibility/accessibility.qml
examples/quick/accessibility/content/Button.qml
src/plugins/accessible/quick/main.cpp
src/plugins/accessible/shared/qqmlaccessible.cpp
src/plugins/accessible/shared/qqmlaccessible.h
src/quick/items/qquickaccessibleattached.cpp

diff --git a/examples/declarative/accessibility/widgets/Checkbox.qml b/examples/declarative/accessibility/widgets/Checkbox.qml
new file mode 100644 (file)
index 0000000..17eb733
--- /dev/null
@@ -0,0 +1,62 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the QtDeclarative module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+import QtQuick 2.0
+
+
+Rectangle {
+    id: checkbox
+
+    Accessible.role: Accessible.CheckBox
+
+    property bool checked // required variable
+
+    width: 30
+    height: 30
+
+
+    Text {
+        id: checkboxText
+        text: parent.checked ? "on" : "off"
+        anchors.centerIn: parent
+    }
+}
diff --git a/examples/declarative/accessibility/widgets/Slider.qml b/examples/declarative/accessibility/widgets/Slider.qml
new file mode 100644 (file)
index 0000000..32aa6a3
--- /dev/null
@@ -0,0 +1,67 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the QtDeclarative module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+import QtQuick 2.0
+
+// Minimal slider implementation
+Rectangle {
+    id: slider
+
+    property alias text : buttonText.text
+    Accessible.role: Accessible.Slider
+
+    property int value             // required
+    property int minimumValue : 0  // optional (default INT_MIN)
+    property int maximumValue : 20 // optional (default INT_MAX)
+    property int stepSize : 1      // optional (default 1)
+
+    width: 30
+    height: 30
+
+
+    Text {
+        id: buttonText
+        text: parent.value
+        anchors.centerIn: parent
+        font.pixelSize: parent.height * .5
+    }
+}
index 2804d2a..e987561 100644 (file)
@@ -59,12 +59,13 @@ Rectangle {
         id: column
         spacing: 6
         anchors.fill: parent
+        anchors.margins: 10
         width: parent.width
         Row {
             spacing: 6
             width: column.width
-            Button { width: 100; height: column.h + 20; text: "Send" }
-            Button { width: 100; height: column.h + 20; text: "Discard" }
+            Button { width: 100; height: column.h + 20; text: "Send"; onClicked : { status.text = "Send" } }
+            Button { width: 100; height: column.h + 20; text: "Discard";  onClicked : { status.text = "Discard" } }
         }
 
         Row {
@@ -106,5 +107,16 @@ Rectangle {
                 wrapMode: TextEdit.WordWrap
             }
         }
+        Text {
+            id : status
+            width: column.width
+        }
+
+        Row {
+            spacing: 6
+            width: column.width
+            Checkbox { checked: false }
+            Slider { value: 10 }
+        }
     }
 }
index 2c203ab..3d5086f 100644 (file)
@@ -49,6 +49,9 @@ Rectangle {
     Accessible.name: text
     Accessible.description: "This button does " + text
     Accessible.role: Accessible.Button
+    function accessiblePressAction() {
+        button.clicked()
+    }
 
     signal clicked
 
@@ -74,7 +77,7 @@ Rectangle {
         id: mouseArea
         anchors.fill: parent
         onClicked: {
-            checked = !checked;
+            parent.clicked()
         }
     }
 }
index 6ff3d5d..2c75e59 100644 (file)
@@ -99,6 +99,7 @@ QAccessibleInterface *AccessibleQuickFactory::create(const QString &classname, Q
         case QAccessible::Slider:
         case QAccessible::SpinBox:
         case QAccessible::Dial:
+        case QAccessible::ScrollBar:
             return new QAccessibleQuickItemValueInterface(item);
         default:
             return new QAccessibleQuickItem(item);
index 1818ebe..70c6b90 100644 (file)
@@ -59,6 +59,13 @@ QQmlAccessible::QQmlAccessible(QObject *object)
 {
 }
 
+void *QQmlAccessible::interface_cast(QAccessible::InterfaceType t)
+{
+    if (t == QAccessible::ActionInterface)
+        return static_cast<QAccessibleActionInterface*>(this);
+    return QAccessibleObject::interface_cast(t);
+}
+
 QQmlAccessible::~QQmlAccessible()
 {
 }
@@ -131,7 +138,15 @@ QStringList QQmlAccessible::actionNames() const
         break;
     case QAccessible::RadioButton:
     case QAccessible::CheckBox:
-        actions << QAccessibleActionInterface::checkAction();
+        actions << QAccessibleActionInterface::checkAction()
+                << QAccessibleActionInterface::uncheckAction()
+                << QAccessibleActionInterface::pressAction();
+        break;
+    case QAccessible::Slider:
+    case QAccessible::SpinBox:
+    case QAccessible::ScrollBar:
+        actions << QAccessibleActionInterface::increaseAction()
+                << QAccessibleActionInterface::decreaseAction();
         break;
     default:
         break;
@@ -141,12 +156,72 @@ QStringList QQmlAccessible::actionNames() const
 
 void QQmlAccessible::doAction(const QString &actionName)
 {
-    if (role() == QAccessible::PushButton && actionName == QAccessibleActionInterface::pressAction()) {
-        QMetaObject::invokeMethod(object(), "accessibleAction");
+    // Look for and call the accessible[actionName]Action() function on the item.
+    // This allows for overriding the default action handling.
+    const QByteArray functionName = "accessible" + actionName.toLatin1() + "Action()";
+    if (object()->metaObject()->indexOfMethod(functionName) != -1) {
+        QMetaObject::invokeMethod(object(), functionName, Q_ARG(QString, actionName));
+        return;
     }
-    if ((role() == QAccessible::CheckBox || role() == QAccessible::RadioButton) && actionName == QAccessibleActionInterface::checkAction()) {
-        bool checked = object()->property("checked").toBool();
-        object()->setProperty("checked",  QVariant(!checked));
+
+    // Role-specific default action handling follows. Items are excepted to provide
+    // properties according to role conventions. These will then be read and/or updated
+    // by the accessibility system.
+    //   Checkable roles   : checked
+    //   Value-based roles : (via the value interface: value, minimumValue, maximumValue), stepSize
+    switch (role()) {
+    case QAccessible::RadioButton:
+    case QAccessible::CheckBox: {
+        QVariant checked = object()->property("checked");
+        if (checked.isValid()) {
+            if (actionName == QAccessibleActionInterface::pressAction()) {
+                object()->setProperty("checked",  QVariant(!checked.toBool()));
+            } else if (actionName == QAccessibleActionInterface::checkAction()) {
+                object()->setProperty("checked",  QVariant(true));
+            } else if (actionName == QAccessibleActionInterface::uncheckAction()) {
+                object()->setProperty("checked",  QVariant(false));
+            }
+        }
+        break;
+    }
+    case QAccessible::Slider:
+    case QAccessible::SpinBox:
+    case QAccessible::Dial:
+    case QAccessible::ScrollBar: {
+        if (actionName != QAccessibleActionInterface::increaseAction() &&
+            actionName != QAccessibleActionInterface::decreaseAction())
+            break;
+
+        // Update the value using QAccessibleValueInterface, respecting
+        // the minimum and maximum value (if set). Also check for and
+        // use the "stepSize" property on the item
+        if (QAccessibleValueInterface *valueIface = valueInterface()) {
+            QVariant valueV = valueIface->currentValue();
+            qreal newValue = valueV.toInt();
+
+            QVariant stepSizeV = object()->property("stepSize");
+            qreal stepSize = stepSizeV.isValid() ? stepSizeV.toReal() : qreal(1.0);
+            if (actionName == QAccessibleActionInterface::increaseAction()) {
+                newValue += stepSize;
+            } else {
+                newValue -= stepSize;
+            }
+
+            QVariant minimumValueV = valueIface->minimumValue();
+            if (minimumValueV.isValid()) {
+                newValue = qMax(newValue, minimumValueV.toReal());
+            }
+            QVariant maximumValueV = valueIface->maximumValue();
+            if (maximumValueV.isValid()) {
+                newValue = qMin(newValue, maximumValueV.toReal());
+            }
+
+            valueIface->setCurrentValue(QVariant(newValue));
+        }
+        break;
+    }
+    default:
+        break;
     }
 }
 
index 570a3c8..ec26aa2 100644 (file)
@@ -74,6 +74,7 @@ class QQmlAccessible: public QAccessibleObject, public QAccessibleActionInterfac
 {
 public:
     ~QQmlAccessible();
+    void *interface_cast(QAccessible::InterfaceType t);
 
     virtual QRect viewRect() const = 0;
     QAccessibleInterface *childAt(int, int) const;
index 177454e..1e07d96 100644 (file)
@@ -104,6 +104,7 @@ QT_BEGIN_NAMESPACE
         }
         Accessible.name: label.text
         Accessible.role: Accessible.Button
+        funtion accessiblePressAction { ... }
     }
     \endqml
 
@@ -117,24 +118,19 @@ QT_BEGIN_NAMESPACE
         \o
 
     \row
-       \o CheckBox
-       \o checked
-       \o The check state of the check box.
+        \o Button
+        \o function accessiblePressAction
+        \o Called when the button receives a press action. The implementation should visually simulate a button click and perform the button action.
     \row
-       \o RadioButton
+       \o CheckBox, Radiobutton
        \o checked
-       \o The selected state of the radio button.
-    \row
-       \o Button
-       \o checkable
-       \o Whether the button is checkable.
+       \o The check state of the check box. Updated on Press, Check and Uncheck actions.
     \row
-       \o Button
-       \o checked
-       \o Whether the button is checked (only if checkable is true).
+       \o Slider, SpinBox, Dial, ScrollBar
+       \o value, minimumValue, maximumValue, stepSize
+       \o value will be updated on increase and decrase actions, in accordance with the other properties
 
     \endtable
-
 */
 
 QQuickAccessibleAttached::QQuickAccessibleAttached(QObject *parent)