return true;
}
+QQuickItem *QQuickItemPrivate::nextTabChildItem(const QQuickItem *item, int start)
+{
+ if (!item) {
+ qWarning() << "QQuickItemPrivate::nextTabChildItem called with null item.";
+ return Q_NULLPTR;
+ }
+ const QList<QQuickItem *> &children = item->childItems();
+ const int count = children.count();
+ if (start < 0 || start >= count) {
+ qWarning() << "QQuickItemPrivate::nextTabChildItem: Start index value out of range for item" << item;
+ return Q_NULLPTR;
+ }
+ while (start < count) {
+ QQuickItem *child = children.at(start);
+ if (!child->d_func()->isTabFence)
+ return child;
+ ++start;
+ }
+ return Q_NULLPTR;
+}
+
+QQuickItem *QQuickItemPrivate::prevTabChildItem(const QQuickItem *item, int start)
+{
+ if (!item) {
+ qWarning() << "QQuickItemPrivate::prevTabChildItem called with null item.";
+ return Q_NULLPTR;
+ }
+ const QList<QQuickItem *> &children = item->childItems();
+ const int count = children.count();
+ if (start == -1)
+ start = count - 1;
+ if (start < 0 || start >= count) {
+ qWarning() << "QQuickItemPrivate::prevTabChildItem: Start index value out of range for item" << item;
+ return Q_NULLPTR;
+ }
+ while (start >= 0) {
+ QQuickItem *child = children.at(start);
+ if (!child->d_func()->isTabFence)
+ return child;
+ --start;
+ }
+ return Q_NULLPTR;
+}
+
QQuickItem* QQuickItemPrivate::nextPrevItemInTabFocusChain(QQuickItem *item, bool forward)
{
Q_ASSERT(item);
from = item->parentItem();
}
bool skip = false;
- const QQuickItem * const originalItem = item;
QQuickItem * startItem = item;
QQuickItem * firstFromItem = from;
QQuickItem *current = item;
QQuickItem *last = current;
bool hasChildren = !current->childItems().isEmpty() && current->isEnabled() && current->isVisible();
+ QQuickItem *firstChild = Q_NULLPTR;
+ QQuickItem *lastChild = Q_NULLPTR;
+ if (hasChildren) {
+ firstChild = nextTabChildItem(current, 0);
+ if (!firstChild)
+ hasChildren = false;
+ else
+ lastChild = prevTabChildItem(current, -1);
+ }
+ bool isTabFence = current->d_func()->isTabFence;
// coming from parent: check children
if (hasChildren && from == current->parentItem()) {
if (forward) {
- current = current->childItems().first();
+ current = firstChild;
} else {
- current = current->childItems().last();
+ current = lastChild;
if (!current->childItems().isEmpty())
skip = true;
}
- } else if (hasChildren && forward && from != current->childItems().last()) {
+ } else if (hasChildren && forward && from != lastChild) {
// 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()) {
+ current = nextTabChildItem(current, nextChild);
+ } else if (hasChildren && !forward && from != firstChild) {
// not first child going backwards
int prevChild = current->childItems().indexOf(from) - 1;
- current = current->childItems().at(prevChild);
+ current = prevTabChildItem(current, prevChild);
if (!current->childItems().isEmpty())
skip = true;
// back to the parent
- } else if (current->parentItem()) {
- current = current->parentItem();
+ } else if (QQuickItem *parent = !isTabFence ? current->parentItem() : Q_NULLPTR) {
// 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())
+ } else if (QQuickItem *firstSibling = !forward ? nextTabChildItem(parent, 0) : Q_NULLPTR) {
+ if (last != firstSibling
+ || (parent->isFocusScope() && parent->activeFocusOnTab() && parent->hasActiveFocus()))
skip = true;
- }
}
+ current = parent;
} else if (hasChildren) {
// Wrap around after checking all items forward
if (forward) {
- current = current->childItems().first();
+ current = firstChild;
} else {
- current = current->childItems().last();
+ current = lastChild;
if (!current->childItems().isEmpty())
skip = true;
}
from = last;
if (current == startItem && from == firstFromItem) {
// wrapped around, avoid endless loops
- if (originalItem == contentItem) {
+ if (item == contentItem) {
qCDebug(DBG_FOCUS) << "QQuickItemPrivate::nextPrevItemInTabFocusChain: looped, return contentItem";
- return item->window()->contentItem();
+ return item;
} else {
qCDebug(DBG_FOCUS) << "QQuickItemPrivate::nextPrevItemInTabFocusChain: looped, return " << startItem;
return startItem;
, activeFocusOnTab(false)
, implicitAntialiasing(false)
, antialiasingValid(false)
+ , isTabFence(false)
, dirtyAttributes(0)
, nextDirtyItem(0)
, prevDirtyItem(0)
bool activeFocusOnTab:1;
bool implicitAntialiasing:1;
bool antialiasingValid:1;
+ // isTabFence: When true, the item acts as a fence within the tab focus chain.
+ // This means that the item and its children will be skipped from the tab focus
+ // chain when navigating from its parent or any of its siblings. Similarly,
+ // when any of the item's descendants gets focus, the item constrains the tab
+ // focus chain and prevents tabbing outside.
+ bool isTabFence:1;
enum DirtyType {
TransformOrigin = 0x00000001,
void itemToParentTransform(QTransform &) const;
static bool focusNextPrev(QQuickItem *item, bool forward);
+ static QQuickItem *nextTabChildItem(const QQuickItem *item, int start);
+ static QQuickItem *prevTabChildItem(const QQuickItem *item, int start);
static QQuickItem *nextPrevItemInTabFocusChain(QQuickItem *item, bool forward);
static bool canAcceptTabFocus(QQuickItem *item);
--- /dev/null
+import QtQuick 2.1
+import Test 1.0
+
+Item {
+ objectName: "root"
+ focus: true
+ width: 800
+ height: 600
+
+ TabFence {
+ objectName: "fence1"
+
+ TextInput {
+ objectName: "input11"
+ activeFocusOnTab: true
+ }
+ TextInput {
+ objectName: "input12"
+ activeFocusOnTab: true
+ }
+ TextInput {
+ objectName: "input13"
+ activeFocusOnTab: true
+ }
+ }
+
+ TextInput {
+ objectName: "input1"
+ activeFocusOnTab: true
+ }
+
+ TextInput {
+ objectName: "input2"
+ activeFocusOnTab: true
+ }
+
+ TabFence {
+ objectName: "fence2"
+ }
+
+ TextInput {
+ objectName: "input3"
+ activeFocusOnTab: true
+ }
+
+ TabFence {
+ objectName: "fence3"
+ }
+}
void nextItemInFocusChain2();
void nextItemInFocusChain3();
+ void tabFence();
+
void keys();
void standardKeys_data();
void standardKeys();
QML_DECLARE_TYPE(HollowTestItem);
+class TabFenceItem : public QQuickItem
+{
+ Q_OBJECT
+
+public:
+ TabFenceItem(QQuickItem *parent = Q_NULLPTR)
+ : QQuickItem(parent)
+ {
+ QQuickItemPrivate *d = QQuickItemPrivate::get(this);
+ d->isTabFence = true;
+ }
+};
+
+QML_DECLARE_TYPE(TabFenceItem);
tst_QQuickItem::tst_QQuickItem()
{
QQmlDataTest::initTestCase();
qmlRegisterType<KeyTestItem>("Test",1,0,"KeyTestItem");
qmlRegisterType<HollowTestItem>("Test", 1, 0, "HollowTestItem");
+ qmlRegisterType<TabFenceItem>("Test", 1, 0, "TabFence");
}
void tst_QQuickItem::cleanup()
QCOMPARE(QGuiApplication::focusWindow(), window);
}
+void verifyTabFocusChain(QQuickView *window, const char **focusChain, bool forward)
+{
+ int idx = 0;
+ for (const char **objectName = focusChain; *objectName; ++objectName, ++idx) {
+ const QString &descrStr = QString("idx=%1 objectName=\"%2\"").arg(idx).arg(*objectName);
+ const char *descr = descrStr.toLocal8Bit().data();
+ QKeyEvent key(QEvent::KeyPress, Qt::Key_Tab, forward ? Qt::NoModifier : Qt::ShiftModifier);
+ QGuiApplication::sendEvent(window, &key);
+ QVERIFY2(key.isAccepted(), descr);
+
+ QQuickItem *item = findItem<QQuickItem>(window->rootObject(), *objectName);
+ QVERIFY2(item, descr);
+ QVERIFY2(item->hasActiveFocus(), descr);
+ }
+}
+
+void tst_QQuickItem::tabFence()
+{
+ QQuickView *window = new QQuickView(0);
+ window->setBaseSize(QSize(800,600));
+
+ window->setSource(testFileUrl("tabFence.qml"));
+ window->show();
+ window->requestActivate();
+ QVERIFY(QTest::qWaitForWindowActive(window));
+ QVERIFY(QGuiApplication::focusWindow() == window);
+ QVERIFY(window->rootObject()->hasActiveFocus());
+
+ const char *rootTabFocusChain[] = {
+ "input1", "input2", "input3", "input1", Q_NULLPTR
+ };
+ verifyTabFocusChain(window, rootTabFocusChain, true /* forward */);
+
+ const char *rootBacktabFocusChain[] = {
+ "input3", "input2", "input1", "input3", Q_NULLPTR
+ };
+ verifyTabFocusChain(window, rootBacktabFocusChain, false /* forward */);
+
+ // Give focus to input11 in fence1
+ QQuickItem *item = findItem<QQuickItem>(window->rootObject(), "input11");
+ item->setFocus(true);
+ QVERIFY(item);
+ QVERIFY(item->hasActiveFocus());
+
+ const char *fence1TabFocusChain[] = {
+ "input12", "input13", "input11", "input12", Q_NULLPTR
+ };
+ verifyTabFocusChain(window, fence1TabFocusChain, true /* forward */);
+
+ const char *fence1BacktabFocusChain[] = {
+ "input11", "input13", "input12", "input11", Q_NULLPTR
+ };
+ verifyTabFocusChain(window, fence1BacktabFocusChain, false /* forward */);
+}
+
void tst_QQuickItem::keys()
{
QQuickView *window = new QQuickView(0);