From b7ae0a90c162cd2f137b259e1336634c737973b6 Mon Sep 17 00:00:00 2001 From: Oliver Eftevaag Date: Wed, 6 Jul 2022 18:26:01 +0200 Subject: [PATCH] QQuickFileDialog: give non-native dialog a text field for filename The non-native filedialog was missing a text input field that would allow the user to easily set the filename in a filedialog with fileMode set to SaveFile. Pick-to: 6.4 Fixes: QTBUG-101502 Change-Id: Id800a1e34de0e65455409a7edc5fa2f5f13b4b19 Reviewed-by: Mitch Curtis --- .../qml/+Fusion/FileDialog.qml | 38 ++++++++-- .../qml/+Imagine/FileDialog.qml | 40 +++++++++-- .../qml/+Material/FileDialog.qml | 49 +++++++++++-- .../qml/+Universal/FileDialog.qml | 35 ++++++++-- .../quickdialogs2quickimpl/qml/FileDialog.qml | 56 +++++++++++---- .../qquickfiledialogimpl.cpp | 70 +++++++++++++++++++ .../qquickfiledialogimpl_p.h | 18 +++++ .../qquickfiledialogimpl_p_p.h | 5 ++ .../tst_qquickfiledialogimpl.cpp | 44 ++++++++++++ 9 files changed, 323 insertions(+), 32 deletions(-) diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qml/+Fusion/FileDialog.qml b/src/quickdialogs2/quickdialogs2quickimpl/qml/+Fusion/FileDialog.qml index 26b6bec534..a928a4b0cc 100644 --- a/src/quickdialogs2/quickdialogs2quickimpl/qml/+Fusion/FileDialog.qml +++ b/src/quickdialogs2/quickdialogs2quickimpl/qml/+Fusion/FileDialog.qml @@ -43,6 +43,8 @@ FileDialogImpl { FileDialogImpl.nameFiltersComboBox: nameFiltersComboBox FileDialogImpl.fileDialogListView: fileDialogListView FileDialogImpl.breadcrumbBar: breadcrumbBar + FileDialogImpl.fileNameLabel: fileNameLabel + FileDialogImpl.fileNameTextField: fileNameTextField background: Rectangle { implicitWidth: 600 @@ -127,16 +129,40 @@ FileDialogImpl { } } - footer: RowLayout { - id: rowLayout - spacing: 12 + footer: GridLayout { + columnSpacing: 12 + columns: 3 + + Label { + id: fileNameLabel + text: qsTr("File name") + Layout.leftMargin: 12 + visible: false + } + + TextField { + id: fileNameTextField + objectName: "fileNameTextField" + text: control.fileName + visible: false + + Layout.fillWidth: true + } + + Label { + text: qsTr("Filter") + Layout.column: 0 + Layout.row: 1 + Layout.leftMargin: 12 + Layout.bottomMargin: 12 + } + ComboBox { // OK to use IDs here, since users shouldn't be overriding this stuff. id: nameFiltersComboBox model: control.nameFilters - Layout.leftMargin: 12 Layout.fillWidth: true Layout.bottomMargin: 12 } @@ -149,6 +175,10 @@ FileDialogImpl { verticalPadding: 0 background: null + // TODO: make the orientation vertical + Layout.row: 1 + Layout.column: 2 + Layout.columnSpan: 1 Layout.rightMargin: 12 Layout.bottomMargin: 12 } diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qml/+Imagine/FileDialog.qml b/src/quickdialogs2/quickdialogs2quickimpl/qml/+Imagine/FileDialog.qml index 762cf8c5cc..664965e571 100644 --- a/src/quickdialogs2/quickdialogs2quickimpl/qml/+Imagine/FileDialog.qml +++ b/src/quickdialogs2/quickdialogs2quickimpl/qml/+Imagine/FileDialog.qml @@ -43,6 +43,8 @@ FileDialogImpl { FileDialogImpl.nameFiltersComboBox: nameFiltersComboBox FileDialogImpl.fileDialogListView: fileDialogListView FileDialogImpl.breadcrumbBar: breadcrumbBar + FileDialogImpl.fileNameLabel: fileNameLabel + FileDialogImpl.fileNameTextField: fileNameTextField background: NinePatchImage { source: Imagine.url + "dialog-background" @@ -116,17 +118,42 @@ FileDialogImpl { } } - footer: RowLayout { - id: rowLayout - spacing: 20 + footer: GridLayout { + columnSpacing: 20 + columns: 3 + + Label { + id: fileNameLabel + text: qsTr("File name") + visible: false + + Layout.leftMargin: 16 + } + + TextField { + id: fileNameTextField + objectName: "fileNameTextField" + text: control.fileName + visible: false + + Layout.fillWidth: true + } + + Label { + text: qsTr("Filter") + + Layout.column: 0 + Layout.row: 1 + Layout.leftMargin: 16 + Layout.bottomMargin: 16 + } ComboBox { id: nameFiltersComboBox model: control.nameFilters - Layout.leftMargin: 16 - Layout.bottomMargin: 16 Layout.fillWidth: true + Layout.bottomMargin: 16 } DialogButtonBox { @@ -134,6 +161,9 @@ FileDialogImpl { standardButtons: control.standardButtons spacing: 12 + Layout.row: 1 + Layout.column: 2 + Layout.columnSpan: 1 Layout.bottomMargin: 16 Layout.rightMargin: 16 } diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qml/+Material/FileDialog.qml b/src/quickdialogs2/quickdialogs2quickimpl/qml/+Material/FileDialog.qml index 73c9cea704..cd2c513c2e 100644 --- a/src/quickdialogs2/quickdialogs2quickimpl/qml/+Material/FileDialog.qml +++ b/src/quickdialogs2/quickdialogs2quickimpl/qml/+Material/FileDialog.qml @@ -36,6 +36,8 @@ FileDialogImpl { FileDialogImpl.nameFiltersComboBox: nameFiltersComboBox FileDialogImpl.fileDialogListView: fileDialogListView FileDialogImpl.breadcrumbBar: breadcrumbBar + FileDialogImpl.fileNameLabel: fileNameLabel + FileDialogImpl.fileNameTextField: fileNameTextField background: Rectangle { implicitWidth: 600 @@ -98,15 +100,46 @@ FileDialogImpl { } } - footer: RowLayout { - id: rowLayout - spacing: 20 + footer: GridLayout { + columnSpacing: 20 + columns: 3 + + Label { + id: fileNameLabel + text: qsTr("File name") + visible: false + + Layout.topMargin: 12 + Layout.leftMargin: 20 + } + + TextField { + id: fileNameTextField + objectName: "fileNameTextField" + text: control.fileName + visible: false + + Layout.topMargin: 12 + Layout.fillWidth: true + } + + Label { + text: qsTr("Filter") + + Layout.row: 1 + Layout.topMargin: fileNameTextField.visible ? 0 : 12 + Layout.leftMargin: 20 + } ComboBox { id: nameFiltersComboBox model: control.nameFilters + flat: true - Layout.leftMargin: 20 + verticalPadding: 0 + topInset: 0 + bottomInset: 0 + Layout.topMargin: fileNameTextField.visible ? 0 : 12 Layout.fillWidth: true } @@ -114,9 +147,13 @@ FileDialogImpl { id: buttonBox standardButtons: control.standardButtons spacing: 12 - horizontalPadding: 0 - verticalPadding: 20 + padding: 0 + topInset: 0 + bottomInset: 0 + Layout.row: 1 + Layout.column: 2 + Layout.topMargin: fileNameTextField.visible ? 0 : 12 Layout.rightMargin: 20 } } diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qml/+Universal/FileDialog.qml b/src/quickdialogs2/quickdialogs2quickimpl/qml/+Universal/FileDialog.qml index a22d7884e6..c029b06293 100644 --- a/src/quickdialogs2/quickdialogs2quickimpl/qml/+Universal/FileDialog.qml +++ b/src/quickdialogs2/quickdialogs2quickimpl/qml/+Universal/FileDialog.qml @@ -34,6 +34,8 @@ FileDialogImpl { FileDialogImpl.nameFiltersComboBox: nameFiltersComboBox FileDialogImpl.fileDialogListView: fileDialogListView FileDialogImpl.breadcrumbBar: breadcrumbBar + FileDialogImpl.fileNameLabel: fileNameLabel + FileDialogImpl.fileNameTextField: fileNameTextField background: Rectangle { implicitWidth: 600 @@ -100,15 +102,40 @@ FileDialogImpl { } } - footer: RowLayout { - id: rowLayout - spacing: 24 + footer: GridLayout { + columnSpacing: 24 + columns: 3 + + Label { + id: fileNameLabel + text: qsTr("File name") + visible: false + + Layout.leftMargin: 24 + } + + TextField { + id: fileNameTextField + objectName: "fileNameTextField" + text: control.fileName + visible: false + + Layout.fillWidth: true + } + + Label { + text: qsTr("Filter") + + Layout.row: 1 + Layout.column: 0 + Layout.leftMargin: 24 + Layout.bottomMargin: 24 + } ComboBox { id: nameFiltersComboBox model: control.nameFilters - Layout.leftMargin: 24 Layout.fillWidth: true Layout.topMargin: 6 Layout.bottomMargin: 24 diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qml/FileDialog.qml b/src/quickdialogs2/quickdialogs2quickimpl/qml/FileDialog.qml index 75aaa10b74..0f25dee35b 100644 --- a/src/quickdialogs2/quickdialogs2quickimpl/qml/FileDialog.qml +++ b/src/quickdialogs2/quickdialogs2quickimpl/qml/FileDialog.qml @@ -46,6 +46,8 @@ FileDialogImpl { FileDialogImpl.nameFiltersComboBox: nameFiltersComboBox FileDialogImpl.fileDialogListView: fileDialogListView FileDialogImpl.breadcrumbBar: breadcrumbBar + FileDialogImpl.fileNameLabel: fileNameLabel + FileDialogImpl.fileNameTextField: fileNameTextField background: Rectangle { implicitWidth: 600 @@ -110,21 +112,48 @@ FileDialogImpl { footer: Rectangle { color: control.palette.light - implicitWidth: rowLayout.implicitWidth - implicitHeight: rowLayout.implicitHeight + implicitWidth: gridLayout.implicitWidth + implicitHeight: gridLayout.implicitHeight + 12 - RowLayout { - id: rowLayout - width: parent.width - height: parent.height - spacing: 20 + GridLayout { + // OK to use IDs here, since users shouldn't be overriding this stuff. + id: gridLayout + anchors.fill: parent + anchors.topMargin: 6 + anchors.bottomMargin: 6 + columnSpacing: 20 + columns: 3 - ComboBox { - // OK to use IDs here, since users shouldn't be overriding this stuff. - id: nameFiltersComboBox - model: control.nameFilters + Label { + id: fileNameLabel + text: qsTr("File name") + visible: false Layout.leftMargin: 20 + } + + TextField { + id: fileNameTextField + objectName: "fileNameTextField" + text: control.fileName + visible: false + + Layout.fillWidth: true + } + + Label { + text: qsTr("Filter") + + Layout.row: 1 + Layout.column: 0 + Layout.leftMargin: 20 + } + + ComboBox { + id: nameFiltersComboBox + model: control.nameFilters + verticalPadding: 0 + Layout.fillWidth: true } @@ -133,9 +162,10 @@ FileDialogImpl { standardButtons: control.standardButtons palette.window: control.palette.light spacing: 12 - horizontalPadding: 0 - verticalPadding: 20 + padding: 0 + Layout.row: 1 + Layout.column: 2 Layout.rightMargin: 20 } } diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qquickfiledialogimpl.cpp b/src/quickdialogs2/quickdialogs2quickimpl/qquickfiledialogimpl.cpp index 72c4ca8336..a490ba1ebc 100644 --- a/src/quickdialogs2/quickdialogs2quickimpl/qquickfiledialogimpl.cpp +++ b/src/quickdialogs2/quickdialogs2quickimpl/qquickfiledialogimpl.cpp @@ -366,6 +366,12 @@ void QQuickFileDialogImpl::setOptions(const QSharedPointer & if (d->options) { d->selectedNameFilter->setOptions(options); d->setNameFilters(options->nameFilters()); + + if (auto attached = d->attachedOrWarn()) { + const bool isSaveMode = d->options->fileMode() == QFileDialogOptions::AnyFile; + attached->fileNameLabel()->setVisible(isSaveMode); + attached->fileNameTextField()->setVisible(isSaveMode); + } } } @@ -451,6 +457,19 @@ void QQuickFileDialogImpl::selectNameFilter(const QString &filter) emit filterSelected(filter); } +QString QQuickFileDialogImpl::fileName() const +{ + return selectedFile().fileName(); +} +void QQuickFileDialogImpl::setFileName(const QString &fileName) +{ + const QString previous = selectedFile().fileName(); + if (previous == fileName) + return; + + setSelectedFile(QUrl(currentFolder().path() + u'/' + fileName)); +} + void QQuickFileDialogImpl::componentComplete() { Q_D(QQuickFileDialogImpl); @@ -554,6 +573,15 @@ void QQuickFileDialogImplAttachedPrivate::fileDialogListViewCurrentIndexChanged( } } +void QQuickFileDialogImplAttachedPrivate::fileNameChangedByUser() +{ + auto fileDialogImpl = qobject_cast(parent); + if (!fileDialogImpl) + return; + + fileDialogImpl->setFileName(fileNameTextField->text()); +} + QQuickFileDialogImplAttached::QQuickFileDialogImplAttached(QObject *parent) : QObject(*(new QQuickFileDialogImplAttachedPrivate), parent) { @@ -683,6 +711,48 @@ void QQuickFileDialogImplAttached::setBreadcrumbBar(QQuickFolderBreadcrumbBar *b emit breadcrumbBarChanged(); } +QQuickLabel *QQuickFileDialogImplAttached::fileNameLabel() const +{ + Q_D(const QQuickFileDialogImplAttached); + return d->fileNameLabel; +} + +void QQuickFileDialogImplAttached::setFileNameLabel(QQuickLabel *fileNameLabel) +{ + Q_D(QQuickFileDialogImplAttached); + if (fileNameLabel == d->fileNameLabel) + return; + + d->fileNameLabel = fileNameLabel; + + emit fileNameLabelChanged(); +} + +QQuickTextField *QQuickFileDialogImplAttached::fileNameTextField() const +{ + Q_D(const QQuickFileDialogImplAttached); + return d->fileNameTextField; +} + +void QQuickFileDialogImplAttached::setFileNameTextField(QQuickTextField *fileNameTextField) +{ + Q_D(QQuickFileDialogImplAttached); + if (fileNameTextField == d->fileNameTextField) + return; + + if (d->fileNameTextField) + QObjectPrivate::disconnect(d->fileNameTextField, &QQuickTextField::editingFinished, + d, &QQuickFileDialogImplAttachedPrivate::fileNameChangedByUser); + + d->fileNameTextField = fileNameTextField; + + if (d->fileNameTextField) + QObjectPrivate::connect(d->fileNameTextField, &QQuickTextField::editingFinished, + d, &QQuickFileDialogImplAttachedPrivate::fileNameChangedByUser); + + emit fileNameTextFieldChanged(); +} + QT_END_NAMESPACE #include "moc_qquickfiledialogimpl_p.cpp" diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qquickfiledialogimpl_p.h b/src/quickdialogs2/quickdialogs2quickimpl/qquickfiledialogimpl_p.h index dca4450de1..4c64c5e4dd 100644 --- a/src/quickdialogs2/quickdialogs2quickimpl/qquickfiledialogimpl_p.h +++ b/src/quickdialogs2/quickdialogs2quickimpl/qquickfiledialogimpl_p.h @@ -24,6 +24,8 @@ QT_BEGIN_NAMESPACE class QQuickComboBox; class QQuickDialogButtonBox; +class QQuickTextField; +class QQuickLabel; class QQuickFileDialogImplAttached; class QQuickFileDialogImplAttachedPrivate; @@ -38,6 +40,7 @@ class Q_QUICKDIALOGS2QUICKIMPL_PRIVATE_EXPORT QQuickFileDialogImpl : public QQui Q_PROPERTY(QUrl selectedFile READ selectedFile WRITE setSelectedFile NOTIFY selectedFileChanged FINAL) Q_PROPERTY(QStringList nameFilters READ nameFilters NOTIFY nameFiltersChanged FINAL) Q_PROPERTY(QQuickFileNameFilter *selectedNameFilter READ selectedNameFilter CONSTANT) + Q_PROPERTY(QString fileName READ fileName WRITE setFileName NOTIFY selectedFileChanged FINAL) QML_NAMED_ELEMENT(FileDialogImpl) QML_ATTACHED(QQuickFileDialogImplAttached) QML_ADDED_IN_VERSION(6, 2) @@ -74,6 +77,9 @@ public: void setAcceptLabel(const QString &label); void setRejectLabel(const QString &label); + QString fileName() const; + void setFileName(const QString &fileName); + public Q_SLOTS: void selectNameFilter(const QString &filter); @@ -99,8 +105,12 @@ class Q_QUICKDIALOGS2QUICKIMPL_PRIVATE_EXPORT QQuickFileDialogImplAttached : pub Q_PROPERTY(QQuickComboBox *nameFiltersComboBox READ nameFiltersComboBox WRITE setNameFiltersComboBox NOTIFY nameFiltersComboBoxChanged) Q_PROPERTY(QQuickListView *fileDialogListView READ fileDialogListView WRITE setFileDialogListView NOTIFY fileDialogListViewChanged) Q_PROPERTY(QQuickFolderBreadcrumbBar *breadcrumbBar READ breadcrumbBar WRITE setBreadcrumbBar NOTIFY breadcrumbBarChanged) + Q_PROPERTY(QQuickLabel *fileNameLabel READ fileNameLabel WRITE setFileNameLabel NOTIFY fileNameLabelChanged FINAL) + Q_PROPERTY(QQuickTextField *fileNameTextField READ fileNameTextField WRITE setFileNameTextField NOTIFY fileNameTextFieldChanged FINAL) Q_MOC_INCLUDE() Q_MOC_INCLUDE() + Q_MOC_INCLUDE() + Q_MOC_INCLUDE() public: explicit QQuickFileDialogImplAttached(QObject *parent = nullptr); @@ -120,11 +130,19 @@ public: QQuickFolderBreadcrumbBar *breadcrumbBar() const; void setBreadcrumbBar(QQuickFolderBreadcrumbBar *breadcrumbBar); + QQuickLabel *fileNameLabel() const; + void setFileNameLabel(QQuickLabel *fileNameLabel); + + QQuickTextField *fileNameTextField() const; + void setFileNameTextField(QQuickTextField *fileNameTextField); + Q_SIGNALS: void buttonBoxChanged(); void nameFiltersComboBoxChanged(); void fileDialogListViewChanged(); void breadcrumbBarChanged(); + void fileNameLabelChanged(); + void fileNameTextFieldChanged(); private: Q_DISABLE_COPY(QQuickFileDialogImplAttached) diff --git a/src/quickdialogs2/quickdialogs2quickimpl/qquickfiledialogimpl_p_p.h b/src/quickdialogs2/quickdialogs2quickimpl/qquickfiledialogimpl_p_p.h index 82bef2e8ba..6f3cc55e86 100644 --- a/src/quickdialogs2/quickdialogs2quickimpl/qquickfiledialogimpl_p_p.h +++ b/src/quickdialogs2/quickdialogs2quickimpl/qquickfiledialogimpl_p_p.h @@ -18,6 +18,8 @@ #include #include #include +#include +#include #include "qquickfiledialogimpl_p.h" @@ -68,6 +70,7 @@ class QQuickFileDialogImplAttachedPrivate : public QObjectPrivate { void nameFiltersComboBoxItemActivated(int index); void fileDialogListViewCurrentIndexChanged(); + void fileNameChangedByUser(); public: Q_DECLARE_PUBLIC(QQuickFileDialogImplAttached) @@ -76,6 +79,8 @@ public: QPointer nameFiltersComboBox; QPointer fileDialogListView; QPointer breadcrumbBar; + QPointer fileNameLabel; + QPointer fileNameTextField; }; QT_END_NAMESPACE diff --git a/tests/auto/quickdialogs/qquickfiledialogimpl/tst_qquickfiledialogimpl.cpp b/tests/auto/quickdialogs/qquickfiledialogimpl/tst_qquickfiledialogimpl.cpp index ae9db3dfd1..e7ff33eee1 100644 --- a/tests/auto/quickdialogs/qquickfiledialogimpl/tst_qquickfiledialogimpl.cpp +++ b/tests/auto/quickdialogs/qquickfiledialogimpl/tst_qquickfiledialogimpl.cpp @@ -86,6 +86,8 @@ private slots: void done(); void setSelectedFile_data(); void setSelectedFile(); + void selectNewFileViaTextField_data(); + void selectNewFileViaTextField(); private: QTemporaryDir tempDir; @@ -1370,6 +1372,48 @@ void tst_QQuickFileDialogImpl::setSelectedFile() } } +void tst_QQuickFileDialogImpl::selectNewFileViaTextField_data() +{ + fileMode_data(); +} +void tst_QQuickFileDialogImpl::selectNewFileViaTextField() +{ + QFETCH(QQuickFileDialog::FileMode, fileMode); + + // Open the dialog. + FileDialogTestHelper dialogHelper(this, "fileDialog.qml"); + dialogHelper.dialog->setFileMode(fileMode); + + if (fileMode == QQuickFileDialog::SaveFile) + dialogHelper.dialog->setSelectedFile(QUrl()); + + OPEN_QUICK_DIALOG(); + QQuickTest::qWaitForPolish(dialogHelper.window()); + + const QQuickTextField *fileNameTextField = + dialogHelper.quickDialog->findChild("fileNameTextField"); + QVERIFY(fileNameTextField); + + QVERIFY2(fileNameTextField->isVisible() == (fileMode == QQuickFileDialog::SaveFile), + "The TextField for file name should only be visible when the FileMode is 'SaveFile'"); + + if (fileMode == QQuickFileDialog::SaveFile) { + const QPoint textFieldCenterPos = + fileNameTextField->mapToScene({ fileNameTextField->width() / 2, fileNameTextField->height() / 2 }).toPoint(); + + QTest::mouseClick(dialogHelper.window(), Qt::LeftButton, Qt::NoModifier, textFieldCenterPos); + QTRY_VERIFY(fileNameTextField->hasActiveFocus()); + + const QByteArray newFileName("foo.txt"); + for (const auto &c : newFileName) + QTest::keyClick(dialogHelper.window(), c); + QTest::keyClick(dialogHelper.window(), Qt::Key_Enter); + + QTRY_COMPARE(fileNameTextField->text(), newFileName); + QCOMPARE(dialogHelper.dialog->selectedFile().fileName(), newFileName); + } +} + QTEST_MAIN(tst_QQuickFileDialogImpl) #include "tst_qquickfiledialogimpl.moc"