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