1// Copyright (C) 2017 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
4#include "qquicklabsplatformfiledialog_p.h"
5
6#if QT_DEPRECATED_SINCE(6, 9)
7
8#include <QtCore/qlist.h>
9
10QT_BEGIN_NAMESPACE
11
12using namespace Qt::StringLiterals;
13
14/*!
15 \qmltype FileDialog
16 \inherits Dialog
17//! \nativetype QQuickLabsPlatformFileDialog
18 \inqmlmodule Qt.labs.platform
19 \since 5.8
20 \deprecated [6.9] Use QtQuick.Dialogs::FileDialog instead.
21 \brief A native file dialog.
22
23 The FileDialog type provides a QML API for native platform file dialogs.
24
25 \image {qtlabsplatform-filedialog-gtk.png} {A native file dialog}
26
27 To show a file dialog, construct an instance of FileDialog, set the
28 desired properties, and call \l {Dialog::}{open()}. The \l currentFile
29 or \l currentFiles properties can be used to determine the currently
30 selected file(s) in the dialog. The \l file and \l files properties
31 are updated only after the final selection has been made by accepting
32 the dialog.
33
34 \code
35 MenuItem {
36 text: "Open..."
37 onTriggered: fileDialog.open()
38 }
39
40 FileDialog {
41 id: fileDialog
42 currentFile: document.source
43 folder: StandardPaths.writableLocation(StandardPaths.DocumentsLocation)
44 }
45
46 MyDocument {
47 id: document
48 source: fileDialog.file
49 }
50 \endcode
51
52 \section2 Availability
53
54 A native platform file dialog is currently available on the following platforms:
55
56 \list
57 \li Android
58 \li iOS
59 \li Linux (when running with the GTK+ platform theme)
60 \li macOS
61 \li Windows
62 \endlist
63
64 \input includes/widgets.qdocinc 1
65
66 \labs
67
68 \sa QtQuick.Dialogs::FileDialog, FolderDialog, StandardPaths
69*/
70
71QQuickLabsPlatformFileDialog::QQuickLabsPlatformFileDialog(QObject *parent)
72 : QQuickLabsPlatformDialog(QPlatformTheme::FileDialog, parent),
73 m_fileMode(OpenFile),
74 m_options(QFileDialogOptions::create()),
75 m_selectedNameFilter(nullptr)
76{
77 m_options->setFileMode(QFileDialogOptions::ExistingFile);
78 m_options->setAcceptMode(QFileDialogOptions::AcceptOpen);
79}
80
81/*!
82 \qmlproperty enumeration Qt.labs.platform::FileDialog::fileMode
83
84 This property holds the mode of the dialog.
85
86 Available values:
87 \value FileDialog.OpenFile The dialog is used to select an existing file (default).
88 \value FileDialog.OpenFiles The dialog is used to select multiple existing files.
89 \value FileDialog.SaveFile The dialog is used to select any file. The file does not have to exist.
90*/
91QQuickLabsPlatformFileDialog::FileMode QQuickLabsPlatformFileDialog::fileMode() const
92{
93 return m_fileMode;
94}
95
96void QQuickLabsPlatformFileDialog::setFileMode(FileMode mode)
97{
98 if (mode == m_fileMode)
99 return;
100
101 switch (mode) {
102 case OpenFile:
103 m_options->setFileMode(QFileDialogOptions::ExistingFile);
104 m_options->setAcceptMode(QFileDialogOptions::AcceptOpen);
105 break;
106 case OpenFiles:
107 m_options->setFileMode(QFileDialogOptions::ExistingFiles);
108 m_options->setAcceptMode(QFileDialogOptions::AcceptOpen);
109 break;
110 case SaveFile:
111 m_options->setFileMode(QFileDialogOptions::AnyFile);
112 m_options->setAcceptMode(QFileDialogOptions::AcceptSave);
113 break;
114 default:
115 break;
116 }
117
118 m_fileMode = mode;
119 emit fileModeChanged();
120}
121
122/*!
123 \qmlproperty url Qt.labs.platform::FileDialog::file
124
125 This property holds the final accepted file.
126
127 Unlike the \l currentFile property, the \c file property is not updated
128 while the user is selecting files in the dialog, but only after the final
129 selection has been made. That is, when the user has clicked \uicontrol OK
130 to accept a file. Alternatively, the \l {Dialog::}{accepted()} signal
131 can be handled to get the final selection.
132
133 \sa currentFile, {Dialog::}{accepted()}
134*/
135QUrl QQuickLabsPlatformFileDialog::file() const
136{
137 return addDefaultSuffix(file: m_files.value(i: 0));
138}
139
140void QQuickLabsPlatformFileDialog::setFile(const QUrl &file)
141{
142 setFiles(QList<QUrl>() << file);
143}
144
145/*!
146 \qmlproperty list<url> Qt.labs.platform::FileDialog::files
147
148 This property holds the final accepted files.
149
150 Unlike the \l currentFiles property, the \c files property is not updated
151 while the user is selecting files in the dialog, but only after the final
152 selection has been made. That is, when the user has clicked \uicontrol OK
153 to accept files. Alternatively, the \l {Dialog::}{accepted()} signal
154 can be handled to get the final selection.
155
156 \sa currentFiles, {Dialog::}{accepted()}
157*/
158QList<QUrl> QQuickLabsPlatformFileDialog::files() const
159{
160 return addDefaultSuffixes(files: m_files);
161}
162
163void QQuickLabsPlatformFileDialog::setFiles(const QList<QUrl> &files)
164{
165 if (m_files == files)
166 return;
167
168 bool firstChanged = m_files.value(i: 0) != files.value(i: 0);
169 m_files = files;
170 if (firstChanged)
171 emit fileChanged();
172 emit filesChanged();
173}
174
175/*!
176 \qmlproperty url Qt.labs.platform::FileDialog::currentFile
177
178 This property holds the currently selected file in the dialog.
179
180 Unlike the \l file property, the \c currentFile property is updated
181 while the user is selecting files in the dialog, even before the final
182 selection has been made.
183
184 \sa file, currentFiles
185*/
186QUrl QQuickLabsPlatformFileDialog::currentFile() const
187{
188 return currentFiles().value(i: 0);
189}
190
191void QQuickLabsPlatformFileDialog::setCurrentFile(const QUrl &file)
192{
193 setCurrentFiles(QList<QUrl>() << file);
194}
195
196/*!
197 \qmlproperty list<url> Qt.labs.platform::FileDialog::currentFiles
198
199 This property holds the currently selected files in the dialog.
200
201 Unlike the \l files property, the \c currentFiles property is updated
202 while the user is selecting files in the dialog, even before the final
203 selection has been made.
204
205 \sa files, currentFile
206*/
207QList<QUrl> QQuickLabsPlatformFileDialog::currentFiles() const
208{
209 if (QPlatformFileDialogHelper *fileDialog = qobject_cast<QPlatformFileDialogHelper *>(object: handle()))
210 return fileDialog->selectedFiles();
211 return m_options->initiallySelectedFiles();
212}
213
214void QQuickLabsPlatformFileDialog::setCurrentFiles(const QList<QUrl> &files)
215{
216 if (QPlatformFileDialogHelper *fileDialog = qobject_cast<QPlatformFileDialogHelper *>(object: handle())) {
217 for (const QUrl &file : files)
218 fileDialog->selectFile(filename: file);
219 }
220 m_options->setInitiallySelectedFiles(files);
221}
222
223/*!
224 \qmlproperty url Qt.labs.platform::FileDialog::folder
225
226 This property holds the folder where files are selected.
227 For selecting a folder, use FolderDialog instead.
228
229 \sa FolderDialog
230*/
231QUrl QQuickLabsPlatformFileDialog::folder() const
232{
233 if (QPlatformFileDialogHelper *fileDialog = qobject_cast<QPlatformFileDialogHelper *>(object: handle()))
234 return fileDialog->directory();
235 return m_options->initialDirectory();
236}
237
238void QQuickLabsPlatformFileDialog::setFolder(const QUrl &folder)
239{
240 if (QPlatformFileDialogHelper *fileDialog = qobject_cast<QPlatformFileDialogHelper *>(object: handle()))
241 fileDialog->setDirectory(folder);
242 m_options->setInitialDirectory(folder);
243}
244
245/*!
246 \qmlproperty flags Qt.labs.platform::FileDialog::options
247
248 This property holds the various options that affect the look and feel of the dialog.
249
250 By default, all options are disabled.
251
252 Options should be set before showing the dialog. Setting them while the dialog is
253 visible is not guaranteed to have an immediate effect on the dialog (depending on
254 the option and on the platform).
255
256 Available options:
257 \value FileDialog.DontResolveSymlinks Don't resolve symlinks in the file dialog. By default symlinks are resolved.
258 \value FileDialog.DontConfirmOverwrite Don't ask for confirmation if an existing file is selected. By default confirmation is requested.
259 \value FileDialog.ReadOnly Indicates that the dialog doesn't allow creating directories.
260 \value FileDialog.HideNameFilterDetails Indicates if the file name filter details are hidden or not.
261*/
262QFileDialogOptions::FileDialogOptions QQuickLabsPlatformFileDialog::options() const
263{
264 return m_options->options();
265}
266
267void QQuickLabsPlatformFileDialog::setOptions(QFileDialogOptions::FileDialogOptions options)
268{
269 if (options == m_options->options())
270 return;
271
272 m_options->setOptions(options);
273 emit optionsChanged();
274}
275
276void QQuickLabsPlatformFileDialog::resetOptions()
277{
278 setOptions({});
279}
280
281/*!
282 \qmlproperty list<string> Qt.labs.platform::FileDialog::nameFilters
283
284 This property holds the filters that restrict the types of files that
285 can be selected.
286
287 \code
288 FileDialog {
289 nameFilters: ["Text files (*.txt)", "HTML files (*.html *.htm)"]
290 }
291 \endcode
292
293 \note \b{*.*} is not a portable filter, because the historical assumption
294 that the file extension determines the file type is not consistent on every
295 operating system. It is possible to have a file with no dot in its name (for
296 example, \c Makefile). In a native Windows file dialog, \b{*.*} will match
297 such files, while in other types of file dialogs it may not. So it is better
298 to use \b{*} if you mean to select any file.
299
300 \sa selectedNameFilter
301*/
302QStringList QQuickLabsPlatformFileDialog::nameFilters() const
303{
304 return m_options->nameFilters();
305}
306
307void QQuickLabsPlatformFileDialog::setNameFilters(const QStringList &filters)
308{
309 if (filters == m_options->nameFilters())
310 return;
311
312 m_options->setNameFilters(filters);
313 if (m_selectedNameFilter) {
314 int index = m_selectedNameFilter->index();
315 if (index < 0 || index >= filters.size())
316 index = 0;
317 m_selectedNameFilter->update(filter: filters.value(i: index));
318 }
319 emit nameFiltersChanged();
320}
321
322void QQuickLabsPlatformFileDialog::resetNameFilters()
323{
324 setNameFilters(QStringList());
325}
326
327/*!
328 \qmlproperty int Qt.labs.platform::FileDialog::selectedNameFilter.index
329 \qmlproperty string Qt.labs.platform::FileDialog::selectedNameFilter.name
330 \qmlproperty list<string> Qt.labs.platform::FileDialog::selectedNameFilter.extensions
331
332 These properties hold the currently selected name filter.
333
334 \table
335 \header
336 \li Name
337 \li Description
338 \row
339 \li \b index : int
340 \li This property determines which \l {nameFilters}{name filter} is selected.
341 The specified filter is selected when the dialog is opened. The value is
342 updated when the user selects another filter.
343 \row
344 \li [read-only] \b name : string
345 \li This property holds the name of the selected filter. In the
346 example below, the name of the first filter is \c {"Text files"}
347 and the second is \c {"HTML files"}.
348 \row
349 \li [read-only] \b extensions : list<string>
350 \li This property holds the list of extensions of the selected filter.
351 In the example below, the list of extensions of the first filter is
352 \c {["txt"]} and the second is \c {["html", "htm"]}.
353 \endtable
354
355 \code
356 FileDialog {
357 id: fileDialog
358 selectedNameFilter.index: 1
359 nameFilters: ["Text files (*.txt)", "HTML files (*.html *.htm)"]
360 }
361
362 MyDocument {
363 id: document
364 fileType: fileDialog.selectedNameFilter.extensions[0]
365 }
366 \endcode
367
368 \sa nameFilters
369*/
370QQuickLabsPlatformFileNameFilter *QQuickLabsPlatformFileDialog::selectedNameFilter() const
371{
372 if (!m_selectedNameFilter) {
373 QQuickLabsPlatformFileDialog *that = const_cast<QQuickLabsPlatformFileDialog *>(this);
374 m_selectedNameFilter = new QQuickLabsPlatformFileNameFilter(that);
375 m_selectedNameFilter->setOptions(m_options);
376 }
377 return m_selectedNameFilter;
378}
379
380/*!
381 \qmlproperty string Qt.labs.platform::FileDialog::defaultSuffix
382
383 This property holds a suffix that is added to selected files that have
384 no suffix specified. The suffix is typically used to indicate the file
385 type (e.g. "txt" indicates a text file).
386
387 If the first character is a dot ('.'), it is removed.
388*/
389QString QQuickLabsPlatformFileDialog::defaultSuffix() const
390{
391 return m_options->defaultSuffix();
392}
393
394void QQuickLabsPlatformFileDialog::setDefaultSuffix(const QString &suffix)
395{
396 if (suffix == m_options->defaultSuffix())
397 return;
398
399 m_options->setDefaultSuffix(suffix);
400 emit defaultSuffixChanged();
401}
402
403void QQuickLabsPlatformFileDialog::resetDefaultSuffix()
404{
405 setDefaultSuffix(QString());
406}
407
408/*!
409 \qmlproperty string Qt.labs.platform::FileDialog::acceptLabel
410
411 This property holds the label text shown on the button that accepts the dialog.
412
413 When set to an empty string, the default label of the underlying platform is used.
414 The default label is typically \uicontrol Open or \uicontrol Save depending on which
415 \l fileMode the dialog is used in.
416
417 The default value is an empty string.
418
419 \sa rejectLabel
420*/
421QString QQuickLabsPlatformFileDialog::acceptLabel() const
422{
423 return m_options->labelText(label: QFileDialogOptions::Accept);
424}
425
426void QQuickLabsPlatformFileDialog::setAcceptLabel(const QString &label)
427{
428 if (label == m_options->labelText(label: QFileDialogOptions::Accept))
429 return;
430
431 m_options->setLabelText(label: QFileDialogOptions::Accept, text: label);
432 emit acceptLabelChanged();
433}
434
435void QQuickLabsPlatformFileDialog::resetAcceptLabel()
436{
437 setAcceptLabel(QString());
438}
439
440/*!
441 \qmlproperty string Qt.labs.platform::FileDialog::rejectLabel
442
443 This property holds the label text shown on the button that rejects the dialog.
444
445 When set to an empty string, the default label of the underlying platform is used.
446 The default label is typically \uicontrol Cancel.
447
448 The default value is an empty string.
449
450 \sa acceptLabel
451*/
452QString QQuickLabsPlatformFileDialog::rejectLabel() const
453{
454 return m_options->labelText(label: QFileDialogOptions::Reject);
455}
456
457void QQuickLabsPlatformFileDialog::setRejectLabel(const QString &label)
458{
459 if (label == m_options->labelText(label: QFileDialogOptions::Reject))
460 return;
461
462 m_options->setLabelText(label: QFileDialogOptions::Reject, text: label);
463 emit rejectLabelChanged();
464}
465
466void QQuickLabsPlatformFileDialog::resetRejectLabel()
467{
468 setRejectLabel(QString());
469}
470
471bool QQuickLabsPlatformFileDialog::useNativeDialog() const
472{
473 return QQuickLabsPlatformDialog::useNativeDialog()
474 && !m_options->testOption(option: QFileDialogOptions::DontUseNativeDialog);
475}
476
477void QQuickLabsPlatformFileDialog::onCreate(QPlatformDialogHelper *dialog)
478{
479 if (QPlatformFileDialogHelper *fileDialog = qobject_cast<QPlatformFileDialogHelper *>(object: dialog)) {
480 // TODO: emit currentFileChanged only when the first entry in currentFiles changes
481 connect(sender: fileDialog, signal: &QPlatformFileDialogHelper::currentChanged, context: this, slot: &QQuickLabsPlatformFileDialog::currentFileChanged);
482 connect(sender: fileDialog, signal: &QPlatformFileDialogHelper::currentChanged, context: this, slot: &QQuickLabsPlatformFileDialog::currentFilesChanged);
483 connect(sender: fileDialog, signal: &QPlatformFileDialogHelper::directoryEntered, context: this, slot: &QQuickLabsPlatformFileDialog::folderChanged);
484 fileDialog->setOptions(m_options);
485 }
486}
487
488void QQuickLabsPlatformFileDialog::onShow(QPlatformDialogHelper *dialog)
489{
490 m_options->setWindowTitle(title());
491 if (QPlatformFileDialogHelper *fileDialog = qobject_cast<QPlatformFileDialogHelper *>(object: dialog)) {
492 fileDialog->setOptions(m_options); // setOptions only assigns a member and isn't virtual
493 if (m_firstShow && m_options->initialDirectory().isValid())
494 fileDialog->setDirectory(m_options->initialDirectory());
495 if (m_selectedNameFilter) {
496 const int index = m_selectedNameFilter->index();
497 const QString filter = m_options->nameFilters().value(i: index);
498 m_options->setInitiallySelectedNameFilter(filter);
499 fileDialog->selectNameFilter(filter);
500 connect(sender: fileDialog, signal: &QPlatformFileDialogHelper::filterSelected, context: m_selectedNameFilter, slot: &QQuickLabsPlatformFileNameFilter::update);
501 }
502 }
503 if (m_firstShow)
504 m_firstShow = false;
505}
506
507void QQuickLabsPlatformFileDialog::onHide(QPlatformDialogHelper *dialog)
508{
509 if (QPlatformFileDialogHelper *fileDialog = qobject_cast<QPlatformFileDialogHelper *>(object: dialog)) {
510 if (m_selectedNameFilter)
511 disconnect(sender: fileDialog, signal: &QPlatformFileDialogHelper::filterSelected, receiver: m_selectedNameFilter, slot: &QQuickLabsPlatformFileNameFilter::update);
512 }
513}
514
515void QQuickLabsPlatformFileDialog::accept()
516{
517 if (QPlatformFileDialogHelper *fileDialog = qobject_cast<QPlatformFileDialogHelper *>(object: handle()))
518 setFiles(fileDialog->selectedFiles());
519 QQuickLabsPlatformDialog::accept();
520}
521
522QUrl QQuickLabsPlatformFileDialog::addDefaultSuffix(const QUrl &file) const
523{
524 QUrl url = file;
525 const QString path = url.path();
526 const QString suffix = m_options->defaultSuffix();
527 // Urls with "content" scheme do not require suffixes. Such schemes are
528 // used on Android.
529 const bool isContentScheme = url.scheme() == u"content"_s;
530 if (!isContentScheme && !suffix.isEmpty() && !path.endsWith(c: QLatin1Char('/'))
531 && path.lastIndexOf(c: QLatin1Char('.')) == -1) {
532 url.setPath(path: path + QLatin1Char('.') + suffix);
533 }
534 return url;
535}
536
537QList<QUrl> QQuickLabsPlatformFileDialog::addDefaultSuffixes(const QList<QUrl> &files) const
538{
539 QList<QUrl> urls;
540 urls.reserve(asize: files.size());
541 for (const QUrl &file : files)
542 urls += addDefaultSuffix(file);
543 return urls;
544}
545
546QQuickLabsPlatformFileNameFilter::QQuickLabsPlatformFileNameFilter(QObject *parent)
547 : QObject(parent), m_index(-1)
548{
549}
550
551int QQuickLabsPlatformFileNameFilter::index() const
552{
553 return m_index;
554}
555
556void QQuickLabsPlatformFileNameFilter::setIndex(int index)
557{
558 if (m_index == index)
559 return;
560
561 m_index = index;
562 emit indexChanged(index);
563}
564
565QString QQuickLabsPlatformFileNameFilter::name() const
566{
567 return m_name;
568}
569
570QStringList QQuickLabsPlatformFileNameFilter::extensions() const
571{
572 return m_extensions;
573}
574
575QSharedPointer<QFileDialogOptions> QQuickLabsPlatformFileNameFilter::options() const
576{
577 return m_options;
578}
579
580void QQuickLabsPlatformFileNameFilter::setOptions(const QSharedPointer<QFileDialogOptions> &options)
581{
582 m_options = options;
583}
584
585static QString extractName(const QString &filter)
586{
587 return filter.left(n: filter.indexOf(ch: QLatin1Char('(')) - 1);
588}
589
590static QString extractExtension(QStringView filter)
591{
592 return filter.mid(pos: filter.indexOf(c: QLatin1Char('.')) + 1).toString();
593}
594
595static QStringList extractExtensions(QStringView filter)
596{
597 QStringList extensions;
598 const int from = filter.indexOf(c: QLatin1Char('('));
599 const int to = filter.lastIndexOf(c: QLatin1Char(')')) - 1;
600 if (from >= 0 && from < to) {
601 const QStringView ref = filter.mid(pos: from + 1, n: to - from);
602 const QList<QStringView> exts = ref.split(sep: QLatin1Char(' '), behavior: Qt::SkipEmptyParts);
603 for (const QStringView &ref : exts)
604 extensions += extractExtension(filter: ref);
605 }
606
607 return extensions;
608}
609
610void QQuickLabsPlatformFileNameFilter::update(const QString &filter)
611{
612 const QStringList filters = nameFilters();
613
614 const int oldIndex = m_index;
615 const QString oldName = m_name;
616 const QStringList oldExtensions = m_extensions;
617
618 m_index = filters.indexOf(str: filter);
619 m_name = extractName(filter);
620 m_extensions = extractExtensions(filter);
621
622 if (oldIndex != m_index)
623 emit indexChanged(index: m_index);
624 if (oldName != m_name)
625 emit nameChanged(name: m_name);
626 if (oldExtensions != m_extensions)
627 emit extensionsChanged(extensions: m_extensions);
628}
629
630QStringList QQuickLabsPlatformFileNameFilter::nameFilters() const
631{
632 return m_options ? m_options->nameFilters() : QStringList();
633}
634
635QString QQuickLabsPlatformFileNameFilter::nameFilter(int index) const
636{
637 return m_options ? m_options->nameFilters().value(i: index) : QString();
638}
639
640QT_END_NAMESPACE
641
642#include "moc_qquicklabsplatformfiledialog_p.cpp"
643
644#endif // QT_DEPRECATED_SINCE(6, 9)
645

source code of qtdeclarative/src/labs/platform/qquicklabsplatformfiledialog.cpp