aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.qmake.conf2
-rw-r--r--README.md2
-rw-r--r--dist/changes-5.15.242
-rw-r--r--src/imports/controls/DialogButtonBox.qml1
-rw-r--r--src/imports/controls/designer/AbstractButtonSection.qml4
-rw-r--r--src/imports/controls/designer/ButtonSection.qml12
-rw-r--r--src/imports/controls/designer/RangeSliderSpecifics.qml2
-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/GroupBox.qml3
-rw-r--r--src/imports/controls/imagine/qquickninepatchimage.cpp16
-rw-r--r--src/imports/controls/material/ComboBox.qml8
-rw-r--r--src/imports/controls/material/DialogButtonBox.qml1
-rw-r--r--src/imports/controls/material/material.pro2
-rw-r--r--src/imports/controls/material/qmldir2
-rw-r--r--src/imports/controls/universal/DialogButtonBox.qml1
-rw-r--r--src/imports/platform/qquickplatformfiledialog.cpp9
-rw-r--r--src/imports/platform/qquickplatformmenu.cpp7
-rw-r--r--src/imports/platform/qquickplatformmenuitem.cpp1
-rw-r--r--src/imports/platform/qquickplatformsystemtrayicon.cpp1
-rw-r--r--src/imports/templates/qtquicktemplates2plugin.cpp2
-rw-r--r--src/quickcontrols2/qquickiconimage.cpp7
-rw-r--r--src/quickcontrols2/qquickiconimage_p_p.h1
-rw-r--r--src/quicktemplates2/accessible/accessible.pri5
-rw-r--r--src/quicktemplates2/accessible/qaccessiblequickpage.cpp81
-rw-r--r--src/quicktemplates2/accessible/qaccessiblequickpage_p.h70
-rw-r--r--src/quicktemplates2/qquickabstractbutton.cpp36
-rw-r--r--src/quicktemplates2/qquickabstractbutton_p_p.h2
-rw-r--r--src/quicktemplates2/qquickaction.cpp3
-rw-r--r--src/quicktemplates2/qquickapplicationwindow.cpp7
-rw-r--r--src/quicktemplates2/qquickcombobox.cpp57
-rw-r--r--src/quicktemplates2/qquickcontrol.cpp48
-rw-r--r--src/quicktemplates2/qquickcontrol_p_p.h3
-rw-r--r--src/quicktemplates2/qquickdial.cpp33
-rw-r--r--src/quicktemplates2/qquickheaderview.cpp11
-rw-r--r--src/quicktemplates2/qquickmenu.cpp4
-rw-r--r--src/quicktemplates2/qquickoverlay.cpp9
-rw-r--r--src/quicktemplates2/qquickpage.cpp3
-rw-r--r--src/quicktemplates2/qquickpopup.cpp50
-rw-r--r--src/quicktemplates2/qquickpopup_p_p.h2
-rw-r--r--src/quicktemplates2/qquickrangeslider.cpp10
-rw-r--r--src/quicktemplates2/qquickscrollbar.cpp35
-rw-r--r--src/quicktemplates2/qquickscrollbar_p_p.h2
-rw-r--r--src/quicktemplates2/qquickscrollview.cpp6
-rw-r--r--src/quicktemplates2/qquickspinbox.cpp2
-rw-r--r--src/quicktemplates2/qquicksplitview.cpp2
-rw-r--r--src/quicktemplates2/qquickswipedelegate.cpp35
-rw-r--r--src/quicktemplates2/qquickswipedelegate_p_p.h1
-rw-r--r--src/quicktemplates2/qquickswipeview.cpp1
-rw-r--r--src/quicktemplates2/qquicktextarea.cpp10
-rw-r--r--src/quicktemplates2/qquicktumbler.cpp5
-rw-r--r--src/quicktemplates2/qtquicktemplates2global.cpp63
-rw-r--r--src/quicktemplates2/qtquicktemplates2global_p.h2
-rw-r--r--src/quicktemplates2/quicktemplates2.pro6
-rw-r--r--tests/auto/accessibility/accessibility.pro4
-rw-r--r--tests/auto/accessibility/data/ordering/page.qml23
-rw-r--r--tests/auto/accessibility/tst_accessibility.cpp30
-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_combobox.qml122
-rw-r--r--tests/auto/controls/data/tst_dial.qml15
-rw-r--r--tests/auto/controls/data/tst_dialogbuttonbox.qml84
-rw-r--r--tests/auto/controls/data/tst_rangeslider.qml52
-rw-r--r--tests/auto/controls/data/tst_scrollbar.qml30
-rw-r--r--tests/auto/controls/data/tst_scrollview.qml121
-rw-r--r--tests/auto/controls/data/tst_slider.qml47
-rw-r--r--tests/auto/controls/data/tst_spinbox.qml29
-rw-r--r--tests/auto/controls/data/tst_splitview.qml3
-rw-r--r--tests/auto/controls/data/tst_swipedelegate.qml75
-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/controls/data/tst_tumbler.qml21
-rw-r--r--tests/auto/qquickapplicationwindow/data/layoutLayout.qml65
-rw-r--r--tests/auto/qquickapplicationwindow/tst_qquickapplicationwindow.cpp37
-rw-r--r--tests/auto/qquickcontrol/tst_qquickcontrol.cpp7
-rw-r--r--tests/auto/qquickdrawer/BLACKLIST8
-rw-r--r--tests/auto/qquickheaderview/tst_qquickheaderview.cpp8
-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/destroyDuringExitTransition.qml114
-rw-r--r--tests/auto/qquickpopup/data/modelessOnModalOnModeless.qml98
-rw-r--r--tests/auto/qquickpopup/tst_qquickpopup.cpp146
84 files changed, 2117 insertions, 138 deletions
diff --git a/.qmake.conf b/.qmake.conf
index 4d40e427..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.2
+MODULE_VERSION = 5.15.13
diff --git a/README.md b/README.md
index d387da81..05f79852 100644
--- a/README.md
+++ b/README.md
@@ -39,7 +39,7 @@ If you have problems or questions, don't hesitate to:
## Installation
The MINIMUM REQUIREMENT for building this project is to use the same branch
-of Qt 5. The dependencies are *qtbase*, *qtxmlpatterns* and *qtdeclarative*.
+of Qt 5. The dependencies are *qtbase* and *qtdeclarative*.
To install the controls into your Qt directory (```QTDIR/qml```):
diff --git a/dist/changes-5.15.2 b/dist/changes-5.15.2
new file mode 100644
index 00000000..e71e2bf3
--- /dev/null
+++ b/dist/changes-5.15.2
@@ -0,0 +1,42 @@
+Qt 5.15.2 is a bug-fix release. It maintains both forward and backward
+compatibility (source and binary) with Qt 5.15.1.
+
+For more details, refer to the online documentation included in this
+distribution. The documentation is also available online:
+
+ https://doc.qt.io/qt-5.15/index.html
+
+The Qt version 5.15 series is binary compatible with the 5.14.x series.
+Applications compiled for 5.14 will continue to run with 5.15.
+
+Some of the changes listed in this file include issue tracking numbers
+corresponding to tasks in the Qt Bug Tracker:
+
+ https://bugreports.qt.io/
+
+Each of these identifiers can be entered in the bug tracker to obtain more
+information about a particular change.
+
+****************************************************************************
+* Controls *
+****************************************************************************
+
+ - [QTBUG-83698] Fixed issue where a menu opened by pressing enter (handled
+ via attached Keys property) would instantly trigger the first menu
+ item.
+ - [QTBUG-86851] QQuickMenu: fixed memory leak due to contentModel not being
+ deleted. Also fixed a heap-use-after-free that was exposed by the
+ previously mentioned fix.
+ - [QTBUG-85884] Fixed expected item not getting focus when a dialog closes.
+ - [QTBUG-85719] Fixed SpinBox failing to validate input correctly after
+ previous input was out of range.
+
+****************************************************************************
+* Styles *
+****************************************************************************
+
+Material
+--------
+
+ - [QTBUG-85699] Fixed binding loops when binding between attached
+ properties.
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/designer/AbstractButtonSection.qml b/src/imports/controls/designer/AbstractButtonSection.qml
index e8aa39c2..35fad2ab 100644
--- a/src/imports/controls/designer/AbstractButtonSection.qml
+++ b/src/imports/controls/designer/AbstractButtonSection.qml
@@ -104,8 +104,8 @@ Section {
}
Label {
- text: qsTr("Repeat")
- tooltip: qsTr("Whether the button repeats while pressed and held down.")
+ text: qsTr("Auto-Repeat")
+ tooltip: qsTr("Whether the button repeats pressed(), released() and clicked() signals while the button is pressed and held down.")
}
SecondColumnLayout {
CheckBox {
diff --git a/src/imports/controls/designer/ButtonSection.qml b/src/imports/controls/designer/ButtonSection.qml
index fef46071..951c8cf4 100644
--- a/src/imports/controls/designer/ButtonSection.qml
+++ b/src/imports/controls/designer/ButtonSection.qml
@@ -43,17 +43,7 @@ Section {
caption: qsTr("Button")
SectionLayout {
- Label {
- text: qsTr("AutoRepeat")
- tooltip: qsTr("Whether the button repeats pressed(), released() and clicked() signals while the button is pressed and held down.")
- }
- SecondColumnLayout {
- CheckBox {
- text: backendValues.autoRepeat.valueToString
- backendValue: backendValues.autoRepeat
- Layout.fillWidth: true
- }
- }
+
Label {
text: qsTr("Flat")
tooltip: qsTr("Whether the button is flat.")
diff --git a/src/imports/controls/designer/RangeSliderSpecifics.qml b/src/imports/controls/designer/RangeSliderSpecifics.qml
index 2324a66f..9372a4ff 100644
--- a/src/imports/controls/designer/RangeSliderSpecifics.qml
+++ b/src/imports/controls/designer/RangeSliderSpecifics.qml
@@ -127,7 +127,7 @@ Column {
}
SecondColumnLayout {
ComboBox {
- backendValue: backendValues.orientation
+ backendValue: backendValues.snapMode
model: [ "NoSnap", "SnapOnRelease", "SnapAlways" ]
scope: "RangeSlider"
Layout.fillWidth: true
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/GroupBox.qml b/src/imports/controls/imagine/GroupBox.qml
index 7abdb6f0..46f9c98a 100644
--- a/src/imports/controls/imagine/GroupBox.qml
+++ b/src/imports/controls/imagine/GroupBox.qml
@@ -53,7 +53,6 @@ T.GroupBox {
leftPadding: background ? background.leftPadding : 0
rightPadding: background ? background.rightPadding : 0
bottomPadding: background ? background.bottomPadding : 0
- padding: 12
label: Label {
width: control.width
@@ -88,7 +87,7 @@ T.GroupBox {
x: -leftInset
y: control.topPadding - control.bottomPadding - topInset
width: control.width + leftInset + rightInset
- height: control.height + topInset + bottomInset - control.topPadding + control.padding
+ height: control.height + topInset + bottomInset - control.topPadding + control.bottomPadding
source: Imagine.url + "groupbox-background"
NinePatchImageSelector on source {
diff --git a/src/imports/controls/imagine/qquickninepatchimage.cpp b/src/imports/controls/imagine/qquickninepatchimage.cpp
index 7d5e4f71..ed388ec1 100644
--- a/src/imports/controls/imagine/qquickninepatchimage.cpp
+++ b/src/imports/controls/imagine/qquickninepatchimage.cpp
@@ -386,7 +386,12 @@ void QQuickNinePatchImage::pixmapChange()
{
Q_D(QQuickNinePatchImage);
if (QFileInfo(d->url.fileName()).completeSuffix().toLower() == QLatin1String("9.png")) {
- d->resetNode = d->ninePatch.isNull();
+ // Keep resetNode if it is already set, we do not want to miss an
+ // ImageNode->NinePatchNode change. Without this there's a chance one gets
+ // an incorrect cast on oldNode every once in a while with source changes.
+ if (!d->resetNode)
+ d->resetNode = d->ninePatch.isNull();
+
d->ninePatch = d->pix.image();
if (d->ninePatch.depth() != 32)
d->ninePatch = d->ninePatch.convertToFormat(QImage::Format_ARGB32);
@@ -434,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;
}
@@ -449,6 +456,13 @@ QSGNode *QQuickNinePatchImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNode
qsgnode_set_description(patchNode, QString::fromLatin1("QQuickNinePatchImage: '%1'").arg(d->url.toString()));
#endif
+ // The image may wrap non-owned data (due to pixmapChange). Ensure we never
+ // pass such an image to the scenegraph, because with a separate render
+ // thread the data may become invalid (in a subsequent pixmapChange on the
+ // gui thread) by the time the renderer gets to do something with the QImage
+ // passed in here.
+ image.detach();
+
QSGTexture *texture = window()->createTextureFromImage(image);
patchNode->initialize(texture, sz * d->devicePixelRatio, image.size(), d->xDivs, d->yDivs, d->devicePixelRatio);
return patchNode;
diff --git a/src/imports/controls/material/ComboBox.qml b/src/imports/controls/material/ComboBox.qml
index a9bdd934..6aada8c5 100644
--- a/src/imports/controls/material/ComboBox.qml
+++ b/src/imports/controls/material/ComboBox.qml
@@ -147,14 +147,14 @@ T.ComboBox {
enter: Transition {
// grow_fade_in
- NumberAnimation { property: "scale"; from: 0.9; to: 1.0; easing.type: Easing.OutQuint; duration: 220 }
- NumberAnimation { property: "opacity"; from: 0.0; to: 1.0; easing.type: Easing.OutCubic; duration: 150 }
+ NumberAnimation { property: "scale"; from: 0.9; easing.type: Easing.OutQuint; duration: 220 }
+ NumberAnimation { property: "opacity"; from: 0.0; easing.type: Easing.OutCubic; duration: 150 }
}
exit: Transition {
// shrink_fade_out
- NumberAnimation { property: "scale"; from: 1.0; to: 0.9; easing.type: Easing.OutQuint; duration: 220 }
- NumberAnimation { property: "opacity"; from: 1.0; to: 0.0; easing.type: Easing.OutCubic; duration: 150 }
+ NumberAnimation { property: "scale"; to: 0.9; easing.type: Easing.OutQuint; duration: 220 }
+ NumberAnimation { property: "opacity"; to: 0.0; easing.type: Easing.OutCubic; duration: 150 }
}
contentItem: ListView {
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/material/material.pro b/src/imports/controls/material/material.pro
index cf08b925..ea74d277 100644
--- a/src/imports/controls/material/material.pro
+++ b/src/imports/controls/material/material.pro
@@ -1,4 +1,4 @@
-TARGET = qtquickcontrols2materialstyleplugin
+TARGET = qqc2materialstyleplugin
TARGETPATH = QtQuick/Controls.2/Material
IMPORT_NAME = QtQuick.Controls.Material
diff --git a/src/imports/controls/material/qmldir b/src/imports/controls/material/qmldir
index 870a0382..d48b7b12 100644
--- a/src/imports/controls/material/qmldir
+++ b/src/imports/controls/material/qmldir
@@ -1,4 +1,4 @@
module QtQuick.Controls.Material
-plugin qtquickcontrols2materialstyleplugin
+plugin qqc2materialstyleplugin
classname QtQuickControls2MaterialStylePlugin
depends QtQuick.Controls 2.5
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 2ef08ef6..af5475fa 100644
--- a/src/imports/platform/qquickplatformfiledialog.cpp
+++ b/src/imports/platform/qquickplatformfiledialog.cpp
@@ -353,7 +353,6 @@ void QQuickPlatformFileDialog::resetNameFilters()
}
/*!
- \qmlpropertygroup Qt.labs.platform::FileDialog::selectedNameFilter
\qmlproperty int Qt.labs.platform::FileDialog::selectedNameFilter.index
\qmlproperty string Qt.labs.platform::FileDialog::selectedNameFilter.name
\qmlproperty list<string> Qt.labs.platform::FileDialog::selectedNameFilter.extensions
@@ -553,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/imports/platform/qquickplatformmenu.cpp b/src/imports/platform/qquickplatformmenu.cpp
index 1f5f52d7..b5289f33 100644
--- a/src/imports/platform/qquickplatformmenu.cpp
+++ b/src/imports/platform/qquickplatformmenu.cpp
@@ -610,10 +610,9 @@ void QQuickPlatformMenu::setFont(const QFont& font)
/*!
\since Qt.labs.platform 1.1 (Qt 5.12)
- \qmlpropertygroup Qt.labs.platform::MenuItem::icon
- \qmlproperty url Qt.labs.platform::MenuItem::icon.source
- \qmlproperty string Qt.labs.platform::MenuItem::icon.name
- \qmlproperty bool Qt.labs.platform::MenuItem::icon.mask
+ \qmlproperty url Qt.labs.platform::Menu::icon.source
+ \qmlproperty string Qt.labs.platform::Menu::icon.name
+ \qmlproperty bool Qt.labs.platform::Menu::icon.mask
This property holds the menu item's icon.
*/
diff --git a/src/imports/platform/qquickplatformmenuitem.cpp b/src/imports/platform/qquickplatformmenuitem.cpp
index 72ad7d5d..5a8b3798 100644
--- a/src/imports/platform/qquickplatformmenuitem.cpp
+++ b/src/imports/platform/qquickplatformmenuitem.cpp
@@ -592,7 +592,6 @@ void QQuickPlatformMenuItem::setFont(const QFont& font)
/*!
\since Qt.labs.platform 1.1 (Qt 5.12)
- \qmlpropertygroup Qt.labs.platform::MenuItem::icon
\qmlproperty url Qt.labs.platform::MenuItem::icon.source
\qmlproperty string Qt.labs.platform::MenuItem::icon.name
\qmlproperty bool Qt.labs.platform::MenuItem::icon.mask
diff --git a/src/imports/platform/qquickplatformsystemtrayicon.cpp b/src/imports/platform/qquickplatformsystemtrayicon.cpp
index da483822..442da037 100644
--- a/src/imports/platform/qquickplatformsystemtrayicon.cpp
+++ b/src/imports/platform/qquickplatformsystemtrayicon.cpp
@@ -356,7 +356,6 @@ QRect QQuickPlatformSystemTrayIcon::geometry() const
/*!
\since Qt.labs.platform 1.1 (Qt 5.12)
- \qmlpropertygroup Qt.labs.platform::SystemTrayIcon::icon
\qmlproperty url Qt.labs.platform::SystemTrayIcon::icon.source
\qmlproperty string Qt.labs.platform::SystemTrayIcon::icon.name
\qmlproperty bool Qt.labs.platform::SystemTrayIcon::icon.mask
diff --git a/src/imports/templates/qtquicktemplates2plugin.cpp b/src/imports/templates/qtquicktemplates2plugin.cpp
index 3b085c56..cee8e53d 100644
--- a/src/imports/templates/qtquicktemplates2plugin.cpp
+++ b/src/imports/templates/qtquicktemplates2plugin.cpp
@@ -142,6 +142,8 @@ private:
QtQuickTemplates2Plugin::QtQuickTemplates2Plugin(QObject *parent)
: QQmlExtensionPlugin(parent), registered(false)
{
+ volatile auto initialization = &QQuickTemplates_initializeModule;
+ Q_UNUSED(initialization)
#if QT_CONFIG(shortcut)
originalContextMatcher = qt_quick_shortcut_context_matcher();
qt_quick_set_shortcut_context_matcher(QQuickShortcutContext::matcher);
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/accessible/accessible.pri b/src/quicktemplates2/accessible/accessible.pri
new file mode 100644
index 00000000..93660b9f
--- /dev/null
+++ b/src/quicktemplates2/accessible/accessible.pri
@@ -0,0 +1,5 @@
+HEADERS += \
+ $$PWD/qaccessiblequickpage_p.h
+
+SOURCES += \
+ $$PWD/qaccessiblequickpage.cpp
diff --git a/src/quicktemplates2/accessible/qaccessiblequickpage.cpp b/src/quicktemplates2/accessible/qaccessiblequickpage.cpp
new file mode 100644
index 00000000..90ac49f9
--- /dev/null
+++ b/src/quicktemplates2/accessible/qaccessiblequickpage.cpp
@@ -0,0 +1,81 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** 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 http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qaccessiblequickpage_p.h"
+#include "qquickpage_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QAccessibleQuickPage::QAccessibleQuickPage(QQuickPage *page)
+ : QAccessibleQuickItem(page)
+{
+}
+
+QAccessibleInterface *QAccessibleQuickPage::child(int index) const
+{
+ const QList<QQuickItem*> kids = orderedChildItems();
+ if (QQuickItem *item = kids.value(index))
+ return QAccessible::queryAccessibleInterface(item);
+ return nullptr;
+}
+
+int QAccessibleQuickPage::indexOfChild(const QAccessibleInterface *iface) const
+{
+ const QList<QQuickItem*> kids = orderedChildItems();
+ return (int)kids.indexOf(static_cast<QQuickItem*>(iface->object()));
+}
+
+QList<QQuickItem *> QAccessibleQuickPage::orderedChildItems() const
+{
+ // Just ensures that the header is first, and footer is last. Other existing order is kept.
+ const QQuickPage *p = page();
+ QList<QQuickItem*> kids = childItems();
+ const qsizetype hidx = kids.indexOf(p->header());
+ if (hidx != -1)
+ kids.move(hidx, 0);
+ const qsizetype fidx = kids.indexOf(p->footer());
+ if (fidx != -1)
+ kids.move(fidx, kids.count() - 1);
+ return kids;
+}
+
+QQuickPage *QAccessibleQuickPage::page() const
+{
+ return static_cast<QQuickPage*>(object());
+}
+
+QT_END_NAMESPACE
+
diff --git a/src/quicktemplates2/accessible/qaccessiblequickpage_p.h b/src/quicktemplates2/accessible/qaccessiblequickpage_p.h
new file mode 100644
index 00000000..9b208c14
--- /dev/null
+++ b/src/quicktemplates2/accessible/qaccessiblequickpage_p.h
@@ -0,0 +1,70 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** 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 http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QACCESSIBLEQUICKPAGE_H
+#define QACCESSIBLEQUICKPAGE_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtQuick/private/qaccessiblequickitem_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQuickPage;
+
+class QAccessibleQuickPage : public QAccessibleQuickItem
+{
+public:
+ QAccessibleQuickPage(QQuickPage *page);
+ QAccessibleInterface *child(int index) const override;
+ int indexOfChild(const QAccessibleInterface *iface) const override;
+private:
+ QQuickPage *page() const;
+ QList<QQuickItem *> orderedChildItems() const;
+};
+
+QT_END_NAMESPACE
+
+#endif // QACCESSIBLEQUICKPAGE_H
diff --git a/src/quicktemplates2/qquickabstractbutton.cpp b/src/quicktemplates2/qquickabstractbutton.cpp
index fc37b6f6..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);
@@ -736,7 +759,6 @@ void QQuickAbstractButton::setIndicator(QQuickItem *indicator)
}
/*!
- \qmlpropertygroup QtQuick.Controls::AbstractButton::icon
\qmlproperty string QtQuick.Controls::AbstractButton::icon.name
\qmlproperty url QtQuick.Controls::AbstractButton::icon.source
\qmlproperty int QtQuick.Controls::AbstractButton::icon.width
@@ -1087,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 0dab3b97..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
@@ -381,7 +381,6 @@ void QQuickAction::setText(const QString &text)
}
/*!
- \qmlpropertygroup QtQuick.Controls::Action::icon
\qmlproperty string QtQuick.Controls::Action::icon.name
\qmlproperty url QtQuick.Controls::Action::icon.source
\qmlproperty int QtQuick.Controls::Action::icon.width
diff --git a/src/quicktemplates2/qquickapplicationwindow.cpp b/src/quicktemplates2/qquickapplicationwindow.cpp
index 903de676..a9a4ad72 100644
--- a/src/quicktemplates2/qquickapplicationwindow.cpp
+++ b/src/quicktemplates2/qquickapplicationwindow.cpp
@@ -48,6 +48,7 @@
#include "qquickdeferredpointer_p_p.h"
#include <QtCore/private/qobject_p.h>
+#include <QtCore/qscopedvaluerollback.h>
#include <QtQuick/private/qquickitem_p.h>
#include <QtQuick/private/qquickitemchangelistener_p.h>
@@ -174,6 +175,7 @@ public:
QPalette palette;
QQuickItem *activeFocusControl = nullptr;
QQuickApplicationWindow *q_ptr = nullptr;
+ bool insideRelayout = false;
};
static void layoutItem(QQuickItem *item, qreal y, qreal width)
@@ -192,9 +194,10 @@ static void layoutItem(QQuickItem *item, qreal y, qreal width)
void QQuickApplicationWindowPrivate::relayout()
{
Q_Q(QQuickApplicationWindow);
- if (!complete)
+ if (!complete || insideRelayout)
return;
+ QScopedValueRollback<bool> guard(insideRelayout, true);
QQuickItem *content = q->contentItem();
qreal hh = header && header->isVisible() ? header->height() : 0;
qreal fh = footer && footer->isVisible() ? footer->height() : 0;
@@ -650,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/qquickcombobox.cpp b/src/quicktemplates2/qquickcombobox.cpp
index 3b87e489..e7d99a5d 100644
--- a/src/quicktemplates2/qquickcombobox.cpp
+++ b/src/quicktemplates2/qquickcombobox.cpp
@@ -234,6 +234,7 @@ public:
void updateCurrentText();
void updateCurrentValue();
void updateCurrentTextAndValue();
+ void updateAcceptableInput();
bool isValidIndex(int index) const;
@@ -265,6 +266,8 @@ public:
void itemImplicitWidthChanged(QQuickItem *item) override;
void itemImplicitHeightChanged(QQuickItem *item) override;
+ void setInputMethodHints(Qt::InputMethodHints hints, bool force = false);
+
static void hideOldPopup(QQuickPopup *popup);
bool flat = false;
@@ -288,6 +291,7 @@ public:
QQmlComponent *delegate = nullptr;
QQuickDeferredPointer<QQuickItem> indicator;
QQuickDeferredPointer<QQuickPopup> popup;
+ bool m_acceptableInput = true;
struct ExtraData {
bool editable = false;
@@ -476,6 +480,26 @@ void QQuickComboBoxPrivate::updateCurrentTextAndValue()
updateCurrentValue();
}
+void QQuickComboBoxPrivate::updateAcceptableInput()
+{
+ Q_Q(QQuickComboBox);
+
+ if (!contentItem)
+ return;
+
+ const QQuickTextInput *textInputContentItem = qobject_cast<QQuickTextInput *>(contentItem);
+
+ if (!textInputContentItem)
+ return;
+
+ const bool newValue = textInputContentItem->hasAcceptableInput();
+
+ if (m_acceptableInput != newValue) {
+ m_acceptableInput = newValue;
+ emit q->acceptableInputChanged();
+ }
+}
+
bool QQuickComboBoxPrivate::isValidIndex(int index) const
{
return delegateModel && index >= 0 && index < delegateModel->count();
@@ -770,6 +794,16 @@ void QQuickComboBoxPrivate::itemImplicitWidthChanged(QQuickItem *item)
emit q->implicitIndicatorWidthChanged();
}
+void QQuickComboBoxPrivate::setInputMethodHints(Qt::InputMethodHints hints, bool force)
+{
+ Q_Q(QQuickComboBox);
+ if (!force && hints == q->inputMethodHints())
+ return;
+
+ extra.value().inputMethodHints = hints;
+ emit q->inputMethodHintsChanged();
+}
+
void QQuickComboBoxPrivate::itemImplicitHeightChanged(QQuickItem *item)
{
Q_Q(QQuickComboBox);
@@ -804,7 +838,8 @@ QQuickComboBox::QQuickComboBox(QQuickItem *parent)
#if QT_CONFIG(cursor)
setCursor(Qt::ArrowCursor);
#endif
- setInputMethodHints(Qt::ImhNoPredictiveText);
+ Q_D(QQuickComboBox);
+ d->setInputMethodHints(Qt::ImhNoPredictiveText, true);
}
QQuickComboBox::~QQuickComboBox()
@@ -1437,11 +1472,7 @@ Qt::InputMethodHints QQuickComboBox::inputMethodHints() const
void QQuickComboBox::setInputMethodHints(Qt::InputMethodHints hints)
{
Q_D(QQuickComboBox);
- if (hints == inputMethodHints())
- return;
-
- d->extra.value().inputMethodHints = hints;
- emit inputMethodHintsChanged();
+ d->setInputMethodHints(hints);
}
/*!
@@ -1476,7 +1507,7 @@ bool QQuickComboBox::isInputMethodComposing() const
bool QQuickComboBox::hasAcceptableInput() const
{
Q_D(const QQuickComboBox);
- return d->contentItem && d->contentItem->property("acceptableInput").toBool();
+ return d->m_acceptableInput;
}
/*!
@@ -1731,7 +1762,11 @@ void QQuickComboBox::focusInEvent(QFocusEvent *event)
{
Q_D(QQuickComboBox);
QQuickControl::focusInEvent(event);
- if (d->contentItem && isEditable())
+ // Setting focus on TextField should not be done when drop down indicator was clicked
+ // That is why, if focus is not set with key reason, it should not be passed to textEdit by default.
+ // Focus on Edit Text should be set only intentionally by user.
+ if ((event->reason() == Qt::TabFocusReason || event->reason() == Qt::BacktabFocusReason ||
+ event->reason() == Qt::ShortcutFocusReason) && d->contentItem && isEditable())
d->contentItem->forceActiveFocus(event->reason());
}
@@ -1913,7 +1948,7 @@ void QQuickComboBox::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem)
QObjectPrivate::disconnect(oldInput, &QQuickTextInput::accepted, d, &QQuickComboBoxPrivate::acceptInput);
QObjectPrivate::disconnect(oldInput, &QQuickTextInput::textChanged, d, &QQuickComboBoxPrivate::updateEditText);
disconnect(oldInput, &QQuickTextInput::inputMethodComposingChanged, this, &QQuickComboBox::inputMethodComposingChanged);
- disconnect(oldInput, &QQuickTextInput::acceptableInputChanged, this, &QQuickComboBox::acceptableInputChanged);
+ QObjectPrivate::disconnect(oldInput, &QQuickTextInput::acceptableInputChanged, d, &QQuickComboBoxPrivate::updateAcceptableInput);
}
}
if (newItem && isEditable()) {
@@ -1922,12 +1957,14 @@ void QQuickComboBox::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem)
QObjectPrivate::connect(newInput, &QQuickTextInput::accepted, d, &QQuickComboBoxPrivate::acceptInput);
QObjectPrivate::connect(newInput, &QQuickTextInput::textChanged, d, &QQuickComboBoxPrivate::updateEditText);
connect(newInput, &QQuickTextInput::inputMethodComposingChanged, this, &QQuickComboBox::inputMethodComposingChanged);
- connect(newInput, &QQuickTextInput::acceptableInputChanged, this, &QQuickComboBox::acceptableInputChanged);
+ QObjectPrivate::connect(newInput, &QQuickTextInput::acceptableInputChanged, d, &QQuickComboBoxPrivate::updateAcceptableInput);
}
#if QT_CONFIG(cursor)
newItem->setCursor(Qt::IBeamCursor);
#endif
}
+
+ d->updateAcceptableInput();
}
void QQuickComboBox::localeChange(const QLocale &newLocale, const QLocale &oldLocale)
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 fa06c97f..a6e624c9 100644
--- a/src/quicktemplates2/qquickcontrol_p_p.h
+++ b/src/quicktemplates2/qquickcontrol_p_p.h
@@ -122,7 +122,7 @@ public:
void setRightInset(qreal value, bool reset = false);
void setBottomInset(qreal value, bool reset = false);
- void resizeBackground();
+ virtual void resizeBackground();
virtual void resizeContent();
virtual QQuickItem *getContentItem();
@@ -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/qquickdial.cpp b/src/quicktemplates2/qquickdial.cpp
index b64d8d6e..906f952a 100644
--- a/src/quicktemplates2/qquickdial.cpp
+++ b/src/quicktemplates2/qquickdial.cpp
@@ -41,6 +41,8 @@
#include <QtQuick/private/qquickflickable_p.h>
#include <QtQuickTemplates2/private/qquickcontrol_p_p.h>
+#include <cmath>
+
QT_BEGIN_NAMESPACE
/*!
@@ -116,25 +118,37 @@ public:
void cancelHandle();
void executeHandle(bool complete = false);
+ void updateAllValuesAreInteger();
+
qreal from = 0;
qreal to = 1;
qreal value = 0;
qreal position = 0;
qreal angle = startAngle;
qreal stepSize = 0;
- bool pressed = false;
QPointF pressPoint;
qreal positionBeforePress = 0;
QQuickDial::SnapMode snapMode = QQuickDial::NoSnap;
QQuickDial::InputMode inputMode = QQuickDial::Circular;
+ QQuickDeferredPointer<QQuickItem> handle;
bool wrap = false;
bool live = true;
- QQuickDeferredPointer<QQuickItem> handle;
+ bool pressed = false;
+ bool allValuesAreInteger = false;
};
qreal QQuickDialPrivate::valueAt(qreal position) const
{
- return from + (to - from) * position;
+ qreal value = from + (to - from) * position;
+
+ /* play nice with users expecting that integer from, to and stepSize leads to
+ integer values - given that we are using floating point internally (and in
+ the API of value), this does not hold, but it is easy enough to handle
+ */
+ if (allValuesAreInteger)
+ value = qRound(value);
+
+ return value;
}
qreal QQuickDialPrivate::snapPosition(qreal position) const
@@ -308,6 +322,16 @@ void QQuickDialPrivate::executeHandle(bool complete)
quickCompleteDeferred(q, handleName(), handle);
}
+static bool areRepresentableAsInteger(qreal num1, qreal num2, qreal num3) {
+ auto check = [](qreal number) -> bool { return std::nearbyint(number) == number; };
+ return check(num1) && check(num2) && check(num3);
+}
+
+void QQuickDialPrivate::updateAllValuesAreInteger()
+{
+ allValuesAreInteger = areRepresentableAsInteger(to, from, stepSize) && stepSize != 0.0;
+}
+
QQuickDial::QQuickDial(QQuickItem *parent)
: QQuickControl(*(new QQuickDialPrivate), parent)
{
@@ -342,6 +366,7 @@ void QQuickDial::setFrom(qreal from)
d->from = from;
emit fromChanged();
+ d->updateAllValuesAreInteger();
if (isComponentComplete()) {
setValue(d->value);
d->updatePosition();
@@ -369,6 +394,7 @@ void QQuickDial::setTo(qreal to)
return;
d->to = to;
+ d->updateAllValuesAreInteger();
emit toChanged();
if (isComponentComplete()) {
setValue(d->value);
@@ -468,6 +494,7 @@ void QQuickDial::setStepSize(qreal step)
return;
d->stepSize = step;
+ d->updateAllValuesAreInteger();
emit stepSizeChanged();
}
diff --git a/src/quicktemplates2/qquickheaderview.cpp b/src/quicktemplates2/qquickheaderview.cpp
index 85291bcb..d99c09eb 100644
--- a/src/quicktemplates2/qquickheaderview.cpp
+++ b/src/quicktemplates2/qquickheaderview.cpp
@@ -364,18 +364,22 @@ QModelIndex QHeaderDataProxyModel::parent(const QModelIndex &child) const
return QModelIndex();
}
-QModelIndex QHeaderDataProxyModel::sibling(int row, int column, const QModelIndex &idx) const
+QModelIndex QHeaderDataProxyModel::sibling(int row, int column, const QModelIndex &) const
{
- return index(row, column, idx);
+ return index(row, column);
}
int QHeaderDataProxyModel::rowCount(const QModelIndex &parent) const
{
+ if (parent.isValid())
+ return 0;
return m_model.isNull() ? -1 : (m_orientation == Qt::Horizontal ? 1 : m_model->rowCount(parent));
}
int QHeaderDataProxyModel::columnCount(const QModelIndex &parent) const
{
+ if (parent.isValid())
+ return 0;
return m_model.isNull() ? -1 : (m_orientation == Qt::Vertical ? 1 : m_model->columnCount(parent));
}
@@ -401,7 +405,8 @@ bool QHeaderDataProxyModel::setData(const QModelIndex &index, const QVariant &va
bool QHeaderDataProxyModel::hasChildren(const QModelIndex &parent) const
{
- Q_UNUSED(parent)
+ if (!parent.isValid())
+ return rowCount(parent) > 0 && columnCount(parent) > 0;
return false;
}
diff --git a/src/quicktemplates2/qquickmenu.cpp b/src/quicktemplates2/qquickmenu.cpp
index 9fd63587..0043dddd 100644
--- a/src/quicktemplates2/qquickmenu.cpp
+++ b/src/quicktemplates2/qquickmenu.cpp
@@ -295,8 +295,8 @@ QQuickItem *QQuickMenuPrivate::beginCreateItem()
QQuickItem *item = qobject_cast<QQuickItem *>(object);
if (!item)
delete object;
-
- QQml_setParent_noEvent(item, q);
+ else
+ QQml_setParent_noEvent(item, q);
return item;
}
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/qquickpage.cpp b/src/quicktemplates2/qquickpage.cpp
index 0a72bad7..bc27740b 100644
--- a/src/quicktemplates2/qquickpage.cpp
+++ b/src/quicktemplates2/qquickpage.cpp
@@ -244,6 +244,9 @@ QQuickPage::~QQuickPage()
The title is often displayed at the top of a page to give
the user context about the page they are viewing.
+ Page does not render the title itself, but instead relies
+ on the application to do so. For example:
+
\code
ApplicationWindow {
visible: true
diff --git a/src/quicktemplates2/qquickpopup.cpp b/src/quicktemplates2/qquickpopup.cpp
index 1a7731f4..7df80a04 100644
--- a/src/quicktemplates2/qquickpopup.cpp
+++ b/src/quicktemplates2/qquickpopup.cpp
@@ -51,6 +51,8 @@
QT_BEGIN_NAMESPACE
+Q_LOGGING_CATEGORY(lcDimmer, "qt.quick.controls.popup.dimmer")
+
/*!
\qmltype Popup
\inherits QtObject
@@ -215,6 +217,19 @@ QT_BEGIN_NAMESPACE
To ensure that the popup is positioned within the bounds of the enclosing
window, the \l margins property can be set to a non-negative value.
+ \section1 Popup Transitions
+
+ Since Qt 5.15.3 the following properties are restored to their original values from before
+ the enter transition after the exit transition is completed.
+
+ \list
+ \li \l opacity
+ \li \l scale
+ \endlist
+
+ This allows the built-in styles to animate on these properties without losing any explicitly
+ defined value.
+
\sa {Popup Controls}, {Customizing Popup}, ApplicationWindow
*/
@@ -441,6 +456,9 @@ bool QQuickPopupPrivate::prepareEnterTransition()
popupItem->setVisible(true);
getPositioner()->setParentItem(parentItem);
emit q->visibleChanged();
+
+ if (focus)
+ popupItem->setFocus(true);
}
return true;
}
@@ -451,6 +469,11 @@ bool QQuickPopupPrivate::prepareExitTransition()
if (transitionState == ExitTransition && transitionManager.isRunning())
return false;
+ // We need to cache the original scale and opacity values so we can reset it after
+ // the exit transition is done so they have the original values again
+ prevScale = popupItem->scale();
+ prevOpacity = popupItem->opacity();
+
if (transitionState != ExitTransition) {
// The setFocus(false) call below removes any active focus before we're
// able to check it in finalizeExitTransition.
@@ -469,8 +492,6 @@ bool QQuickPopupPrivate::prepareExitTransition()
void QQuickPopupPrivate::finalizeEnterTransition()
{
Q_Q(QQuickPopup);
- if (focus)
- popupItem->setFocus(true);
transitionState = NoTransition;
getPositioner()->reposition();
emit q->openedChanged();
@@ -481,8 +502,10 @@ void QQuickPopupPrivate::finalizeExitTransition()
{
Q_Q(QQuickPopup);
getPositioner()->setParentItem(nullptr);
- popupItem->setParentItem(nullptr);
- popupItem->setVisible(false);
+ if (popupItem) {
+ popupItem->setParentItem(nullptr);
+ popupItem->setVisible(false);
+ }
destroyOverlay();
if (hadActiveFocusBeforeExitTransition && window) {
@@ -491,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);
@@ -513,6 +537,10 @@ void QQuickPopupPrivate::finalizeExitTransition()
hadActiveFocusBeforeExitTransition = false;
emit q->visibleChanged();
emit q->closed();
+ if (popupItem) {
+ popupItem->setScale(prevScale);
+ popupItem->setOpacity(prevOpacity);
+ }
}
QMarginsF QQuickPopupPrivate::getMargins() const
@@ -575,7 +603,6 @@ void QQuickPopupPrivate::setBottomMargin(qreal value, bool reset)
/*!
\since QtQuick.Controls 2.5 (Qt 5.12)
- \qmlpropertygroup QtQuick.Controls::Popup::anchors
\qmlproperty Object QtQuick.Controls::Popup::anchors.centerIn
Anchors provide a way to position an item by specifying its
@@ -703,6 +730,8 @@ static QQuickItem *createDimmer(QQmlComponent *component, QQuickPopup *popup, QQ
if (component)
component->completeCreate();
}
+ qCDebug(lcDimmer) << "finished creating dimmer from component" << component
+ << "for popup" << popup << "with parent" << parent << "- item is:" << item;
return item;
}
@@ -729,6 +758,7 @@ void QQuickPopupPrivate::createOverlay()
void QQuickPopupPrivate::destroyOverlay()
{
if (dimmer) {
+ qCDebug(lcDimmer) << "destroying dimmer" << dimmer;
dimmer->setParentItem(nullptr);
dimmer->deleteLater();
dimmer = nullptr;
@@ -827,6 +857,12 @@ QQuickPopup::~QQuickPopup()
d->popupItem = nullptr;
delete d->positioner;
d->positioner = nullptr;
+
+ // If the popup is destroyed before the exit transition finishes,
+ // the necessary cleanup (removing modal dimmers that block mouse events,
+ // emitting closed signal, etc.) won't happen. That's why we do it manually here.
+ if (d->transitionState == QQuickPopupPrivate::ExitTransition && d->transitionManager.isRunning())
+ d->finalizeExitTransition();
}
/*!
diff --git a/src/quicktemplates2/qquickpopup_p_p.h b/src/quicktemplates2/qquickpopup_p_p.h
index 8a85f914..ef4b112e 100644
--- a/src/quicktemplates2/qquickpopup_p_p.h
+++ b/src/quicktemplates2/qquickpopup_p_p.h
@@ -196,6 +196,8 @@ public:
QList<QQuickStateAction> exitActions;
QQuickPopupTransitionManager transitionManager;
QQuickPopupAnchors *anchors = nullptr;
+ qreal prevOpacity = 0;
+ qreal prevScale = 0;
friend class QQuickPopupTransitionManager;
};
diff --git a/src/quicktemplates2/qquickrangeslider.cpp b/src/quicktemplates2/qquickrangeslider.cpp
index 3702c971..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();
}
}
@@ -746,7 +754,6 @@ qreal QQuickRangeSlider::valueAt(qreal position) const
}
/*!
- \qmlpropertygroup QtQuick.Controls::RangeSlider::first
\qmlproperty real QtQuick.Controls::RangeSlider::first.value
\qmlproperty real QtQuick.Controls::RangeSlider::first.position
\qmlproperty real QtQuick.Controls::RangeSlider::first.visualPosition
@@ -825,7 +832,6 @@ QQuickRangeSliderNode *QQuickRangeSlider::first() const
*/
/*!
- \qmlpropertygroup QtQuick.Controls::RangeSlider::second
\qmlproperty real QtQuick.Controls::RangeSlider::second.value
\qmlproperty real QtQuick.Controls::RangeSlider::second.position
\qmlproperty real QtQuick.Controls::RangeSlider::second.visualPosition
diff --git a/src/quicktemplates2/qquickscrollbar.cpp b/src/quicktemplates2/qquickscrollbar.cpp
index 4733d665..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());
@@ -827,6 +838,16 @@ void QQuickScrollBarAttachedPrivate::cleanupHorizontal()
{
Q_ASSERT(flickable && horizontal);
+ QQuickControlPrivate::hideOldItem(horizontal);
+ // ScrollBar.qml has a binding to visible and ScrollView.qml has a binding to parent.
+ // If we just set visible to false and parent to null, these bindings will overwrite
+ // them upon component completion as part of the binding evaluation.
+ // That's why we remove the binding completely.
+ const QQmlProperty visibleProperty(horizontal, QStringLiteral("visible"));
+ const QQmlProperty parentProperty(horizontal, QStringLiteral("parent"));
+ QQmlPropertyPrivate::removeBinding(visibleProperty);
+ QQmlPropertyPrivate::removeBinding(parentProperty);
+
disconnect(flickable, &QQuickFlickable::movingHorizontallyChanged, this, &QQuickScrollBarAttachedPrivate::activateHorizontal);
// TODO: export QQuickFlickableVisibleArea
@@ -839,6 +860,12 @@ void QQuickScrollBarAttachedPrivate::cleanupVertical()
{
Q_ASSERT(flickable && vertical);
+ QQuickControlPrivate::hideOldItem(vertical);
+ const QQmlProperty visibleProperty(vertical, QStringLiteral("visible"));
+ const QQmlProperty parentProperty(vertical, QStringLiteral("parent"));
+ QQmlPropertyPrivate::removeBinding(visibleProperty);
+ QQmlPropertyPrivate::removeBinding(parentProperty);
+
disconnect(flickable, &QQuickFlickable::movingVerticallyChanged, this, &QQuickScrollBarAttachedPrivate::activateVertical);
// TODO: export QQuickFlickableVisibleArea
@@ -869,6 +896,9 @@ class QQuickFriendlyFlickable : public QQuickFlickable
void QQuickScrollBarAttachedPrivate::scrollHorizontal()
{
+ if (!flickable)
+ return;
+
QQuickFriendlyFlickable *f = reinterpret_cast<QQuickFriendlyFlickable *>(flickable);
const qreal viewwidth = f->width();
@@ -881,6 +911,9 @@ void QQuickScrollBarAttachedPrivate::scrollHorizontal()
void QQuickScrollBarAttachedPrivate::scrollVertical()
{
+ if (!flickable)
+ return;
+
QQuickFriendlyFlickable *f = reinterpret_cast<QQuickFriendlyFlickable *>(flickable);
const qreal viewheight = f->height();
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/qquickscrollview.cpp b/src/quicktemplates2/qquickscrollview.cpp
index f385778d..01e19b16 100644
--- a/src/quicktemplates2/qquickscrollview.cpp
+++ b/src/quicktemplates2/qquickscrollview.cpp
@@ -38,6 +38,7 @@
#include "qquickpane_p_p.h"
#include "qquickscrollbar_p_p.h"
+#include <QtQml/qqmlinfo.h>
#include <QtQuick/private/qquickflickable_p.h>
QT_BEGIN_NAMESPACE
@@ -575,7 +576,10 @@ void QQuickScrollView::contentItemChange(QQuickItem *newItem, QQuickItem *oldIte
// assume/require that it has an explicit content size assigned.
d->flickableHasExplicitContentWidth = true;
d->flickableHasExplicitContentHeight = true;
- d->setFlickable(qobject_cast<QQuickFlickable *>(newItem), false);
+ auto newItemAsFlickable = qobject_cast<QQuickFlickable *>(newItem);
+ if (newItem && !newItemAsFlickable)
+ qmlWarning(this) << "ScrollView only supports Flickable types as its contentItem";
+ d->setFlickable(newItemAsFlickable, false);
}
QQuickPane::contentItemChange(newItem, oldItem);
}
diff --git a/src/quicktemplates2/qquickspinbox.cpp b/src/quicktemplates2/qquickspinbox.cpp
index f04ceff8..a3a73472 100644
--- a/src/quicktemplates2/qquickspinbox.cpp
+++ b/src/quicktemplates2/qquickspinbox.cpp
@@ -726,7 +726,6 @@ void QQuickSpinBox::setValueFromText(const QJSValue &callback)
}
/*!
- \qmlpropertygroup QtQuick.Controls::SpinBox::up
\qmlproperty bool QtQuick.Controls::SpinBox::up.pressed
\qmlproperty Item QtQuick.Controls::SpinBox::up.indicator
\qmlproperty bool QtQuick.Controls::SpinBox::up.hovered
@@ -747,7 +746,6 @@ QQuickSpinButton *QQuickSpinBox::up() const
}
/*!
- \qmlpropertygroup QtQuick.Controls::SpinBox::down
\qmlproperty bool QtQuick.Controls::SpinBox::down.pressed
\qmlproperty Item QtQuick.Controls::SpinBox::down.indicator
\qmlproperty bool QtQuick.Controls::SpinBox::down.hovered
diff --git a/src/quicktemplates2/qquicksplitview.cpp b/src/quicktemplates2/qquicksplitview.cpp
index a3566c56..a06c8d31 100644
--- a/src/quicktemplates2/qquicksplitview.cpp
+++ b/src/quicktemplates2/qquicksplitview.cpp
@@ -49,7 +49,7 @@ QT_BEGIN_NAMESPACE
/*!
\qmltype SplitView
- \inherits Control
+ \inherits Container
//! \instantiates QQuickSplitView
\inqmlmodule QtQuick.Controls
\since 5.13
diff --git a/src/quicktemplates2/qquickswipedelegate.cpp b/src/quicktemplates2/qquickswipedelegate.cpp
index c6214243..629bf23d 100644
--- a/src/quicktemplates2/qquickswipedelegate.cpp
+++ b/src/quicktemplates2/qquickswipedelegate.cpp
@@ -713,6 +713,29 @@ QQuickSwipeDelegatePrivate::QQuickSwipeDelegatePrivate(QQuickSwipeDelegate *cont
{
}
+void QQuickSwipeDelegatePrivate::resizeBackground()
+{
+ if (!background)
+ return;
+
+ resizingBackground = true;
+
+ QQuickItemPrivate *p = QQuickItemPrivate::get(background);
+ const bool extraAllocated = extra.isAllocated();
+ // Don't check for or set the x here since it will just be overwritten by reposition().
+ if (((!p->widthValid || !extraAllocated || !extra->hasBackgroundWidth))
+ || (extraAllocated && (extra->hasLeftInset || extra->hasRightInset))) {
+ background->setWidth(width - getLeftInset() - getRightInset());
+ }
+ if (((!p->heightValid || !extraAllocated || !extra->hasBackgroundHeight) && qFuzzyIsNull(background->y()))
+ || (extraAllocated && (extra->hasTopInset || extra->hasBottomInset))) {
+ background->setY(getTopInset());
+ background->setHeight(height - getTopInset() - getBottomInset());
+ }
+
+ resizingBackground = false;
+}
+
bool QQuickSwipeDelegatePrivate::handleMousePressEvent(QQuickItem *item, QMouseEvent *event)
{
Q_Q(QQuickSwipeDelegate);
@@ -778,9 +801,10 @@ bool QQuickSwipeDelegatePrivate::handleMouseMoveEvent(QQuickItem *item, QMouseEv
const QPointF mappedEventPos = item->mapToItem(q, event->pos());
const qreal distance = (mappedEventPos - pressPoint).x();
if (!q->keepMouseGrab()) {
- // Taken from QQuickDrawerPrivate::grabMouse; see comments there.
- int threshold = qMax(20, QGuiApplication::styleHints()->startDragDistance() + 5);
- const bool overThreshold = QQuickWindowPrivate::dragOverThreshold(distance, Qt::XAxis, event, threshold);
+ // We used to use the custom threshold that QQuickDrawerPrivate::grabMouse used,
+ // but since it's larger than what Flickable uses, it results in Flickable
+ // stealing events from us (QTBUG-50045), so now we use the default.
+ const bool overThreshold = QQuickWindowPrivate::dragOverThreshold(distance, Qt::XAxis, event);
if (window && overThreshold) {
QQuickItem *grabber = q->window()->mouseGrabberItem();
if (!grabber || !grabber->keepMouseGrab()) {
@@ -935,13 +959,15 @@ void QQuickSwipeDelegatePrivate::resizeContent()
// If the background and contentItem are repositioned due to a swipe,
// we don't want to call QQuickControlPrivate's implementation of this function,
// as it repositions the contentItem to be visible.
- // However, we still want to resize the control vertically.
+ // However, we still want to position the contentItem vertically
+ // and resize it (in case the control was resized while open).
QQuickSwipePrivate *swipePrivate = QQuickSwipePrivate::get(&swipe);
if (!swipePrivate->complete) {
QQuickItemDelegatePrivate::resizeContent();
} else if (contentItem) {
Q_Q(QQuickSwipeDelegate);
contentItem->setY(q->topPadding());
+ contentItem->setWidth(q->availableWidth());
contentItem->setHeight(q->availableHeight());
}
}
@@ -1025,7 +1051,6 @@ QQuickSwipeDelegate::QQuickSwipeDelegate(QQuickItem *parent)
*/
/*!
- \qmlpropertygroup QtQuick.Controls::SwipeDelegate::swipe
\qmlproperty real QtQuick.Controls::SwipeDelegate::swipe.position
\qmlproperty bool QtQuick.Controls::SwipeDelegate::swipe.complete
\qmlproperty bool QtQuick.Controls::SwipeDelegate::swipe.enabled
diff --git a/src/quicktemplates2/qquickswipedelegate_p_p.h b/src/quicktemplates2/qquickswipedelegate_p_p.h
index 78c72ec8..95a999a0 100644
--- a/src/quicktemplates2/qquickswipedelegate_p_p.h
+++ b/src/quicktemplates2/qquickswipedelegate_p_p.h
@@ -67,6 +67,7 @@ public:
bool handleMouseReleaseEvent(QQuickItem *item, QMouseEvent *event);
void resizeContent() override;
+ void resizeBackground() override;
QQuickSwipe swipe;
};
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 d0a08c47..64fc631d 100644
--- a/src/quicktemplates2/qquicktextarea.cpp
+++ b/src/quicktemplates2/qquicktextarea.cpp
@@ -358,6 +358,7 @@ void QQuickTextAreaPrivate::attachFlickable(QQuickFlickable *item)
QObject::connect(flickable, &QQuickFlickable::contentYChanged, q, &QQuickItem::update);
QQuickItemPrivate::get(flickable)->updateOrAddGeometryChangeListener(this, QQuickGeometryChange::Size);
+ QQuickItemPrivate::get(flickable)->addItemChangeListener(this, QQuickItemPrivate::Destroyed);
QObjectPrivate::connect(flickable, &QQuickFlickable::contentWidthChanged, this, &QQuickTextAreaPrivate::resizeFlickableControl);
QObjectPrivate::connect(flickable, &QQuickFlickable::contentHeightChanged, this, &QQuickTextAreaPrivate::resizeFlickableControl);
@@ -378,6 +379,7 @@ void QQuickTextAreaPrivate::detachFlickable()
QObject::disconnect(flickable, &QQuickFlickable::contentYChanged, q, &QQuickItem::update);
QQuickItemPrivate::get(flickable)->updateOrRemoveGeometryChangeListener(this, QQuickGeometryChange::Nothing);
+ QQuickItemPrivate::get(flickable)->removeItemChangeListener(this, QQuickItemPrivate::Destroyed);
QObjectPrivate::disconnect(flickable, &QQuickFlickable::contentWidthChanged, this, &QQuickTextAreaPrivate::resizeFlickableControl);
QObjectPrivate::disconnect(flickable, &QQuickFlickable::contentHeightChanged, this, &QQuickTextAreaPrivate::resizeFlickableControl);
@@ -562,6 +564,8 @@ void QQuickTextAreaPrivate::itemDestroyed(QQuickItem *item)
background = nullptr;
emit q->implicitBackgroundWidthChanged();
emit q->implicitBackgroundHeightChanged();
+ } else if (item == flickable) {
+ detachFlickable();
}
}
@@ -1082,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/qquicktumbler.cpp b/src/quicktemplates2/qquicktumbler.cpp
index c1d1c00f..35ed9680 100644
--- a/src/quicktemplates2/qquicktumbler.cpp
+++ b/src/quicktemplates2/qquicktumbler.cpp
@@ -662,8 +662,9 @@ void QQuickTumblerPrivate::syncCurrentIndex()
return;
}
- // PathView likes to use 0 as currentIndex for empty models, but we use -1 for that.
- if (q->count() == 0 && actualViewIndex == 0)
+ // actualViewIndex might be 0 or -1 for PathView and ListView respectively,
+ // but we always use -1 for that.
+ if (q->count() == 0 && actualViewIndex <= 0)
return;
ignoreCurrentIndexChanges = true;
diff --git a/src/quicktemplates2/qtquicktemplates2global.cpp b/src/quicktemplates2/qtquicktemplates2global.cpp
new file mode 100644
index 00000000..5d7816b4
--- /dev/null
+++ b/src/quicktemplates2/qtquicktemplates2global.cpp
@@ -0,0 +1,63 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include "qquickpage_p.h"
+#include "accessible/qaccessiblequickpage_p.h"
+
+QT_BEGIN_NAMESPACE
+
+#if QT_CONFIG(accessibility)
+static QAccessibleInterface *qQuickAccessibleFactory(const QString &classname, QObject *object)
+{
+ if (classname == u"QQuickPage") {
+ return new QAccessibleQuickPage(qobject_cast<QQuickPage *>(object));
+ }
+ return nullptr;
+}
+#endif
+
+void QQuickTemplates_initializeModule()
+{
+#if QT_CONFIG(accessibility)
+ QAccessible::installFactory(&qQuickAccessibleFactory);
+#endif
+}
+
+Q_CONSTRUCTOR_FUNCTION(QQuickTemplates_initializeModule)
+
+QT_END_NAMESPACE
diff --git a/src/quicktemplates2/qtquicktemplates2global_p.h b/src/quicktemplates2/qtquicktemplates2global_p.h
index e5ee3f2e..9b6610dd 100644
--- a/src/quicktemplates2/qtquicktemplates2global_p.h
+++ b/src/quicktemplates2/qtquicktemplates2global_p.h
@@ -64,6 +64,8 @@ QT_BEGIN_NAMESPACE
# define Q_QUICKTEMPLATES2_PRIVATE_EXPORT
#endif
+Q_QUICKTEMPLATES2_PRIVATE_EXPORT void QQuickTemplates_initializeModule();
+
QT_END_NAMESPACE
#endif // QTQUICKTEMPLATES2GLOBAL_P_H
diff --git a/src/quicktemplates2/quicktemplates2.pro b/src/quicktemplates2/quicktemplates2.pro
index 8ed0151a..a3f778b2 100644
--- a/src/quicktemplates2/quicktemplates2.pro
+++ b/src/quicktemplates2/quicktemplates2.pro
@@ -10,5 +10,11 @@ DEFINES += QT_NO_CAST_TO_ASCII QT_NO_CAST_FROM_ASCII
HEADERS += \
$$PWD/qtquicktemplates2global_p.h
+SOURCES += \
+ $$PWD/qtquicktemplates2global.cpp
+
include(quicktemplates2.pri)
+qtConfig(accessibility) {
+ include(accessible/accessible.pri)
+}
load(qt_module)
diff --git a/tests/auto/accessibility/accessibility.pro b/tests/auto/accessibility/accessibility.pro
index d8d5bb95..4cc101fb 100644
--- a/tests/auto/accessibility/accessibility.pro
+++ b/tests/auto/accessibility/accessibility.pro
@@ -12,5 +12,7 @@ include (../shared/util.pri)
TESTDATA = data/*
OTHER_FILES += \
- data/*.qml
+ data/defaults\*.qml \
+ data/ordering\*.qml \
+ data/override*.qml
diff --git a/tests/auto/accessibility/data/ordering/page.qml b/tests/auto/accessibility/data/ordering/page.qml
new file mode 100644
index 00000000..5eeb1530
--- /dev/null
+++ b/tests/auto/accessibility/data/ordering/page.qml
@@ -0,0 +1,23 @@
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+
+Page {
+ title: "Page"
+ Accessible.role: Accessible.Pane
+
+ header: Label {
+ text: "Header"
+ }
+
+ footer: Label {
+ text: "Footer"
+ }
+
+ Label {
+ text: "Content item 1"
+ }
+
+ Label {
+ text: "Content item 2"
+ }
+}
diff --git a/tests/auto/accessibility/tst_accessibility.cpp b/tests/auto/accessibility/tst_accessibility.cpp
index 6e5a37df..30bd4757 100644
--- a/tests/auto/accessibility/tst_accessibility.cpp
+++ b/tests/auto/accessibility/tst_accessibility.cpp
@@ -60,6 +60,7 @@ private slots:
void override_data();
void override();
+ void ordering();
private:
QQmlEngine engine;
};
@@ -284,6 +285,35 @@ void tst_accessibility::override()
Q_UNUSED(text)
#endif
}
+template <typename Predicate>
+void a11yDescendants(QAccessibleInterface *iface, Predicate pred)
+{
+ for (int i = 0; i < iface->childCount(); ++i) {
+ if (QAccessibleInterface *child = iface->child(i)) {
+ pred(child);
+ a11yDescendants(child, pred);
+ }
+ }
+}
+
+void tst_accessibility::ordering()
+{
+ QQmlComponent component(&engine);
+ component.loadUrl(testFileUrl("ordering/page.qml"));
+
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY2(!object.isNull(), qPrintable(component.errorString()));
+
+#if QT_CONFIG(accessibility)
+ QQuickItem *item = findItem(object.data());
+ QVERIFY(item);
+ QAccessibleInterface *iface = QAccessible::queryAccessibleInterface(item);
+ QVERIFY(iface);
+ QStringList strings;
+ a11yDescendants(iface, [&](QAccessibleInterface *iface) {strings << iface->text(QAccessible::Name);});
+ QCOMPARE(strings.join(QLatin1String(", ")), "Header, Content item 1, Content item 2, Footer");
+#endif
+}
QTEST_MAIN(tst_accessibility)
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_combobox.qml b/tests/auto/controls/data/tst_combobox.qml
index 1c58372f..9bbea26d 100644
--- a/tests/auto/controls/data/tst_combobox.qml
+++ b/tests/auto/controls/data/tst_combobox.qml
@@ -157,6 +157,8 @@ TestCase {
verify(control.delegate)
verify(control.indicator)
verify(control.popup)
+ verify(control.acceptableInput)
+ compare(control.inputMethodHints, Qt.ImhNoPredictiveText)
}
function test_array() {
@@ -948,6 +950,56 @@ TestCase {
tryCompare(control.popup, "visible", false)
}
+ Component {
+ id: reopenCombo
+ Window {
+ property alias innerCombo: innerCombo
+ visible: true
+ width: 300
+ height: 300
+ ComboBox {
+ id: innerCombo
+ model: 10
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+ }
+
+ // This test checks that when reopening the combobox that it is still appears at the same y position as
+ // previously
+ function test_reopen_popup() {
+ var control = createTemporaryObject(reopenCombo, testCase)
+ verify(control)
+ var y = 0;
+ for (var i = 0; i < 2; ++i) {
+ tryCompare(control.innerCombo.popup, "visible", false)
+ control.innerCombo.y = control.height - (control.innerCombo.popup.contentItem.height * 0.99)
+ var popupYSpy = createTemporaryObject(signalSpy, testCase, {target: control.innerCombo.popup, signalName: "yChanged"})
+ verify(popupYSpy.valid)
+ mousePress(control.innerCombo)
+ compare(control.innerCombo.pressed, true)
+ compare(control.innerCombo.popup.visible, false)
+ mouseRelease(control.innerCombo)
+ compare(control.innerCombo.pressed, false)
+ compare(control.innerCombo.popup.visible, true)
+ if (control.innerCombo.popup.enter)
+ tryCompare(control.innerCombo.popup.enter, "running", false)
+ // Check on the second opening that it has the same y position as before
+ if (i !== 0) {
+ // y should not have changed again
+ verify(popupYSpy.count === 0)
+ verify(y === control.innerCombo.popup.y)
+ } else {
+ // In some cases on the initial show, y changes more than once
+ verify(popupYSpy.count >= 1)
+ y = control.innerCombo.popup.y
+ mouseClick(control.innerCombo)
+ compare(control.innerCombo.pressed, false)
+ tryCompare(control.innerCombo.popup, "visible", false)
+ }
+ }
+ }
+
function test_mouse() {
var control = createTemporaryObject(comboBox, testCase, {model: 3, hoverEnabled: false})
verify(control)
@@ -1496,7 +1548,7 @@ TestCase {
control.editText = ""
compare(control.acceptableInput, true)
control.editText = ""
- control.forceActiveFocus()
+ control.contentItem.forceActiveFocus()
keyPress(Qt.Key_A)
compare(control.editText, "")
keyPress(Qt.Key_A)
@@ -1533,7 +1585,7 @@ TestCase {
compare(control.currentIndex, 0)
compare(control.currentText, "first")
- control.forceActiveFocus()
+ control.contentItem.forceActiveFocus()
compare(control.activeFocus, true)
control.selectAll()
@@ -1556,7 +1608,7 @@ TestCase {
var control = createTemporaryObject(comboBox, testCase, {editable: true, model: ["Banana", "Coco", "Coconut", "Apple", "Cocomuffin"]})
verify(control)
- control.forceActiveFocus()
+ control.contentItem.forceActiveFocus()
verify(control.activeFocus)
var acceptCount = 0
@@ -1707,7 +1759,7 @@ TestCase {
var control = createTemporaryObject(keysAttachedBox, testCase)
verify(control)
- control.forceActiveFocus()
+ control.contentItem.forceActiveFocus()
verify(control.activeFocus)
verify(!control.gotit)
@@ -1962,7 +2014,7 @@ TestCase {
// Give the first ComboBox focus and type in 0 to select "Item 10" (default is "Item 1").
waitForRendering(comboBox1)
- comboBox1.forceActiveFocus()
+ comboBox1.contentItem.forceActiveFocus()
verify(comboBox1.activeFocus)
keyClick(Qt.Key_0)
compare(comboBox1.editText, "Item 10")
@@ -1980,7 +2032,7 @@ TestCase {
// Give focus back to the first ComboBox, and try the same thing except
// with non-existing text; the currentIndex should not change.
- comboBox1.forceActiveFocus()
+ comboBox1.contentItem.forceActiveFocus()
verify(comboBox1.activeFocus)
keySequence(StandardKey.SelectAll)
compare(comboBox1.contentItem.selectedText, "Item 10")
@@ -1992,4 +2044,62 @@ TestCase {
compare(comboBox1.currentIndex, 9)
compare(currentIndexSpy.count, 1)
}
+
+ // QTBUG-61021: text line should not be focused by default
+ // It causes (e.g. on Android) showing virtual keyboard when it is not needed
+ function test_doNotFocusTextLineByDefault() {
+ var control = createTemporaryObject(comboBox, testCase)
+ // Focus not set after creating combobox
+ verify(!control.activeFocus)
+ verify(!control.contentItem.focus)
+
+ // After setting focus on combobox, text line should not be focused
+ control.forceActiveFocus()
+ verify(control.activeFocus)
+ verify(!control.contentItem.focus)
+
+ // Text line is focused after intentional setting focus on it
+ control.contentItem.forceActiveFocus()
+ verify(control.activeFocus)
+ verify(control.contentItem.focus)
+ }
+
+ Component {
+ id: intValidatorComponent
+ IntValidator {
+ bottom: 0
+ top: 255
+ }
+ }
+
+ function test_acceptableInput_QTBUG_94307() {
+ let items = [
+ { text: "A" },
+ { text: "2" },
+ { text: "3" }
+ ]
+ let control = createTemporaryObject(comboBox, testCase, {model: items, editable: true})
+ verify(control)
+
+ verify(control.acceptableInput)
+ compare(control.displayText, "A")
+
+ let acceptableInputSpy = signalSpy.createObject(control, {target: control, signalName: "acceptableInputChanged"})
+ verify(acceptableInputSpy.valid)
+
+ let intValidator = intValidatorComponent.createObject(testCase)
+ verify(intValidator)
+
+ control.validator = intValidator
+
+ compare(acceptableInputSpy.count, 1)
+ compare(control.displayText, "A")
+ compare(control.acceptableInput, false)
+
+ control.currentIndex = 1
+
+ compare(acceptableInputSpy.count, 2)
+ compare(control.displayText, "2")
+ compare(control.acceptableInput, true)
+ }
}
diff --git a/tests/auto/controls/data/tst_dial.qml b/tests/auto/controls/data/tst_dial.qml
index 26f30c33..e3ad2b2f 100644
--- a/tests/auto/controls/data/tst_dial.qml
+++ b/tests/auto/controls/data/tst_dial.qml
@@ -691,4 +691,19 @@ TestCase {
compare(control.pressed, false);
compare(control.position, data.expectedPosition);
}
+
+ function test_integerStepping() {
+ var dial = createTemporaryObject(dialComponent, testCase)
+ verify(dial)
+
+ dial.from = 1
+ dial.to = 8
+ dial.stepSize = 1
+
+ for (let i = 1; i < 8; ++i) {
+ // compare as strings to avoid a fuzzy compare; we want an exact match
+ compare(""+dial.value, ""+1)
+ keyClick(Qt.Key_Right)
+ }
+ }
}
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 24f8a207..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)
@@ -612,7 +649,7 @@ TestCase {
}
function test_overlappingHandles() {
- var control = createTemporaryObject(sliderComponent, testCase, { orientation: data.orientation })
+ var control = createTemporaryObject(sliderComponent, testCase)
verify(control)
// By default, we force the second handle to be after the first in
@@ -1064,17 +1101,16 @@ TestCase {
function test_valueAt_data() {
return [
- { tag: "0.0..1.0", from: 0.0, to: 1.0, values: [0.0, 0.2, 0.5, 1.0] },
- { tag: "0..100", from: 0, to: 100, values: [0, 20, 50, 100] },
- { tag: "100..-100", from: 100, to: -100, values: [100, 60, 0, -100] },
- { tag: "-7..7", from: -7, to: 7, stepSize: 1.0, values: [-7.0, -4.0, 0.0, 7.0] },
- { tag: "-3..7", from: -3, to: 7, stepSize: 5.0, values: [-3.0, -3.0, 2.0, 7.0] },
+ { tag: "0.0..1.0", properties: { from: 0.0, to: 1.0 }, values: [0.0, 0.2, 0.5, 1.0] },
+ { tag: "0..100", properties: { from: 0, to: 100 }, values: [0, 20, 50, 100] },
+ { tag: "100..-100", properties: { from: 100, to: -100 }, values: [100, 60, 0, -100] },
+ { tag: "-7..7", properties: { from: -7, to: 7, stepSize: 1.0 }, values: [-7.0, -4.0, 0.0, 7.0] },
+ { tag: "-3..7", properties: { from: -3, to: 7, stepSize: 5.0 }, values: [-3.0, -3.0, 2.0, 7.0] },
]
}
function test_valueAt(data) {
- var control = createTemporaryObject(sliderComponent, testCase,
- { from: data.from, to: data.to, stepSize: data.stepSize })
+ var control = createTemporaryObject(sliderComponent, testCase, data.properties)
verify(control)
compare(control.valueAt(0.0), data.values[0])
diff --git a/tests/auto/controls/data/tst_scrollbar.qml b/tests/auto/controls/data/tst_scrollbar.qml
index 9d21fa8b..b018899e 100644
--- a/tests/auto/controls/data/tst_scrollbar.qml
+++ b/tests/auto/controls/data/tst_scrollbar.qml
@@ -189,6 +189,36 @@ TestCase {
compare(horizontal.width, oldWidth)
}
+ function test_attachTwice() {
+ let container = createTemporaryObject(flickable, testCase)
+ verify(container)
+ waitForRendering(container)
+
+ container.ScrollBar.vertical = scrollBar.createObject(container, { objectName: "oldVerticalScrollBar" })
+ verify(container.ScrollBar.vertical)
+ let oldVerticalScrollBar = findChild(container, "oldVerticalScrollBar")
+ verify(oldVerticalScrollBar)
+ verify(oldVerticalScrollBar.visible)
+
+ container.ScrollBar.horizontal = scrollBar.createObject(container, { objectName: "oldHorizontalScrollBar" })
+ verify(container.ScrollBar.horizontal)
+ let oldHorizontalScrollBar = findChild(container, "oldHorizontalScrollBar")
+ verify(oldHorizontalScrollBar)
+ verify(oldHorizontalScrollBar.visible)
+
+ container.ScrollBar.vertical = scrollBar.createObject(container, { objectName: "newVerticalScrollBar" })
+ let newVerticalScrollBar = findChild(container, "newVerticalScrollBar")
+ verify(newVerticalScrollBar)
+ verify(newVerticalScrollBar.visible)
+ verify(!oldVerticalScrollBar.visible)
+
+ container.ScrollBar.horizontal = scrollBar.createObject(container, { objectName: "newHorizontalScrollBar" })
+ let newHorizontalScrollBar = findChild(container, "newHorizontalScrollBar")
+ verify(newHorizontalScrollBar)
+ verify(newHorizontalScrollBar.visible)
+ verify(!oldHorizontalScrollBar.visible)
+ }
+
function test_mouse_data() {
return [
{ tag: "horizontal", properties: { visible: true, orientation: Qt.Horizontal, width: testCase.width } },
diff --git a/tests/auto/controls/data/tst_scrollview.qml b/tests/auto/controls/data/tst_scrollview.qml
index 87c39509..cd493118 100644
--- a/tests/auto/controls/data/tst_scrollview.qml
+++ b/tests/auto/controls/data/tst_scrollview.qml
@@ -71,6 +71,11 @@ TestCase {
}
Component {
+ id: scrollBarComponent
+ ScrollBar {}
+ }
+
+ Component {
id: scrollableLabel
ScrollView {
Label {
@@ -188,6 +193,15 @@ TestCase {
}
}
}
+ Component {
+ id: scrollableTextAreaWithSibling
+ ScrollView {
+ Item {
+ }
+ TextArea {
+ }
+ }
+ }
function test_scrollBars() {
var control = createTemporaryObject(scrollView, testCase, {width: 200, height: 200})
@@ -502,4 +516,111 @@ TestCase {
compare(control.contentWidth, flickable.contentWidth)
compare(control.contentHeight, flickable.contentHeight)
}
+
+ function test_textAreaWithSibling() {
+ // Checks that it does not crash when the ScrollView is deleted
+ var control = createTemporaryObject(scrollableTextAreaWithSibling, testCase)
+ verify(control)
+ }
+
+ Component {
+ id: zeroSizedContentItemComponent
+ ScrollView {
+ width: 100
+ height: 100
+ contentItem: Item {}
+ }
+ }
+
+ function test_zeroSizedContentItem() {
+ ignoreWarning(/ScrollView only supports Flickable types as its contentItem/)
+ let control = createTemporaryObject(zeroSizedContentItemComponent, testCase)
+ verify(control)
+
+ let verticalScrollBar = control.ScrollBar.vertical
+ verify(verticalScrollBar)
+ // Scrolling a ScrollView with a zero-sized contentItem shouldn't crash.
+ mouseDrag(verticalScrollBar, verticalScrollBar.width / 2, verticalScrollBar.height / 2, 0, 50)
+
+ let horizontalScrollBar = control.ScrollBar.horizontal
+ verify(verticalScrollBar)
+ mouseDrag(horizontalScrollBar, horizontalScrollBar.width / 2, horizontalScrollBar.height / 2, 50, 0)
+ }
+
+ function test_customScrollBars() {
+ let control = createTemporaryObject(scrollView, testCase)
+ verify(control)
+ control.ScrollBar.vertical.objectName = "oldVerticalScrollBar"
+ control.ScrollBar.horizontal.objectName = "oldHorizontalScrollBar"
+
+ let oldVerticalScrollBar = control.ScrollBar.vertical
+ verify(oldVerticalScrollBar)
+ compare(oldVerticalScrollBar.objectName, "oldVerticalScrollBar")
+
+ let oldHorizontalScrollBar = control.ScrollBar.horizontal
+ verify(oldHorizontalScrollBar)
+ compare(oldHorizontalScrollBar.objectName, "oldHorizontalScrollBar")
+
+ // Create the new scroll bars imperatively so that we can easily access the old ones.
+ control.ScrollBar.vertical = scrollBarComponent.createObject(control, { objectName: "newVerticalScrollBar" })
+ verify(control.ScrollBar.vertical)
+ let newVerticalScrollBar = findChild(control, "newVerticalScrollBar")
+ verify(newVerticalScrollBar)
+ verify(newVerticalScrollBar.visible)
+ verify(!oldVerticalScrollBar.visible)
+
+ control.ScrollBar.horizontal = scrollBarComponent.createObject(control, { objectName: "newHorizontalScrollBar" })
+ verify(control.ScrollBar.horizontal)
+ let newHorizontalScrollBar = findChild(control, "newHorizontalScrollBar")
+ verify(newHorizontalScrollBar)
+ 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 280138c5..bf65e486 100644
--- a/tests/auto/controls/data/tst_slider.qml
+++ b/tests/auto/controls/data/tst_slider.qml
@@ -831,16 +831,16 @@ TestCase {
function test_valueAt_data() {
return [
- { tag: "0.0..1.0", from: 0.0, to: 1.0, values: [0.0, 0.2, 0.5, 1.0] },
- { tag: "0..100", from: 0, to: 100, values: [0, 20, 50, 100] },
- { tag: "100..-100", from: 100, to: -100, values: [100, 60, 0, -100] },
- { tag: "-7..7", from: -7, to: 7, stepSize: 1.0, values: [-7.0, -4.0, 0.0, 7.0] },
- { tag: "-3..7", from: -3, to: 7, stepSize: 5.0, values: [-3.0, -3.0, 2.0, 7.0] },
+ { tag: "0.0..1.0", properties: { from: 0.0, to: 1.0 }, values: [0.0, 0.2, 0.5, 1.0] },
+ { tag: "0..100", properties: { from: 0, to: 100 }, values: [0, 20, 50, 100] },
+ { tag: "100..-100", properties: { from: 100, to: -100 }, values: [100, 60, 0, -100] },
+ { tag: "-7..7", properties: { from: -7, to: 7, stepSize: 1.0 }, values: [-7.0, -4.0, 0.0, 7.0] },
+ { tag: "-3..7", properties: { from: -3, to: 7, stepSize: 5.0 }, values: [-3.0, -3.0, 2.0, 7.0] },
]
}
function test_valueAt(data) {
- var control = createTemporaryObject(slider, testCase, {from: data.from, to: data.to, stepSize: data.stepSize})
+ let control = createTemporaryObject(slider, testCase, data.properties)
verify(control)
compare(control.valueAt(0.0), data.values[0])
@@ -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_spinbox.qml b/tests/auto/controls/data/tst_spinbox.qml
index adb70a30..14ebba21 100644
--- a/tests/auto/controls/data/tst_spinbox.qml
+++ b/tests/auto/controls/data/tst_spinbox.qml
@@ -244,17 +244,17 @@ TestCase {
function test_keys_data() {
return [
- { tag: "1", from: 1, to: 10, value: 1, stepSize: 1, upSteps: [2,3,4], downSteps: [3,2,1,1] },
- { tag: "2", from: 1, to: 10, value: 10, stepSize: 2, upSteps: [10,10], downSteps: [8,6,4] },
- { tag: "25", from: 0, to: 100, value: 50, stepSize: 25, upSteps: [75,100,100], downSteps: [75,50,25,0,0] },
- { tag: "wrap1", wrap: true, from: 1, to: 10, value: 1, stepSize: 1, upSteps: [2,3], downSteps: [2,1,10,9] },
- { tag: "wrap2", wrap: true, from: 1, to: 10, value: 10, stepSize: 2, upSteps: [1,3,5], downSteps: [3,1,10,8,6] },
- { tag: "wrap25", wrap: true, from: 0, to: 100, value: 50, stepSize: 25, upSteps: [75,100,0,25], downSteps: [0,100,75] }
+ { tag: "1", properties: { from: 1, to: 10, value: 1, stepSize: 1 }, upSteps: [2,3,4], downSteps: [3,2,1,1] },
+ { tag: "2", properties: { from: 1, to: 10, value: 10, stepSize: 2 }, upSteps: [10,10], downSteps: [8,6,4] },
+ { tag: "25", properties: { from: 0, to: 100, value: 50, stepSize: 25 }, upSteps: [75,100,100], downSteps: [75,50,25,0,0] },
+ { tag: "wrap1", properties: { wrap: true, from: 1, to: 10, value: 1, stepSize: 1 }, upSteps: [2,3], downSteps: [2,1,10,9] },
+ { tag: "wrap2", properties: { wrap: true, from: 1, to: 10, value: 10, stepSize: 2 }, upSteps: [1,3,5], downSteps: [3,1,10,8,6] },
+ { tag: "wrap25", properties: { wrap: true, from: 0, to: 100, value: 50, stepSize: 25 }, upSteps: [75,100,0,25], downSteps: [0,100,75] }
]
}
function test_keys(data) {
- var control = createTemporaryObject(spinBox, testCase, {wrap: data.wrap, from: data.from, to: data.to, value: data.value, stepSize: data.stepSize})
+ var control = createTemporaryObject(spinBox, testCase, data.properties)
verify(control)
var upPressedCount = 0
@@ -396,12 +396,12 @@ TestCase {
function test_wheel_data() {
return [
- { tag: "1", from: 1, to: 10, value: 1, stepSize: 1, upSteps: [2,3,4], downSteps: [3,2,1,1] },
- { tag: "2", from: 1, to: 10, value: 10, stepSize: 2, upSteps: [10,10], downSteps: [8,6,4] },
- { tag: "25", from: 0, to: 100, value: 50, stepSize: 25, upSteps: [75,100,100], downSteps: [75,50,25,0,0] },
- { tag: "wrap1", wrap: true, from: 1, to: 10, value: 1, stepSize: 1, upSteps: [2,3], downSteps: [2,1,10,9] },
- { tag: "wrap2", wrap: true, from: 1, to: 10, value: 10, stepSize: 2, upSteps: [1,3,5], downSteps: [3,1,10,8,6] },
- { tag: "wrap25", wrap: true, from: 0, to: 100, value: 50, stepSize: 25, upSteps: [75,100,0,25], downSteps: [0,100,75] }
+ { tag: "1", properties: { from: 1, to: 10, value: 1, stepSize: 1 }, upSteps: [2,3,4], downSteps: [3,2,1,1] },
+ { tag: "2", properties: { from: 1, to: 10, value: 10, stepSize: 2 }, upSteps: [10,10], downSteps: [8,6,4] },
+ { tag: "25", properties: { from: 0, to: 100, value: 50, stepSize: 25 }, upSteps: [75,100,100], downSteps: [75,50,25,0,0] },
+ { tag: "wrap1", properties: { wrap: true, from: 1, to: 10, value: 1, stepSize: 1 }, upSteps: [2,3], downSteps: [2,1,10,9] },
+ { tag: "wrap2", properties: { wrap: true, from: 1, to: 10, value: 10, stepSize: 2 }, upSteps: [1,3,5], downSteps: [3,1,10,8,6] },
+ { tag: "wrap25", properties: { wrap: true, from: 0, to: 100, value: 50, stepSize: 25 }, upSteps: [75,100,0,25], downSteps: [0,100,75] }
]
}
@@ -409,7 +409,8 @@ TestCase {
var ma = createTemporaryObject(mouseArea, testCase, {width: 100, height: 100})
verify(ma)
- var control = spinBox.createObject(ma, {wrap: data.wrap, from: data.from, to: data.to, value: data.value, stepSize: data.stepSize, wheelEnabled: true})
+ data.properties.wheelEnabled = true
+ var control = spinBox.createObject(ma, data.properties)
verify(control)
var valueModifiedCount = 0
diff --git a/tests/auto/controls/data/tst_splitview.qml b/tests/auto/controls/data/tst_splitview.qml
index ae8179b2..aa167472 100644
--- a/tests/auto/controls/data/tst_splitview.qml
+++ b/tests/auto/controls/data/tst_splitview.qml
@@ -1838,8 +1838,7 @@ TestCase {
var flickable = createTemporaryObject(flickableComponent, testCase)
verify(flickable)
- var control = threeSizedItemsComponent.createObject(flickable.contentItem,
- { "orientation": data.orientation })
+ var control = threeSizedItemsComponent.createObject(flickable.contentItem)
verify(control)
control.anchors.fill = undefined
diff --git a/tests/auto/controls/data/tst_swipedelegate.qml b/tests/auto/controls/data/tst_swipedelegate.qml
index d37ea42b..255dd881 100644
--- a/tests/auto/controls/data/tst_swipedelegate.qml
+++ b/tests/auto/controls/data/tst_swipedelegate.qml
@@ -702,7 +702,9 @@ TestCase {
property alias removeAnimation: onRemoveAnimation
- ListView.onRemove: SequentialAnimation {
+ ListView.onRemove: onRemoveAnimation.start()
+
+ SequentialAnimation {
id: onRemoveAnimation
PropertyAction {
@@ -1273,10 +1275,10 @@ TestCase {
// When this happens, it will grab the mouse and hence we must clear
// that action's pressed state so that it doesn't stay pressed after releasing.
function test_dragSideAction() {
- var listView = createTemporaryObject(removableDelegatesComponent, testCase);
+ let listView = createTemporaryObject(removableDelegatesComponent, testCase);
verify(listView);
- var control = listView.itemAt(0, 0);
+ let control = listView.itemAt(0, 0);
verify(control);
// Expose the side action.
@@ -1284,15 +1286,43 @@ TestCase {
verify(control.swipe.leftItem);
tryCompare(control.swipe, "complete", true);
- var pressedSpy = signalSpyComponent.createObject(control,
+ let pressedSpy = signalSpyComponent.createObject(control,
{ target: control.swipe.leftItem.SwipeDelegate, signalName: "pressedChanged" });
verify(pressedSpy);
verify(pressedSpy.valid);
+ let movingHorizontallySpy = createTemporaryObject(signalSpyComponent, testCase,
+ { target: listView, signalName: "movingHorizontallyChanged" })
+ verify(movingHorizontallySpy)
+ verify(movingHorizontallySpy.valid)
+
+ let movingVerticallySpy = createTemporaryObject(signalSpyComponent, testCase,
+ { target: listView, signalName: "movingVerticallyChanged" })
+ verify(movingVerticallySpy)
+ verify(movingVerticallySpy.valid)
+
+ let flickingHorizontallySpy = createTemporaryObject(signalSpyComponent, testCase,
+ { target: listView, signalName: "flickingHorizontallyChanged" })
+ verify(flickingHorizontallySpy)
+ verify(flickingHorizontallySpy.valid)
+
+ let flickingVerticallySpy = createTemporaryObject(signalSpyComponent, testCase,
+ { target: listView, signalName: "flickingVerticallyChanged" })
+ verify(flickingVerticallySpy)
+ verify(flickingVerticallySpy.valid)
+
+ // Drag the ListView vertically; its contentY should change.
mouseDrag(listView, 20, 20, 0, listView.height);
compare(pressedSpy.count, 2);
- verify(listView.contentY !== 0);
+ // Wait for it to stop moving.
+ tryCompare(listView, "flickingVertically", false)
+
+ // 2 because it should change to true then false.
+ compare(movingHorizontallySpy.count, 0)
+ compare(movingVerticallySpy.count, 2)
+ compare(flickingHorizontallySpy.count, 0)
+ compare(flickingVerticallySpy.count, 2)
compare(control.swipe.leftItem.SwipeDelegate.pressed, false);
}
@@ -1713,4 +1743,39 @@ TestCase {
break;
}
}
+
+ function test_resizeParent() {
+ let container = createTemporaryObject(itemComponent, testCase, { objectName: "container", width: 100, height: 200 })
+ verify(container)
+
+ let control = swipeDelegateComponent.createObject(container, { width: Qt.binding(function() { return container.width }) })
+ verify(control)
+
+ // Resize while closed.
+ container.width = 200
+ compare(container.width, 200)
+ compare(control.width, 200)
+ compare(control.background.width, 200)
+ compare(control.contentItem.width, 200 - control.leftPadding - control.rightPadding)
+
+ // Return to original size.
+ container.width = 100
+ compare(control.width, 100)
+ compare(control.background.width, 100)
+ compare(control.contentItem.width, 100 - control.leftPadding - control.rightPadding)
+
+ // Swipe to the left to open.
+ swipe(control, 0, -1.0)
+ // Nothing should have changed except positions.
+ compare(control.width, 100)
+ compare(control.background.width, 100)
+ compare(control.contentItem.width, 100 - control.leftPadding - control.rightPadding)
+
+ // Resize while open.
+ container.width = 200
+ // The items should fill the width as usual.
+ compare(control.width, 200)
+ compare(control.background.width, 200)
+ compare(control.contentItem.width, 200 - control.leftPadding - control.rightPadding)
+ }
}
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/controls/data/tst_tumbler.qml b/tests/auto/controls/data/tst_tumbler.qml
index 5b3ef6e3..fd8b7d92 100644
--- a/tests/auto/controls/data/tst_tumbler.qml
+++ b/tests/auto/controls/data/tst_tumbler.qml
@@ -1257,4 +1257,25 @@ TestCase {
tumbler.height *= 1.4
tryCompare(delegate, "displacement", 0)
}
+
+ //QTBUG-84426
+ Component {
+ id: initialCurrentIndexTumbler
+
+ Tumbler {
+ anchors.centerIn: parent
+ width: 60
+ height: 200
+ delegate: Text {text: modelData}
+ model: 10
+ currentIndex: 4
+ }
+ }
+
+ function test_initialCurrentIndex() {
+ var tumbler = createTemporaryObject(initialCurrentIndexTumbler, testCase, {wrap: true});
+ compare(tumbler.currentIndex, 4);
+ tumbler = createTemporaryObject(initialCurrentIndexTumbler, testCase, {wrap: false});
+ compare(tumbler.currentIndex, 4);
+ }
}
diff --git a/tests/auto/qquickapplicationwindow/data/layoutLayout.qml b/tests/auto/qquickapplicationwindow/data/layoutLayout.qml
new file mode 100644
index 00000000..24eeb57b
--- /dev/null
+++ b/tests/auto/qquickapplicationwindow/data/layoutLayout.qml
@@ -0,0 +1,65 @@
+/****************************************************************************
+**
+** 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
+import QtQuick.Layouts 1.15
+
+ApplicationWindow {
+ width: 200
+ height: 200
+ visible: true
+
+ header: RowLayout {
+ Rectangle { color: "red"; implicitWidth: 20; implicitHeight: 20; Layout.fillWidth: true}
+ }
+ footer: ColumnLayout {
+ Rectangle { color: "green"; implicitWidth: 20; implicitHeight: 20; Layout.fillWidth: true}
+ }
+}
diff --git a/tests/auto/qquickapplicationwindow/tst_qquickapplicationwindow.cpp b/tests/auto/qquickapplicationwindow/tst_qquickapplicationwindow.cpp
index 80124230..ebdc763a 100644
--- a/tests/auto/qquickapplicationwindow/tst_qquickapplicationwindow.cpp
+++ b/tests/auto/qquickapplicationwindow/tst_qquickapplicationwindow.cpp
@@ -76,6 +76,7 @@ private slots:
void focusAfterPopupClosed();
void clearFocusOnDestruction();
void layout();
+ void layoutLayout();
void componentComplete();
};
@@ -862,6 +863,42 @@ void tst_QQuickApplicationWindow::layout()
QCOMPARE(content->height(), qreal(window->height()));
}
+void tst_QQuickApplicationWindow::layoutLayout()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine);
+ component.loadUrl(testFileUrl("layoutLayout.qml"));
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY2(!object.isNull(), qPrintable(component.errorString()));
+
+ QQuickApplicationWindow* window = qobject_cast<QQuickApplicationWindow*>(object.data());
+ QVERIFY(window);
+ QVERIFY(QTest::qWaitForWindowExposed(window));
+
+ QQuickItem *content = window->contentItem();
+ QVERIFY(content);
+ QQuickItem *header = window->header();
+ QVERIFY(header);
+ QQuickItem *footer = window->footer();
+ QVERIFY(footer);
+
+ QQuickItem *headerChild = header->findChild<QQuickItem*>();
+ QVERIFY(headerChild);
+ QCOMPARE(header->x(), 0.0);
+ QCOMPARE(header->y(), -header->height());
+ QCOMPARE(header->width(), qreal(window->width()));
+ QCOMPARE(headerChild->width(), qreal(window->width()));
+ QVERIFY(header->height() > 0);
+
+ QQuickItem *footerChild = header->findChild<QQuickItem*>();
+ QVERIFY(footerChild);
+ QCOMPARE(footer->x(), 0.0);
+ QCOMPARE(footer->y(), content->height());
+ QCOMPARE(footer->width(), qreal(window->width()));
+ QCOMPARE(footerChild->width(), qreal(window->width()));
+ QVERIFY(footer->height() > 0.0);
+}
+
class FriendlyApplicationWindow : public QQuickApplicationWindow
{
friend class tst_QQuickApplicationWindow;
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/qquickdrawer/BLACKLIST b/tests/auto/qquickdrawer/BLACKLIST
new file mode 100644
index 00000000..9f3f96be
--- /dev/null
+++ b/tests/auto/qquickdrawer/BLACKLIST
@@ -0,0 +1,8 @@
+# See qtbase/src/testlib/qtestblacklist.cpp for format
+
+# QTBUG-77946
+[slider]
+opensuse-leap
+
+[position]
+opensuse-leap
diff --git a/tests/auto/qquickheaderview/tst_qquickheaderview.cpp b/tests/auto/qquickheaderview/tst_qquickheaderview.cpp
index 611e39cb..f335aa86 100644
--- a/tests/auto/qquickheaderview/tst_qquickheaderview.cpp
+++ b/tests/auto/qquickheaderview/tst_qquickheaderview.cpp
@@ -58,8 +58,10 @@ public:
{
}
- int rowCount(const QModelIndex & = QModelIndex()) const override
+ int rowCount(const QModelIndex &index = QModelIndex()) const override
{
+ if (index.isValid())
+ return 0;
return m_rows;
}
virtual void setRowCount(int count)
@@ -70,8 +72,10 @@ public:
endResetModel();
}
- int columnCount(const QModelIndex & = QModelIndex()) const override
+ int columnCount(const QModelIndex &index = QModelIndex()) const override
{
+ if (index.isValid())
+ return 0;
return m_cols;
}
virtual void setColumnCount(int count)
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/destroyDuringExitTransition.qml b/tests/auto/qquickpopup/data/destroyDuringExitTransition.qml
new file mode 100644
index 00000000..ae72669f
--- /dev/null
+++ b/tests/auto/qquickpopup/data/destroyDuringExitTransition.qml
@@ -0,0 +1,114 @@
+/****************************************************************************
+**
+** 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.15
+import QtQuick.Controls 2.15
+
+ApplicationWindow {
+ id: window
+ width: 400
+ height: 400
+ title: "destroyDuringExitTransition"
+
+ property Dialog dialog1
+ property Dialog dialog2
+
+ Component {
+ id: dlg
+
+ Dialog {
+ dim: true
+ modal: true
+ closePolicy: Popup.CloseOnEscape
+ visible: true
+
+ property alias button: button
+
+ Column {
+ Text {
+ text: "button is " + (button.down ? "down" : "up")
+ }
+
+ Button {
+ id: button
+ text: "Try to press this button"
+ }
+ }
+ }
+ }
+
+ Component {
+ id: brokenDlg
+ Dialog {
+ dim: true
+ modal: true
+ closePolicy: Popup.CloseOnEscape
+ visible: true
+
+ Text {
+ text: "Press Esc key to reject this dialog"
+ }
+
+ exit: Transition {
+ NumberAnimation { property: "opacity"; from: 1.0; to: 0.0; duration: 100 }
+ }
+ }
+ }
+
+
+ Component.onCompleted: {
+ dialog1 = dlg.createObject(window)
+ dialog2 = brokenDlg.createObject(window)
+
+ dialog2.onRejected.connect(function(){
+ dialog2.destroy()
+ })
+ }
+}
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 41955d7d..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();
@@ -96,6 +99,7 @@ private slots:
void tabFence();
void invisibleToolTipOpen();
void centerInOverlayWithinStackViewItem();
+ void destroyDuringExitTransition();
};
void tst_QQuickPopup::initTestCase()
@@ -727,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");
@@ -914,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()
{
@@ -1429,6 +1546,35 @@ void tst_QQuickPopup::centerInOverlayWithinStackViewItem()
// Shouldn't crash on exit.
}
+void tst_QQuickPopup::destroyDuringExitTransition()
+{
+ QQuickApplicationHelper helper(this, "destroyDuringExitTransition.qml");
+ QVERIFY2(helper.ready, helper.failureMessage());
+
+ QQuickWindow *window = helper.window;
+ window->show();
+ QVERIFY(QTest::qWaitForWindowActive(window));
+
+ QPointer<QQuickPopup> dialog2 = window->property("dialog2").value<QQuickPopup*>();
+ QVERIFY(dialog2);
+ QTRY_COMPARE(dialog2->isVisible(), true);
+
+ // Close the second dialog, destroying it before its exit transition can finish.
+ QTest::keyClick(window, Qt::Key_Escape);
+ QTRY_VERIFY(!dialog2);
+
+ // Events should go through to the dialog underneath.
+ QQuickPopup *dialog1 = window->property("dialog1").value<QQuickPopup*>();
+ QVERIFY(dialog1);
+ QQuickButton *button = dialog1->property("button").value<QQuickButton*>();
+ QVERIFY(button);
+ const auto buttonClickPos = button->mapToScene(QPointF(button->width() / 2, button->height() / 2)).toPoint();
+ QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, buttonClickPos);
+ QVERIFY(button->isDown());
+ QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, buttonClickPos);
+ QVERIFY(!button->isDown());
+}
+
QTEST_QUICKCONTROLS_MAIN(tst_QQuickPopup)
#include "tst_qquickpopup.moc"