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