Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquickfiledialog.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
6
7#include <QtCore/qlist.h>
8#include <QtCore/qloggingcategory.h>
9#include <QtQml/qqmlfile.h>
10#include <QtQml/qqmlinfo.h>
11#if QT_CONFIG(quick_listview) && QT_CONFIG(quick_draganddrop)
12#include <QtQuickDialogs2QuickImpl/private/qquickplatformfiledialog_p.h>
13#include <QtQuickDialogs2QuickImpl/private/qquickfiledialogimpl_p.h>
14#endif
15
16#include <QtQuickDialogs2Utils/private/qquickfilenamefilter_p.h>
17
19
20using namespace Qt::StringLiterals;
21
22Q_STATIC_LOGGING_CATEGORY(lcFileDialog, "qt.quick.dialogs.filedialog")
23
24/*!
25 \qmltype FileDialog
26 \inherits Dialog
27//! \nativetype QQuickFileDialog
28 \inqmlmodule QtQuick.Dialogs
29 \since 6.2
30 \brief A file dialog.
31
32 The FileDialog type provides a QML API for file dialogs.
33
34 \image qtquickdialogs-filedialog-gtk.png {The file dialog type allow dialogs to copy the native look and feel of the platform}
35
36 To show a file dialog, construct an instance of FileDialog, set the desired
37 properties, and call \l {Dialog::}{open()}. The \l currentFolder property
38 can be used to determine the folder in which the dialog opens. The
39 \l selectedFile and \l selectedFiles properties can be used to determine
40 which file(s) are selected when the dialog opens, and are also updated
41 when the user selects a file in the dialog and when the dialog is accepted.
42
43 \snippet qtquickdialogs-filedialog.qml file
44
45 \section2 Availability
46
47 A native platform file dialog is currently available on the following platforms:
48
49 \list
50 \li Android
51 \li iOS
52 \li Linux (when running with the GTK+ platform theme)
53 \li macOS
54 \li Windows
55 \endlist
56
57 \include includes/fallback.qdocinc
58
59 \sa FolderDialog, {QtCore::}{StandardPaths}
60*/
61
62QQuickFileDialog::QQuickFileDialog(QObject *parent)
63 : QQuickAbstractDialog(QQuickDialogType::FileDialog, parent),
64 m_fileMode(OpenFile),
65 m_options(QFileDialogOptions::create()),
66 m_selectedNameFilter(nullptr)
67{
68 m_options->setFileMode(QFileDialogOptions::ExistingFile);
69 m_options->setAcceptMode(QFileDialogOptions::AcceptOpen);
70 m_options->setSupportedSchemes({u"file"_s});
71}
72
73/*!
74 \qmlproperty enumeration QtQuick.Dialogs::FileDialog::fileMode
75
76 This property holds the mode of the dialog.
77
78 Available values:
79 \value FileDialog.OpenFile The dialog is used to select an existing file (default).
80 \value FileDialog.OpenFiles The dialog is used to select multiple existing files.
81 \value FileDialog.SaveFile The dialog is used to select any file. The file does not have to exist.
82 \value FileDialog.OpenUrl The dialog is used to select an existing URL.
83 \value FileDialog.OpenUrls The dialog is used to select multiple existing URLs.
84 \value FileDialog.SaveUrl The dialog is used to select any URL. The file does not have to exist.
85*/
86QQuickFileDialog::FileMode QQuickFileDialog::fileMode() const
87{
88 return m_fileMode;
89}
90
91void QQuickFileDialog::setFileMode(FileMode mode)
92{
93 qCDebug(lcFileDialog) << "setFileMode called with" << mode;
94 if (mode == m_fileMode)
95 return;
96
97 m_options->setSupportedSchemes(QStringList());
98 switch (mode) {
99 case OpenFile:
100 m_options->setSupportedSchemes(QStringList(u"file"_s));
101 [[fallthrough]];
102 case OpenUrl:
103 m_options->setFileMode(QFileDialogOptions::ExistingFile);
104 m_options->setAcceptMode(QFileDialogOptions::AcceptOpen);
105 break;
106 case OpenFiles:
107 m_options->setSupportedSchemes(QStringList(u"file"_s));
108 [[fallthrough]];
109 case OpenUrls:
110 m_options->setFileMode(QFileDialogOptions::ExistingFiles);
111 m_options->setAcceptMode(QFileDialogOptions::AcceptOpen);
112 break;
113 case SaveFile:
114 m_options->setSupportedSchemes(QStringList(u"file"_s));
115 [[fallthrough]];
116 case SaveUrl:
117 m_options->setFileMode(QFileDialogOptions::AnyFile);
118 m_options->setAcceptMode(QFileDialogOptions::AcceptSave);
119 break;
120 default:
121 break;
122 }
123
124 m_fileMode = mode;
125 emit fileModeChanged();
126}
127
128/*!
129 \qmlproperty url QtQuick.Dialogs::FileDialog::selectedFile
130
131 This property holds the last file that was selected in the dialog.
132
133 It can be set to control the file that is selected when the dialog is
134 opened.
135
136 If there are multiple selected files, this property refers to the first
137 file.
138
139 The value of this property is updated each time the user selects a file in
140 the dialog, and when the dialog is accepted. Handle the
141 \l {Dialog::}{accepted()} signal to get the final selection.
142
143 \sa selectedFiles, {Dialog::}{accepted()}, currentFolder
144*/
145QUrl QQuickFileDialog::selectedFile() const
146{
147 return addDefaultSuffix(m_selectedFiles.value(0));
148}
149
150void QQuickFileDialog::setSelectedFile(const QUrl &selectedFile)
151{
152 setSelectedFiles({ selectedFile });
153}
154
155/*!
156 \qmlproperty list<url> QtQuick.Dialogs::FileDialog::selectedFiles
157
158 This property holds the last files that were selected in the dialog.
159
160 The value of this property is updated each time the user selects files in
161 the dialog, and when the dialog is accepted. Handle the
162 \l {Dialog::}{accepted()} signal to get the final selection.
163
164 \sa {Dialog::}{accepted()}, currentFolder
165*/
166QList<QUrl> QQuickFileDialog::selectedFiles() const
167{
168 return addDefaultSuffixes(m_selectedFiles);
169}
170
171void QQuickFileDialog::setSelectedFiles(const QList<QUrl> &selectedFiles)
172{
173 qCDebug(lcFileDialog) << "setSelectedFiles called with" << selectedFiles;
174 if (m_selectedFiles == selectedFiles)
175 return;
176
177 if ((m_fileMode == SaveFile || m_fileMode == SaveUrl) && selectedFiles.size() > 1) {
178 qmlWarning(this) << "Cannot set more than one selected file when fileMode is SaveFile";
179 return;
180 }
181
182 if (m_fileMode != SaveFile && m_fileMode != SaveUrl) {
183 for (const auto &selectedFile : selectedFiles) {
184 if ((m_fileMode == OpenUrl || m_fileMode == OpenUrls) && selectedFile.scheme() != "file"_L1)
185 // we can't test existence for non-local URLs
186 continue;
187 const QString selectedFilePath = QQmlFile::urlToLocalFileOrQrc(selectedFile);
188 if (!QFileInfo::exists(selectedFilePath)) {
189 qmlWarning(this) << "Cannot set " << selectedFilePath
190 << " as a selected file because it doesn't exist";
191 return;
192 }
193 }
194 }
195
196 const auto newFirstSelectedFile = selectedFiles.value(0);
197 const bool firstChanged = m_selectedFiles.value(0) != newFirstSelectedFile;
198 m_selectedFiles = selectedFiles;
199 m_options->setInitiallySelectedFiles(m_selectedFiles);
200 if (firstChanged) {
201 emit selectedFileChanged();
202 emit currentFileChanged();
203 }
204 emit selectedFilesChanged();
205 emit currentFilesChanged();
206}
207
208/*!
209 \qmlproperty url QtQuick.Dialogs::FileDialog::currentFile
210 \deprecated [6.3] Use \l selectedFile instead.
211
212 This property holds the currently selected file in the dialog.
213
214 \sa selectedFile, currentFiles, currentFolder
215*/
216QUrl QQuickFileDialog::currentFile() const
217{
218 return selectedFile();
219}
220
221void QQuickFileDialog::setCurrentFile(const QUrl &file)
222{
223 setSelectedFiles(QList<QUrl>() << file);
224}
225
226/*!
227 \qmlproperty list<url> QtQuick.Dialogs::FileDialog::currentFiles
228 \deprecated [6.3] Use \l selectedFiles instead.
229
230 This property holds the currently selected files in the dialog.
231
232 \sa selectedFiles, currentFile, currentFolder
233*/
234QList<QUrl> QQuickFileDialog::currentFiles() const
235{
236 return selectedFiles();
237}
238
239void QQuickFileDialog::setCurrentFiles(const QList<QUrl> &currentFiles)
240{
241 setSelectedFiles(currentFiles);
242}
243
244/*!
245 \qmlproperty url QtQuick.Dialogs::FileDialog::currentFolder
246
247 This property holds the folder where files are selected. It can be set to
248 control the initial directory that is shown when the dialog is opened.
249
250 For selecting a folder, use \l FolderDialog instead.
251*/
252QUrl QQuickFileDialog::currentFolder() const
253{
254 if (QPlatformFileDialogHelper *fileDialog = qobject_cast<QPlatformFileDialogHelper *>(handle()))
255 return fileDialog->directory();
256
257 // If we're not using a native file dialog and the folder is invalid,
258 // return the current directory.
259 if (!m_options->initialDirectory().isValid())
260 return QUrl::fromLocalFile(QDir::currentPath());
261
262 return m_options->initialDirectory();
263}
264
265void QQuickFileDialog::setCurrentFolder(const QUrl &currentFolder)
266{
267 qCDebug(lcFileDialog) << "setCurrentFolder called with" << currentFolder;
268 if (QPlatformFileDialogHelper *fileDialog = qobject_cast<QPlatformFileDialogHelper *>(handle()))
269 fileDialog->setDirectory(currentFolder);
270 m_options->setInitialDirectory(currentFolder);
271}
272
273/*!
274 \qmlproperty flags QtQuick.Dialogs::FileDialog::options
275
276 This property holds the various options that affect the look and feel of the dialog.
277
278 By default, all options are disabled.
279
280 Options should be set before showing the dialog. Setting them while the dialog is
281 visible is not guaranteed to have an immediate effect on the dialog (depending on
282 the option and on the platform).
283
284 Available options:
285 \value FileDialog.DontResolveSymlinks Don't resolve symlinks in the file dialog. By default symlinks are resolved.
286 \value FileDialog.DontConfirmOverwrite Don't ask for confirmation if an existing file is selected. By default confirmation is requested.
287 \value FileDialog.ReadOnly Indicates that the dialog doesn't allow creating directories.
288 \value FileDialog.HideNameFilterDetails Indicates if the file name filter details are hidden or not.
289 \value FileDialog.DontUseNativeDialog Forces the dialog to use a non-native quick implementation.
290*/
291QFileDialogOptions::FileDialogOptions QQuickFileDialog::options() const
292{
293 return m_options->options();
294}
295
296void QQuickFileDialog::setOptions(QFileDialogOptions::FileDialogOptions options)
297{
298 if (options == m_options->options())
299 return;
300
301 m_options->setOptions(options);
302 emit optionsChanged();
303}
304
305void QQuickFileDialog::resetOptions()
306{
307 setOptions({});
308}
309
310/*!
311 \qmlproperty list<string> QtQuick.Dialogs::FileDialog::nameFilters
312
313 This property holds the filters that restrict the types of files that
314 can be selected.
315
316 \code
317 FileDialog {
318 nameFilters: ["Text files (*.txt)", "HTML files (*.html *.htm)"]
319 }
320 \endcode
321
322 Different platforms may restrict the files that can be selected in
323 different ways. For example, macOS will disable file entries that do not
324 match the filters, whereas Windows will hide them.
325
326 \note \b{*.*} is not a portable filter, because the historical assumption
327 that the file extension determines the file type is not consistent on every
328 operating system. It is possible to have a file with no dot in its name (for
329 example, \c Makefile). In a native Windows file dialog, \b{*.*} will match
330 such files, while in other types of file dialogs it may not. So it is better
331 to use \b{*} if you mean to select any file.
332
333 \sa selectedNameFilter
334*/
335QStringList QQuickFileDialog::nameFilters() const
336{
337 return m_options->nameFilters();
338}
339
340void QQuickFileDialog::setNameFilters(const QStringList &filters)
341{
342 qCDebug(lcFileDialog).nospace() << "setNameFilters called with " << filters
343 << " (old filters were: " << m_options->nameFilters() << ")";
344 if (filters == m_options->nameFilters())
345 return;
346
347 m_options->setNameFilters(filters);
348 if (m_selectedNameFilter) {
349 int index = m_selectedNameFilter->index();
350 if (index < 0 || index >= filters.size())
351 index = 0;
352 m_selectedNameFilter->update(filters.value(index));
353 }
354 emit nameFiltersChanged();
355}
356
357void QQuickFileDialog::resetNameFilters()
358{
359 setNameFilters(QStringList());
360}
361
362/*!
363 \qmlproperty int QtQuick.Dialogs::FileDialog::selectedNameFilter.index
364 \qmlproperty string QtQuick.Dialogs::FileDialog::selectedNameFilter.name
365 \qmlproperty list<string> QtQuick.Dialogs::FileDialog::selectedNameFilter.extensions
366 \qmlproperty list<string> QtQuick.Dialogs::FileDialog::selectedNameFilter.globs
367
368 These properties hold the currently selected name filter.
369
370 \table
371 \header
372 \li Name
373 \li Description
374 \row
375 \li \b index : int
376 \li This property determines which \l {nameFilters}{name filter} is selected.
377 The specified filter is selected when the dialog is opened. The value is
378 updated when the user selects another filter.
379 \row
380 \li [read-only] \b name : string
381 \li This property holds the name of the selected filter. In the
382 example below, the name of the first filter is \c {"Text files"}
383 and the second is \c {"HTML files"}.
384 \row
385 \li [read-only] \b extensions : list<string>
386 \li This property holds the list of extensions of the selected filter.
387 In the example below, the list of extensions of the first filter is
388 \c {["txt"]} and the second is \c {["html", "htm"]}.
389 \row
390 \li [read-only] \b globs : list<string>
391 \li This property holds the list of globs of the selected filter.
392 In the example below, the list of globs of the first filter is
393 \c {["*.txt"]} and the second is \c {["*.html", "*.htm"]}.
394
395 This property is useful in conjunction with \l {FolderListModel}'s
396 \l {FolderListModel::}{nameFilters} property, for example.
397 \endtable
398
399 \code
400 FileDialog {
401 id: fileDialog
402 selectedNameFilter.index: 1
403 nameFilters: ["Text files (*.txt)", "HTML files (*.html *.htm)"]
404 }
405
406 MyDocument {
407 id: document
408 fileType: fileDialog.selectedNameFilter.extensions[0]
409 }
410 \endcode
411
412 \sa nameFilters
413*/
414QQuickFileNameFilter *QQuickFileDialog::selectedNameFilter() const
415{
416 if (!m_selectedNameFilter) {
417 QQuickFileDialog *that = const_cast<QQuickFileDialog *>(this);
418 m_selectedNameFilter = new QQuickFileNameFilter(that);
419 m_selectedNameFilter->setOptions(m_options);
420 }
421 return m_selectedNameFilter;
422}
423
424/*!
425 \qmlproperty string QtQuick.Dialogs::FileDialog::defaultSuffix
426
427 This property holds a suffix that is added to selected files that have
428 no suffix specified. The suffix is typically used to indicate the file
429 type (e.g. "txt" indicates a text file).
430
431 If the first character is a dot ('.'), it is removed.
432*/
433QString QQuickFileDialog::defaultSuffix() const
434{
435 return m_options->defaultSuffix();
436}
437
438void QQuickFileDialog::setDefaultSuffix(const QString &suffix)
439{
440 if (suffix == m_options->defaultSuffix())
441 return;
442
443 m_options->setDefaultSuffix(suffix);
444 emit defaultSuffixChanged();
445}
446
447void QQuickFileDialog::resetDefaultSuffix()
448{
449 setDefaultSuffix(QString());
450}
451
452/*!
453 \qmlproperty string QtQuick.Dialogs::FileDialog::acceptLabel
454
455 This property holds the label text shown on the button that accepts the dialog.
456
457 When set to an empty string, the default label of the underlying platform is used.
458 The default label is typically \uicontrol Open or \uicontrol Save depending on which
459 \l fileMode the dialog is used in.
460
461 The default value is an empty string.
462
463 \sa rejectLabel
464*/
465QString QQuickFileDialog::acceptLabel() const
466{
467 return m_options->labelText(QFileDialogOptions::Accept);
468}
469
470void QQuickFileDialog::setAcceptLabel(const QString &label)
471{
472 if (label == m_options->labelText(QFileDialogOptions::Accept))
473 return;
474
475 m_options->setLabelText(QFileDialogOptions::Accept, label);
476 emit acceptLabelChanged();
477}
478
479void QQuickFileDialog::resetAcceptLabel()
480{
481 setAcceptLabel(QString());
482}
483
484/*!
485 \qmlproperty string QtQuick.Dialogs::FileDialog::rejectLabel
486
487 This property holds the label text shown on the button that rejects the dialog.
488
489 When set to an empty string, the default label of the underlying platform is used.
490 The default label is typically \uicontrol Cancel.
491
492 The default value is an empty string.
493
494 \sa acceptLabel
495*/
496QString QQuickFileDialog::rejectLabel() const
497{
498 return m_options->labelText(QFileDialogOptions::Reject);
499}
500
501void QQuickFileDialog::setRejectLabel(const QString &label)
502{
503 if (label == m_options->labelText(QFileDialogOptions::Reject))
504 return;
505
506 m_options->setLabelText(QFileDialogOptions::Reject, label);
507 emit rejectLabelChanged();
508}
509
510void QQuickFileDialog::resetRejectLabel()
511{
512 setRejectLabel(QString());
513}
514
515bool QQuickFileDialog::useNativeDialog() const
516{
517 if (!QQuickAbstractDialog::useNativeDialog())
518 return false;
519
520 if (m_options->testOption(QFileDialogOptions::DontUseNativeDialog)) {
521 qCDebug(lcDialogs) << " - the FileDialog was told not to use a native dialog; not using native dialog";
522 return false;
523 }
524
525 return true;
526}
527
528void QQuickFileDialog::onCreate(QPlatformDialogHelper *dialog)
529{
530 if (QPlatformFileDialogHelper *fileDialog = qobject_cast<QPlatformFileDialogHelper *>(dialog)) {
531 connect(fileDialog, &QPlatformFileDialogHelper::currentChanged,
532 this, [this, fileDialog](){ setSelectedFiles(fileDialog->selectedFiles()); });
533 connect(fileDialog, &QPlatformFileDialogHelper::directoryEntered, this, &QQuickFileDialog::currentFolderChanged);
534 fileDialog->setOptions(m_options);
535
536 // If the user didn't set an initial selectedFile, ensure that we are synced
537 // with the underlying dialog in case it has set an initially selected file
538 // (as QQuickFileDialogImplPrivate::updateSelectedFile does).
539 if (m_options->initiallySelectedFiles().isEmpty()) {
540 const auto selectedFiles = fileDialog->selectedFiles();
541 if (!selectedFiles.isEmpty())
542 setSelectedFiles(selectedFiles);
543 }
544 }
545}
546
547void QQuickFileDialog::onShow(QPlatformDialogHelper *dialog)
548{
549 m_options->setWindowTitle(title());
550 if (QPlatformFileDialogHelper *fileDialog = qobject_cast<QPlatformFileDialogHelper *>(dialog)) {
551 // Ensure that a name filter is always selected.
552 int index = selectedNameFilter()->index();
553 if (index == -1)
554 index = 0;
555 const QString filter = m_options->nameFilters().value(index);
556 m_options->setInitiallySelectedNameFilter(filter);
557
558 fileDialog->setOptions(m_options); // setOptions only assigns a member and isn't virtual
559
560 connect(fileDialog, &QPlatformFileDialogHelper::filterSelected, m_selectedNameFilter, &QQuickFileNameFilter::update);
561 fileDialog->selectNameFilter(filter);
562
563 // If both selectedFile and currentFolder are set, prefer the former.
564 if (!m_options->initiallySelectedFiles().isEmpty()) {
565 // The user set an initial selectedFile.
566 const QUrl selectedFile = m_options->initiallySelectedFiles().first();
567 fileDialog->selectFile(selectedFile);
568 } else {
569 // The user set an initial currentFolder.
570 const QUrl initialDir = m_options->initialDirectory();
571 // If it's not valid, or it's a file and not a directory, we shouldn't set it.
572 if (m_firstShow && initialDir.isValid() && QDir(QQmlFile::urlToLocalFileOrQrc(initialDir)).exists())
573 fileDialog->setDirectory(m_options->initialDirectory());
574 }
575 }
576#if QT_CONFIG(quick_listview) && QT_CONFIG(quick_draganddrop)
577 if (QQuickPlatformFileDialog *fileDialog = qobject_cast<QQuickPlatformFileDialog *>(dialog); fileDialog && fileDialog->dialog())
578 fileDialog->dialog()->setPopupType(m_popupType);
579#endif
580
581 QQuickAbstractDialog::onShow(dialog);
582}
583
584void QQuickFileDialog::onHide(QPlatformDialogHelper *dialog)
585{
586 if (QPlatformFileDialogHelper *fileDialog = qobject_cast<QPlatformFileDialogHelper *>(dialog)) {
587 if (m_selectedNameFilter)
588 disconnect(fileDialog, &QPlatformFileDialogHelper::filterSelected, m_selectedNameFilter, &QQuickFileNameFilter::update);
589 }
590}
591
592QUrl QQuickFileDialog::addDefaultSuffix(const QUrl &file) const
593{
594 QUrl url = file;
595 const QString path = url.path();
596 const QString suffix = m_options->defaultSuffix();
597 // Urls with "content" scheme do not require suffixes. Such schemes are
598 // used on Android.
599 const bool isContentScheme = url.scheme() == u"content"_s;
600 if (!isContentScheme && !suffix.isEmpty() && !path.endsWith(QLatin1Char('/'))
601 && path.lastIndexOf(QLatin1Char('.')) == -1) {
602 url.setPath(path + QLatin1Char('.') + suffix);
603 }
604 return url;
605}
606
607void QQuickFileDialog::accept()
608{
609 if (QPlatformFileDialogHelper *fileDialog = qobject_cast<QPlatformFileDialogHelper *>(handle())) {
610 // Take the currently selected files and make them the final set of files.
611 setSelectedFiles(fileDialog->selectedFiles());
612 }
613 QQuickAbstractDialog::accept();
614}
615
616QList<QUrl> QQuickFileDialog::addDefaultSuffixes(const QList<QUrl> &files) const
617{
618 QList<QUrl> urls;
619 urls.reserve(files.size());
620 for (const QUrl &file : files)
621 urls += addDefaultSuffix(file);
622 return urls;
623}
624
625QT_END_NAMESPACE
626
627#include "moc_qquickfiledialog_p.cpp"
Combined button and popup list for selecting options.
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)