aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.qmake.conf2
-rw-r--r--src/imports/controls/DialogButtonBox.qml1
-rw-r--r--src/imports/controls/fusion/DialogButtonBox.qml1
-rw-r--r--src/imports/controls/imagine/DialogButtonBox.qml1
-rw-r--r--src/imports/controls/imagine/qquickninepatchimage.cpp2
-rw-r--r--src/imports/controls/material/DialogButtonBox.qml1
-rw-r--r--src/imports/controls/universal/DialogButtonBox.qml1
-rw-r--r--src/imports/platform/qquickplatformfiledialog.cpp8
-rw-r--r--src/quickcontrols2/qquickiconimage.cpp7
-rw-r--r--src/quickcontrols2/qquickiconimage_p_p.h1
-rw-r--r--src/quicktemplates2/qquickabstractbutton.cpp35
-rw-r--r--src/quicktemplates2/qquickabstractbutton_p_p.h2
-rw-r--r--src/quicktemplates2/qquickaction.cpp2
-rw-r--r--src/quicktemplates2/qquickapplicationwindow.cpp2
-rw-r--r--src/quicktemplates2/qquickcontrol.cpp48
-rw-r--r--src/quicktemplates2/qquickcontrol_p_p.h1
-rw-r--r--src/quicktemplates2/qquickoverlay.cpp9
-rw-r--r--src/quicktemplates2/qquickpopup.cpp10
-rw-r--r--src/quicktemplates2/qquickrangeslider.cpp8
-rw-r--r--src/quicktemplates2/qquickscrollbar.cpp13
-rw-r--r--src/quicktemplates2/qquickscrollbar_p_p.h2
-rw-r--r--src/quicktemplates2/qquickswipeview.cpp1
-rw-r--r--src/quicktemplates2/qquicktextarea.cpp6
-rw-r--r--src/quicktemplates2/quicktemplates2.pro4
-rw-r--r--tests/auto/controls/data/tst_abstractbutton.qml49
-rw-r--r--tests/auto/controls/data/tst_action.qml33
-rw-r--r--tests/auto/controls/data/tst_dialogbuttonbox.qml84
-rw-r--r--tests/auto/controls/data/tst_rangeslider.qml37
-rw-r--r--tests/auto/controls/data/tst_scrollview.qml47
-rw-r--r--tests/auto/controls/data/tst_slider.qml35
-rw-r--r--tests/auto/controls/data/tst_swipeview.qml63
-rw-r--r--tests/auto/controls/data/tst_switch.qml23
-rw-r--r--tests/auto/qquickcontrol/tst_qquickcontrol.cpp7
-rw-r--r--tests/auto/qquickimaginestyle/data/tst_imagine.qml56
-rw-r--r--tests/auto/qquickpopup/BLACKLIST8
-rw-r--r--tests/auto/qquickpopup/data/activeFocusAfterExit.qml79
-rw-r--r--tests/auto/qquickpopup/data/activeFocusOnDelayedEnter.qml73
-rw-r--r--tests/auto/qquickpopup/data/modelessOnModalOnModeless.qml98
-rw-r--r--tests/auto/qquickpopup/tst_qquickpopup.cpp116
39 files changed, 950 insertions, 26 deletions
diff --git a/.qmake.conf b/.qmake.conf
index 8a589b4e..722a8b53 100644
--- a/.qmake.conf
+++ b/.qmake.conf
@@ -5,4 +5,4 @@ DEFINES += QT_NO_FOREACH QT_NO_JAVA_STYLE_ITERATORS QT_NO_LINKED_LIST
QQC2_SOURCE_TREE = $$PWD
-MODULE_VERSION = 5.15.6
+MODULE_VERSION = 5.15.13
diff --git a/src/imports/controls/DialogButtonBox.qml b/src/imports/controls/DialogButtonBox.qml
index 3c9d5b48..cc148ac4 100644
--- a/src/imports/controls/DialogButtonBox.qml
+++ b/src/imports/controls/DialogButtonBox.qml
@@ -55,6 +55,7 @@ T.DialogButtonBox {
}
contentItem: ListView {
+ implicitWidth: contentWidth
model: control.contentModel
spacing: control.spacing
orientation: ListView.Horizontal
diff --git a/src/imports/controls/fusion/DialogButtonBox.qml b/src/imports/controls/fusion/DialogButtonBox.qml
index a0b0f243..4673e421 100644
--- a/src/imports/controls/fusion/DialogButtonBox.qml
+++ b/src/imports/controls/fusion/DialogButtonBox.qml
@@ -56,6 +56,7 @@ T.DialogButtonBox {
delegate: Button { }
contentItem: ListView {
+ implicitWidth: contentWidth
model: control.contentModel
spacing: control.spacing
orientation: ListView.Horizontal
diff --git a/src/imports/controls/imagine/DialogButtonBox.qml b/src/imports/controls/imagine/DialogButtonBox.qml
index c24b29fc..fd27a876 100644
--- a/src/imports/controls/imagine/DialogButtonBox.qml
+++ b/src/imports/controls/imagine/DialogButtonBox.qml
@@ -66,6 +66,7 @@ T.DialogButtonBox {
}
contentItem: ListView {
+ implicitWidth: contentWidth
model: control.contentModel
spacing: control.spacing
orientation: ListView.Horizontal
diff --git a/src/imports/controls/imagine/qquickninepatchimage.cpp b/src/imports/controls/imagine/qquickninepatchimage.cpp
index 71243988..ed388ec1 100644
--- a/src/imports/controls/imagine/qquickninepatchimage.cpp
+++ b/src/imports/controls/imagine/qquickninepatchimage.cpp
@@ -439,6 +439,8 @@ QSGNode *QQuickNinePatchImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNode
QSizeF sz = size();
QImage image = d->pix.image();
if (!sz.isValid() || image.isNull()) {
+ if (d->provider)
+ d->provider->updateTexture(nullptr);
delete oldNode;
return nullptr;
}
diff --git a/src/imports/controls/material/DialogButtonBox.qml b/src/imports/controls/material/DialogButtonBox.qml
index c53b8210..d148fb24 100644
--- a/src/imports/controls/material/DialogButtonBox.qml
+++ b/src/imports/controls/material/DialogButtonBox.qml
@@ -60,6 +60,7 @@ T.DialogButtonBox {
delegate: Button { flat: true }
contentItem: ListView {
+ implicitWidth: contentWidth
model: control.contentModel
spacing: control.spacing
orientation: ListView.Horizontal
diff --git a/src/imports/controls/universal/DialogButtonBox.qml b/src/imports/controls/universal/DialogButtonBox.qml
index 0458c39d..103b46c2 100644
--- a/src/imports/controls/universal/DialogButtonBox.qml
+++ b/src/imports/controls/universal/DialogButtonBox.qml
@@ -59,6 +59,7 @@ T.DialogButtonBox {
}
contentItem: ListView {
+ implicitWidth: contentWidth
model: control.contentModel
spacing: control.spacing
orientation: ListView.Horizontal
diff --git a/src/imports/platform/qquickplatformfiledialog.cpp b/src/imports/platform/qquickplatformfiledialog.cpp
index 81b851a5..af5475fa 100644
--- a/src/imports/platform/qquickplatformfiledialog.cpp
+++ b/src/imports/platform/qquickplatformfiledialog.cpp
@@ -552,8 +552,14 @@ QUrl QQuickPlatformFileDialog::addDefaultSuffix(const QUrl &file) const
QUrl url = file;
const QString path = url.path();
const QString suffix = m_options->defaultSuffix();
- if (!suffix.isEmpty() && !path.endsWith(QLatin1Char('/')) && path.lastIndexOf(QLatin1Char('.')) == -1)
+ // Urls with "content" scheme do not require suffixes. Such schemes are
+ // used on Android.
+ const bool isContentScheme = url.scheme() == QStringLiteral("content");
+ if (!isContentScheme && !suffix.isEmpty() && !path.endsWith(QLatin1Char('/'))
+ && path.lastIndexOf(QLatin1Char('.')) == -1) {
url.setPath(path + QLatin1Char('.') + suffix);
+ }
+
return url;
}
diff --git a/src/quickcontrols2/qquickiconimage.cpp b/src/quickcontrols2/qquickiconimage.cpp
index d86afd7f..11bb3bca 100644
--- a/src/quickcontrols2/qquickiconimage.cpp
+++ b/src/quickcontrols2/qquickiconimage.cpp
@@ -42,6 +42,12 @@
QT_BEGIN_NAMESPACE
+QQuickIconImagePrivate::~QQuickIconImagePrivate()
+{
+ qDeleteAll(icon.entries);
+ icon.entries.clear();
+}
+
bool QQuickIconImagePrivate::updateDevicePixelRatio(qreal targetDevicePixelRatio)
{
if (isThemeIcon) {
@@ -132,6 +138,7 @@ void QQuickIconImage::setName(const QString &name)
if (d->icon.iconName == name)
return;
+ qDeleteAll(d->icon.entries);
d->icon = QIconLoader::instance()->loadIcon(name);
if (isComponentComplete())
d->updateIcon();
diff --git a/src/quickcontrols2/qquickiconimage_p_p.h b/src/quickcontrols2/qquickiconimage_p_p.h
index 0c755ff6..11bf5e92 100644
--- a/src/quickcontrols2/qquickiconimage_p_p.h
+++ b/src/quickcontrols2/qquickiconimage_p_p.h
@@ -59,6 +59,7 @@ class Q_QUICKCONTROLS2_PRIVATE_EXPORT QQuickIconImagePrivate : public QQuickImag
Q_DECLARE_PUBLIC(QQuickIconImage)
public:
+ ~QQuickIconImagePrivate() override;
void updateIcon();
void updateFillMode();
qreal calculateDevicePixelRatio() const;
diff --git a/src/quicktemplates2/qquickabstractbutton.cpp b/src/quicktemplates2/qquickabstractbutton.cpp
index 950e4e09..20cf59c1 100644
--- a/src/quicktemplates2/qquickabstractbutton.cpp
+++ b/src/quicktemplates2/qquickabstractbutton.cpp
@@ -215,8 +215,20 @@ bool QQuickAbstractButtonPrivate::acceptKeyClick(Qt::Key key) const
bool QQuickAbstractButtonPrivate::isPressAndHoldConnected()
{
Q_Q(QQuickAbstractButton);
- const auto signal = &QQuickAbstractButton::pressAndHold;
- const QMetaMethod method = QMetaMethod::fromSignal(signal);
+ static const QMetaMethod method = [&]() {
+ const auto signal = &QQuickAbstractButton::pressAndHold;
+ return QMetaMethod::fromSignal(signal);
+ }();
+ return q->isSignalConnected(method);
+}
+
+bool QQuickAbstractButtonPrivate::isDoubleClickConnected()
+{
+ Q_Q(QQuickAbstractButton);
+ static const QMetaMethod method = [&]() {
+ const auto signal = &QQuickAbstractButton::doubleClicked;
+ return QMetaMethod::fromSignal(signal);
+ }();
return q->isSignalConnected(method);
}
@@ -387,6 +399,17 @@ void QQuickAbstractButtonPrivate::itemImplicitHeightChanged(QQuickItem *item)
emit q->implicitIndicatorHeightChanged();
}
+void QQuickAbstractButtonPrivate::itemDestroyed(QQuickItem *item)
+{
+ Q_Q(QQuickAbstractButton);
+ QQuickControlPrivate::itemDestroyed(item);
+ if (item == indicator) {
+ indicator = nullptr;
+ emit q->implicitIndicatorWidthChanged();
+ emit q->implicitIndicatorHeightChanged();
+ }
+}
+
QQuickAbstractButton *QQuickAbstractButtonPrivate::findCheckedButton() const
{
Q_Q(const QQuickAbstractButton);
@@ -1086,9 +1109,11 @@ void QQuickAbstractButton::mousePressEvent(QMouseEvent *event)
void QQuickAbstractButton::mouseDoubleClickEvent(QMouseEvent *event)
{
Q_D(QQuickAbstractButton);
- QQuickControl::mouseDoubleClickEvent(event);
- emit doubleClicked();
- d->wasDoubleClick = true;
+ if (d->isDoubleClickConnected()) {
+ QQuickControl::mouseDoubleClickEvent(event);
+ emit doubleClicked();
+ d->wasDoubleClick = true;
+ }
}
void QQuickAbstractButton::timerEvent(QTimerEvent *event)
diff --git a/src/quicktemplates2/qquickabstractbutton_p_p.h b/src/quicktemplates2/qquickabstractbutton_p_p.h
index 9291c1a8..907790dc 100644
--- a/src/quicktemplates2/qquickabstractbutton_p_p.h
+++ b/src/quicktemplates2/qquickabstractbutton_p_p.h
@@ -80,6 +80,7 @@ public:
virtual bool acceptKeyClick(Qt::Key key) const;
bool isPressAndHoldConnected();
+ bool isDoubleClickConnected();
void startPressAndHold();
void stopPressAndHold();
@@ -109,6 +110,7 @@ public:
void itemImplicitWidthChanged(QQuickItem *item) override;
void itemImplicitHeightChanged(QQuickItem *item) override;
+ void itemDestroyed(QQuickItem *item) override;
// copied from qabstractbutton.cpp
static const int AUTO_REPEAT_DELAY = 300;
diff --git a/src/quicktemplates2/qquickaction.cpp b/src/quicktemplates2/qquickaction.cpp
index 2041e774..8610cdfa 100644
--- a/src/quicktemplates2/qquickaction.cpp
+++ b/src/quicktemplates2/qquickaction.cpp
@@ -145,7 +145,7 @@ int QQuickActionPrivate::ShortcutEntry::shortcutId() const
void QQuickActionPrivate::ShortcutEntry::grab(const QKeySequence &shortcut, bool enabled)
{
- if (shortcut.isEmpty())
+ if (shortcut.isEmpty() || m_shortcutId)
return;
Qt::ShortcutContext context = Qt::WindowShortcut; // TODO
diff --git a/src/quicktemplates2/qquickapplicationwindow.cpp b/src/quicktemplates2/qquickapplicationwindow.cpp
index bb1b9b15..a9a4ad72 100644
--- a/src/quicktemplates2/qquickapplicationwindow.cpp
+++ b/src/quicktemplates2/qquickapplicationwindow.cpp
@@ -653,6 +653,8 @@ QQuickOverlay *QQuickApplicationWindow::overlay() const
if (!d->overlay) {
d->overlay = new QQuickOverlay(QQuickWindow::contentItem());
+ // make the overlay discoverable by the virtual keyboard
+ d->q_ptr->setProperty("_q_QQuickOverlay", QVariant::fromValue<QQuickItem*>(d->overlay));
d->overlay->stackAfter(QQuickApplicationWindow::contentItem());
}
return d->overlay;
diff --git a/src/quicktemplates2/qquickcontrol.cpp b/src/quicktemplates2/qquickcontrol.cpp
index bbbd0e62..a719efd3 100644
--- a/src/quicktemplates2/qquickcontrol.cpp
+++ b/src/quicktemplates2/qquickcontrol.cpp
@@ -177,11 +177,19 @@ bool QQuickControlPrivate::acceptTouch(const QTouchEvent::TouchPoint &point)
return true;
}
- // If the control is on a Flickable that has a pressDelay, then the press is never
- // sent as a touch event, therefore we need to check for this case.
- if (touchId == -1 && pressWasTouch && point.state() == Qt::TouchPointReleased &&
- point.pos() == previousPressPos) {
- return true;
+ // If the control is on a Flickable that has a pressDelay, the press is sent
+ // as a mouse event rather than touch; so it detect and deal with it.
+ if (touchId == -1 && pressWasTouch) {
+ const auto delta = QVector2D(point.pos() - previousPressPos);
+ const bool overThreshold = QQuickWindowPrivate::dragOverThreshold(delta);
+ if (point.state() == Qt::TouchPointReleased && !overThreshold) {
+ // touchpoint was released near the press position: don't expect any more events, but just handle the release
+ return true;
+ } else if (point.state() == Qt::TouchPointMoved && overThreshold) {
+ // touchpoint was dragged over the drag threshold: accept it, and remember to handle all moves from now on
+ touchId = point.id();
+ return true;
+ }
}
return false;
}
@@ -845,6 +853,13 @@ void QQuickControlPrivate::executeBackground(bool complete)
quickCompleteDeferred(q, backgroundName(), background);
}
+/*
+ \internal
+
+ Hides an item that was replaced by a newer one, rather than
+ deleting it, as the item is typically created in QML and hence
+ we don't own it.
+*/
void QQuickControlPrivate::hideOldItem(QQuickItem *item)
{
if (!item)
@@ -863,6 +878,29 @@ void QQuickControlPrivate::hideOldItem(QQuickItem *item)
#endif
}
+/*
+ \internal
+
+ Named "unhide" because it's used for cases where an item
+ that was previously hidden by \l hideOldItem() wants to be
+ shown by a control again, such as a ScrollBar in ScrollView.
+*/
+void QQuickControlPrivate::unhideOldItem(QQuickControl *control, QQuickItem *item)
+{
+ Q_ASSERT(item);
+ qCDebug(lcItemManagement) << "unhiding old item" << item;
+
+ item->setVisible(true);
+ item->setParentItem(control);
+
+#if QT_CONFIG(accessibility)
+ // Add the item back in to the accessibility tree.
+ QQuickAccessibleAttached *accessible = accessibleAttached(item);
+ if (accessible)
+ accessible->setIgnored(false);
+#endif
+}
+
void QQuickControlPrivate::updateBaselineOffset()
{
Q_Q(QQuickControl);
diff --git a/src/quicktemplates2/qquickcontrol_p_p.h b/src/quicktemplates2/qquickcontrol_p_p.h
index 8e979079..a6e624c9 100644
--- a/src/quicktemplates2/qquickcontrol_p_p.h
+++ b/src/quicktemplates2/qquickcontrol_p_p.h
@@ -173,6 +173,7 @@ public:
virtual void executeBackground(bool complete = false);
static void hideOldItem(QQuickItem *item);
+ static void unhideOldItem(QQuickControl *control, QQuickItem *item);
void updateBaselineOffset();
diff --git a/src/quicktemplates2/qquickoverlay.cpp b/src/quicktemplates2/qquickoverlay.cpp
index b8d417f2..91bd5918 100644
--- a/src/quicktemplates2/qquickoverlay.cpp
+++ b/src/quicktemplates2/qquickoverlay.cpp
@@ -471,22 +471,27 @@ bool QQuickOverlay::childMouseEventFilter(QQuickItem *item, QEvent *event)
// background dimming OR over another popup underneath, in case the popup
// does not have background dimming.
if (item == p->dimmer || !p->popupItem->isAncestorOf(item)) {
+ bool handled = false;
switch (event->type()) {
#if QT_CONFIG(quicktemplates2_multitouch)
case QEvent::TouchBegin:
case QEvent::TouchUpdate:
case QEvent::TouchEnd:
- return d->handleTouchEvent(item, static_cast<QTouchEvent *>(event), popup);
+ handled = d->handleTouchEvent(item, static_cast<QTouchEvent *>(event), popup);
+ break;
#endif
case QEvent::MouseButtonPress:
case QEvent::MouseMove:
case QEvent::MouseButtonRelease:
- return d->handleMouseEvent(item, static_cast<QMouseEvent *>(event), popup);
+ handled = d->handleMouseEvent(item, static_cast<QMouseEvent *>(event), popup);
+ break;
default:
break;
}
+ if (handled)
+ return true;
}
}
return false;
diff --git a/src/quicktemplates2/qquickpopup.cpp b/src/quicktemplates2/qquickpopup.cpp
index bdc6dff2..7df80a04 100644
--- a/src/quicktemplates2/qquickpopup.cpp
+++ b/src/quicktemplates2/qquickpopup.cpp
@@ -456,6 +456,9 @@ bool QQuickPopupPrivate::prepareEnterTransition()
popupItem->setVisible(true);
getPositioner()->setParentItem(parentItem);
emit q->visibleChanged();
+
+ if (focus)
+ popupItem->setFocus(true);
}
return true;
}
@@ -489,8 +492,6 @@ bool QQuickPopupPrivate::prepareExitTransition()
void QQuickPopupPrivate::finalizeEnterTransition()
{
Q_Q(QQuickPopup);
- if (focus)
- popupItem->setFocus(true);
transitionState = NoTransition;
getPositioner()->reposition();
emit q->openedChanged();
@@ -513,13 +514,14 @@ void QQuickPopupPrivate::finalizeExitTransition()
if (QQuickOverlay *overlay = QQuickOverlay::overlay(window)) {
const auto stackingOrderPopups = QQuickOverlayPrivate::get(overlay)->stackingOrderPopups();
for (auto popup : stackingOrderPopups) {
- if (QQuickPopupPrivate::get(popup)->transitionState != ExitTransition) {
+ if (QQuickPopupPrivate::get(popup)->transitionState != ExitTransition
+ && popup->hasFocus()) {
nextFocusPopup = popup;
break;
}
}
}
- if (nextFocusPopup && nextFocusPopup->hasFocus()) {
+ if (nextFocusPopup) {
nextFocusPopup->forceActiveFocus();
} else {
QQuickApplicationWindow *applicationWindow = qobject_cast<QQuickApplicationWindow*>(window);
diff --git a/src/quicktemplates2/qquickrangeslider.cpp b/src/quicktemplates2/qquickrangeslider.cpp
index eb9c1bb1..f1e5c805 100644
--- a/src/quicktemplates2/qquickrangeslider.cpp
+++ b/src/quicktemplates2/qquickrangeslider.cpp
@@ -665,6 +665,10 @@ void QQuickRangeSlider::setFrom(qreal from)
if (isComponentComplete()) {
d->first->setValue(d->first->value());
d->second->setValue(d->second->value());
+ auto *firstPrivate = QQuickRangeSliderNodePrivate::get(d->first);
+ auto *secondPrivate = QQuickRangeSliderNodePrivate::get(d->second);
+ firstPrivate->updatePosition(true);
+ secondPrivate->updatePosition();
}
}
@@ -693,6 +697,10 @@ void QQuickRangeSlider::setTo(qreal to)
if (isComponentComplete()) {
d->first->setValue(d->first->value());
d->second->setValue(d->second->value());
+ auto *firstPrivate = QQuickRangeSliderNodePrivate::get(d->first);
+ auto *secondPrivate = QQuickRangeSliderNodePrivate::get(d->second);
+ firstPrivate->updatePosition(true);
+ secondPrivate->updatePosition();
}
}
diff --git a/src/quicktemplates2/qquickscrollbar.cpp b/src/quicktemplates2/qquickscrollbar.cpp
index 4e2f509d..f0783708 100644
--- a/src/quicktemplates2/qquickscrollbar.cpp
+++ b/src/quicktemplates2/qquickscrollbar.cpp
@@ -77,7 +77,7 @@ QT_BEGIN_NAMESPACE
\list
\li \l orientation
\li \l position
- \li \l size
+ \li \l {ScrollBar::} {size}
\li \l active
\endlist
@@ -797,6 +797,14 @@ void QQuickScrollBarAttachedPrivate::initHorizontal()
if (parent && parent == flickable->parentItem())
horizontal->stackAfter(flickable);
+ // If a scroll bar was previously hidden (due to e.g. setting a new contentItem
+ // on a ScrollView), we need to make sure that we un-hide it.
+ // We don't bother checking if the item is actually the old one, because
+ // if it's not, all of the things the function does (setting parent, visibility, etc.)
+ // should be no-ops anyway.
+ if (auto control = qobject_cast<QQuickControl*>(q_func()->parent()))
+ QQuickControlPrivate::unhideOldItem(control, horizontal);
+
layoutHorizontal();
horizontal->setSize(area->property("widthRatio").toReal());
horizontal->setPosition(area->property("xPosition").toReal());
@@ -818,6 +826,9 @@ void QQuickScrollBarAttachedPrivate::initVertical()
if (parent && parent == flickable->parentItem())
vertical->stackAfter(flickable);
+ if (auto control = qobject_cast<QQuickControl*>(q_func()->parent()))
+ QQuickControlPrivate::unhideOldItem(control, vertical);
+
layoutVertical();
vertical->setSize(area->property("heightRatio").toReal());
vertical->setPosition(area->property("yPosition").toReal());
diff --git a/src/quicktemplates2/qquickscrollbar_p_p.h b/src/quicktemplates2/qquickscrollbar_p_p.h
index 5c7628b9..c58c2ed1 100644
--- a/src/quicktemplates2/qquickscrollbar_p_p.h
+++ b/src/quicktemplates2/qquickscrollbar_p_p.h
@@ -107,6 +107,8 @@ public:
class QQuickScrollBarAttachedPrivate : public QObjectPrivate, public QQuickItemChangeListener
{
+ Q_DECLARE_PUBLIC(QQuickScrollBar)
+
public:
static QQuickScrollBarAttachedPrivate *get(QQuickScrollBarAttached *attached)
{
diff --git a/src/quicktemplates2/qquickswipeview.cpp b/src/quicktemplates2/qquickswipeview.cpp
index dab20b93..98d4d22b 100644
--- a/src/quicktemplates2/qquickswipeview.cpp
+++ b/src/quicktemplates2/qquickswipeview.cpp
@@ -310,7 +310,6 @@ void QQuickSwipeView::geometryChanged(const QRectF &newGeometry, const QRectF &o
void QQuickSwipeView::itemAdded(int index, QQuickItem *item)
{
Q_D(QQuickSwipeView);
- QQuickItemPrivate::get(item)->setCulled(true); // QTBUG-51078, QTBUG-51669
if (isComponentComplete())
item->setSize(QSizeF(d->contentItem->width(), d->contentItem->height()));
QQuickSwipeViewAttached *attached = qobject_cast<QQuickSwipeViewAttached *>(qmlAttachedPropertiesObject<QQuickSwipeView>(item));
diff --git a/src/quicktemplates2/qquicktextarea.cpp b/src/quicktemplates2/qquicktextarea.cpp
index d09d9c3b..64fc631d 100644
--- a/src/quicktemplates2/qquicktextarea.cpp
+++ b/src/quicktemplates2/qquicktextarea.cpp
@@ -1086,7 +1086,11 @@ QSGNode *QQuickTextArea::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
if (d->flickable)
clipper = d->flickable;
- const QRectF cr = clipper->clipRect().adjusted(leftPadding(), topPadding(), -rightPadding(), -bottomPadding());
+ const QRectF cr = clipper->clipRect().adjusted(
+ leftPadding(), topPadding(),
+ (!d->cursorItem && effectiveHAlign() == HAlignment::AlignRight ? 1 : 0) - rightPadding(),
+ -bottomPadding());
+
clipNode->setRect(!d->flickable ? cr : cr.translated(d->flickable->contentX(), d->flickable->contentY()));
clipNode->update();
diff --git a/src/quicktemplates2/quicktemplates2.pro b/src/quicktemplates2/quicktemplates2.pro
index 5138b610..a3f778b2 100644
--- a/src/quicktemplates2/quicktemplates2.pro
+++ b/src/quicktemplates2/quicktemplates2.pro
@@ -14,5 +14,7 @@ SOURCES += \
$$PWD/qtquicktemplates2global.cpp
include(quicktemplates2.pri)
-include(accessible/accessible.pri)
+qtConfig(accessibility) {
+ include(accessible/accessible.pri)
+}
load(qt_module)
diff --git a/tests/auto/controls/data/tst_abstractbutton.qml b/tests/auto/controls/data/tst_abstractbutton.qml
index da5642cc..6181e526 100644
--- a/tests/auto/controls/data/tst_abstractbutton.qml
+++ b/tests/auto/controls/data/tst_abstractbutton.qml
@@ -910,4 +910,53 @@ TestCase {
compare(clickedSpy.count, 1)
compare(doubleClickedSpy.count, 1)
}
+
+ // It should be possible to quickly click a button whose doubleClicked signal
+ // is not connected to anything.
+ function test_fastClick() {
+ let control = createTemporaryObject(button, testCase, { text: "Hello" })
+ verify(control)
+
+ let pressedSpy = signalSpy.createObject(control, { target: control, signalName: "pressed" })
+ verify(pressedSpy.valid)
+
+ let releasedSpy = signalSpy.createObject(control, { target: control, signalName: "released" })
+ verify(releasedSpy.valid)
+
+ let clickedSpy = signalSpy.createObject(control, { target: control, signalName: "clicked" })
+ verify(clickedSpy.valid)
+
+ // Can't listen to doubleClicked because it would cause it to be emitted.
+ // We instead just check that clicked is emitted twice.
+
+ mouseDoubleClickSequence(control)
+ compare(pressedSpy.count, 2)
+ compare(releasedSpy.count, 2)
+ compare(clickedSpy.count, 2)
+
+ let touch = touchEvent(control)
+ touch.press(0, control)
+ touch.commit()
+ compare(pressedSpy.count, 3)
+ compare(releasedSpy.count, 2)
+ compare(clickedSpy.count, 2)
+
+ touch.release(0, control)
+ touch.commit()
+ compare(pressedSpy.count, 3)
+ compare(releasedSpy.count, 3)
+ compare(clickedSpy.count, 3)
+
+ touch.press(0, control)
+ touch.commit()
+ compare(pressedSpy.count, 4)
+ compare(releasedSpy.count, 3)
+ compare(clickedSpy.count, 3)
+
+ touch.release(0, control)
+ touch.commit()
+ compare(pressedSpy.count, 4)
+ compare(releasedSpy.count, 4)
+ compare(clickedSpy.count, 4)
+ }
}
diff --git a/tests/auto/controls/data/tst_action.qml b/tests/auto/controls/data/tst_action.qml
index 0e41b7f3..7ed4aa11 100644
--- a/tests/auto/controls/data/tst_action.qml
+++ b/tests/auto/controls/data/tst_action.qml
@@ -194,4 +194,37 @@ TestCase {
verify(container)
compare(container.indirect.nativeText, container.direct.nativeText);
}
+
+ Component {
+ id: shortcutCleanup
+ Item {
+ property alias page: page
+ property alias action: action
+ property alias menu: menu
+ Item {
+ id: page
+ Action {
+ id: action
+ text: "action"
+ shortcut: "Insert"
+ }
+ Menu {
+ id: menu
+ MenuItem { action: action }
+ }
+ }
+ }
+ }
+
+ function test_shortcutCleanup() {
+ {
+ var container = createTemporaryObject(shortcutCleanup, testCase);
+ verify(container)
+ container.action.shortcut = "Delete"
+ container.menu.open()
+ container.page.destroy()
+ tryVerify(function() { return !container.page })
+ }
+ keyClick(Qt.Key_Delete, Qt.NoModifier)
+ }
}
diff --git a/tests/auto/controls/data/tst_dialogbuttonbox.qml b/tests/auto/controls/data/tst_dialogbuttonbox.qml
index a651713a..63cefa46 100644
--- a/tests/auto/controls/data/tst_dialogbuttonbox.qml
+++ b/tests/auto/controls/data/tst_dialogbuttonbox.qml
@@ -398,7 +398,7 @@ TestCase {
// QTBUG-72886
function test_changeCustomButtonText(data) {
- var control = createTemporaryObject(customButtonBox, testCase, {})
+ var control = createTemporaryObject(data.component, testCase, {})
verify(control)
var listView = control.contentItem
@@ -418,6 +418,88 @@ TestCase {
}
Component {
+ id: customButtonBoxInDialog
+
+ Dialog {
+ width: 300
+ visible: true
+
+ footer: DialogButtonBox {
+ objectName: "customButtonBoxInDialog"
+ alignment: Qt.AlignRight
+
+ property alias okButton: okButton
+
+ Button {
+ id: okButton
+ text: "OK"
+
+ DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
+ }
+ }
+ }
+ }
+
+ Component {
+ id: customButtonBoxTwoButtonsInDialog
+
+ Dialog {
+ width: 300
+ visible: true
+
+ footer: DialogButtonBox {
+ objectName: "customButtonBoxTwoButtonsInDialog"
+ alignment: Qt.AlignRight
+
+ property alias okButton: okButton
+
+ Button {
+ id: okButton
+ text: "OK"
+
+ DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
+ }
+ Button {
+ text: "Cancel"
+
+ DialogButtonBox.buttonRole: DialogButtonBox.RejectRole
+ }
+ }
+ }
+ }
+
+ function test_changeCustomButtonImplicitWidth_data() {
+ return [
+ { tag: "oneButton", component: customButtonBoxInDialog },
+ { tag: "twoButtons", component: customButtonBoxTwoButtonsInDialog },
+ ]
+ }
+
+ // QTBUG-102558
+ function test_changeCustomButtonImplicitWidth(data) {
+ let dialog = createTemporaryObject(data.component, testCase, {})
+ verify(dialog)
+
+ let control = dialog.footer
+ verify(control)
+
+ let listView = control.contentItem
+ waitForRendering(listView)
+
+ let button = control.okButton
+ verify(button)
+ button.implicitWidth *= 1.5
+
+ // The button should never go outside of the box.
+ tryVerify(function() { return button.mapToItem(control, 0, 0).x >= 0 },
+ 1000, "Expected left edge of button to be within left edge of DialogButtonBox (i.e. greater than or equal to 0)" +
+ ", but it's " + button.mapToItem(control, 0, 0).x)
+ tryVerify(function() { return button.mapToItem(control, 0, 0).x + button.width <= control.width },
+ 1000, "Expected right edge of button to be within right edge of DialogButtonBox (i.e. less than or equal to " +
+ control.width + "), but it's " + (button.mapToItem(control, 0, 0).x + button.width))
+ }
+
+ Component {
id: noRolesDialog
Dialog {
diff --git a/tests/auto/controls/data/tst_rangeslider.qml b/tests/auto/controls/data/tst_rangeslider.qml
index d645ec70..42f5bad5 100644
--- a/tests/auto/controls/data/tst_rangeslider.qml
+++ b/tests/auto/controls/data/tst_rangeslider.qml
@@ -159,6 +159,43 @@ TestCase {
compare(control.first.position, 0.5)
}
+ function test_setToFromUpdatesHandles() {
+ var control = createTemporaryObject(sliderComponent, testCase, { from: 0, to: 100, "first.value": 50, "second.value": 75 })
+ verify(control)
+
+ let firstPos = control.first.position
+ let secondPos = control.second.position
+
+ var firstPosChangesSpy = signalSpy.createObject(control, {target: control.first, signalName: "positionChanged"})
+ verify(firstPosChangesSpy.valid)
+
+ var secondPosChangesSpy = signalSpy.createObject(control, {target: control.second, signalName: "positionChanged"})
+ verify(secondPosChangesSpy.valid)
+
+ // Increasing the 'to' value, so the positions of the handles should be
+ // moved to the left (become smaller)
+ control.to = 200;
+ compare(firstPosChangesSpy.count, 1)
+ compare(secondPosChangesSpy.count, 1)
+ verify(control.first.position < firstPos)
+ verify(control.second.position < secondPos)
+
+ // resetting the values
+ control.to = 100
+ firstPosChangesSpy.clear()
+ secondPosChangesSpy.clear()
+ firstPos = control.first.position
+ secondPos = control.second.position
+
+ // Decreasing the 'from' value, so the positions of the handles should
+ // be moved to the right (become larger)
+ control.from = -100
+ compare(firstPosChangesSpy.count, 1)
+ compare(secondPosChangesSpy.count, 1)
+ verify(control.first.position > firstPos)
+ verify(control.second.position > secondPos)
+ }
+
function test_setValues() {
var control = createTemporaryObject(sliderComponent, testCase)
verify(control)
diff --git a/tests/auto/controls/data/tst_scrollview.qml b/tests/auto/controls/data/tst_scrollview.qml
index 0e8b0835..cd493118 100644
--- a/tests/auto/controls/data/tst_scrollview.qml
+++ b/tests/auto/controls/data/tst_scrollview.qml
@@ -576,4 +576,51 @@ TestCase {
verify(newHorizontalScrollBar.visible)
verify(!oldHorizontalScrollBar.visible)
}
+
+ Component {
+ id: bindingToContentItemAndStandaloneFlickable
+
+ Item {
+ width: 200
+ height: 200
+
+ property alias scrollView: scrollView
+
+ ScrollView {
+ id: scrollView
+ anchors.fill: parent
+ contentItem: listView
+
+ property Item someBinding: contentItem
+ }
+ ListView {
+ id: listView
+ model: 10
+ delegate: ItemDelegate {
+ text: modelData
+ width: listView.width
+ }
+ }
+ }
+ }
+
+ // Tests that scroll bars show up for a ScrollView where
+ // - its contentItem is declared as a standalone, separate item
+ // - there is a binding to contentItem (which causes a default Flickable to be created)
+ function test_bindingToContentItemAndStandaloneFlickable() {
+ let root = createTemporaryObject(bindingToContentItemAndStandaloneFlickable, testCase)
+ verify(root)
+
+ let control = root.scrollView
+ let verticalScrollBar = control.ScrollBar.vertical
+ let horizontalScrollBar = control.ScrollBar.horizontal
+ compare(verticalScrollBar.parent, control)
+ compare(horizontalScrollBar.parent, control)
+ verify(verticalScrollBar.visible)
+ verify(horizontalScrollBar.visible)
+
+ mouseDrag(verticalScrollBar, verticalScrollBar.width / 2, verticalScrollBar.height / 2, 0, 50)
+ verify(verticalScrollBar.active)
+ verify(horizontalScrollBar.active)
+ }
}
diff --git a/tests/auto/controls/data/tst_slider.qml b/tests/auto/controls/data/tst_slider.qml
index e1e1ed97..bf65e486 100644
--- a/tests/auto/controls/data/tst_slider.qml
+++ b/tests/auto/controls/data/tst_slider.qml
@@ -923,4 +923,39 @@ TestCase {
touch.release(0, control, x0 + data.dx2, y0 + data.dy2).commit()
}
+
+ Component {
+ id: listViewWithPressDelayAndSliders
+ ListView {
+ width: 300
+ height: 500
+ model: 3
+ pressDelay: 150
+ delegate: Slider {
+ width: 300
+ height: 150
+ }
+ }
+ }
+
+ function test_listViewWithPressDelay() {
+ var listView = createTemporaryObject(listViewWithPressDelayAndSliders, testCase, { width: parent.width, height: parent.height })
+ verify(listView)
+ var control = listView.itemAtIndex(0)
+ verify(control)
+ var movedSpy = signalSpy.createObject(control, {target: control, signalName: "moved"})
+ verify(movedSpy.valid)
+
+ var touch = touchEvent(control)
+ var x0 = control.handle.x + control.handle.width * 0.5
+ var y0 = control.handle.y + control.handle.height * 0.5
+ touch.press(0, control, x0, y0).commit()
+ tryCompare(control, "pressed", true)
+ fuzzyCompare(control.value, 0, 0.01)
+
+ touch.move(0, control, x0 + 100, y0).commit()
+ tryVerify(function() { return (control.value > 0.3); }) // around 0.35, depending on style
+ tryVerify(function() { return (movedSpy.count > 0); }) // ideally == 1, but in Material and Fusion it's 2
+ touch.release(0)
+ }
}
diff --git a/tests/auto/controls/data/tst_swipeview.qml b/tests/auto/controls/data/tst_swipeview.qml
index 5775491c..07d0cc0c 100644
--- a/tests/auto/controls/data/tst_swipeview.qml
+++ b/tests/auto/controls/data/tst_swipeview.qml
@@ -623,4 +623,67 @@ TestCase {
compare(control.rectanglePressCount, 1)
compare(control.rectangleReleaseCount, 1)
}
+
+ // We have a particular customer who came up with this hack to make SwipeView wrap around at the end.
+ // It's not advisible, and perhaps the test can be removed when we add a bool wrap property.
+ Component {
+ id: pathViewWorkaroundComponent
+
+ SwipeView {
+ id: swipeView
+ anchors.left: parent.left
+ width: 100
+ height: 100
+ clip: true
+ Repeater {
+ id: repeater
+ objectName: "peter"
+ delegate: Rectangle {
+ id: rect
+ color: "#ffff00"
+ border.color: "black"
+ width: 100
+ height: 100
+ Text {
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.horizontalCenter: parent.horizontalCenter
+ font.pixelSize: 24
+ text: model.index
+ }
+ }
+ }
+ contentItem: PathView {
+ id: pathview
+
+ preferredHighlightBegin: 0.5
+ preferredHighlightEnd: 0.5
+ model: swipeView.contentModel
+
+ path: Path {
+ id: path
+ startX: (swipeView.width / 2 * -1) * (swipeView.count - 1)
+ startY: swipeView.height / 2
+ PathLine {
+ relativeX: swipeView.width * swipeView.count
+ relativeY: 0
+ }
+ }
+ }
+ }
+ }
+
+ function test_child_pathview() {
+ const control = createTemporaryObject(pathViewWorkaroundComponent, testCase)
+ verify(control)
+ const repeater = control.children[0].children[0]
+ const spy = signalSpy.createObject(repeater, {target: repeater, signalName: "itemAdded"})
+ repeater.model = 1
+ tryCompare(spy, "count", 1)
+ const rect = repeater.itemAt(0)
+ tryCompare(rect, "visible", true)
+ if (Qt.platform.pluginName === "offscreen")
+ skip("grabImage() is not functional on the offscreen platform (QTBUG-63185)")
+ var image = grabImage(control)
+ compare(image.pixel(3, 3), "#ffff00")
+ }
}
diff --git a/tests/auto/controls/data/tst_switch.qml b/tests/auto/controls/data/tst_switch.qml
index b3fab41c..10b6baa0 100644
--- a/tests/auto/controls/data/tst_switch.qml
+++ b/tests/auto/controls/data/tst_switch.qml
@@ -608,4 +608,27 @@ TestCase {
mouseClick(control.indicator)
verify(control.activeFocus)
}
+
+ Component {
+ id: deletionOrder1
+ Item {
+ Image { id: innerImage }
+ Switch { indicator: innerImage }
+ }
+ }
+
+ Component {
+ id: deletionOrder2
+ Item {
+ Switch { indicator: innerImage }
+ Image { id: innerImage }
+ }
+ }
+
+ function test_deletionOrder() {
+ var control1 = createTemporaryObject(deletionOrder1, testCase)
+ verify(control1)
+ var control2 = createTemporaryObject(deletionOrder2, testCase)
+ verify(control2)
+ }
}
diff --git a/tests/auto/qquickcontrol/tst_qquickcontrol.cpp b/tests/auto/qquickcontrol/tst_qquickcontrol.cpp
index c8d34756..df3b31c8 100644
--- a/tests/auto/qquickcontrol/tst_qquickcontrol.cpp
+++ b/tests/auto/qquickcontrol/tst_qquickcontrol.cpp
@@ -98,9 +98,12 @@ void tst_QQuickControl::flickable()
QSignalSpy buttonClickedSpy(button, SIGNAL(clicked()));
QVERIFY(buttonClickedSpy.isValid());
- QTest::touchEvent(window, touchDevice.data()).press(0, QPoint(button->width() / 2, button->height() / 2));
+ QPoint p(button->width() / 2, button->height() / 2);
+ QTest::touchEvent(window, touchDevice.data()).press(0, p);
QTRY_COMPARE(buttonPressedSpy.count(), 1);
- QTest::touchEvent(window, touchDevice.data()).release(0, QPoint(button->width() / 2, button->height() / 2));
+ p += QPoint(1, 1); // less than the drag threshold
+ QTest::touchEvent(window, touchDevice.data()).move(0, p);
+ QTest::touchEvent(window, touchDevice.data()).release(0, p);
QTRY_COMPARE(buttonReleasedSpy.count(), 1);
QTRY_COMPARE(buttonClickedSpy.count(), 1);
}
diff --git a/tests/auto/qquickimaginestyle/data/tst_imagine.qml b/tests/auto/qquickimaginestyle/data/tst_imagine.qml
index b9078d78..f4ba1c71 100644
--- a/tests/auto/qquickimaginestyle/data/tst_imagine.qml
+++ b/tests/auto/qquickimaginestyle/data/tst_imagine.qml
@@ -153,4 +153,60 @@ TestCase {
// Shouldn't result in a crash.
afterRenderingSpy.wait(1000)
}
+
+ Component {
+ id: invalidNinePatchImageProvider
+ Item {
+ width: 200
+ height: 200
+ property alias ninePatchImage: np
+
+ NinePatchImage {
+ id: np
+ source : "qrc:/test-assets/button-background-1.png"
+ cache: false
+ visible: false
+ }
+ ShaderEffect {
+ width: 300
+ height: 300
+ property variant src: np
+ vertexShader: "
+ uniform highp mat4 qt_Matrix;
+ attribute highp vec4 qt_Vertex;
+ attribute highp vec2 qt_MultiTexCoord0;
+ varying highp vec2 coord;
+ void main() {
+ coord = qt_MultiTexCoord0;
+ gl_Position = qt_Matrix * qt_Vertex;
+ }"
+ fragmentShader: "
+ varying highp vec2 coord;
+ uniform sampler2D src;
+ uniform lowp float qt_Opacity;
+ void main() {
+ lowp vec4 tex = texture2D(src, coord);
+ gl_FragColor = vec4(vec3(dot(tex.rgb,
+ vec3(0.344, 0.5, 0.156))),
+ tex.a) * qt_Opacity;
+ }"
+ }
+ }
+ }
+
+ function test_invalidNinePatchImageProvide() {
+ var container = createTemporaryObject(invalidNinePatchImageProvider, testCase)
+ verify(container);
+
+ var afterRenderingSpy = signalSpyComponent.createObject(null,
+ { target: testCase.Window.window, signalName: "afterRendering" })
+ verify(afterRenderingSpy.valid)
+
+ afterRenderingSpy.wait(100)
+ container.ninePatchImage.source = ""
+ testCase.Window.window.update()
+ // Shouldn't result in a crash.
+ wait(10)
+ afterRenderingSpy.wait(100)
+ }
}
diff --git a/tests/auto/qquickpopup/BLACKLIST b/tests/auto/qquickpopup/BLACKLIST
new file mode 100644
index 00000000..9246d301
--- /dev/null
+++ b/tests/auto/qquickpopup/BLACKLIST
@@ -0,0 +1,8 @@
+# See qtbase/src/testlib/qtestblacklist.cpp for format
+
+# QTBUG-94251
+[closePolicy]
+opensuse-leap
+
+[cursorShape]
+opensuse-leap
diff --git a/tests/auto/qquickpopup/data/activeFocusAfterExit.qml b/tests/auto/qquickpopup/data/activeFocusAfterExit.qml
new file mode 100644
index 00000000..06b0c068
--- /dev/null
+++ b/tests/auto/qquickpopup/data/activeFocusAfterExit.qml
@@ -0,0 +1,79 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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 The Qt Company Ltd 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.12
+import QtQuick.Controls 2.12
+
+ApplicationWindow {
+ width: 400
+ height: 400
+
+ property alias popup1: popup1
+ property alias popup2: popup2
+ property alias popup3: popup3
+
+ Popup {
+ id: popup1
+ focus: true
+ z: 1
+ }
+
+ Popup {
+ id: popup2
+ focus: false
+ z: 2
+ }
+
+ Popup {
+ id: popup3
+ focus: true
+ z: 3
+ }
+}
diff --git a/tests/auto/qquickpopup/data/activeFocusOnDelayedEnter.qml b/tests/auto/qquickpopup/data/activeFocusOnDelayedEnter.qml
new file mode 100644
index 00000000..1ceea99c
--- /dev/null
+++ b/tests/auto/qquickpopup/data/activeFocusOnDelayedEnter.qml
@@ -0,0 +1,73 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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 The Qt Company Ltd 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.12
+import QtQuick.Controls 2.12
+
+ApplicationWindow {
+ width: 400
+ height: 400
+
+ property alias popup1: popup1
+ property alias popup2: popup2
+
+ Popup {
+ id: popup1
+ focus: true
+ enter: Transition {
+ NumberAnimation { property: "opacity"; from: 0.0; to: 1.0; duration: 100 }
+ }
+ }
+
+ Popup {
+ id: popup2
+ focus: true
+ }
+}
diff --git a/tests/auto/qquickpopup/data/modelessOnModalOnModeless.qml b/tests/auto/qquickpopup/data/modelessOnModalOnModeless.qml
new file mode 100644
index 00000000..7f05cb67
--- /dev/null
+++ b/tests/auto/qquickpopup/data/modelessOnModalOnModeless.qml
@@ -0,0 +1,98 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, 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 The Qt Company Ltd 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.13
+import QtQuick.Controls 2.13
+
+ApplicationWindow {
+ width: 400
+ height: 400
+
+ property alias modelessPopup: modelessPopup
+ property alias button: button
+ property alias modalPopup: modalPopup
+ property alias tooltip: tooltip
+
+ Popup {
+ id: modelessPopup
+ modal: false
+ closePolicy: Popup.NoAutoClose
+ width: 200
+ height: 200
+ anchors.centerIn: parent
+
+ Button {
+ id: button
+ checkable: true
+ x: 0
+ y: 0
+ text: "Click me"
+ }
+
+ Popup {
+ id: modalPopup
+ modal: true
+ closePolicy: Popup.NoAutoClose
+ width: 100
+ height: 100
+ anchors.centerIn: parent
+
+ Popup {
+ id: tooltip
+ modal: false
+ closePolicy: Popup.NoAutoClose
+ width: 50
+ height: 50
+ anchors.centerIn: parent
+ }
+ }
+ }
+
+}
diff --git a/tests/auto/qquickpopup/tst_qquickpopup.cpp b/tests/auto/qquickpopup/tst_qquickpopup.cpp
index ede3640c..54952d12 100644
--- a/tests/auto/qquickpopup/tst_qquickpopup.cpp
+++ b/tests/auto/qquickpopup/tst_qquickpopup.cpp
@@ -73,12 +73,15 @@ private slots:
void activeFocusOnClose2();
void activeFocusOnClose3();
void activeFocusOnClosingSeveralPopups();
+ void activeFocusAfterExit();
+ void activeFocusOnDelayedEnter();
void hover_data();
void hover();
void wheel_data();
void wheel();
void parentDestroyed();
void nested();
+ void modelessOnModalOnModeless();
void grabber();
void cursorShape();
void componentComplete();
@@ -728,6 +731,79 @@ void tst_QQuickPopup::activeFocusOnClosingSeveralPopups()
QTRY_COMPARE(button->hasActiveFocus(), true);
}
+void tst_QQuickPopup::activeFocusAfterExit()
+{
+ // Test that after closing a popup the highest one in z-order receives it instead.
+ QQuickApplicationHelper helper(this, QStringLiteral("activeFocusAfterExit.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+ QQuickApplicationWindow *window = helper.appWindow;
+ window->show();
+ window->requestActivate();
+ QVERIFY(QTest::qWaitForWindowActive(window));
+
+ QQuickPopup *popup1 = window->property("popup1").value<QQuickPopup*>();
+ QVERIFY(popup1);
+
+ QQuickPopup *popup2 = window->property("popup2").value<QQuickPopup*>();
+ QVERIFY(popup2);
+ QSignalSpy closedSpy2(popup2, SIGNAL(closed()));
+ QVERIFY(closedSpy2.isValid());
+
+ QQuickPopup *popup3 = window->property("popup3").value<QQuickPopup*>();
+ QVERIFY(popup3);
+ QSignalSpy closedSpy3(popup3, SIGNAL(closed()));
+ QVERIFY(closedSpy3.isValid());
+
+ popup1->open();
+ QVERIFY(popup1->isVisible());
+ QTRY_VERIFY(popup1->hasActiveFocus());
+
+ popup2->open();
+ QVERIFY(popup2->isVisible());
+ QTRY_VERIFY(!popup2->hasActiveFocus());
+
+ popup3->open();
+ QVERIFY(popup3->isVisible());
+ QTRY_VERIFY(popup3->hasActiveFocus());
+
+ popup3->close();
+ closedSpy3.wait();
+ QVERIFY(!popup3->isVisible());
+ QTRY_VERIFY(!popup3->hasActiveFocus());
+ QTRY_VERIFY(!popup2->hasActiveFocus());
+ QTRY_VERIFY(popup1->hasActiveFocus());
+
+ popup2->close();
+ closedSpy2.wait();
+ QVERIFY(!popup2->isVisible());
+ QTRY_VERIFY(!popup2->hasActiveFocus());
+ QTRY_VERIFY(popup1->hasActiveFocus());
+}
+
+void tst_QQuickPopup::activeFocusOnDelayedEnter()
+{
+ // Test that after opening two popups, first of which has an animation, does not cause
+ // the first one to receive focus after the animation stops.
+ QQuickApplicationHelper helper(this, QStringLiteral("activeFocusOnDelayedEnter.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+ QQuickApplicationWindow *window = helper.appWindow;
+ window->show();
+ window->requestActivate();
+ QVERIFY(QTest::qWaitForWindowActive(window));
+
+ QQuickPopup *popup1 = window->property("popup1").value<QQuickPopup*>();
+ QVERIFY(popup1);
+ QSignalSpy openedSpy(popup1, SIGNAL(opened()));
+
+ QQuickPopup *popup2 = window->property("popup2").value<QQuickPopup*>();
+ QVERIFY(popup2);
+
+ popup1->open();
+ popup2->open();
+ openedSpy.wait();
+ QTRY_VERIFY(popup2->hasActiveFocus());
+}
+
void tst_QQuickPopup::hover_data()
{
QTest::addColumn<QString>("source");
@@ -915,6 +991,46 @@ void tst_QQuickPopup::nested()
QCOMPARE(modalPopup->isVisible(), true);
}
+void tst_QQuickPopup::modelessOnModalOnModeless()
+{
+ QQuickApplicationHelper helper(this, QStringLiteral("modelessOnModalOnModeless.qml"));
+ QVERIFY2(helper.ready, helper.failureMessage());
+ QQuickWindow *window = helper.window;
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window));
+
+ QQuickPopup *modelessPopup = window->property("modelessPopup").value<QQuickPopup *>();
+ QVERIFY(modelessPopup);
+
+ QQuickButton *button = window->property("button").value<QQuickButton *>();
+ QVERIFY(button);
+ QQuickPopup *modalPopup = window->property("modalPopup").value<QQuickPopup *>();
+ QVERIFY(modalPopup);
+ QQuickPopup *tooltip = window->property("tooltip").value<QQuickPopup *>();
+ QVERIFY(modalPopup);
+
+ modelessPopup->open();
+ QCOMPARE(modelessPopup->isVisible(), true);
+ QTRY_COMPARE(modelessPopup->isOpened(), true);
+ const auto buttonPoint = button->mapToScene(button->boundingRect().center()).toPoint();
+ // click into the button, should not be blocked
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, buttonPoint);
+ QVERIFY(button->isChecked());
+ modalPopup->open();
+ QCOMPARE(modalPopup->isVisible(), true);
+ QTRY_COMPARE(modalPopup->isOpened(), true);
+ // click into the button, should be blocked
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, buttonPoint);
+ QVERIFY(button->isChecked());
+
+ tooltip->setVisible(true);
+ QCOMPARE(tooltip->isVisible(), true);
+ QTRY_COMPARE(tooltip->isOpened(), true);
+ // click into the button, should be blocked
+ QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, buttonPoint);
+ QVERIFY(button->isChecked());
+}
+
// QTBUG-56697
void tst_QQuickPopup::grabber()
{