Add isTabFence private flag
When an item has this flag set, the user can't tab-navigate either out of it, or enter it. We use this flag to implement QQuickPanel as an item for platforms that only support one single top-level window. Change-Id: I1f4313912ae1c70217af0d4d21064932b50a9438 Reviewed-by: Mitch Curtis <mitch.curtis@theqtcompany.com>
This commit is contained in:
parent
80ff6ded28
commit
6328dd2d27
|
@ -2422,6 +2422,50 @@ bool QQuickItemPrivate::focusNextPrev(QQuickItem *item, bool forward)
|
|||
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);
|
||||
|
@ -2444,7 +2488,6 @@ QQuickItem* QQuickItemPrivate::nextPrevItemInTabFocusChain(QQuickItem *item, boo
|
|||
from = item->parentItem();
|
||||
}
|
||||
bool skip = false;
|
||||
const QQuickItem * const originalItem = item;
|
||||
QQuickItem * startItem = item;
|
||||
QQuickItem * firstFromItem = from;
|
||||
QQuickItem *current = item;
|
||||
|
@ -2453,46 +2496,53 @@ QQuickItem* QQuickItemPrivate::nextPrevItemInTabFocusChain(QQuickItem *item, boo
|
|||
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;
|
||||
}
|
||||
|
@ -2500,9 +2550,9 @@ QQuickItem* QQuickItemPrivate::nextPrevItemInTabFocusChain(QQuickItem *item, boo
|
|||
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;
|
||||
|
@ -3026,6 +3076,7 @@ QQuickItemPrivate::QQuickItemPrivate()
|
|||
, activeFocusOnTab(false)
|
||||
, implicitAntialiasing(false)
|
||||
, antialiasingValid(false)
|
||||
, isTabFence(false)
|
||||
, dirtyAttributes(0)
|
||||
, nextDirtyItem(0)
|
||||
, prevDirtyItem(0)
|
||||
|
|
|
@ -427,6 +427,12 @@ public:
|
|||
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,
|
||||
|
@ -498,6 +504,8 @@ public:
|
|||
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);
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
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"
|
||||
}
|
||||
}
|
|
@ -73,6 +73,8 @@ private slots:
|
|||
void nextItemInFocusChain2();
|
||||
void nextItemInFocusChain3();
|
||||
|
||||
void tabFence();
|
||||
|
||||
void keys();
|
||||
void standardKeys_data();
|
||||
void standardKeys();
|
||||
|
@ -289,6 +291,20 @@ private:
|
|||
|
||||
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()
|
||||
{
|
||||
|
@ -299,6 +315,7 @@ void tst_QQuickItem::initTestCase()
|
|||
QQmlDataTest::initTestCase();
|
||||
qmlRegisterType<KeyTestItem>("Test",1,0,"KeyTestItem");
|
||||
qmlRegisterType<HollowTestItem>("Test", 1, 0, "HollowTestItem");
|
||||
qmlRegisterType<TabFenceItem>("Test", 1, 0, "TabFence");
|
||||
}
|
||||
|
||||
void tst_QQuickItem::cleanup()
|
||||
|
@ -1120,6 +1137,61 @@ void tst_QQuickItem::nextItemInFocusChain3()
|
|||
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);
|
||||
|
|
Loading…
Reference in New Issue