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 <mitch.curtis@qt.io>
This commit is contained in:
Oliver Eftevaag 2022-07-06 18:26:01 +02:00
parent 58bae53237
commit b7ae0a90c1
9 changed files with 323 additions and 32 deletions

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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

View File

@ -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
}
}

View File

@ -366,6 +366,12 @@ void QQuickFileDialogImpl::setOptions(const QSharedPointer<QFileDialogOptions> &
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<QQuickFileDialogImpl *>(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"

View File

@ -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(<QtQuickTemplates2/private/qquickdialogbuttonbox_p.h>)
Q_MOC_INCLUDE(<QtQuickTemplates2/private/qquickcombobox_p.h>)
Q_MOC_INCLUDE(<QtQuickTemplates2/private/qquicktextfield_p.h>)
Q_MOC_INCLUDE(<QtQuickTemplates2/private/qquicklabel_p.h>)
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)

View File

@ -18,6 +18,8 @@
#include <QtQuickTemplates2/private/qquickcombobox_p.h>
#include <QtQuickTemplates2/private/qquickdialog_p_p.h>
#include <QtQuickTemplates2/private/qquickdialogbuttonbox_p.h>
#include <QtQuickTemplates2/private/qquicklabel_p.h>
#include <QtQuickTemplates2/private/qquicktextfield_p.h>
#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<QQuickComboBox> nameFiltersComboBox;
QPointer<QQuickListView> fileDialogListView;
QPointer<QQuickFolderBreadcrumbBar> breadcrumbBar;
QPointer<QQuickLabel> fileNameLabel;
QPointer<QQuickTextField> fileNameTextField;
};
QT_END_NAMESPACE

View File

@ -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<QQuickTextField *>("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"