diff options
-rw-r--r-- | examples/quick/keyinteraction/focus/Core/GridMenu.qml | 3 | ||||
-rw-r--r-- | examples/quick/keyinteraction/focus/Core/TabMenu.qml | 104 | ||||
-rw-r--r-- | examples/quick/keyinteraction/focus/focus.qml | 46 | ||||
-rw-r--r-- | examples/quick/keyinteraction/keyinteraction.qrc | 1 | ||||
-rw-r--r-- | src/quick/items/qquickitem.cpp | 157 | ||||
-rw-r--r-- | src/quick/items/qquickitem.h | 5 | ||||
-rw-r--r-- | src/quick/items/qquickitem_p.h | 6 | ||||
-rw-r--r-- | tests/auto/quick/qquickitem2/data/activeFocusOnTab.qml | 136 | ||||
-rw-r--r-- | tests/auto/quick/qquickitem2/data/activeFocusOnTab3.qml | 250 | ||||
-rw-r--r-- | tests/auto/quick/qquickitem2/tst_qquickitem.cpp | 329 |
10 files changed, 1023 insertions, 14 deletions
diff --git a/examples/quick/keyinteraction/focus/Core/GridMenu.qml b/examples/quick/keyinteraction/focus/Core/GridMenu.qml index 3dd0637344..e18ec1f0d3 100644 --- a/examples/quick/keyinteraction/focus/Core/GridMenu.qml +++ b/examples/quick/keyinteraction/focus/Core/GridMenu.qml @@ -45,7 +45,7 @@ FocusScope { onActiveFocusChanged: { if (activeFocus) - mainView.state = "" + mainView.state = "showGridViews" } Rectangle { @@ -63,6 +63,7 @@ FocusScope { focus: true model: 12 + KeyNavigation.up: tabMenu KeyNavigation.down: listMenu KeyNavigation.left: contextMenu diff --git a/examples/quick/keyinteraction/focus/Core/TabMenu.qml b/examples/quick/keyinteraction/focus/Core/TabMenu.qml new file mode 100644 index 0000000000..5eea611b44 --- /dev/null +++ b/examples/quick/keyinteraction/focus/Core/TabMenu.qml @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +FocusScope { + onActiveFocusChanged: { + if (activeFocus) + mainView.state = "showTabViews" + } + + Rectangle { + anchors.fill: parent + clip: true + gradient: Gradient { + GradientStop { position: 0.0; color: "#193441" } + GradientStop { position: 1.0; color: Qt.darker("#193441") } + } + + Row { + id: tabView + anchors.fill: parent; anchors.leftMargin: 20; anchors.rightMargin: 20 + Repeater { + activeFocusOnTab: false + model: 5 + Item { + id: container + width: 152; height: 152 + activeFocusOnTab: true + focus: true + + KeyNavigation.up: listMenu + KeyNavigation.down: gridMenu + + Rectangle { + id: content + color: "transparent" + antialiasing: true + anchors.fill: parent; anchors.margins: 20; radius: 10 + + Rectangle { color: "#91AA9D"; anchors.fill: parent; anchors.margins: 3; radius: 8; antialiasing: true } + Image { source: "images/qt-logo.png"; anchors.centerIn: parent } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + + onClicked: { + container.forceActiveFocus() + } + } + + states: State { + name: "active"; when: container.activeFocus + PropertyChanges { target: content; color: "#FCFFF5"; scale: 1.1 } + } + + transitions: Transition { + NumberAnimation { properties: "scale"; duration: 100 } + } + } + } + } + } +} diff --git a/examples/quick/keyinteraction/focus/focus.qml b/examples/quick/keyinteraction/focus/focus.qml index 193b3d7b3a..91fb146e40 100644 --- a/examples/quick/keyinteraction/focus/focus.qml +++ b/examples/quick/keyinteraction/focus/focus.qml @@ -44,7 +44,7 @@ import "Core" Rectangle { id: window - width: 800; height: 480 + width: 800; height: 640 color: "#3E606F" FocusScope { @@ -53,17 +53,24 @@ Rectangle { width: parent.width; height: parent.height focus: true - GridMenu { - id: gridMenu - width: parent.width; height: 320 + TabMenu { + id: tabMenu + y: 160; width: parent.width; height: 160 focus: true - interactive: parent.activeFocus + activeFocusOnTab: true + } + + GridMenu { + id: gridMenu + y: 320; width: parent.width; height: 320 + activeFocusOnTab: true } ListMenu { id: listMenu - y: 320; width: parent.width; height: 320 + y: 640; width: parent.width; height: 320 + activeFocusOnTab: true } Rectangle { @@ -73,11 +80,28 @@ Rectangle { opacity: 0 } - states: State { - name: "showListViews" - PropertyChanges { target: gridMenu; y: -160 } - PropertyChanges { target: listMenu; y: 160 } - } + states: [ + State { + name: "showTabViews" + PropertyChanges { target: tabMenu; y: 160 } + PropertyChanges { target: gridMenu; y: 320 } + PropertyChanges { target: listMenu; y: 640 } + }, + + State { + name: "showGridViews" + PropertyChanges { target: tabMenu; y: 0 } + PropertyChanges { target: gridMenu; y: 160 } + PropertyChanges { target: listMenu; y: 480 } + }, + + State { + name: "showListViews" + PropertyChanges { target: tabMenu; y: -160 } + PropertyChanges { target: gridMenu; y: 0 } + PropertyChanges { target: listMenu; y: 320 } + } + ] transitions: Transition { NumberAnimation { properties: "y"; duration: 600; easing.type: Easing.OutQuint } diff --git a/examples/quick/keyinteraction/keyinteraction.qrc b/examples/quick/keyinteraction/keyinteraction.qrc index 21bde4472c..c719654835 100644 --- a/examples/quick/keyinteraction/keyinteraction.qrc +++ b/examples/quick/keyinteraction/keyinteraction.qrc @@ -8,5 +8,6 @@ <file>focus/Core/GridMenu.qml</file> <file>focus/Core/ListMenu.qml</file> <file>focus/Core/ListViewDelegate.qml</file> + <file>focus/Core/TabMenu.qml</file> </qresource> </RCC> diff --git a/src/quick/items/qquickitem.cpp b/src/quick/items/qquickitem.cpp index 53773553ee..67c4fd2140 100644 --- a/src/quick/items/qquickitem.cpp +++ b/src/quick/items/qquickitem.cpp @@ -1863,6 +1863,11 @@ void QQuickItemPrivate::updateSubFocusItem(QQuickItem *scope, bool focus) */ /*! + \fn void QQuickItem::activeFocusOnTabChanged(bool) + \internal +*/ + +/*! \fn void QQuickItem::childrenChanged() \internal */ @@ -2023,6 +2028,93 @@ QQuickItem::~QQuickItem() } /*! + \internal + \brief QQuickItemPrivate::focusNextPrev focuses the next/prev item in the tab-focus-chain + \param item The item that currently has the focus + \param forward The direction + \return Whether the next item in the focus chain is found or not + + If \a next is true, the next item visited will be in depth-first order relative to \a item. + If \a next is false, the next item visited will be in reverse depth-first order relative to \a item. +*/ +bool QQuickItemPrivate::focusNextPrev(QQuickItem *item, bool forward) +{ + Q_ASSERT(item); + Q_ASSERT(item->activeFocusOnTab()); + + QQuickItem *from = 0; + if (forward) { + from = item->parentItem(); + } else { + if (!item->childItems().isEmpty()) + from = item->childItems().first(); + else + from = item->parentItem(); + } + bool skip = false; + QQuickItem *current = item; + do { + skip = false; + QQuickItem *last = current; + + bool hasChildren = !current->childItems().isEmpty() && current->isEnabled() && current->isVisible(); + + // coming from parent: check children + if (hasChildren && from == current->parentItem()) { + if (forward) { + current = current->childItems().first(); + } else { + current = current->childItems().last(); + if (!current->childItems().isEmpty()) + skip = true; + } + } else if (hasChildren && forward && from != current->childItems().last()) { + // not last child going forwards + int nextChild = current->childItems().indexOf(from) + 1; + current = current->childItems().at(nextChild); + } else if (hasChildren && !forward && from != current->childItems().first()) { + // not first child going backwards + int prevChild = current->childItems().indexOf(from) - 1; + current = current->childItems().at(prevChild); + if (!current->childItems().isEmpty()) + skip = true; + // back to the parent + } else if (current->parentItem()) { + current = current->parentItem(); + // we would evaluate the parent twice, thus we skip + if (forward) { + skip = true; + } else if (!forward && !current->childItems().isEmpty()) { + if (last != current->childItems().first()) { + skip = true; + } else if (last == current->childItems().first()) { + if (current->isFocusScope() && current->activeFocusOnTab() && current->hasActiveFocus()) + skip = true; + } + } + } else if (hasChildren) { + // Wrap around after checking all items forward + if (forward) { + current = current->childItems().first(); + } else { + current = current->childItems().last(); + if (!current->childItems().isEmpty()) + skip = true; + } + } + + from = last; + } while (skip || !current->activeFocusOnTab() || !current->isEnabled() || !current->isVisible()); + + if (current == item) + return false; + + current->forceActiveFocus(); + + return true; +} + +/*! \qmlproperty Item QtQuick2::Item::parent This property holds the visual parent of the item. @@ -2522,6 +2614,7 @@ QQuickItemPrivate::QQuickItemPrivate() , isAccessible(false) , culled(false) , hasCursor(false) + , activeFocusOnTab(false) , dirtyAttributes(0) , nextDirtyItem(0) , prevDirtyItem(0) @@ -4170,6 +4263,23 @@ void QQuickItemPrivate::deliverKeyEvent(QKeyEvent *e) else extra->keyHandler->keyReleased(e, true); } + + if (e->isAccepted()) + return; + + //only care about KeyPress now + if (q->activeFocusOnTab() && e->type() == QEvent::KeyPress) { + bool res = false; + if (!(e->modifiers() & (Qt::ControlModifier | Qt::AltModifier))) { //### Add MetaModifier? + if (e->key() == Qt::Key_Backtab + || (e->key() == Qt::Key_Tab && (e->modifiers() & Qt::ShiftModifier))) + res = QQuickItemPrivate::focusNextPrev(q, false); + else if (e->key() == Qt::Key_Tab) + res = QQuickItemPrivate::focusNextPrev(q, true); + if (res) + e->setAccepted(true); + } + } } #ifndef QT_NO_IM @@ -5332,6 +5442,53 @@ void QQuickItem::setSmooth(bool smooth) } /*! + \qmlproperty bool QtQuick2::Item::activeFocusOnTab + + This property holds whether the item wants to be in tab focus + chain. By default this is set to false. + + The tab focus chain traverses elements by visiting first the + parent, and then its children in the order they occur in the + children property. Pressing the tab key on an item in the tab + focus chain will move keyboard focus to the next item in the + chain. Pressing BackTab (normally Shift+Tab) will move focus + to the previous item. + + To set up a manual tab focus chain, see \l KeyNavigation. Tab + key events used by Keys or KeyNavigation have precedence over + focus chain behavior, ignore the events in other key handlers + to allow it to propagate. +*/ +/*! + \property QQuickItem::activeFocusOnTab + + This property holds whether the item wants to be in tab focus + chain. By default this is set to false. +*/ +bool QQuickItem::activeFocusOnTab() const +{ + Q_D(const QQuickItem); + return d->activeFocusOnTab; +} +void QQuickItem::setActiveFocusOnTab(bool activeFocusOnTab) +{ + Q_D(QQuickItem); + if (d->activeFocusOnTab == activeFocusOnTab) + return; + + if (window()) { + if ((this == window()->activeFocusItem()) && !activeFocusOnTab) { + qWarning("QQuickItem: Cannot set activeFocusOnTab to false once item is the active focus item."); + return; + } + } + + d->activeFocusOnTab = activeFocusOnTab; + + emit activeFocusOnTabChanged(activeFocusOnTab); +} + +/*! \qmlproperty bool QtQuick2::Item::antialiasing Primarily used in Rectangle and image based elements to decide if the item should diff --git a/src/quick/items/qquickitem.h b/src/quick/items/qquickitem.h index 71681698b9..c37bc10bdd 100644 --- a/src/quick/items/qquickitem.h +++ b/src/quick/items/qquickitem.h @@ -132,6 +132,7 @@ class Q_QUICK_EXPORT QQuickItem : public QObject, public QQmlParserStatus Q_PROPERTY(bool focus READ hasFocus WRITE setFocus NOTIFY focusChanged FINAL) Q_PROPERTY(bool activeFocus READ hasActiveFocus NOTIFY activeFocusChanged FINAL) + Q_PROPERTY(bool activeFocusOnTab READ activeFocusOnTab WRITE setActiveFocusOnTab NOTIFY activeFocusOnTabChanged FINAL) Q_PROPERTY(qreal rotation READ rotation WRITE setRotation NOTIFY rotationChanged) Q_PROPERTY(qreal scale READ scale WRITE setScale NOTIFY scaleChanged) @@ -261,6 +262,9 @@ public: bool smooth() const; void setSmooth(bool); + bool activeFocusOnTab() const; + void setActiveFocusOnTab(bool); + bool antialiasing() const; void setAntialiasing(bool); @@ -345,6 +349,7 @@ Q_SIGNALS: void stateChanged(const QString &); void focusChanged(bool); void activeFocusChanged(bool); + void activeFocusOnTabChanged(bool); void parentChanged(QQuickItem *); void transformOriginChanged(TransformOrigin); void smoothChanged(bool); diff --git a/src/quick/items/qquickitem_p.h b/src/quick/items/qquickitem_p.h index 8f6865b40b..4bd9d82c20 100644 --- a/src/quick/items/qquickitem_p.h +++ b/src/quick/items/qquickitem_p.h @@ -396,8 +396,8 @@ public: bool antialiasing:1; bool focus:1; bool activeFocus:1; - bool notifiedFocus:1; // Bit 16 + bool notifiedFocus:1; bool notifiedActiveFocus:1; bool filtersChildMouseEvents:1; bool explicitVisible:1; @@ -413,8 +413,8 @@ public: bool isAccessible:1; bool culled:1; bool hasCursor:1; - // bool dummy:1 // Bit 32 + bool activeFocusOnTab:1; enum DirtyType { TransformOrigin = 0x00000001, @@ -484,6 +484,8 @@ public: QTransform itemToWindowTransform() const; void itemToParentTransform(QTransform &) const; + static bool focusNextPrev(QQuickItem *item, bool forward); + qreal x; qreal y; qreal width; diff --git a/tests/auto/quick/qquickitem2/data/activeFocusOnTab.qml b/tests/auto/quick/qquickitem2/data/activeFocusOnTab.qml new file mode 100644 index 0000000000..e064b41efe --- /dev/null +++ b/tests/auto/quick/qquickitem2/data/activeFocusOnTab.qml @@ -0,0 +1,136 @@ +import QtQuick 2.0 + +Item { + id: main + objectName: "main" + width: 800 + height: 600 + focus: true + Component.onCompleted: button12.focus = true + Item { + id: sub1 + objectName: "sub1" + width: 230 + height: 600 + anchors.top: parent.top + anchors.left: parent.left + Item { + id: button11 + objectName: "button11" + width: 100 + height: 50 + activeFocusOnTab: true + Rectangle { + anchors.fill: parent + color: parent.activeFocus ? "red" : "black" + } + + anchors.top: parent.top + anchors.topMargin: 100 + } + Item { + id: button12 + objectName: "button12" + activeFocusOnTab: true + Rectangle { + anchors.fill: parent + color: parent.activeFocus ? "red" : "black" + } + width: 100 + height: 50 + + anchors.top: button11.bottom + anchors.bottomMargin: 100 + } + Item { + id: button13 + objectName: "button13" + enabled: false + activeFocusOnTab: true + Rectangle { + anchors.fill: parent + color: parent.activeFocus ? "red" : "black" + } + width: 100 + height: 50 + + anchors.top: button12.bottom + anchors.bottomMargin: 100 + } + Item { + id: button14 + objectName: "button14" + visible: false + activeFocusOnTab: true + Rectangle { + anchors.fill: parent + color: parent.activeFocus ? "red" : "black" + } + width: 100 + height: 50 + + anchors.top: button12.bottom + anchors.bottomMargin: 100 + } + } + Item { + id: sub2 + objectName: "sub2" + activeFocusOnTab: true + width: 230 + height: 600 + anchors.top: parent.top + anchors.left: sub1.right + Item { + id: button21 + objectName: "button21" + width: 100 + height: 50 + activeFocusOnTab: true + Rectangle { + anchors.fill: parent + color: parent.activeFocus ? "red" : "black" + } + + anchors.top: parent.top + anchors.topMargin: 100 + } + Item { + id: button22 + objectName: "button22" + width: 100 + height: 50 + activeFocusOnTab: true + Rectangle { + anchors.fill: parent + color: parent.activeFocus ? "red" : "black" + } + + anchors.top: button21.bottom + anchors.bottomMargin: 100 + } + } + Item { + id: sub3 + objectName: "sub3" + width: 230 + height: 600 + anchors.top: parent.top + anchors.left: sub2.right + TextEdit { + id: edit + objectName: "edit" + width: 230 + height: 400 + readOnly: false + activeFocusOnTab: true + wrapMode: TextEdit.Wrap + textFormat: TextEdit.RichText + + text: "aaa\n" + +"bbb\n" + +"ccc\n" + +"ddd\n" + } + } +} diff --git a/tests/auto/quick/qquickitem2/data/activeFocusOnTab3.qml b/tests/auto/quick/qquickitem2/data/activeFocusOnTab3.qml new file mode 100644 index 0000000000..00fb82d59e --- /dev/null +++ b/tests/auto/quick/qquickitem2/data/activeFocusOnTab3.qml @@ -0,0 +1,250 @@ +import QtQuick 2.0 + +Item { + id: main + objectName: "main" + width: 400 + height: 700 + focus: true + Component.onCompleted: button1.focus = true + Item { + id: sub1 + objectName: "sub1" + activeFocusOnTab: false + width: 100 + height: 50 + anchors.top: parent.top + Item { + id: button1 + objectName: "button1" + width: 100 + height: 50 + activeFocusOnTab: true + Rectangle { + anchors.fill: parent + color: parent.activeFocus ? "red" : "black" + } + } + } + Row { + id: row_repeater + objectName: "row_repeater" + activeFocusOnTab: false + anchors.top: sub1.bottom + Repeater { + activeFocusOnTab: false + model: 3 + Rectangle { + activeFocusOnTab: true + width: 100; height: 40 + border.width: 1 + color: activeFocus ? "red" : "yellow" + } + } + } + Item { + id: sub2 + objectName: "sub2" + activeFocusOnTab: false + anchors.top: row_repeater.bottom + width: 100 + height: 50 + Item { + id: button2 + objectName: "button2" + width: 100 + height: 50 + activeFocusOnTab: true + Rectangle { + anchors.fill: parent + color: parent.activeFocus ? "red" : "black" + } + } + } + Row { + id: row + objectName: "row" + activeFocusOnTab: false + anchors.top: sub2.bottom + Rectangle { activeFocusOnTab: true; color: activeFocus ? "red" : "yellow"; width: 50; height: 50 } + Rectangle { activeFocusOnTab: true; color: activeFocus ? "red" : "green"; width: 20; height: 50 } + Rectangle { activeFocusOnTab: true; color: activeFocus ? "red" : "blue"; width: 50; height: 20 } + } + Item { + id: sub3 + objectName: "sub3" + activeFocusOnTab: false + anchors.top: row.bottom + width: 100 + height: 50 + Item { + id: button3 + objectName: "button3" + width: 100 + height: 50 + activeFocusOnTab: true + Rectangle { + anchors.fill: parent + color: parent.activeFocus ? "red" : "black" + } + } + } + Flow { + id: flow + objectName: "flow" + activeFocusOnTab: false + anchors.top: sub3.bottom + width: parent.width + anchors.margins: 4 + spacing: 10 + Rectangle { activeFocusOnTab: true; color: activeFocus ? "red" : "yellow"; width: 50; height: 50 } + Rectangle { activeFocusOnTab: true; color: activeFocus ? "red" : "green"; width: 20; height: 50 } + Rectangle { activeFocusOnTab: true; color: activeFocus ? "red" : "blue"; width: 50; height: 20 } + } + Item { + id: sub4 + objectName: "sub4" + activeFocusOnTab: false + anchors.top: flow.bottom + width: 100 + height: 50 + Item { + id: button4 + objectName: "button4" + width: 100 + height: 50 + activeFocusOnTab: true + Rectangle { + anchors.fill: parent + color: parent.activeFocus ? "red" : "black" + } + } + } + FocusScope { + id: focusscope + objectName: "focusscope" + activeFocusOnTab: false + anchors.top: sub4.bottom + height: 40 + Row { + id: row_focusscope + objectName: "row_focusscope" + activeFocusOnTab: false + anchors.fill: parent + Repeater { + activeFocusOnTab: false + model: 3 + Rectangle { + activeFocusOnTab: true + width: 100; height: 40 + border.width: 1 + color: activeFocus ? "red" : "yellow" + } + } + } + } + Item { + id: sub5 + objectName: "sub5" + activeFocusOnTab: false + anchors.top: focusscope.bottom + width: 100 + height: 50 + Item { + id: button5 + objectName: "button5" + width: 100 + height: 50 + activeFocusOnTab: true + Rectangle { + anchors.fill: parent + color: parent.activeFocus ? "red" : "black" + } + } + } + FocusScope { + id: focusscope2 + objectName: "focusscope2" + activeFocusOnTab: true + anchors.top: sub5.bottom + height: 40 + Row { + id: row_focusscope2 + objectName: "row_focusscope2" + activeFocusOnTab: false + anchors.fill: parent + Repeater { + activeFocusOnTab: false + model: 3 + Rectangle { + activeFocusOnTab: true + focus: true + width: 100; height: 40 + border.width: 1 + color: activeFocus ? "red" : "yellow" + } + } + } + } + Item { + id: sub6 + objectName: "sub6" + activeFocusOnTab: false + anchors.top: focusscope2.bottom + width: 100 + height: 50 + Item { + id: button6 + objectName: "button6" + width: 100 + height: 50 + activeFocusOnTab: true + Rectangle { + anchors.fill: parent + color: parent.activeFocus ? "red" : "black" + } + } + } + FocusScope { + id: focusscope3 + objectName: "focusscope3" + activeFocusOnTab: true + anchors.top: sub6.bottom + height: 40 + Row { + id: row_focusscope3 + objectName: "row_focusscope3" + activeFocusOnTab: false + anchors.fill: parent + Repeater { + activeFocusOnTab: false + model: 3 + Rectangle { + activeFocusOnTab: true + width: 100; height: 40 + border.width: 1 + color: activeFocus ? "red" : "yellow" + } + } + } + } + Item { + id: sub7 + objectName: "sub7" + activeFocusOnTab: false + anchors.top: focusscope3.bottom + width: 100 + height: 50 + Item { + id: button7 + objectName: "button7" + width: 100 + height: 50 + activeFocusOnTab: true + Rectangle { + anchors.fill: parent + color: parent.activeFocus ? "red" : "black" + } + } + } +} diff --git a/tests/auto/quick/qquickitem2/tst_qquickitem.cpp b/tests/auto/quick/qquickitem2/tst_qquickitem.cpp index c82372c287..668b5e2945 100644 --- a/tests/auto/quick/qquickitem2/tst_qquickitem.cpp +++ b/tests/auto/quick/qquickitem2/tst_qquickitem.cpp @@ -64,6 +64,10 @@ private slots: void initTestCase(); void cleanup(); + void activeFocusOnTab(); + void activeFocusOnTab2(); + void activeFocusOnTab3(); + void keys(); void keysProcessingOrder(); void keysim(); @@ -273,6 +277,331 @@ void tst_QQuickItem::cleanup() inputMethodPrivate->testContext = 0; } +void tst_QQuickItem::activeFocusOnTab() +{ + QQuickView *window = new QQuickView(0); + window->setBaseSize(QSize(800,600)); + + window->setSource(testFileUrl("activeFocusOnTab.qml")); + window->show(); + window->requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QGuiApplication::focusWindow() == window); + + // original: button12 + QQuickItem *item = findItem<QQuickItem>(window->rootObject(), "button12"); + QVERIFY(item); + QVERIFY(item->hasActiveFocus()); + + // Tab: button12->sub2 + QKeyEvent key(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1); + QGuiApplication::sendEvent(window, &key); + QVERIFY(key.isAccepted()); + + item = findItem<QQuickItem>(window->rootObject(), "sub2"); + QVERIFY(item); + QVERIFY(item->hasActiveFocus()); + + // Tab: sub2->button21 + key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1); + QGuiApplication::sendEvent(window, &key); + QVERIFY(key.isAccepted()); + + item = findItem<QQuickItem>(window->rootObject(), "button21"); + QVERIFY(item); + QVERIFY(item->hasActiveFocus()); + + // Tab: button21->button22 + key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1); + QGuiApplication::sendEvent(window, &key); + QVERIFY(key.isAccepted()); + + item = findItem<QQuickItem>(window->rootObject(), "button22"); + QVERIFY(item); + QVERIFY(item->hasActiveFocus()); + + // Tab: button22->edit + key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1); + QGuiApplication::sendEvent(window, &key); + QVERIFY(key.isAccepted()); + + item = findItem<QQuickItem>(window->rootObject(), "edit"); + QVERIFY(item); + QVERIFY(item->hasActiveFocus()); + + // BackTab: edit->button22 + key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1); + QGuiApplication::sendEvent(window, &key); + QVERIFY(key.isAccepted()); + + item = findItem<QQuickItem>(window->rootObject(), "button22"); + QVERIFY(item); + QVERIFY(item->hasActiveFocus()); + + // BackTab: button22->button21 + key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1); + QGuiApplication::sendEvent(window, &key); + QVERIFY(key.isAccepted()); + + item = findItem<QQuickItem>(window->rootObject(), "button21"); + QVERIFY(item); + QVERIFY(item->hasActiveFocus()); + + // BackTab: button21->sub2 + key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1); + QGuiApplication::sendEvent(window, &key); + QVERIFY(key.isAccepted()); + + item = findItem<QQuickItem>(window->rootObject(), "sub2"); + QVERIFY(item); + QVERIFY(item->hasActiveFocus()); + + // BackTab: sub2->button12 + key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1); + QGuiApplication::sendEvent(window, &key); + QVERIFY(key.isAccepted()); + + item = findItem<QQuickItem>(window->rootObject(), "button12"); + QVERIFY(item); + QVERIFY(item->hasActiveFocus()); + + // BackTab: button12->button11 + key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1); + QGuiApplication::sendEvent(window, &key); + QVERIFY(key.isAccepted()); + + item = findItem<QQuickItem>(window->rootObject(), "button11"); + QVERIFY(item); + QVERIFY(item->hasActiveFocus()); + + // BackTab: button11->edit + key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1); + QGuiApplication::sendEvent(window, &key); + QVERIFY(key.isAccepted()); + + item = findItem<QQuickItem>(window->rootObject(), "edit"); + QVERIFY(item); + QVERIFY(item->hasActiveFocus()); + + delete window; +} + +void tst_QQuickItem::activeFocusOnTab2() +{ + QQuickView *window = new QQuickView(0); + window->setBaseSize(QSize(800,600)); + + window->setSource(testFileUrl("activeFocusOnTab.qml")); + window->show(); + window->requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QGuiApplication::focusWindow() == window); + + // original: button12 + QQuickItem *item = findItem<QQuickItem>(window->rootObject(), "button12"); + QVERIFY(item); + QVERIFY(item->hasActiveFocus()); + + // BackTab: button12->button11 + QKeyEvent key(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1); + QGuiApplication::sendEvent(window, &key); + QVERIFY(key.isAccepted()); + + item = findItem<QQuickItem>(window->rootObject(), "button11"); + QVERIFY(item); + QVERIFY(item->hasActiveFocus()); + + // BackTab: button11->edit + key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1); + QGuiApplication::sendEvent(window, &key); + QVERIFY(key.isAccepted()); + + item = findItem<QQuickItem>(window->rootObject(), "edit"); + QVERIFY(item); + QVERIFY(item->hasActiveFocus()); + + delete window; +} + +void tst_QQuickItem::activeFocusOnTab3() +{ + QQuickView *window = new QQuickView(0); + window->setBaseSize(QSize(800,600)); + + window->setSource(testFileUrl("activeFocusOnTab3.qml")); + window->show(); + window->requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QGuiApplication::focusWindow() == window); + + // original: button1 + QQuickItem *item = findItem<QQuickItem>(window->rootObject(), "button1"); + QVERIFY(item); + QVERIFY(item->hasActiveFocus()); + + // 4 Tabs: button1->button2, through a repeater + QKeyEvent key(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);; + for (int i = 0; i < 4; ++i) { + QGuiApplication::sendEvent(window, &key); + QVERIFY(key.isAccepted()); + } + + item = findItem<QQuickItem>(window->rootObject(), "button2"); + QVERIFY(item); + QVERIFY(item->hasActiveFocus()); + + // 4 Tabs: button2->button3, through a row + key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);; + for (int i = 0; i < 4; ++i) { + QGuiApplication::sendEvent(window, &key); + QVERIFY(key.isAccepted()); + } + + item = findItem<QQuickItem>(window->rootObject(), "button3"); + QVERIFY(item); + QVERIFY(item->hasActiveFocus()); + + // 4 Tabs: button3->button4, through a flow + key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);; + for (int i = 0; i < 4; ++i) { + QGuiApplication::sendEvent(window, &key); + QVERIFY(key.isAccepted()); + } + + item = findItem<QQuickItem>(window->rootObject(), "button4"); + QVERIFY(item); + QVERIFY(item->hasActiveFocus()); + + // 4 Tabs: button4->button5, through a focusscope + // parent is activeFocusOnTab:false, one of children is focus:true + key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);; + for (int i = 0; i < 4; ++i) { + QGuiApplication::sendEvent(window, &key); + QVERIFY(key.isAccepted()); + } + + item = findItem<QQuickItem>(window->rootObject(), "button5"); + QVERIFY(item); + QVERIFY(item->hasActiveFocus()); + + // 4 Tabs: button5->button6, through a focusscope + // parent is activeFocusOnTab:true, one of children is focus:true + key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);; + for (int i = 0; i < 4; ++i) { + QGuiApplication::sendEvent(window, &key); + QVERIFY(key.isAccepted()); + } + + item = findItem<QQuickItem>(window->rootObject(), "button6"); + QVERIFY(item); + QVERIFY(item->hasActiveFocus()); + + // 5 Tabs: button6->button7, through a focusscope + // parent is activeFocusOnTab:true, none of children is focus:true + key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);; + for (int i = 0; i < 5; ++i) { + QGuiApplication::sendEvent(window, &key); + QVERIFY(key.isAccepted()); + } + + item = findItem<QQuickItem>(window->rootObject(), "button7"); + QVERIFY(item); + QVERIFY(item->hasActiveFocus()); + + // 4 BackTabs: button7->button6, through a focusscope + // parent is activeFocusOnTab:true, one of children got focus:true in previous code + key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1); + for (int i = 0; i < 4; ++i) { + QGuiApplication::sendEvent(window, &key); + QVERIFY(key.isAccepted()); + } + + item = findItem<QQuickItem>(window->rootObject(), "button6"); + QVERIFY(item); + QVERIFY(item->hasActiveFocus()); + + // 4 Tabs: button6->button7, through a focusscope + // parent is activeFocusOnTab:true, one of children got focus:true in previous code + key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);; + for (int i = 0; i < 4; ++i) { + QGuiApplication::sendEvent(window, &key); + QVERIFY(key.isAccepted()); + } + + item = findItem<QQuickItem>(window->rootObject(), "button7"); + QVERIFY(item); + QVERIFY(item->hasActiveFocus()); + + // 4 BackTabs: button7->button6, through a focusscope + // parent is activeFocusOnTab:true, one of children got focus:true in previous code + key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1); + for (int i = 0; i < 4; ++i) { + QGuiApplication::sendEvent(window, &key); + QVERIFY(key.isAccepted()); + } + + item = findItem<QQuickItem>(window->rootObject(), "button6"); + QVERIFY(item); + QVERIFY(item->hasActiveFocus()); + + // 4 BackTabs: button6->button5, through a focusscope(parent is activeFocusOnTab: false) + key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1); + for (int i = 0; i < 4; ++i) { + QGuiApplication::sendEvent(window, &key); + QVERIFY(key.isAccepted()); + } + + item = findItem<QQuickItem>(window->rootObject(), "button5"); + QVERIFY(item); + QVERIFY(item->hasActiveFocus()); + + // 4 BackTabs: button5->button4, through a focusscope(parent is activeFocusOnTab: false) + key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1); + for (int i = 0; i < 4; ++i) { + QGuiApplication::sendEvent(window, &key); + QVERIFY(key.isAccepted()); + } + + item = findItem<QQuickItem>(window->rootObject(), "button4"); + QVERIFY(item); + QVERIFY(item->hasActiveFocus()); + + // 4 BackTabs: button4->button3, through a flow + key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1); + for (int i = 0; i < 4; ++i) { + QGuiApplication::sendEvent(window, &key); + QVERIFY(key.isAccepted()); + } + + item = findItem<QQuickItem>(window->rootObject(), "button3"); + QVERIFY(item); + QVERIFY(item->hasActiveFocus()); + + // 4 BackTabs: button3->button2, through a row + key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1); + for (int i = 0; i < 4; ++i) { + QGuiApplication::sendEvent(window, &key); + QVERIFY(key.isAccepted()); + } + + item = findItem<QQuickItem>(window->rootObject(), "button2"); + QVERIFY(item); + QVERIFY(item->hasActiveFocus()); + + // 4 BackTabs: button2->button1, through a repeater + key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1); + for (int i = 0; i < 4; ++i) { + QGuiApplication::sendEvent(window, &key); + QVERIFY(key.isAccepted()); + } + + item = findItem<QQuickItem>(window->rootObject(), "button1"); + QVERIFY(item); + QVERIFY(item->hasActiveFocus()); + + delete window; +} + void tst_QQuickItem::keys() { QQuickView *window = new QQuickView(0); |