1// Copyright (C) 2020 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#define QT_NO_URL_CAST_FROM_STRING
5
6#include <qvariant.h>
7#include <private/qwidgetitemdata_p.h>
8#include "qfiledialog.h"
9
10#include "qfiledialog_p.h"
11#include <private/qapplication_p.h>
12#include <private/qguiapplication_p.h>
13#include <qfontmetrics.h>
14#include <qaction.h>
15#include <qactiongroup.h>
16#include <qheaderview.h>
17#if QT_CONFIG(shortcut)
18# include <qshortcut.h>
19#endif
20#include <qgridlayout.h>
21#if QT_CONFIG(menu)
22#include <qmenu.h>
23#endif
24#if QT_CONFIG(messagebox)
25#include <qmessagebox.h>
26#endif
27#include <stdlib.h>
28#if QT_CONFIG(settings)
29#include <qsettings.h>
30#endif
31#include <qdebug.h>
32#if QT_CONFIG(mimetype)
33#include <qmimedatabase.h>
34#endif
35#if QT_CONFIG(regularexpression)
36#include <qregularexpression.h>
37#endif
38#include <qapplication.h>
39#include <qstylepainter.h>
40#include "ui_qfiledialog.h"
41#if defined(Q_OS_UNIX)
42#include <pwd.h>
43#include <unistd.h> // for pathconf() on OS X
44#elif defined(Q_OS_WIN)
45# include <QtCore/qt_windows.h>
46#endif
47#if defined(Q_OS_WASM)
48#include <private/qwasmlocalfileaccess_p.h>
49#endif
50
51#include <algorithm>
52
53QT_BEGIN_NAMESPACE
54
55using namespace Qt::StringLiterals;
56
57Q_GLOBAL_STATIC(QUrl, lastVisitedDir)
58
59/*!
60 \class QFileDialog
61 \brief The QFileDialog class provides a dialog that allows users to select files or directories.
62 \ingroup standard-dialogs
63 \inmodule QtWidgets
64
65 The QFileDialog class enables a user to traverse the file system
66 to select one or many files or a directory.
67
68 \image qtquickdialogs-filedialog-gtk.png
69
70 The easiest way to create a QFileDialog is to use the static functions,
71 such as \l getOpenFileName().
72
73 \snippet code/src_gui_dialogs_qfiledialog.cpp 0
74
75 In the above example, a modal QFileDialog is created using a static
76 function. The dialog initially displays the contents of the "/home/jana"
77 directory, and displays files matching the patterns given in the
78 string "Image Files (*.png *.jpg *.bmp)". The parent of the file dialog
79 is set to \e this, and the window title is set to "Open Image".
80
81 If you want to use multiple filters, separate each one with
82 \e two semicolons. For example:
83
84 \snippet code/src_gui_dialogs_qfiledialog.cpp 1
85
86 You can create your own QFileDialog without using the static
87 functions. By calling setFileMode(), you can specify what the user must
88 select in the dialog:
89
90 \snippet code/src_gui_dialogs_qfiledialog.cpp 2
91
92 In the above example, the mode of the file dialog is set to
93 AnyFile, meaning that the user can select any file, or even specify a
94 file that doesn't exist. This mode is useful for creating a
95 "Save As" file dialog. Use ExistingFile if the user must select an
96 existing file, or \l Directory if only a directory can be selected.
97 See the \l QFileDialog::FileMode enum for the complete list of modes.
98
99 The fileMode property contains the mode of operation for the dialog;
100 this indicates what types of objects the user is expected to select.
101 Use setNameFilter() to set the dialog's file filter. For example:
102
103 \snippet code/src_gui_dialogs_qfiledialog.cpp 3
104
105 In the above example, the filter is set to \c{"Images (*.png *.xpm *.jpg)"}.
106 This means that only files with the extension \c png, \c xpm,
107 or \c jpg are shown in the QFileDialog. You can apply
108 several filters by using setNameFilters(). Use selectNameFilter() to select
109 one of the filters you've given as the file dialog's default filter.
110
111 The file dialog has two view modes: \l{QFileDialog::}{List} and
112 \l{QFileDialog::}{Detail}.
113 \l{QFileDialog::}{List} presents the contents of the current directory
114 as a list of file and directory names. \l{QFileDialog::}{Detail} also
115 displays a list of file and directory names, but provides additional
116 information alongside each name, such as the file size and modification
117 date. Set the mode with setViewMode():
118
119 \snippet code/src_gui_dialogs_qfiledialog.cpp 4
120
121 The last important function you need to use when creating your
122 own file dialog is selectedFiles().
123
124 \snippet code/src_gui_dialogs_qfiledialog.cpp 5
125
126 In the above example, a modal file dialog is created and shown. If
127 the user clicked OK, the file they selected is put in \c fileName.
128
129 The dialog's working directory can be set with setDirectory().
130 Each file in the current directory can be selected using
131 the selectFile() function.
132
133 The \l{dialogs/standarddialogs}{Standard Dialogs} example shows
134 how to use QFileDialog as well as other built-in Qt dialogs.
135
136 By default, a platform-native file dialog is used if the platform has
137 one. In that case, the widgets that would otherwise be used to construct the
138 dialog are not instantiated, so related accessors such as layout() and
139 itemDelegate() return null. Also, not all platforms show file dialogs
140 with a title bar, so be aware that the caption text might not be visible to
141 the user. You can set the \l DontUseNativeDialog option or set the
142 \l{Qt::AA_DontUseNativeDialogs}{AA_DontUseNativeDialogs} application attribute
143 to ensure that the widget-based implementation is used instead of the native dialog.
144
145 \sa QDir, QFileInfo, QFile, QColorDialog, QFontDialog, {Standard Dialogs Example}
146*/
147
148/*!
149 \enum QFileDialog::AcceptMode
150
151 \value AcceptOpen
152 \value AcceptSave
153*/
154
155/*!
156 \enum QFileDialog::ViewMode
157
158 This enum describes the view mode of the file dialog; that is, what
159 information about each file is displayed.
160
161 \value Detail Displays an icon, a name, and details for each item in
162 the directory.
163 \value List Displays only an icon and a name for each item in the
164 directory.
165
166 \sa setViewMode()
167*/
168
169/*!
170 \enum QFileDialog::FileMode
171
172 This enum is used to indicate what the user may select in the file
173 dialog; that is, what the dialog returns if the user clicks OK.
174
175 \value AnyFile The name of a file, whether it exists or not.
176 \value ExistingFile The name of a single existing file.
177 \value Directory The name of a directory. Both files and
178 directories are displayed. However, the native Windows
179 file dialog does not support displaying files in the
180 directory chooser.
181 \value ExistingFiles The names of zero or more existing files.
182
183 \sa setFileMode()
184*/
185
186/*!
187 \enum QFileDialog::Option
188
189 Options that influence the behavior of the dialog.
190
191 \value ShowDirsOnly Only show directories. By
192 default, both files and directories are shown.\br
193 This option is only effective in the \l Directory file mode.
194
195 \value DontResolveSymlinks Don't resolve symlinks.
196 By default, symlinks are resolved.
197
198 \value DontConfirmOverwrite Don't ask for confirmation if an
199 existing file is selected. By default, confirmation is requested.\br
200 This option is only effective if \l acceptMode is \l {QFileDialog::}{AcceptSave}).
201 It is furthermore not used on macOS for native file dialogs.
202
203 \value DontUseNativeDialog Don't use a platform-native file dialog,
204 but the widget-based one provided by Qt.\br
205 By default, a native file dialog is shown unless you use a subclass
206 of QFileDialog that contains the Q_OBJECT macro, the global
207 \l{Qt::}{AA_DontUseNativeDialogs} application attribute is set, or the platform
208 does not have a native dialog of the type that you require.\br
209 For the option to be effective, you must set it before changing
210 other properties of the dialog, or showing the dialog.
211
212 \value ReadOnly Indicates that the model is read-only.
213
214 \value HideNameFilterDetails Indicates if the file name filter details are
215 hidden or not.
216
217 \value DontUseCustomDirectoryIcons Always use the default directory icon.\br
218 Some platforms allow the user to set a different icon, but custom icon lookup
219 might cause significant performance issues over network or removable drives.\br
220 Setting this will enable the
221 \l{QAbstractFileIconProvider::}{DontUseCustomDirectoryIcons}
222 option in \l{iconProvider()}.\br
223 This enum value was added in Qt 5.2.
224
225 \sa options, testOption
226*/
227
228/*!
229 \enum QFileDialog::DialogLabel
230
231 \value LookIn
232 \value FileName
233 \value FileType
234 \value Accept
235 \value Reject
236*/
237
238/*!
239 \fn void QFileDialog::filesSelected(const QStringList &selected)
240
241 When the selection changes for local operations and the dialog is
242 accepted, this signal is emitted with the (possibly empty) list
243 of \a selected files.
244
245 \sa currentChanged(), QDialog::Accepted
246*/
247
248/*!
249 \fn void QFileDialog::urlsSelected(const QList<QUrl> &urls)
250
251 When the selection changes and the dialog is accepted, this signal is
252 emitted with the (possibly empty) list of selected \a urls.
253
254 \sa currentUrlChanged(), QDialog::Accepted
255 \since 5.2
256*/
257
258/*!
259 \fn void QFileDialog::fileSelected(const QString &file)
260
261 When the selection changes for local operations and the dialog is
262 accepted, this signal is emitted with the (possibly empty)
263 selected \a file.
264
265 \sa currentChanged(), QDialog::Accepted
266*/
267
268/*!
269 \fn void QFileDialog::urlSelected(const QUrl &url)
270
271 When the selection changes and the dialog is accepted, this signal is
272 emitted with the (possibly empty) selected \a url.
273
274 \sa currentUrlChanged(), QDialog::Accepted
275 \since 5.2
276*/
277
278/*!
279 \fn void QFileDialog::currentChanged(const QString &path)
280
281 When the current file changes for local operations, this signal is
282 emitted with the new file name as the \a path parameter.
283
284 \sa filesSelected()
285*/
286
287/*!
288 \fn void QFileDialog::currentUrlChanged(const QUrl &url)
289
290 When the current file changes, this signal is emitted with the
291 new file URL as the \a url parameter.
292
293 \sa urlsSelected()
294 \since 5.2
295*/
296
297/*!
298 \fn void QFileDialog::directoryEntered(const QString &directory)
299
300 This signal is emitted for local operations when the user enters
301 a \a directory.
302*/
303
304/*!
305 \fn void QFileDialog::directoryUrlEntered(const QUrl &directory)
306
307 This signal is emitted when the user enters a \a directory.
308
309 \since 5.2
310*/
311
312/*!
313 \fn void QFileDialog::filterSelected(const QString &filter)
314
315 This signal is emitted when the user selects a \a filter.
316*/
317
318QT_BEGIN_INCLUDE_NAMESPACE
319#include <QMetaEnum>
320#if QT_CONFIG(shortcut)
321# include <qshortcut.h>
322#endif
323QT_END_INCLUDE_NAMESPACE
324
325/*!
326 \fn QFileDialog::QFileDialog(QWidget *parent, Qt::WindowFlags flags)
327
328 Constructs a file dialog with the given \a parent and widget \a flags.
329*/
330QFileDialog::QFileDialog(QWidget *parent, Qt::WindowFlags f)
331 : QDialog(*new QFileDialogPrivate, parent, f)
332{
333 Q_D(QFileDialog);
334 QFileDialogArgs args;
335 d->init(args);
336}
337
338/*!
339 Constructs a file dialog with the given \a parent and \a caption that
340 initially displays the contents of the specified \a directory.
341 The contents of the directory are filtered before being shown in the
342 dialog, using a semicolon-separated list of filters specified by
343 \a filter.
344*/
345QFileDialog::QFileDialog(QWidget *parent,
346 const QString &caption,
347 const QString &directory,
348 const QString &filter)
349 : QDialog(*new QFileDialogPrivate, parent, { })
350{
351 Q_D(QFileDialog);
352 QFileDialogArgs args(QUrl::fromLocalFile(localfile: directory));
353 args.filter = filter;
354 args.caption = caption;
355 d->init(args);
356}
357
358/*!
359 \internal
360*/
361QFileDialog::QFileDialog(const QFileDialogArgs &args)
362 : QDialog(*new QFileDialogPrivate, args.parent, { })
363{
364 Q_D(QFileDialog);
365 d->init(args);
366 setFileMode(args.mode);
367 setOptions(args.options);
368 selectFile(filename: args.selection);
369}
370
371/*!
372 Destroys the file dialog.
373*/
374QFileDialog::~QFileDialog()
375{
376 Q_D(QFileDialog);
377#if QT_CONFIG(settings)
378 d->saveSettings();
379#endif
380 if (QPlatformFileDialogHelper *platformHelper = d->platformFileDialogHelper()) {
381 // QIOSFileDialog emits directoryChanged while hiding, causing an assert
382 // because of a partially destroyed QFileDialog.
383 QObjectPrivate::disconnect(sender: platformHelper, signal: &QPlatformFileDialogHelper::directoryEntered,
384 receiverPrivate: d, slot: &QFileDialogPrivate::nativeEnterDirectory);
385 }
386}
387
388/*!
389 Sets the \a urls that are located in the sidebar.
390
391 For instance:
392
393 \snippet filedialogurls/filedialogurls.cpp 0
394
395 Then the file dialog looks like this:
396
397 \image filedialogurls.png
398
399 \sa sidebarUrls()
400*/
401void QFileDialog::setSidebarUrls(const QList<QUrl> &urls)
402{
403 Q_D(QFileDialog);
404 if (!d->nativeDialogInUse)
405 d->qFileDialogUi->sidebar->setUrls(urls);
406}
407
408/*!
409 Returns a list of urls that are currently in the sidebar
410*/
411QList<QUrl> QFileDialog::sidebarUrls() const
412{
413 Q_D(const QFileDialog);
414 return (d->nativeDialogInUse ? QList<QUrl>() : d->qFileDialogUi->sidebar->urls());
415}
416
417static const qint32 QFileDialogMagic = 0xbe;
418
419/*!
420 Saves the state of the dialog's layout, history and current directory.
421
422 Typically this is used in conjunction with QSettings to remember the size
423 for a future session. A version number is stored as part of the data.
424*/
425QByteArray QFileDialog::saveState() const
426{
427 Q_D(const QFileDialog);
428 int version = 4;
429 QByteArray data;
430 QDataStream stream(&data, QIODevice::WriteOnly);
431 stream.setVersion(QDataStream::Qt_5_0);
432
433 stream << qint32(QFileDialogMagic);
434 stream << qint32(version);
435 if (d->usingWidgets()) {
436 stream << d->qFileDialogUi->splitter->saveState();
437 stream << d->qFileDialogUi->sidebar->urls();
438 } else {
439 stream << d->splitterState;
440 stream << d->sidebarUrls;
441 }
442 stream << history();
443 stream << *lastVisitedDir();
444 if (d->usingWidgets())
445 stream << d->qFileDialogUi->treeView->header()->saveState();
446 else
447 stream << d->headerData;
448 stream << qint32(viewMode());
449 return data;
450}
451
452/*!
453 Restores the dialogs's layout, history and current directory to the \a state specified.
454
455 Typically this is used in conjunction with QSettings to restore the size
456 from a past session.
457
458 Returns \c false if there are errors
459*/
460bool QFileDialog::restoreState(const QByteArray &state)
461{
462 Q_D(QFileDialog);
463 QByteArray sd = state;
464 QDataStream stream(&sd, QIODevice::ReadOnly);
465 stream.setVersion(QDataStream::Qt_5_0);
466 if (stream.atEnd())
467 return false;
468 QStringList history;
469 QUrl currentDirectory;
470 qint32 marker;
471 qint32 v;
472 qint32 viewMode;
473 stream >> marker;
474 stream >> v;
475 // the code below only supports versions 3 and 4
476 if (marker != QFileDialogMagic || (v != 3 && v != 4))
477 return false;
478
479 stream >> d->splitterState
480 >> d->sidebarUrls
481 >> history;
482 if (v == 3) {
483 QString currentDirectoryString;
484 stream >> currentDirectoryString;
485 currentDirectory = QUrl::fromLocalFile(localfile: currentDirectoryString);
486 } else {
487 stream >> currentDirectory;
488 }
489 stream >> d->headerData
490 >> viewMode;
491
492 setDirectoryUrl(lastVisitedDir()->isEmpty() ? currentDirectory : *lastVisitedDir());
493 setViewMode(static_cast<QFileDialog::ViewMode>(viewMode));
494
495 if (!d->usingWidgets())
496 return true;
497
498 return d->restoreWidgetState(history, splitterPosition: -1);
499}
500
501/*!
502 \reimp
503*/
504void QFileDialog::changeEvent(QEvent *e)
505{
506 Q_D(QFileDialog);
507 if (e->type() == QEvent::LanguageChange) {
508 d->retranslateWindowTitle();
509 d->retranslateStrings();
510 }
511 QDialog::changeEvent(e);
512}
513
514QFileDialogPrivate::QFileDialogPrivate()
515 :
516#if QT_CONFIG(proxymodel)
517 proxyModel(nullptr),
518#endif
519 model(nullptr),
520 currentHistoryLocation(-1),
521 renameAction(nullptr),
522 deleteAction(nullptr),
523 showHiddenAction(nullptr),
524 useDefaultCaption(true),
525 qFileDialogUi(nullptr),
526 options(QFileDialogOptions::create())
527{
528}
529
530QFileDialogPrivate::~QFileDialogPrivate()
531{
532}
533
534void QFileDialogPrivate::initHelper(QPlatformDialogHelper *h)
535{
536 Q_Q(QFileDialog);
537 auto *fileDialogHelper = static_cast<QPlatformFileDialogHelper *>(h);
538 QObjectPrivate::connect(sender: fileDialogHelper, signal: &QPlatformFileDialogHelper::fileSelected,
539 receiverPrivate: this, slot: &QFileDialogPrivate::emitUrlSelected);
540 QObjectPrivate::connect(sender: fileDialogHelper, signal: &QPlatformFileDialogHelper::filesSelected,
541 receiverPrivate: this, slot: &QFileDialogPrivate::emitUrlsSelected);
542 QObjectPrivate::connect(sender: fileDialogHelper, signal: &QPlatformFileDialogHelper::currentChanged,
543 receiverPrivate: this, slot: &QFileDialogPrivate::nativeCurrentChanged);
544 QObjectPrivate::connect(sender: fileDialogHelper, signal: &QPlatformFileDialogHelper::directoryEntered,
545 receiverPrivate: this, slot: &QFileDialogPrivate::nativeEnterDirectory);
546 QObject::connect(sender: fileDialogHelper, signal: &QPlatformFileDialogHelper::filterSelected,
547 context: q, slot: &QFileDialog::filterSelected);
548 fileDialogHelper->setOptions(options);
549}
550
551void QFileDialogPrivate::helperPrepareShow(QPlatformDialogHelper *)
552{
553 Q_Q(QFileDialog);
554 options->setWindowTitle(q->windowTitle());
555 options->setHistory(q->history());
556 if (usingWidgets())
557 options->setSidebarUrls(qFileDialogUi->sidebar->urls());
558 if (options->initiallySelectedNameFilter().isEmpty())
559 options->setInitiallySelectedNameFilter(q->selectedNameFilter());
560 if (options->initiallySelectedFiles().isEmpty())
561 options->setInitiallySelectedFiles(userSelectedFiles());
562}
563
564void QFileDialogPrivate::helperDone(QDialog::DialogCode code, QPlatformDialogHelper *)
565{
566 if (code == QDialog::Accepted) {
567 Q_Q(QFileDialog);
568 q->setViewMode(static_cast<QFileDialog::ViewMode>(options->viewMode()));
569 q->setSidebarUrls(options->sidebarUrls());
570 q->setHistory(options->history());
571 }
572}
573
574void QFileDialogPrivate::retranslateWindowTitle()
575{
576 Q_Q(QFileDialog);
577 if (!useDefaultCaption || setWindowTitle != q->windowTitle())
578 return;
579 if (q->acceptMode() == QFileDialog::AcceptOpen) {
580 const QFileDialog::FileMode fileMode = q->fileMode();
581 if (fileMode == QFileDialog::Directory)
582 q->setWindowTitle(QFileDialog::tr(s: "Find Directory"));
583 else
584 q->setWindowTitle(QFileDialog::tr(s: "Open"));
585 } else
586 q->setWindowTitle(QFileDialog::tr(s: "Save As"));
587
588 setWindowTitle = q->windowTitle();
589}
590
591void QFileDialogPrivate::setLastVisitedDirectory(const QUrl &dir)
592{
593 *lastVisitedDir() = dir;
594}
595
596void QFileDialogPrivate::updateLookInLabel()
597{
598 if (options->isLabelExplicitlySet(label: QFileDialogOptions::LookIn))
599 setLabelTextControl(label: QFileDialog::LookIn, text: options->labelText(label: QFileDialogOptions::LookIn));
600}
601
602void QFileDialogPrivate::updateFileNameLabel()
603{
604 if (options->isLabelExplicitlySet(label: QFileDialogOptions::FileName)) {
605 setLabelTextControl(label: QFileDialog::FileName, text: options->labelText(label: QFileDialogOptions::FileName));
606 } else {
607 switch (q_func()->fileMode()) {
608 case QFileDialog::Directory:
609 setLabelTextControl(label: QFileDialog::FileName, text: QFileDialog::tr(s: "Directory:"));
610 break;
611 default:
612 setLabelTextControl(label: QFileDialog::FileName, text: QFileDialog::tr(s: "File &name:"));
613 break;
614 }
615 }
616}
617
618void QFileDialogPrivate::updateFileTypeLabel()
619{
620 if (options->isLabelExplicitlySet(label: QFileDialogOptions::FileType))
621 setLabelTextControl(label: QFileDialog::FileType, text: options->labelText(label: QFileDialogOptions::FileType));
622}
623
624void QFileDialogPrivate::updateOkButtonText(bool saveAsOnFolder)
625{
626 Q_Q(QFileDialog);
627 // 'Save as' at a folder: Temporarily change to "Open".
628 if (saveAsOnFolder) {
629 setLabelTextControl(label: QFileDialog::Accept, text: QFileDialog::tr(s: "&Open"));
630 } else if (options->isLabelExplicitlySet(label: QFileDialogOptions::Accept)) {
631 setLabelTextControl(label: QFileDialog::Accept, text: options->labelText(label: QFileDialogOptions::Accept));
632 return;
633 } else {
634 switch (q->fileMode()) {
635 case QFileDialog::Directory:
636 setLabelTextControl(label: QFileDialog::Accept, text: QFileDialog::tr(s: "&Choose"));
637 break;
638 default:
639 setLabelTextControl(label: QFileDialog::Accept,
640 text: q->acceptMode() == QFileDialog::AcceptOpen ?
641 QFileDialog::tr(s: "&Open") :
642 QFileDialog::tr(s: "&Save"));
643 break;
644 }
645 }
646}
647
648void QFileDialogPrivate::updateCancelButtonText()
649{
650 if (options->isLabelExplicitlySet(label: QFileDialogOptions::Reject))
651 setLabelTextControl(label: QFileDialog::Reject, text: options->labelText(label: QFileDialogOptions::Reject));
652}
653
654void QFileDialogPrivate::retranslateStrings()
655{
656 Q_Q(QFileDialog);
657 /* WIDGETS */
658 if (options->useDefaultNameFilters())
659 q->setNameFilter(QFileDialogOptions::defaultNameFilterString());
660 if (!usingWidgets())
661 return;
662
663 QList<QAction*> actions = qFileDialogUi->treeView->header()->actions();
664 QAbstractItemModel *abstractModel = model;
665#if QT_CONFIG(proxymodel)
666 if (proxyModel)
667 abstractModel = proxyModel;
668#endif
669 const int total = qMin(a: abstractModel->columnCount(parent: QModelIndex()), b: int(actions.size() + 1));
670 for (int i = 1; i < total; ++i) {
671 actions.at(i: i - 1)->setText(QFileDialog::tr(s: "Show ") + abstractModel->headerData(section: i, orientation: Qt::Horizontal, role: Qt::DisplayRole).toString());
672 }
673
674 /* MENU ACTIONS */
675 renameAction->setText(QFileDialog::tr(s: "&Rename"));
676 deleteAction->setText(QFileDialog::tr(s: "&Delete"));
677 showHiddenAction->setText(QFileDialog::tr(s: "Show &hidden files"));
678 newFolderAction->setText(QFileDialog::tr(s: "&New Folder"));
679 qFileDialogUi->retranslateUi(q);
680 updateLookInLabel();
681 updateFileNameLabel();
682 updateFileTypeLabel();
683 updateCancelButtonText();
684}
685
686void QFileDialogPrivate::emitFilesSelected(const QStringList &files)
687{
688 Q_Q(QFileDialog);
689 emit q->filesSelected(files);
690 if (files.size() == 1)
691 emit q->fileSelected(file: files.first());
692}
693
694bool QFileDialogPrivate::canBeNativeDialog() const
695{
696 // Don't use Q_Q here! This function is called from ~QDialog,
697 // so Q_Q calling q_func() invokes undefined behavior (invalid cast in q_func()).
698 const QDialog * const q = static_cast<const QDialog*>(q_ptr);
699 if (nativeDialogInUse)
700 return true;
701 if (QCoreApplication::testAttribute(attribute: Qt::AA_DontUseNativeDialogs)
702 || q->testAttribute(attribute: Qt::WA_DontShowOnScreen)
703 || (options->options() & QFileDialog::DontUseNativeDialog)) {
704 return false;
705 }
706
707 return strcmp(s1: QFileDialog::staticMetaObject.className(), s2: q->metaObject()->className()) == 0;
708}
709
710bool QFileDialogPrivate::usingWidgets() const
711{
712 return !nativeDialogInUse && qFileDialogUi;
713}
714
715/*!
716 Sets the given \a option to be enabled if \a on is true; otherwise,
717 clears the given \a option.
718
719 Options (particularly the \l DontUseNativeDialog option) should be set
720 before changing dialog properties or showing the dialog.
721
722 Setting options while the dialog is visible is not guaranteed to have
723 an immediate effect on the dialog (depending on the option and on the
724 platform).
725
726 Setting options after changing other properties may cause these
727 values to have no effect.
728
729 \sa options, testOption()
730*/
731void QFileDialog::setOption(Option option, bool on)
732{
733 const QFileDialog::Options previousOptions = options();
734 if (!(previousOptions & option) != !on)
735 setOptions(previousOptions ^ option);
736}
737
738/*!
739 Returns \c true if the given \a option is enabled; otherwise, returns
740 false.
741
742 \sa options, setOption()
743*/
744bool QFileDialog::testOption(Option option) const
745{
746 Q_D(const QFileDialog);
747 return d->options->testOption(option: static_cast<QFileDialogOptions::FileDialogOption>(option));
748}
749
750/*!
751 \property QFileDialog::options
752 \brief The various options that affect the look and feel of the dialog.
753
754 By default, all options are disabled.
755
756 Options (particularly the \l DontUseNativeDialog option) should be set
757 before changing dialog properties or showing the dialog.
758
759 Setting options while the dialog is visible is not guaranteed to have
760 an immediate effect on the dialog (depending on the option and on the
761 platform).
762
763 Setting options after changing other properties may cause these
764 values to have no effect.
765
766 \sa setOption(), testOption()
767*/
768void QFileDialog::setOptions(Options options)
769{
770 Q_D(QFileDialog);
771
772 Options changed = (options ^ QFileDialog::options());
773 if (!changed)
774 return;
775
776 d->options->setOptions(QFileDialogOptions::FileDialogOptions(int(options)));
777
778 if (options & DontUseNativeDialog) {
779 d->nativeDialogInUse = false;
780 d->createWidgets();
781 }
782
783 if (d->usingWidgets()) {
784 if (changed & DontResolveSymlinks)
785 d->model->setResolveSymlinks(!(options & DontResolveSymlinks));
786 if (changed & ReadOnly) {
787 bool ro = (options & ReadOnly);
788 d->model->setReadOnly(ro);
789 d->qFileDialogUi->newFolderButton->setEnabled(!ro);
790 d->renameAction->setEnabled(!ro);
791 d->deleteAction->setEnabled(!ro);
792 }
793
794 if (changed & DontUseCustomDirectoryIcons) {
795 QFileIconProvider::Options providerOptions = iconProvider()->options();
796 providerOptions.setFlag(flag: QFileIconProvider::DontUseCustomDirectoryIcons,
797 on: options & DontUseCustomDirectoryIcons);
798 iconProvider()->setOptions(providerOptions);
799 }
800 }
801
802 if (changed & HideNameFilterDetails)
803 setNameFilters(d->options->nameFilters());
804
805 if (changed & ShowDirsOnly)
806 setFilter((options & ShowDirsOnly) ? filter() & ~QDir::Files : filter() | QDir::Files);
807}
808
809QFileDialog::Options QFileDialog::options() const
810{
811 Q_D(const QFileDialog);
812 static_assert((int)QFileDialog::ShowDirsOnly == (int)QFileDialogOptions::ShowDirsOnly);
813 static_assert((int)QFileDialog::DontResolveSymlinks == (int)QFileDialogOptions::DontResolveSymlinks);
814 static_assert((int)QFileDialog::DontConfirmOverwrite == (int)QFileDialogOptions::DontConfirmOverwrite);
815 static_assert((int)QFileDialog::DontUseNativeDialog == (int)QFileDialogOptions::DontUseNativeDialog);
816 static_assert((int)QFileDialog::ReadOnly == (int)QFileDialogOptions::ReadOnly);
817 static_assert((int)QFileDialog::HideNameFilterDetails == (int)QFileDialogOptions::HideNameFilterDetails);
818 static_assert((int)QFileDialog::DontUseCustomDirectoryIcons == (int)QFileDialogOptions::DontUseCustomDirectoryIcons);
819 return QFileDialog::Options(int(d->options->options()));
820}
821
822/*!
823 This function shows the dialog, and connects the slot specified by \a receiver
824 and \a member to the signal that informs about selection changes. If the fileMode is
825 ExistingFiles, this is the filesSelected() signal, otherwise it is the fileSelected() signal.
826
827 The signal is disconnected from the slot when the dialog is closed.
828*/
829void QFileDialog::open(QObject *receiver, const char *member)
830{
831 Q_D(QFileDialog);
832 const char *signal = (fileMode() == ExistingFiles) ? SIGNAL(filesSelected(QStringList))
833 : SIGNAL(fileSelected(QString));
834 connect(sender: this, signal, receiver, member);
835 d->signalToDisconnectOnClose = signal;
836 d->receiverToDisconnectOnClose = receiver;
837 d->memberToDisconnectOnClose = member;
838
839 QDialog::open();
840}
841
842
843/*!
844 \reimp
845*/
846void QFileDialog::setVisible(bool visible)
847{
848 // will call QFileDialogPrivate::setVisible override
849 QDialog::setVisible(visible);
850}
851
852/*!
853 \internal
854
855 The logic has to live here so that the call to hide() in ~QDialog calls
856 this function; it wouldn't call an override of QDialog::setVisible().
857*/
858void QFileDialogPrivate::setVisible(bool visible)
859{
860 Q_Q(QFileDialog);
861
862 if (canBeNativeDialog()){
863 if (setNativeDialogVisible(visible)){
864 // Set WA_DontShowOnScreen so that QDialogPrivate::setVisible(visible) below
865 // updates the state correctly, but skips showing the non-native version:
866 q->setAttribute(Qt::WA_DontShowOnScreen);
867#if QT_CONFIG(fscompleter)
868 // So the completer doesn't try to complete and therefore show a popup
869 if (!nativeDialogInUse)
870 completer->setModel(nullptr);
871#endif
872 } else {
873 createWidgets();
874 q->setAttribute(Qt::WA_DontShowOnScreen, on: false);
875#if QT_CONFIG(fscompleter)
876 if (!nativeDialogInUse) {
877 if (proxyModel != nullptr)
878 completer->setModel(proxyModel);
879 else
880 completer->setModel(model);
881 }
882#endif
883 }
884 }
885
886 if (visible && usingWidgets())
887 qFileDialogUi->fileNameEdit->setFocus();
888
889 QDialogPrivate::setVisible(visible);
890}
891
892/*!
893 \internal
894 set the directory to url
895*/
896void QFileDialogPrivate::goToUrl(const QUrl &url)
897{
898 //The shortcut in the side bar may have a parent that is not fetched yet (e.g. an hidden file)
899 //so we force the fetching
900 QFileSystemModelPrivate::QFileSystemNode *node = model->d_func()->node(path: url.toLocalFile(), fetch: true);
901 QModelIndex idx = model->d_func()->index(node);
902 enterDirectory(index: idx);
903}
904
905/*!
906 \fn void QFileDialog::setDirectory(const QDir &directory)
907
908 \overload
909*/
910
911/*!
912 Sets the file dialog's current \a directory.
913
914 \note On iOS, if you set \a directory to \l{QStandardPaths::standardLocations()}
915 {QStandardPaths::standardLocations(QStandardPaths::PicturesLocation).last()},
916 a native image picker dialog is used for accessing the user's photo album.
917 The filename returned can be loaded using QFile and related APIs.
918 For this to be enabled, the Info.plist assigned to QMAKE_INFO_PLIST in the
919 project file must contain the key \c NSPhotoLibraryUsageDescription. See
920 Info.plist documentation from Apple for more information regarding this key.
921 This feature was added in Qt 5.5.
922*/
923void QFileDialog::setDirectory(const QString &directory)
924{
925 Q_D(QFileDialog);
926 QString newDirectory = directory;
927 //we remove .. and . from the given path if exist
928 if (!directory.isEmpty())
929 newDirectory = QDir::cleanPath(path: directory);
930
931 if (!directory.isEmpty() && newDirectory.isEmpty())
932 return;
933
934 QUrl newDirUrl = QUrl::fromLocalFile(localfile: newDirectory);
935 QFileDialogPrivate::setLastVisitedDirectory(newDirUrl);
936
937 d->options->setInitialDirectory(QUrl::fromLocalFile(localfile: directory));
938 if (!d->usingWidgets()) {
939 d->setDirectory_sys(newDirUrl);
940 return;
941 }
942 if (d->rootPath() == newDirectory)
943 return;
944 QModelIndex root = d->model->setRootPath(newDirectory);
945 if (!d->nativeDialogInUse) {
946 d->qFileDialogUi->newFolderButton->setEnabled(d->model->flags(index: root) & Qt::ItemIsDropEnabled);
947 if (root != d->rootIndex()) {
948#if QT_CONFIG(fscompleter)
949 if (directory.endsWith(c: u'/'))
950 d->completer->setCompletionPrefix(newDirectory);
951 else
952 d->completer->setCompletionPrefix(newDirectory + u'/');
953#endif
954 d->setRootIndex(root);
955 }
956 d->qFileDialogUi->listView->selectionModel()->clear();
957 }
958}
959
960/*!
961 Returns the directory currently being displayed in the dialog.
962*/
963QDir QFileDialog::directory() const
964{
965 Q_D(const QFileDialog);
966 if (d->nativeDialogInUse) {
967 QString dir = d->directory_sys().toLocalFile();
968 return QDir(dir.isEmpty() ? d->options->initialDirectory().toLocalFile() : dir);
969 }
970 return d->rootPath();
971}
972
973/*!
974 Sets the file dialog's current \a directory url.
975
976 \note The non-native QFileDialog supports only local files.
977
978 \note On Windows, it is possible to pass URLs representing
979 one of the \e {virtual folders}, such as "Computer" or "Network".
980 This is done by passing a QUrl using the scheme \c clsid followed
981 by the CLSID value with the curly braces removed. For example the URL
982 \c clsid:374DE290-123F-4565-9164-39C4925E467B denotes the download
983 location. For a complete list of possible values, see the MSDN documentation on
984 \l{https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid}{KNOWNFOLDERID}.
985 This feature was added in Qt 5.5.
986
987 \sa QUuid
988 \since 5.2
989*/
990void QFileDialog::setDirectoryUrl(const QUrl &directory)
991{
992 Q_D(QFileDialog);
993 if (!directory.isValid())
994 return;
995
996 QFileDialogPrivate::setLastVisitedDirectory(directory);
997 d->options->setInitialDirectory(directory);
998
999 if (d->nativeDialogInUse)
1000 d->setDirectory_sys(directory);
1001 else if (directory.isLocalFile())
1002 setDirectory(directory.toLocalFile());
1003 else if (Q_UNLIKELY(d->usingWidgets()))
1004 qWarning(msg: "Non-native QFileDialog supports only local files");
1005}
1006
1007/*!
1008 Returns the url of the directory currently being displayed in the dialog.
1009
1010 \since 5.2
1011*/
1012QUrl QFileDialog::directoryUrl() const
1013{
1014 Q_D(const QFileDialog);
1015 if (d->nativeDialogInUse)
1016 return d->directory_sys();
1017 else
1018 return QUrl::fromLocalFile(localfile: directory().absolutePath());
1019}
1020
1021// FIXME Qt 5.4: Use upcoming QVolumeInfo class to determine this information?
1022static inline bool isCaseSensitiveFileSystem(const QString &path)
1023{
1024 Q_UNUSED(path);
1025#if defined(Q_OS_WIN)
1026 // Return case insensitive unconditionally, even if someone has a case sensitive
1027 // file system mounted, wrongly capitalized drive letters will cause mismatches.
1028 return false;
1029#elif defined(Q_OS_MACOS)
1030 return pathconf(QFile::encodeName(path).constData(), _PC_CASE_SENSITIVE) == 1;
1031#else
1032 return true;
1033#endif
1034}
1035
1036// Determine the file name to be set on the line edit from the path
1037// passed to selectFile() in mode QFileDialog::AcceptSave.
1038static inline QString fileFromPath(const QString &rootPath, QString path)
1039{
1040 if (!QFileInfo(path).isAbsolute())
1041 return path;
1042 if (path.startsWith(s: rootPath, cs: isCaseSensitiveFileSystem(path: rootPath) ? Qt::CaseSensitive : Qt::CaseInsensitive))
1043 path.remove(i: 0, len: rootPath.size());
1044
1045 if (path.isEmpty())
1046 return path;
1047
1048 if (path.at(i: 0) == QDir::separator()
1049#ifdef Q_OS_WIN
1050 //On Windows both cases can happen
1051 || path.at(0) == u'/'
1052#endif
1053 ) {
1054 path.remove(i: 0, len: 1);
1055 }
1056 return path;
1057}
1058
1059/*!
1060 Selects the given \a filename in the file dialog.
1061
1062 \sa selectedFiles()
1063*/
1064void QFileDialog::selectFile(const QString &filename)
1065{
1066 Q_D(QFileDialog);
1067 if (filename.isEmpty())
1068 return;
1069
1070 if (!d->usingWidgets()) {
1071 QUrl url;
1072 if (QFileInfo(filename).isRelative()) {
1073 url = d->options->initialDirectory();
1074 QString path = url.path();
1075 if (!path.endsWith(c: u'/'))
1076 path += u'/';
1077 url.setPath(path: path + filename);
1078 } else {
1079 url = QUrl::fromLocalFile(localfile: filename);
1080 }
1081 d->selectFile_sys(filename: url);
1082 d->options->setInitiallySelectedFiles(QList<QUrl>() << url);
1083 return;
1084 }
1085
1086 if (!QDir::isRelativePath(path: filename)) {
1087 QFileInfo info(filename);
1088 QString filenamePath = info.absoluteDir().path();
1089
1090 if (d->model->rootPath() != filenamePath)
1091 setDirectory(filenamePath);
1092 }
1093
1094 QModelIndex index = d->model->index(path: filename);
1095 d->qFileDialogUi->listView->selectionModel()->clear();
1096 if (!isVisible() || !d->lineEdit()->hasFocus())
1097 d->lineEdit()->setText(index.isValid() ? index.data().toString() : fileFromPath(rootPath: d->rootPath(), path: filename));
1098}
1099
1100/*!
1101 Selects the given \a url in the file dialog.
1102
1103 \note The non-native QFileDialog supports only local files.
1104
1105 \sa selectedUrls()
1106 \since 5.2
1107*/
1108void QFileDialog::selectUrl(const QUrl &url)
1109{
1110 Q_D(QFileDialog);
1111 if (!url.isValid())
1112 return;
1113
1114 if (d->nativeDialogInUse)
1115 d->selectFile_sys(filename: url);
1116 else if (url.isLocalFile())
1117 selectFile(filename: url.toLocalFile());
1118 else
1119 qWarning(msg: "Non-native QFileDialog supports only local files");
1120}
1121
1122#ifdef Q_OS_UNIX
1123static QString homeDirFromPasswdEntry(const QString &path, const QByteArray &userName)
1124{
1125#if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD) && !defined(Q_OS_WASM)
1126 passwd pw;
1127 passwd *tmpPw;
1128 long bufSize = ::sysconf(_SC_GETPW_R_SIZE_MAX);
1129 if (bufSize == -1)
1130 bufSize = 1024;
1131 QVarLengthArray<char, 1024> buf(bufSize);
1132 int err = 0;
1133# if defined(Q_OS_SOLARIS) && (_POSIX_C_SOURCE - 0 < 199506L)
1134 tmpPw = getpwnam_r(userName.constData(), &pw, buf.data(), buf.size());
1135# else
1136 err = getpwnam_r(name: userName.constData(), resultbuf: &pw, buffer: buf.data(), buflen: buf.size(), result: &tmpPw);
1137# endif
1138 if (err || !tmpPw)
1139 return path;
1140 return QFile::decodeName(localFileName: pw.pw_dir);
1141#else
1142 passwd *pw = getpwnam(userName.constData());
1143 if (!pw)
1144 return path;
1145 return QFile::decodeName(pw->pw_dir);
1146#endif // defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD) && !defined(Q_OS_WASM)
1147}
1148
1149Q_AUTOTEST_EXPORT QString qt_tildeExpansion(const QString &path)
1150{
1151 if (!path.startsWith(c: u'~'))
1152 return path;
1153
1154 if (path.size() == 1) // '~'
1155 return QDir::homePath();
1156
1157 QStringView sv(path);
1158 const qsizetype sepIndex = sv.indexOf(c: QDir::separator());
1159 if (sepIndex == 1) // '~/' or '~/a/b/c'
1160 return QDir::homePath() + sv.sliced(pos: 1);
1161
1162#if defined(Q_OS_VXWORKS) || defined(Q_OS_INTEGRITY)
1163 if (sepIndex == -1)
1164 return QDir::homePath();
1165 return QDir::homePath() + sv.sliced(sepIndex);
1166#else
1167 const qsizetype userNameLen = sepIndex != -1 ? sepIndex - strlen(s: "~") // '~user/a/b'
1168 : path.size() - strlen(s: "~"); // '~user'
1169 const QByteArray userName = sv.sliced(pos: 1, n: userNameLen).toLocal8Bit();
1170 QString homePath = homeDirFromPasswdEntry(path, userName);
1171 if (sepIndex == -1)
1172 return homePath;
1173 return homePath + sv.sliced(pos: sepIndex);
1174#endif // defined(Q_OS_VXWORKS) || defined(Q_OS_INTEGRITY)
1175}
1176#endif
1177
1178/**
1179 Returns the text in the line edit which can be one or more file names
1180 */
1181QStringList QFileDialogPrivate::typedFiles() const
1182{
1183 Q_Q(const QFileDialog);
1184 QStringList files;
1185 QString editText = lineEdit()->text();
1186 if (!editText.contains(c: u'"')) {
1187#ifdef Q_OS_UNIX
1188 const QString prefix = q->directory().absolutePath() + QDir::separator();
1189 if (QFile::exists(fileName: prefix + editText))
1190 files << editText;
1191 else
1192 files << qt_tildeExpansion(path: editText);
1193#else
1194 files << editText;
1195 Q_UNUSED(q);
1196#endif
1197 } else {
1198 // " is used to separate files like so: "file1" "file2" "file3" ...
1199 // ### need escape character for filenames with quotes (")
1200 QStringList tokens = editText.split(sep: u'\"');
1201 for (int i=0; i<tokens.size(); ++i) {
1202 if ((i % 2) == 0)
1203 continue; // Every even token is a separator
1204#ifdef Q_OS_UNIX
1205 const QString token = tokens.at(i);
1206 const QString prefix = q->directory().absolutePath() + QDir::separator();
1207 if (QFile::exists(fileName: prefix + token))
1208 files << token;
1209 else
1210 files << qt_tildeExpansion(path: token);
1211#else
1212 files << toInternal(tokens.at(i));
1213#endif
1214 }
1215 }
1216 return addDefaultSuffixToFiles(filesToFix: files);
1217}
1218
1219// Return selected files without defaulting to the root of the file system model
1220// used for initializing QFileDialogOptions for native dialogs. The default is
1221// not suitable for native dialogs since it mostly equals directory().
1222QList<QUrl> QFileDialogPrivate::userSelectedFiles() const
1223{
1224 QList<QUrl> files;
1225
1226 if (!usingWidgets())
1227 return addDefaultSuffixToUrls(urlsToFix: selectedFiles_sys());
1228
1229 const QModelIndexList selectedRows = qFileDialogUi->listView->selectionModel()->selectedRows();
1230 files.reserve(asize: selectedRows.size());
1231 for (const QModelIndex &index : selectedRows)
1232 files.append(QUrl::fromLocalFile(index.data(QFileSystemModel::FilePathRole).toString()));
1233
1234 if (files.isEmpty() && !lineEdit()->text().isEmpty()) {
1235 const QStringList typedFilesList = typedFiles();
1236 files.reserve(asize: typedFilesList.size());
1237 for (const QString &path : typedFilesList)
1238 files.append(t: QUrl::fromLocalFile(localfile: path));
1239 }
1240
1241 return files;
1242}
1243
1244QStringList QFileDialogPrivate::addDefaultSuffixToFiles(const QStringList &filesToFix) const
1245{
1246 QStringList files;
1247 for (int i=0; i<filesToFix.size(); ++i) {
1248 QString name = toInternal(path: filesToFix.at(i));
1249 QFileInfo info(name);
1250 // if the filename has no suffix, add the default suffix
1251 const QString defaultSuffix = options->defaultSuffix();
1252 if (!defaultSuffix.isEmpty() && !info.isDir() && !info.fileName().contains(c: u'.'))
1253 name += u'.' + defaultSuffix;
1254
1255 if (info.isAbsolute()) {
1256 files.append(t: name);
1257 } else {
1258 // at this point the path should only have Qt path separators.
1259 // This check is needed since we might be at the root directory
1260 // and on Windows it already ends with slash.
1261 QString path = rootPath();
1262 if (!path.endsWith(c: u'/'))
1263 path += u'/';
1264 path += name;
1265 files.append(t: path);
1266 }
1267 }
1268 return files;
1269}
1270
1271QList<QUrl> QFileDialogPrivate::addDefaultSuffixToUrls(const QList<QUrl> &urlsToFix) const
1272{
1273 QList<QUrl> urls;
1274 urls.reserve(asize: urlsToFix.size());
1275 // if the filename has no suffix, add the default suffix
1276 const QString defaultSuffix = options->defaultSuffix();
1277 for (QUrl url : urlsToFix) {
1278 if (!defaultSuffix.isEmpty()) {
1279 const QString urlPath = url.path();
1280 const auto idx = urlPath.lastIndexOf(c: u'/');
1281 if (idx != (urlPath.size() - 1) && !QStringView{urlPath}.mid(pos: idx + 1).contains(c: u'.'))
1282 url.setPath(path: urlPath + u'.' + defaultSuffix);
1283 }
1284 urls.append(t: url);
1285 }
1286 return urls;
1287}
1288
1289
1290/*!
1291 Returns a list of strings containing the absolute paths of the
1292 selected files in the dialog. If no files are selected, or
1293 the mode is not ExistingFiles or ExistingFile, selectedFiles() contains the current path in the viewport.
1294
1295 \sa selectedNameFilter(), selectFile()
1296*/
1297QStringList QFileDialog::selectedFiles() const
1298{
1299 Q_D(const QFileDialog);
1300
1301 QStringList files;
1302 const QList<QUrl> userSelectedFiles = d->userSelectedFiles();
1303 files.reserve(asize: userSelectedFiles.size());
1304 for (const QUrl &file : userSelectedFiles)
1305 files.append(t: file.toString(options: QUrl::PreferLocalFile));
1306
1307 if (files.isEmpty() && d->usingWidgets()) {
1308 const FileMode fm = fileMode();
1309 if (fm != ExistingFile && fm != ExistingFiles)
1310 files.append(t: d->rootIndex().data(arole: QFileSystemModel::FilePathRole).toString());
1311 }
1312 return files;
1313}
1314
1315/*!
1316 Returns a list of urls containing the selected files in the dialog.
1317 If no files are selected, or the mode is not ExistingFiles or
1318 ExistingFile, selectedUrls() contains the current path in the viewport.
1319
1320 \sa selectedNameFilter(), selectUrl()
1321 \since 5.2
1322*/
1323QList<QUrl> QFileDialog::selectedUrls() const
1324{
1325 Q_D(const QFileDialog);
1326 if (d->nativeDialogInUse) {
1327 return d->userSelectedFiles();
1328 } else {
1329 QList<QUrl> urls;
1330 const QStringList selectedFileList = selectedFiles();
1331 urls.reserve(asize: selectedFileList.size());
1332 for (const QString &file : selectedFileList)
1333 urls.append(t: QUrl::fromLocalFile(localfile: file));
1334 return urls;
1335 }
1336}
1337
1338/*
1339 Makes a list of filters from ;;-separated text.
1340 Used by the mac and windows implementations
1341*/
1342QStringList qt_make_filter_list(const QString &filter)
1343{
1344 if (filter.isEmpty())
1345 return QStringList();
1346
1347 auto sep = ";;"_L1;
1348 if (!filter.contains(s: sep) && filter.contains(c: u'\n'))
1349 sep = "\n"_L1;
1350
1351 return filter.split(sep);
1352}
1353
1354/*!
1355 Sets the filter used in the file dialog to the given \a filter.
1356
1357 If \a filter contains a pair of parentheses containing one or more
1358 filename-wildcard patterns, separated by spaces, then only the
1359 text contained in the parentheses is used as the filter. This means
1360 that these calls are all equivalent:
1361
1362 \snippet code/src_gui_dialogs_qfiledialog.cpp 6
1363
1364 \note With Android's native file dialog, the mime type matching the given
1365 name filter is used because only mime types are supported.
1366
1367 \sa setMimeTypeFilters(), setNameFilters()
1368*/
1369void QFileDialog::setNameFilter(const QString &filter)
1370{
1371 setNameFilters(qt_make_filter_list(filter));
1372}
1373
1374
1375/*
1376 Strip the filters by removing the details, e.g. (*.*).
1377*/
1378QStringList qt_strip_filters(const QStringList &filters)
1379{
1380#if QT_CONFIG(regularexpression)
1381 QStringList strippedFilters;
1382 static const QRegularExpression r(QString::fromLatin1(ba: QPlatformFileDialogHelper::filterRegExp));
1383 strippedFilters.reserve(asize: filters.size());
1384 for (const QString &filter : filters) {
1385 QString filterName;
1386 auto match = r.match(subject: filter);
1387 if (match.hasMatch())
1388 filterName = match.captured(nth: 1);
1389 strippedFilters.append(t: filterName.simplified());
1390 }
1391 return strippedFilters;
1392#else
1393 return filters;
1394#endif
1395}
1396
1397
1398/*!
1399 Sets the \a filters used in the file dialog.
1400
1401 Note that the filter \b{*.*} is not portable, because the historical
1402 assumption that the file extension determines the file type is not
1403 consistent on every operating system. It is possible to have a file with no
1404 dot in its name (for example, \c Makefile). In a native Windows file
1405 dialog, \b{*.*} matches such files, while in other types of file dialogs
1406 it might not match. So, it's better to use \b{*} if you mean to select any file.
1407
1408 \snippet code/src_gui_dialogs_qfiledialog.cpp 7
1409
1410 \l setMimeTypeFilters() has the advantage of providing all possible name
1411 filters for each file type. For example, JPEG images have three possible
1412 extensions; if your application can open such files, selecting the
1413 \c image/jpeg mime type as a filter allows you to open all of them.
1414*/
1415void QFileDialog::setNameFilters(const QStringList &filters)
1416{
1417 Q_D(QFileDialog);
1418 QStringList cleanedFilters;
1419 cleanedFilters.reserve(asize: filters.size());
1420 for (const QString &filter : filters)
1421 cleanedFilters << filter.simplified();
1422
1423 d->options->setNameFilters(cleanedFilters);
1424
1425 if (!d->usingWidgets())
1426 return;
1427
1428 d->qFileDialogUi->fileTypeCombo->clear();
1429 if (cleanedFilters.isEmpty())
1430 return;
1431
1432 if (testOption(option: HideNameFilterDetails))
1433 d->qFileDialogUi->fileTypeCombo->addItems(qt_strip_filters(filters: cleanedFilters));
1434 else
1435 d->qFileDialogUi->fileTypeCombo->addItems(cleanedFilters);
1436
1437 d->useNameFilter(index: 0);
1438}
1439
1440/*!
1441 Returns the file type filters that are in operation on this file
1442 dialog.
1443*/
1444QStringList QFileDialog::nameFilters() const
1445{
1446 return d_func()->options->nameFilters();
1447}
1448
1449/*!
1450 Sets the current file type \a filter. Multiple filters can be
1451 passed in \a filter by separating them with semicolons or spaces.
1452
1453 \sa setNameFilter(), setNameFilters(), selectedNameFilter()
1454*/
1455void QFileDialog::selectNameFilter(const QString &filter)
1456{
1457 Q_D(QFileDialog);
1458 d->options->setInitiallySelectedNameFilter(filter);
1459 if (!d->usingWidgets()) {
1460 d->selectNameFilter_sys(filter);
1461 return;
1462 }
1463 int i = -1;
1464 if (testOption(option: HideNameFilterDetails)) {
1465 const QStringList filters = qt_strip_filters(filters: qt_make_filter_list(filter));
1466 if (!filters.isEmpty())
1467 i = d->qFileDialogUi->fileTypeCombo->findText(filters.first());
1468 } else {
1469 i = d->qFileDialogUi->fileTypeCombo->findText(filter);
1470 }
1471 if (i >= 0) {
1472 d->qFileDialogUi->fileTypeCombo->setCurrentIndex(i);
1473 d->useNameFilter(index: d->qFileDialogUi->fileTypeCombo->currentIndex());
1474 }
1475}
1476
1477/*!
1478 Returns the filter that the user selected in the file dialog.
1479
1480 \sa selectedFiles()
1481*/
1482QString QFileDialog::selectedNameFilter() const
1483{
1484 Q_D(const QFileDialog);
1485 if (!d->usingWidgets())
1486 return d->selectedNameFilter_sys();
1487
1488 if (testOption(option: HideNameFilterDetails)) {
1489 const auto idx = d->qFileDialogUi->fileTypeCombo->currentIndex();
1490 if (idx >= 0 && idx < d->options->nameFilters().size())
1491 return d->options->nameFilters().at(i: d->qFileDialogUi->fileTypeCombo->currentIndex());
1492 }
1493 return d->qFileDialogUi->fileTypeCombo->currentText();
1494}
1495
1496/*!
1497 Returns the filter that is used when displaying files.
1498
1499 \sa setFilter()
1500*/
1501QDir::Filters QFileDialog::filter() const
1502{
1503 Q_D(const QFileDialog);
1504 if (d->usingWidgets())
1505 return d->model->filter();
1506 return d->options->filter();
1507}
1508
1509/*!
1510 Sets the filter used by the model to \a filters. The filter is used
1511 to specify the kind of files that should be shown.
1512
1513 \sa filter()
1514*/
1515
1516void QFileDialog::setFilter(QDir::Filters filters)
1517{
1518 Q_D(QFileDialog);
1519 d->options->setFilter(filters);
1520 if (!d->usingWidgets()) {
1521 d->setFilter_sys();
1522 return;
1523 }
1524
1525 d->model->setFilter(filters);
1526 d->showHiddenAction->setChecked((filters & QDir::Hidden));
1527}
1528
1529#if QT_CONFIG(mimetype)
1530
1531static QString nameFilterForMime(const QString &mimeType)
1532{
1533 QMimeDatabase db;
1534 QMimeType mime(db.mimeTypeForName(nameOrAlias: mimeType));
1535 if (mime.isValid()) {
1536 if (mime.isDefault()) {
1537 return QFileDialog::tr(s: "All files (*)");
1538 } else {
1539 const QString patterns = mime.globPatterns().join(sep: u' ');
1540 return mime.comment() + " ("_L1 + patterns + u')';
1541 }
1542 }
1543 return QString();
1544}
1545
1546/*!
1547 \since 5.2
1548
1549 Sets the \a filters used in the file dialog, from a list of MIME types.
1550
1551 Convenience method for setNameFilters().
1552 Uses QMimeType to create a name filter from the glob patterns and description
1553 defined in each MIME type.
1554
1555 Use application/octet-stream for the "All files (*)" filter, since that
1556 is the base MIME type for all files.
1557
1558 Calling setMimeTypeFilters overrides any previously set name filters,
1559 and changes the return value of nameFilters().
1560
1561 \snippet code/src_gui_dialogs_qfiledialog.cpp 13
1562*/
1563void QFileDialog::setMimeTypeFilters(const QStringList &filters)
1564{
1565 Q_D(QFileDialog);
1566 QStringList nameFilters;
1567 for (const QString &mimeType : filters) {
1568 const QString text = nameFilterForMime(mimeType);
1569 if (!text.isEmpty())
1570 nameFilters.append(t: text);
1571 }
1572 setNameFilters(nameFilters);
1573 d->options->setMimeTypeFilters(filters);
1574}
1575
1576/*!
1577 \since 5.2
1578
1579 Returns the MIME type filters that are in operation on this file
1580 dialog.
1581*/
1582QStringList QFileDialog::mimeTypeFilters() const
1583{
1584 return d_func()->options->mimeTypeFilters();
1585}
1586
1587/*!
1588 \since 5.2
1589
1590 Sets the current MIME type \a filter.
1591
1592*/
1593void QFileDialog::selectMimeTypeFilter(const QString &filter)
1594{
1595 Q_D(QFileDialog);
1596 d->options->setInitiallySelectedMimeTypeFilter(filter);
1597
1598 const QString filterForMime = nameFilterForMime(mimeType: filter);
1599
1600 if (!d->usingWidgets()) {
1601 d->selectMimeTypeFilter_sys(filter);
1602 if (d->selectedMimeTypeFilter_sys().isEmpty() && !filterForMime.isEmpty()) {
1603 selectNameFilter(filter: filterForMime);
1604 }
1605 } else if (!filterForMime.isEmpty()) {
1606 selectNameFilter(filter: filterForMime);
1607 }
1608}
1609
1610#endif // mimetype
1611
1612/*!
1613 * \since 5.9
1614 * \return The mimetype of the file that the user selected in the file dialog.
1615 */
1616QString QFileDialog::selectedMimeTypeFilter() const
1617{
1618 Q_D(const QFileDialog);
1619 QString mimeTypeFilter;
1620 if (!d->usingWidgets())
1621 mimeTypeFilter = d->selectedMimeTypeFilter_sys();
1622
1623#if QT_CONFIG(mimetype)
1624 if (mimeTypeFilter.isNull() && !d->options->mimeTypeFilters().isEmpty()) {
1625 const auto nameFilter = selectedNameFilter();
1626 const auto mimeTypes = d->options->mimeTypeFilters();
1627 for (const auto &mimeType: mimeTypes) {
1628 QString filter = nameFilterForMime(mimeType);
1629 if (testOption(option: HideNameFilterDetails))
1630 filter = qt_strip_filters(filters: { filter }).constFirst();
1631 if (filter == nameFilter) {
1632 mimeTypeFilter = mimeType;
1633 break;
1634 }
1635 }
1636 }
1637#endif
1638
1639 return mimeTypeFilter;
1640}
1641
1642/*!
1643 \property QFileDialog::viewMode
1644 \brief The way files and directories are displayed in the dialog.
1645
1646 By default, the \c Detail mode is used to display information about
1647 files and directories.
1648
1649 \sa ViewMode
1650*/
1651void QFileDialog::setViewMode(QFileDialog::ViewMode mode)
1652{
1653 Q_D(QFileDialog);
1654 d->options->setViewMode(static_cast<QFileDialogOptions::ViewMode>(mode));
1655 if (!d->usingWidgets())
1656 return;
1657 if (mode == Detail)
1658 d->showDetailsView();
1659 else
1660 d->showListView();
1661}
1662
1663QFileDialog::ViewMode QFileDialog::viewMode() const
1664{
1665 Q_D(const QFileDialog);
1666 if (!d->usingWidgets())
1667 return static_cast<QFileDialog::ViewMode>(d->options->viewMode());
1668 return (d->qFileDialogUi->stackedWidget->currentWidget() == d->qFileDialogUi->listView->parent() ? QFileDialog::List : QFileDialog::Detail);
1669}
1670
1671/*!
1672 \property QFileDialog::fileMode
1673 \brief The file mode of the dialog.
1674
1675 The file mode defines the number and type of items that the user is
1676 expected to select in the dialog.
1677
1678 By default, this property is set to AnyFile.
1679
1680 This function sets the labels for the FileName and
1681 \l{QFileDialog::}{Accept} \l{DialogLabel}s. It is possible to set
1682 custom text after the call to setFileMode().
1683
1684 \sa FileMode
1685*/
1686void QFileDialog::setFileMode(QFileDialog::FileMode mode)
1687{
1688 Q_D(QFileDialog);
1689 d->options->setFileMode(static_cast<QFileDialogOptions::FileMode>(mode));
1690 if (!d->usingWidgets())
1691 return;
1692
1693 d->retranslateWindowTitle();
1694
1695 // set selection mode and behavior
1696 QAbstractItemView::SelectionMode selectionMode;
1697 if (mode == QFileDialog::ExistingFiles)
1698 selectionMode = QAbstractItemView::ExtendedSelection;
1699 else
1700 selectionMode = QAbstractItemView::SingleSelection;
1701 d->qFileDialogUi->listView->setSelectionMode(selectionMode);
1702 d->qFileDialogUi->treeView->setSelectionMode(selectionMode);
1703 // set filter
1704 d->model->setFilter(d->filterForMode(filters: filter()));
1705 // setup file type for directory
1706 if (mode == Directory) {
1707 d->qFileDialogUi->fileTypeCombo->clear();
1708 d->qFileDialogUi->fileTypeCombo->addItem(tr(s: "Directories"));
1709 d->qFileDialogUi->fileTypeCombo->setEnabled(false);
1710 }
1711 d->updateFileNameLabel();
1712 d->updateOkButtonText();
1713 d->qFileDialogUi->fileTypeCombo->setEnabled(!testOption(option: ShowDirsOnly));
1714 d->updateOkButton();
1715}
1716
1717QFileDialog::FileMode QFileDialog::fileMode() const
1718{
1719 Q_D(const QFileDialog);
1720 return static_cast<FileMode>(d->options->fileMode());
1721}
1722
1723/*!
1724 \property QFileDialog::acceptMode
1725 \brief The accept mode of the dialog.
1726
1727 The action mode defines whether the dialog is for opening or saving files.
1728
1729 By default, this property is set to \l{AcceptOpen}.
1730
1731 \sa AcceptMode
1732*/
1733void QFileDialog::setAcceptMode(QFileDialog::AcceptMode mode)
1734{
1735 Q_D(QFileDialog);
1736 d->options->setAcceptMode(static_cast<QFileDialogOptions::AcceptMode>(mode));
1737 // clear WA_DontShowOnScreen so that d->canBeNativeDialog() doesn't return false incorrectly
1738 setAttribute(Qt::WA_DontShowOnScreen, on: false);
1739 if (!d->usingWidgets())
1740 return;
1741 QDialogButtonBox::StandardButton button = (mode == AcceptOpen ? QDialogButtonBox::Open : QDialogButtonBox::Save);
1742 d->qFileDialogUi->buttonBox->setStandardButtons(button | QDialogButtonBox::Cancel);
1743 d->qFileDialogUi->buttonBox->button(button)->setEnabled(false);
1744 d->updateOkButton();
1745 if (mode == AcceptSave) {
1746 d->qFileDialogUi->lookInCombo->setEditable(false);
1747 }
1748 d->retranslateWindowTitle();
1749}
1750
1751/*!
1752 \property QFileDialog::supportedSchemes
1753 \brief The URL schemes that the file dialog should allow navigating to.
1754 \since 5.6
1755
1756 Setting this property allows to restrict the type of URLs the
1757 user can select. It is a way for the application to declare
1758 the protocols it supports to fetch the file content. An empty list
1759 means that no restriction is applied (the default).
1760 Support for local files ("file" scheme) is implicit and always enabled;
1761 it is not necessary to include it in the restriction.
1762*/
1763
1764void QFileDialog::setSupportedSchemes(const QStringList &schemes)
1765{
1766 Q_D(QFileDialog);
1767 d->options->setSupportedSchemes(schemes);
1768}
1769
1770QStringList QFileDialog::supportedSchemes() const
1771{
1772 return d_func()->options->supportedSchemes();
1773}
1774
1775/*
1776 Returns the file system model index that is the root index in the
1777 views
1778*/
1779QModelIndex QFileDialogPrivate::rootIndex() const {
1780 return mapToSource(index: qFileDialogUi->listView->rootIndex());
1781}
1782
1783QAbstractItemView *QFileDialogPrivate::currentView() const {
1784 if (!qFileDialogUi->stackedWidget)
1785 return nullptr;
1786 if (qFileDialogUi->stackedWidget->currentWidget() == qFileDialogUi->listView->parent())
1787 return qFileDialogUi->listView;
1788 return qFileDialogUi->treeView;
1789}
1790
1791QLineEdit *QFileDialogPrivate::lineEdit() const {
1792 return (QLineEdit*)qFileDialogUi->fileNameEdit;
1793}
1794
1795long QFileDialogPrivate::maxNameLength(const QString &path)
1796{
1797#if defined(Q_OS_UNIX)
1798 return ::pathconf(path: QFile::encodeName(fileName: path).data(), _PC_NAME_MAX);
1799#elif defined(Q_OS_WIN)
1800 DWORD maxLength;
1801 const QString drive = path.left(3);
1802 if (::GetVolumeInformation(reinterpret_cast<const wchar_t *>(drive.utf16()), NULL, 0, NULL, &maxLength, NULL, NULL, 0) == false)
1803 return -1;
1804 return maxLength;
1805#else
1806 Q_UNUSED(path);
1807#endif
1808 return -1;
1809}
1810
1811/*
1812 Sets the view root index to be the file system model index
1813*/
1814void QFileDialogPrivate::setRootIndex(const QModelIndex &index) const {
1815 Q_ASSERT(index.isValid() ? index.model() == model : true);
1816 QModelIndex idx = mapFromSource(index);
1817 qFileDialogUi->treeView->setRootIndex(idx);
1818 qFileDialogUi->listView->setRootIndex(idx);
1819}
1820/*
1821 Select a file system model index
1822 returns the index that was selected (or not depending upon sortfilterproxymodel)
1823*/
1824QModelIndex QFileDialogPrivate::select(const QModelIndex &index) const {
1825 Q_ASSERT(index.isValid() ? index.model() == model : true);
1826
1827 QModelIndex idx = mapFromSource(index);
1828 if (idx.isValid() && !qFileDialogUi->listView->selectionModel()->isSelected(idx))
1829 qFileDialogUi->listView->selectionModel()->select(idx,
1830 QItemSelectionModel::Select | QItemSelectionModel::Rows);
1831 return idx;
1832}
1833
1834QFileDialog::AcceptMode QFileDialog::acceptMode() const
1835{
1836 Q_D(const QFileDialog);
1837 return static_cast<AcceptMode>(d->options->acceptMode());
1838}
1839
1840/*!
1841 \property QFileDialog::defaultSuffix
1842 \brief Suffix added to the filename if no other suffix was specified.
1843
1844 This property specifies a string that is added to the
1845 filename if it has no suffix yet. The suffix is typically
1846 used to indicate the file type (e.g. "txt" indicates a text
1847 file).
1848
1849 If the first character is a dot ('.'), it is removed.
1850*/
1851void QFileDialog::setDefaultSuffix(const QString &suffix)
1852{
1853 Q_D(QFileDialog);
1854 d->options->setDefaultSuffix(suffix);
1855}
1856
1857QString QFileDialog::defaultSuffix() const
1858{
1859 Q_D(const QFileDialog);
1860 return d->options->defaultSuffix();
1861}
1862
1863/*!
1864 Sets the browsing history of the filedialog to contain the given
1865 \a paths.
1866*/
1867void QFileDialog::setHistory(const QStringList &paths)
1868{
1869 Q_D(QFileDialog);
1870 if (d->usingWidgets())
1871 d->qFileDialogUi->lookInCombo->setHistory(paths);
1872}
1873
1874void QFileDialogComboBox::setHistory(const QStringList &paths)
1875{
1876 m_history = paths;
1877 // Only populate the first item, showPopup will populate the rest if needed
1878 QList<QUrl> list;
1879 const QModelIndex idx = d_ptr->model->index(path: d_ptr->rootPath());
1880 //On windows the popup display the "C:\", convert to nativeSeparators
1881 const QUrl url = idx.isValid()
1882 ? QUrl::fromLocalFile(localfile: QDir::toNativeSeparators(pathName: idx.data(arole: QFileSystemModel::FilePathRole).toString()))
1883 : QUrl("file:"_L1);
1884 if (url.isValid())
1885 list.append(t: url);
1886 urlModel->setUrls(list);
1887}
1888
1889/*!
1890 Returns the browsing history of the filedialog as a list of paths.
1891*/
1892QStringList QFileDialog::history() const
1893{
1894 Q_D(const QFileDialog);
1895 if (!d->usingWidgets())
1896 return QStringList();
1897 QStringList currentHistory = d->qFileDialogUi->lookInCombo->history();
1898 //On windows the popup display the "C:\", convert to nativeSeparators
1899 QString newHistory = QDir::toNativeSeparators(pathName: d->rootIndex().data(arole: QFileSystemModel::FilePathRole).toString());
1900 if (!currentHistory.contains(str: newHistory))
1901 currentHistory << newHistory;
1902 return currentHistory;
1903}
1904
1905/*!
1906 Sets the item delegate used to render items in the views in the
1907 file dialog to the given \a delegate.
1908
1909 Any existing delegate will be removed, but not deleted. QFileDialog
1910 does not take ownership of \a delegate.
1911
1912 \warning You should not share the same instance of a delegate between views.
1913 Doing so can cause incorrect or unintuitive editing behavior since each
1914 view connected to a given delegate may receive the \l{QAbstractItemDelegate::}{closeEditor()}
1915 signal, and attempt to access, modify or close an editor that has already been closed.
1916
1917 Note that the model used is QFileSystemModel. It has custom item data roles, which is
1918 described by the \l{QFileSystemModel::}{Roles} enum. You can use a QFileIconProvider if
1919 you only want custom icons.
1920
1921 \sa itemDelegate(), setIconProvider(), QFileSystemModel
1922*/
1923void QFileDialog::setItemDelegate(QAbstractItemDelegate *delegate)
1924{
1925 Q_D(QFileDialog);
1926 if (!d->usingWidgets())
1927 return;
1928 d->qFileDialogUi->listView->setItemDelegate(delegate);
1929 d->qFileDialogUi->treeView->setItemDelegate(delegate);
1930}
1931
1932/*!
1933 Returns the item delegate used to render the items in the views in the filedialog.
1934*/
1935QAbstractItemDelegate *QFileDialog::itemDelegate() const
1936{
1937 Q_D(const QFileDialog);
1938 if (!d->usingWidgets())
1939 return nullptr;
1940 return d->qFileDialogUi->listView->itemDelegate();
1941}
1942
1943/*!
1944 Sets the icon provider used by the filedialog to the specified \a provider.
1945*/
1946void QFileDialog::setIconProvider(QAbstractFileIconProvider *provider)
1947{
1948 Q_D(QFileDialog);
1949 if (!d->usingWidgets())
1950 return;
1951 d->model->setIconProvider(provider);
1952 //It forces the refresh of all entries in the side bar, then we can get new icons
1953 d->qFileDialogUi->sidebar->setUrls(d->qFileDialogUi->sidebar->urls());
1954}
1955
1956/*!
1957 Returns the icon provider used by the filedialog.
1958*/
1959QAbstractFileIconProvider *QFileDialog::iconProvider() const
1960{
1961 Q_D(const QFileDialog);
1962 if (!d->model)
1963 return nullptr;
1964 return d->model->iconProvider();
1965}
1966
1967void QFileDialogPrivate::setLabelTextControl(QFileDialog::DialogLabel label, const QString &text)
1968{
1969 if (!qFileDialogUi)
1970 return;
1971 switch (label) {
1972 case QFileDialog::LookIn:
1973 qFileDialogUi->lookInLabel->setText(text);
1974 break;
1975 case QFileDialog::FileName:
1976 qFileDialogUi->fileNameLabel->setText(text);
1977 break;
1978 case QFileDialog::FileType:
1979 qFileDialogUi->fileTypeLabel->setText(text);
1980 break;
1981 case QFileDialog::Accept:
1982 if (q_func()->acceptMode() == QFileDialog::AcceptOpen) {
1983 if (QPushButton *button = qFileDialogUi->buttonBox->button(QDialogButtonBox::Open))
1984 button->setText(text);
1985 } else {
1986 if (QPushButton *button = qFileDialogUi->buttonBox->button(QDialogButtonBox::Save))
1987 button->setText(text);
1988 }
1989 break;
1990 case QFileDialog::Reject:
1991 if (QPushButton *button = qFileDialogUi->buttonBox->button(QDialogButtonBox::Cancel))
1992 button->setText(text);
1993 break;
1994 }
1995}
1996
1997/*!
1998 Sets the \a text shown in the filedialog in the specified \a label.
1999*/
2000
2001void QFileDialog::setLabelText(DialogLabel label, const QString &text)
2002{
2003 Q_D(QFileDialog);
2004 d->options->setLabelText(label: static_cast<QFileDialogOptions::DialogLabel>(label), text);
2005 d->setLabelTextControl(label, text);
2006}
2007
2008/*!
2009 Returns the text shown in the filedialog in the specified \a label.
2010*/
2011QString QFileDialog::labelText(DialogLabel label) const
2012{
2013 Q_D(const QFileDialog);
2014 if (!d->usingWidgets())
2015 return d->options->labelText(label: static_cast<QFileDialogOptions::DialogLabel>(label));
2016 QPushButton *button;
2017 switch (label) {
2018 case LookIn:
2019 return d->qFileDialogUi->lookInLabel->text();
2020 case FileName:
2021 return d->qFileDialogUi->fileNameLabel->text();
2022 case FileType:
2023 return d->qFileDialogUi->fileTypeLabel->text();
2024 case Accept:
2025 if (acceptMode() == AcceptOpen)
2026 button = d->qFileDialogUi->buttonBox->button(QDialogButtonBox::Open);
2027 else
2028 button = d->qFileDialogUi->buttonBox->button(QDialogButtonBox::Save);
2029 if (button)
2030 return button->text();
2031 break;
2032 case Reject:
2033 button = d->qFileDialogUi->buttonBox->button(QDialogButtonBox::Cancel);
2034 if (button)
2035 return button->text();
2036 break;
2037 }
2038 return QString();
2039}
2040
2041/*!
2042 This is a convenience static function that returns an existing file
2043 selected by the user. If the user presses Cancel, it returns a null string.
2044
2045 \snippet code/src_gui_dialogs_qfiledialog.cpp 8
2046
2047 The function creates a modal file dialog with the given \a parent widget.
2048 If \a parent is not \nullptr, the dialog is shown centered over the
2049 parent widget.
2050
2051 The file dialog's working directory is set to \a dir. If \a dir
2052 includes a file name, the file is selected. Only files that match the
2053 given \a filter are shown. The selected filter is set to \a selectedFilter.
2054 The parameters \a dir, \a selectedFilter, and \a filter may be empty
2055 strings. If you want multiple filters, separate them with ';;', for
2056 example:
2057
2058 \snippet code/src_gui_dialogs_qfiledialog.cpp 14
2059
2060 The \a options argument holds various options about how to run the dialog.
2061 See the QFileDialog::Option enum for more information on the flags you can
2062 pass.
2063
2064 The dialog's caption is set to \a caption. If \a caption is not specified,
2065 then a default caption will be used.
2066
2067 On Windows, and \macos, this static function uses the
2068 native file dialog and not a QFileDialog. Note that the \macos native file
2069 dialog does not show a title bar.
2070
2071 On Windows the dialog spins a blocking modal event loop that does not
2072 dispatch any QTimers, and if \a parent is not \nullptr then it positions
2073 the dialog just below the parent's title bar.
2074
2075 On Unix/X11, the normal behavior of the file dialog is to resolve and
2076 follow symlinks. For example, if \c{/usr/tmp} is a symlink to \c{/var/tmp},
2077 the file dialog changes to \c{/var/tmp} after entering \c{/usr/tmp}. If
2078 \a options includes DontResolveSymlinks, the file dialog treats
2079 symlinks as regular directories.
2080
2081 \sa getOpenFileNames(), getSaveFileName(), getExistingDirectory()
2082*/
2083QString QFileDialog::getOpenFileName(QWidget *parent,
2084 const QString &caption,
2085 const QString &dir,
2086 const QString &filter,
2087 QString *selectedFilter,
2088 Options options)
2089{
2090 const QStringList schemes = QStringList(QStringLiteral("file"));
2091 const QUrl selectedUrl = getOpenFileUrl(parent, caption, dir: QUrl::fromLocalFile(localfile: dir), filter,
2092 selectedFilter, options, supportedSchemes: schemes);
2093 if (selectedUrl.isLocalFile() || selectedUrl.isEmpty())
2094 return selectedUrl.toLocalFile();
2095 else
2096 return selectedUrl.toString();
2097}
2098
2099/*!
2100 This is a convenience static function that returns an existing file
2101 selected by the user. If the user presses Cancel, it returns an
2102 empty url.
2103
2104 The function is used similarly to QFileDialog::getOpenFileName(). In
2105 particular \a parent, \a caption, \a dir, \a filter, \a selectedFilter
2106 and \a options are used in exactly the same way.
2107
2108 The main difference with QFileDialog::getOpenFileName() comes from
2109 the ability offered to the user to select a remote file. That's why
2110 the return type and the type of \a dir is QUrl.
2111
2112 The \a supportedSchemes argument allows to restrict the type of URLs the
2113 user is able to select. It is a way for the application to declare
2114 the protocols it will support to fetch the file content. An empty list
2115 means that no restriction is applied (the default).
2116 Support for local files ("file" scheme) is implicit and always enabled;
2117 it is not necessary to include it in the restriction.
2118
2119 When possible, this static function uses the native file dialog and
2120 not a QFileDialog. On platforms that don't support selecting remote
2121 files, Qt will allow to select only local files.
2122
2123 \sa getOpenFileName(), getOpenFileUrls(), getSaveFileUrl(), getExistingDirectoryUrl()
2124 \since 5.2
2125*/
2126QUrl QFileDialog::getOpenFileUrl(QWidget *parent,
2127 const QString &caption,
2128 const QUrl &dir,
2129 const QString &filter,
2130 QString *selectedFilter,
2131 Options options,
2132 const QStringList &supportedSchemes)
2133{
2134 QFileDialogArgs args(dir);
2135 args.parent = parent;
2136 args.caption = caption;
2137 args.filter = filter;
2138 args.mode = ExistingFile;
2139 args.options = options;
2140
2141 QAutoPointer<QFileDialog> dialog(new QFileDialog(args));
2142 dialog->setSupportedSchemes(supportedSchemes);
2143 if (selectedFilter && !selectedFilter->isEmpty())
2144 dialog->selectNameFilter(filter: *selectedFilter);
2145 const int execResult = dialog->exec();
2146 if (bool(dialog) && execResult == QDialog::Accepted) {
2147 if (selectedFilter)
2148 *selectedFilter = dialog->selectedNameFilter();
2149 return dialog->selectedUrls().value(i: 0);
2150 }
2151 return QUrl();
2152}
2153
2154/*!
2155 This is a convenience static function that returns one or more existing
2156 files selected by the user.
2157
2158 \snippet code/src_gui_dialogs_qfiledialog.cpp 9
2159
2160 This function creates a modal file dialog with the given \a parent widget.
2161 If \a parent is not \nullptr, the dialog is shown centered over the
2162 parent widget.
2163
2164 The file dialog's working directory is set to \a dir. If \a dir
2165 includes a file name, the file is selected. The filter is set to
2166 \a filter so that only those files which match the filter are shown. The
2167 filter selected is set to \a selectedFilter. The parameters \a dir,
2168 \a selectedFilter and \a filter can be empty strings. If you need multiple
2169 filters, separate them with ';;', for instance:
2170
2171 \snippet code/src_gui_dialogs_qfiledialog.cpp 14
2172
2173 The dialog's caption is set to \a caption. If \a caption is not specified,
2174 then a default caption is used.
2175
2176 On Windows and \macos, this static function uses the
2177 native file dialog and not a QFileDialog. Note that the \macos native file
2178 dialog does not show a title bar.
2179
2180 On Windows the dialog spins a blocking modal event loop that does not
2181 dispatch any QTimers, and if \a parent is not \nullptr then it positions
2182 the dialog just below the parent's title bar.
2183
2184 On Unix/X11, the normal behavior of the file dialog is to resolve and
2185 follow symlinks. For example, if \c{/usr/tmp} is a symlink to \c{/var/tmp},
2186 the file dialog will change to \c{/var/tmp} after entering \c{/usr/tmp}.
2187 The \a options argument holds various options about how to run the dialog,
2188 see the QFileDialog::Option enum for more information on the flags you can
2189 pass.
2190
2191 \sa getOpenFileName(), getSaveFileName(), getExistingDirectory()
2192*/
2193QStringList QFileDialog::getOpenFileNames(QWidget *parent,
2194 const QString &caption,
2195 const QString &dir,
2196 const QString &filter,
2197 QString *selectedFilter,
2198 Options options)
2199{
2200 const QStringList schemes = QStringList(QStringLiteral("file"));
2201 const QList<QUrl> selectedUrls = getOpenFileUrls(parent, caption, dir: QUrl::fromLocalFile(localfile: dir),
2202 filter, selectedFilter, options, supportedSchemes: schemes);
2203 QStringList fileNames;
2204 fileNames.reserve(asize: selectedUrls.size());
2205 for (const QUrl &url : selectedUrls)
2206 fileNames.append(t: url.toString(options: QUrl::PreferLocalFile));
2207 return fileNames;
2208}
2209
2210/*!
2211 This is a convenience static function that returns one or more existing
2212 files selected by the user. If the user presses Cancel, it returns an
2213 empty list.
2214
2215 The function is used similarly to QFileDialog::getOpenFileNames(). In
2216 particular \a parent, \a caption, \a dir, \a filter, \a selectedFilter
2217 and \a options are used in exactly the same way.
2218
2219 The main difference with QFileDialog::getOpenFileNames() comes from
2220 the ability offered to the user to select remote files. That's why
2221 the return type and the type of \a dir are respectively QList<QUrl>
2222 and QUrl.
2223
2224 The \a supportedSchemes argument allows to restrict the type of URLs the
2225 user can select. It is a way for the application to declare
2226 the protocols it supports to fetch the file content. An empty list
2227 means that no restriction is applied (the default).
2228 Support for local files ("file" scheme) is implicit and always enabled;
2229 it is not necessary to include it in the restriction.
2230
2231 When possible, this static function uses the native file dialog and
2232 not a QFileDialog. On platforms that don't support selecting remote
2233 files, Qt will allow to select only local files.
2234
2235 \sa getOpenFileNames(), getOpenFileUrl(), getSaveFileUrl(), getExistingDirectoryUrl()
2236 \since 5.2
2237*/
2238QList<QUrl> QFileDialog::getOpenFileUrls(QWidget *parent,
2239 const QString &caption,
2240 const QUrl &dir,
2241 const QString &filter,
2242 QString *selectedFilter,
2243 Options options,
2244 const QStringList &supportedSchemes)
2245{
2246 QFileDialogArgs args(dir);
2247 args.parent = parent;
2248 args.caption = caption;
2249 args.filter = filter;
2250 args.mode = ExistingFiles;
2251 args.options = options;
2252
2253 QAutoPointer<QFileDialog> dialog(new QFileDialog(args));
2254 dialog->setSupportedSchemes(supportedSchemes);
2255 if (selectedFilter && !selectedFilter->isEmpty())
2256 dialog->selectNameFilter(filter: *selectedFilter);
2257 const int execResult = dialog->exec();
2258 if (bool(dialog) && execResult == QDialog::Accepted) {
2259 if (selectedFilter)
2260 *selectedFilter = dialog->selectedNameFilter();
2261 return dialog->selectedUrls();
2262 }
2263 return QList<QUrl>();
2264}
2265
2266/*!
2267 This is a convenience static function that returns the content of a file
2268 selected by the user.
2269
2270 Use this function to access local files on Qt for WebAssembly, if the web sandbox
2271 restricts file access. Its implementation enables displaying a native file dialog in
2272 the browser, where the user selects a file based on the \a nameFilter parameter.
2273
2274 \a parent is ignored on Qt for WebAssembly. Pass \a parent on other platforms, to make
2275 the popup a child of another widget. If the platform doesn't support native file
2276 dialogs, the function falls back to QFileDialog.
2277
2278 The function is asynchronous and returns immediately. The \a fileOpenCompleted
2279 callback will be called when a file has been selected and its contents have been
2280 read into memory.
2281
2282 \snippet code/src_gui_dialogs_qfiledialog.cpp 15
2283 \since 5.13
2284*/
2285void QFileDialog::getOpenFileContent(const QString &nameFilter, const std::function<void(const QString &, const QByteArray &)> &fileOpenCompleted, QWidget *parent)
2286{
2287#ifdef Q_OS_WASM
2288 Q_UNUSED(parent);
2289 auto openFileImpl = std::make_shared<std::function<void(void)>>();
2290 QString fileName;
2291 QByteArray fileContent;
2292 *openFileImpl = [=]() mutable {
2293 auto fileDialogClosed = [&](bool fileSelected) {
2294 if (!fileSelected) {
2295 fileOpenCompleted(fileName, fileContent);
2296 openFileImpl.reset();
2297 }
2298 };
2299 auto acceptFile = [&](uint64_t size, const std::string name) -> char * {
2300 const uint64_t twoGB = 1ULL << 31; // QByteArray limit
2301 if (size > twoGB)
2302 return nullptr;
2303
2304 fileName = QString::fromStdString(name);
2305 fileContent.resize(size);
2306 return fileContent.data();
2307 };
2308 auto fileContentReady = [&]() mutable {
2309 fileOpenCompleted(fileName, fileContent);
2310 openFileImpl.reset();
2311 };
2312
2313 QWasmLocalFileAccess::openFile(nameFilter.toStdString(), fileDialogClosed, acceptFile, fileContentReady);
2314 };
2315
2316 (*openFileImpl)();
2317#else
2318 QFileDialog *dialog = new QFileDialog(parent);
2319 dialog->setFileMode(QFileDialog::ExistingFile);
2320 dialog->setNameFilter(nameFilter);
2321 dialog->setAttribute(Qt::WA_DeleteOnClose);
2322
2323 auto fileSelected = [=](const QString &fileName) {
2324 QByteArray fileContent;
2325 if (!fileName.isNull()) {
2326 QFile selectedFile(fileName);
2327 if (selectedFile.open(flags: QIODevice::ReadOnly))
2328 fileContent = selectedFile.readAll();
2329 }
2330 fileOpenCompleted(fileName, fileContent);
2331 };
2332
2333 connect(sender: dialog, signal: &QFileDialog::fileSelected, context: dialog, slot&: fileSelected);
2334 dialog->show();
2335#endif
2336}
2337
2338/*!
2339 This is a convenience static function that saves \a fileContent to a file, using
2340 a file name and location chosen by the user. \a fileNameHint can be provided to
2341 suggest a file name to the user.
2342
2343 Use this function to save content to local files on Qt for WebAssembly, if the web sandbox
2344 restricts file access. Its implementation enables displaying a native file dialog in the
2345 browser, where the user specifies an output file based on the \a fileNameHint argument.
2346
2347 \a parent is ignored on Qt for WebAssembly. Pass \a parent on other platforms, to make
2348 the popup a child of another widget. If the platform doesn't support native file
2349 dialogs, the function falls back to QFileDialog.
2350
2351 The function is asynchronous and returns immediately.
2352
2353 \snippet code/src_gui_dialogs_qfiledialog.cpp 16
2354 \since 5.14
2355*/
2356void QFileDialog::saveFileContent(const QByteArray &fileContent, const QString &fileNameHint, QWidget *parent)
2357{
2358#ifdef Q_OS_WASM
2359 Q_UNUSED(parent);
2360 QWasmLocalFileAccess::saveFile(fileContent, fileNameHint.toStdString());
2361#else
2362 QFileDialog *dialog = new QFileDialog(parent);
2363 dialog->setAcceptMode(QFileDialog::AcceptSave);
2364 dialog->setFileMode(QFileDialog::AnyFile);
2365 dialog->selectFile(filename: fileNameHint);
2366
2367 auto fileSelected = [=](const QString &fileName) {
2368 if (!fileName.isNull()) {
2369 QFile selectedFile(fileName);
2370 if (selectedFile.open(flags: QIODevice::WriteOnly))
2371 selectedFile.write(data: fileContent);
2372 }
2373 };
2374
2375 connect(sender: dialog, signal: &QFileDialog::fileSelected, context: dialog, slot&: fileSelected);
2376 dialog->setAttribute(Qt::WA_DeleteOnClose);
2377 dialog->show();
2378#endif
2379}
2380
2381/*!
2382 This is a convenience static function that returns a file name selected
2383 by the user. The file does not have to exist.
2384
2385 It creates a modal file dialog with the given \a parent widget. If
2386 \a parent is not \nullptr, the dialog will be shown centered over the
2387 parent widget.
2388
2389 \snippet code/src_gui_dialogs_qfiledialog.cpp 11
2390
2391 The file dialog's working directory is set to \a dir. If \a dir
2392 includes a file name, the file is selected. Only files that match the
2393 \a filter are shown. The filter selected is set to \a selectedFilter. The
2394 parameters \a dir, \a selectedFilter, and \a filter may be empty strings.
2395 Multiple filters are separated with ';;'. For instance:
2396
2397 \snippet code/src_gui_dialogs_qfiledialog.cpp 14
2398
2399 The \a options argument holds various options about how to run the dialog,
2400 see the QFileDialog::Option enum for more information on the flags you can
2401 pass.
2402
2403 The default filter can be chosen by setting \a selectedFilter to the
2404 desired value.
2405
2406 The dialog's caption is set to \a caption. If \a caption is not specified,
2407 a default caption is used.
2408
2409 On Windows, and \macos, this static function uses the
2410 native file dialog and not a QFileDialog.
2411
2412 On Windows the dialog spins a blocking modal event loop that does not
2413 dispatch any QTimers, and if \a parent is not \nullptr then it
2414 positions the dialog just below the parent's title bar. On \macos, with its
2415 native file dialog, the filter argument is ignored.
2416
2417 On Unix/X11, the normal behavior of the file dialog is to resolve and
2418 follow symlinks. For example, if \c{/usr/tmp} is a symlink to \c{/var/tmp},
2419 the file dialog changes to \c{/var/tmp} after entering \c{/usr/tmp}. If
2420 \a options includes DontResolveSymlinks, the file dialog treats symlinks
2421 as regular directories.
2422
2423 \sa getOpenFileName(), getOpenFileNames(), getExistingDirectory()
2424*/
2425QString QFileDialog::getSaveFileName(QWidget *parent,
2426 const QString &caption,
2427 const QString &dir,
2428 const QString &filter,
2429 QString *selectedFilter,
2430 Options options)
2431{
2432 const QStringList schemes = QStringList(QStringLiteral("file"));
2433 const QUrl selectedUrl = getSaveFileUrl(parent, caption, dir: QUrl::fromLocalFile(localfile: dir), filter,
2434 selectedFilter, options, supportedSchemes: schemes);
2435 if (selectedUrl.isLocalFile() || selectedUrl.isEmpty())
2436 return selectedUrl.toLocalFile();
2437 else
2438 return selectedUrl.toString();
2439}
2440
2441/*!
2442 This is a convenience static function that returns a file selected by
2443 the user. The file does not have to exist. If the user presses Cancel,
2444 it returns an empty url.
2445
2446 The function is used similarly to QFileDialog::getSaveFileName(). In
2447 particular \a parent, \a caption, \a dir, \a filter, \a selectedFilter
2448 and \a options are used in exactly the same way.
2449
2450 The main difference with QFileDialog::getSaveFileName() comes from
2451 the ability offered to the user to select a remote file. That's why
2452 the return type and the type of \a dir is QUrl.
2453
2454 The \a supportedSchemes argument allows to restrict the type of URLs the
2455 user can select. It is a way for the application to declare
2456 the protocols it supports to save the file content. An empty list
2457 means that no restriction is applied (the default).
2458 Support for local files ("file" scheme) is implicit and always enabled;
2459 it is not necessary to include it in the restriction.
2460
2461 When possible, this static function uses the native file dialog and
2462 not a QFileDialog. On platforms that don't support selecting remote
2463 files, Qt will allow to select only local files.
2464
2465 \sa getSaveFileName(), getOpenFileUrl(), getOpenFileUrls(), getExistingDirectoryUrl()
2466 \since 5.2
2467*/
2468QUrl QFileDialog::getSaveFileUrl(QWidget *parent,
2469 const QString &caption,
2470 const QUrl &dir,
2471 const QString &filter,
2472 QString *selectedFilter,
2473 Options options,
2474 const QStringList &supportedSchemes)
2475{
2476 QFileDialogArgs args(dir);
2477 args.parent = parent;
2478 args.caption = caption;
2479 args.filter = filter;
2480 args.mode = AnyFile;
2481 args.options = options;
2482
2483 QAutoPointer<QFileDialog> dialog(new QFileDialog(args));
2484 dialog->setSupportedSchemes(supportedSchemes);
2485 dialog->setAcceptMode(AcceptSave);
2486 if (selectedFilter && !selectedFilter->isEmpty())
2487 dialog->selectNameFilter(filter: *selectedFilter);
2488 const int execResult = dialog->exec();
2489 if (bool(dialog) && execResult == QDialog::Accepted) {
2490 if (selectedFilter)
2491 *selectedFilter = dialog->selectedNameFilter();
2492 return dialog->selectedUrls().value(i: 0);
2493 }
2494 return QUrl();
2495}
2496
2497/*!
2498 This is a convenience static function that returns an existing
2499 directory selected by the user.
2500
2501 \snippet code/src_gui_dialogs_qfiledialog.cpp 12
2502
2503 This function creates a modal file dialog with the given \a parent widget.
2504 If \a parent is not \nullptr, the dialog is shown centered over the
2505 parent widget.
2506
2507 The dialog's working directory is set to \a dir, and the caption is set to
2508 \a caption. Either of these can be an empty string in which case the
2509 current directory and a default caption are used respectively.
2510
2511 The \a options argument holds various options about how to run the dialog.
2512 See the QFileDialog::Option enum for more information on the flags you can
2513 pass. To ensure a native file dialog, \l{QFileDialog::}{ShowDirsOnly} must
2514 be set.
2515
2516 On Windows and \macos, this static function uses the
2517 native file dialog and not a QFileDialog. However, the native Windows file
2518 dialog does not support displaying files in the directory chooser. You need
2519 to pass the \l{QFileDialog::}{DontUseNativeDialog} option, or set the global
2520 \\l{Qt::}{AA_DontUseNativeDialogs} application attribute to display files using a
2521 QFileDialog.
2522
2523 Note that the \macos native file dialog does not show a title bar.
2524
2525 On Unix/X11, the normal behavior of the file dialog is to resolve and
2526 follow symlinks. For example, if \c{/usr/tmp} is a symlink to \c{/var/tmp},
2527 the file dialog changes to \c{/var/tmp} after entering \c{/usr/tmp}. If
2528 \a options includes DontResolveSymlinks, the file dialog treats
2529 symlinks as regular directories.
2530
2531 On Windows, the dialog spins a blocking modal event loop that does not
2532 dispatch any QTimers, and if \a parent is not \nullptr then it positions
2533 the dialog just below the parent's title bar.
2534
2535 \sa getOpenFileName(), getOpenFileNames(), getSaveFileName()
2536*/
2537QString QFileDialog::getExistingDirectory(QWidget *parent,
2538 const QString &caption,
2539 const QString &dir,
2540 Options options)
2541{
2542 const QStringList schemes = QStringList(QStringLiteral("file"));
2543 const QUrl selectedUrl =
2544 getExistingDirectoryUrl(parent, caption, dir: QUrl::fromLocalFile(localfile: dir), options, supportedSchemes: schemes);
2545 if (selectedUrl.isLocalFile() || selectedUrl.isEmpty())
2546 return selectedUrl.toLocalFile();
2547 else
2548 return selectedUrl.toString();
2549}
2550
2551/*!
2552 This is a convenience static function that returns an existing
2553 directory selected by the user. If the user presses Cancel, it
2554 returns an empty url.
2555
2556 The function is used similarly to QFileDialog::getExistingDirectory().
2557 In particular \a parent, \a caption, \a dir and \a options are used
2558 in exactly the same way.
2559
2560 The main difference with QFileDialog::getExistingDirectory() comes from
2561 the ability offered to the user to select a remote directory. That's why
2562 the return type and the type of \a dir is QUrl.
2563
2564 The \a supportedSchemes argument allows to restrict the type of URLs the
2565 user is able to select. It is a way for the application to declare
2566 the protocols it supports to fetch the file content. An empty list
2567 means that no restriction is applied (the default).
2568 Support for local files ("file" scheme) is implicit and always enabled;
2569 it is not necessary to include it in the restriction.
2570
2571 When possible, this static function uses the native file dialog and
2572 not a QFileDialog. On platforms that don't support selecting remote
2573 files, Qt allows to select only local files.
2574
2575 \sa getExistingDirectory(), getOpenFileUrl(), getOpenFileUrls(), getSaveFileUrl()
2576 \since 5.2
2577*/
2578QUrl QFileDialog::getExistingDirectoryUrl(QWidget *parent,
2579 const QString &caption,
2580 const QUrl &dir,
2581 Options options,
2582 const QStringList &supportedSchemes)
2583{
2584 QFileDialogArgs args(dir);
2585 args.parent = parent;
2586 args.caption = caption;
2587 args.mode = Directory;
2588 args.options = options;
2589
2590 QAutoPointer<QFileDialog> dialog(new QFileDialog(args));
2591 dialog->setSupportedSchemes(supportedSchemes);
2592 const int execResult = dialog->exec();
2593 if (bool(dialog) && execResult == QDialog::Accepted)
2594 return dialog->selectedUrls().value(i: 0);
2595 return QUrl();
2596}
2597
2598inline static QUrl _qt_get_directory(const QUrl &url, const QFileInfo &local)
2599{
2600 if (url.isLocalFile()) {
2601 QFileInfo info = local;
2602 if (!local.isAbsolute())
2603 info = QFileInfo(QDir::current(), url.toLocalFile());
2604 const QFileInfo pathInfo(info.absolutePath());
2605 if (!pathInfo.exists() || !pathInfo.isDir())
2606 return QUrl();
2607 if (info.exists() && info.isDir())
2608 return QUrl::fromLocalFile(localfile: QDir::cleanPath(path: info.absoluteFilePath()));
2609 return QUrl::fromLocalFile(localfile: pathInfo.absoluteFilePath());
2610 } else {
2611 return url;
2612 }
2613}
2614
2615/*
2616 Initialize working directory and selection from \a url.
2617*/
2618QFileDialogArgs::QFileDialogArgs(const QUrl &url)
2619{
2620 // default case, re-use QFileInfo to avoid stat'ing
2621 const QFileInfo local(url.toLocalFile());
2622 // Get the initial directory URL
2623 if (!url.isEmpty())
2624 directory = _qt_get_directory(url, local);
2625 if (directory.isEmpty()) {
2626 const QUrl lastVisited = *lastVisitedDir();
2627 if (lastVisited != url)
2628 directory = _qt_get_directory(url: lastVisited, local: QFileInfo());
2629 }
2630 if (directory.isEmpty())
2631 directory = QUrl::fromLocalFile(localfile: QDir::currentPath());
2632
2633 /*
2634 The initial directory can contain both the initial directory
2635 and initial selection, e.g. /home/user/foo.txt
2636 */
2637 if (selection.isEmpty() && !url.isEmpty()) {
2638 if (url.isLocalFile()) {
2639 if (!local.isDir())
2640 selection = local.fileName();
2641 } else {
2642 // With remote URLs we can only assume.
2643 selection = url.fileName();
2644 }
2645 }
2646}
2647
2648/*!
2649 \reimp
2650*/
2651void QFileDialog::done(int result)
2652{
2653 Q_D(QFileDialog);
2654
2655 QDialog::done(result);
2656
2657 if (d->receiverToDisconnectOnClose) {
2658 disconnect(sender: this, signal: d->signalToDisconnectOnClose,
2659 receiver: d->receiverToDisconnectOnClose, member: d->memberToDisconnectOnClose);
2660 d->receiverToDisconnectOnClose = nullptr;
2661 }
2662 d->memberToDisconnectOnClose.clear();
2663 d->signalToDisconnectOnClose.clear();
2664}
2665
2666bool QFileDialogPrivate::itemAlreadyExists(const QString &fileName)
2667{
2668#if QT_CONFIG(messagebox)
2669 Q_Q(QFileDialog);
2670 const QString msg = QFileDialog::tr(s: "%1 already exists.\nDo you want to replace it?").arg(a: fileName);
2671 using B = QMessageBox;
2672 const auto res = B::warning(parent: q, title: q->windowTitle(), text: msg, buttons: B::Yes | B::No, defaultButton: B::No);
2673 return res == B::Yes;
2674#endif
2675 return false;
2676}
2677
2678void QFileDialogPrivate::itemNotFound(const QString &fileName, QFileDialog::FileMode mode)
2679{
2680#if QT_CONFIG(messagebox)
2681 Q_Q(QFileDialog);
2682 const QString message = mode == QFileDialog::Directory
2683 ? QFileDialog::tr(s: "%1\nDirectory not found.\n"
2684 "Please verify the correct directory name was given.")
2685 : QFileDialog::tr(s: "%1\nFile not found.\nPlease verify the "
2686 "correct file name was given.");
2687
2688 QMessageBox::warning(parent: q, title: q->windowTitle(), text: message.arg(a: fileName));
2689#endif // QT_CONFIG(messagebox)
2690}
2691
2692/*!
2693 \reimp
2694*/
2695void QFileDialog::accept()
2696{
2697 Q_D(QFileDialog);
2698 if (!d->usingWidgets()) {
2699 const QList<QUrl> urls = selectedUrls();
2700 if (urls.isEmpty())
2701 return;
2702 d->emitUrlsSelected(files: urls);
2703 if (urls.size() == 1)
2704 d->emitUrlSelected(file: urls.first());
2705 QDialog::accept();
2706 return;
2707 }
2708
2709 const QStringList files = selectedFiles();
2710 if (files.isEmpty())
2711 return;
2712 QString lineEditText = d->lineEdit()->text();
2713 // "hidden feature" type .. and then enter, and it will move up a dir
2714 // special case for ".."
2715 if (lineEditText == ".."_L1) {
2716 d->navigateToParent();
2717 const QSignalBlocker blocker(d->qFileDialogUi->fileNameEdit);
2718 d->lineEdit()->selectAll();
2719 return;
2720 }
2721
2722 const auto mode = fileMode();
2723 switch (mode) {
2724 case Directory: {
2725 QString fn = files.first();
2726 QFileInfo info(fn);
2727 if (!info.exists())
2728 info = QFileInfo(d->getEnvironmentVariable(string: fn));
2729 if (!info.exists()) {
2730 d->itemNotFound(fileName: info.fileName(), mode);
2731 return;
2732 }
2733 if (info.isDir()) {
2734 d->emitFilesSelected(files);
2735 QDialog::accept();
2736 }
2737 return;
2738 }
2739
2740 case AnyFile: {
2741 QString fn = files.first();
2742 QFileInfo info(fn);
2743 if (info.isDir()) {
2744 setDirectory(info.absoluteFilePath());
2745 return;
2746 }
2747
2748 if (!info.exists()) {
2749 const long maxNameLength = d->maxNameLength(path: info.path());
2750 if (maxNameLength >= 0 && info.fileName().size() > maxNameLength)
2751 return;
2752 }
2753
2754 // check if we have to ask for permission to overwrite the file
2755 if (!info.exists() || testOption(option: DontConfirmOverwrite) || acceptMode() == AcceptOpen) {
2756 d->emitFilesSelected(files: QStringList(fn));
2757 QDialog::accept();
2758 } else {
2759 if (d->itemAlreadyExists(fileName: info.fileName())) {
2760 d->emitFilesSelected(files: QStringList(fn));
2761 QDialog::accept();
2762 }
2763 }
2764 return;
2765 }
2766
2767 case ExistingFile:
2768 case ExistingFiles:
2769 for (const auto &file : files) {
2770 QFileInfo info(file);
2771 if (!info.exists())
2772 info = QFileInfo(d->getEnvironmentVariable(string: file));
2773 if (!info.exists()) {
2774 d->itemNotFound(fileName: info.fileName(), mode);
2775 return;
2776 }
2777 if (info.isDir()) {
2778 setDirectory(info.absoluteFilePath());
2779 d->lineEdit()->clear();
2780 return;
2781 }
2782 }
2783 d->emitFilesSelected(files);
2784 QDialog::accept();
2785 return;
2786 }
2787}
2788
2789#if QT_CONFIG(settings)
2790void QFileDialogPrivate::saveSettings()
2791{
2792 Q_Q(QFileDialog);
2793 QSettings settings(QSettings::UserScope, u"QtProject"_s);
2794 settings.beginGroup(prefix: "FileDialog");
2795
2796 if (usingWidgets()) {
2797 settings.setValue(key: "sidebarWidth", value: qFileDialogUi->splitter->sizes().constFirst());
2798 settings.setValue(key: "shortcuts", value: QUrl::toStringList(uris: qFileDialogUi->sidebar->urls()));
2799 settings.setValue(key: "treeViewHeader", value: qFileDialogUi->treeView->header()->saveState());
2800 }
2801 QStringList historyUrls;
2802 const QStringList history = q->history();
2803 historyUrls.reserve(asize: history.size());
2804 for (const QString &path : history)
2805 historyUrls << QUrl::fromLocalFile(localfile: path).toString();
2806 settings.setValue(key: "history", value: historyUrls);
2807 settings.setValue(key: "lastVisited", value: lastVisitedDir()->toString());
2808 const QMetaEnum &viewModeMeta = q->metaObject()->enumerator(index: q->metaObject()->indexOfEnumerator(name: "ViewMode"));
2809 settings.setValue(key: "viewMode", value: QLatin1StringView(viewModeMeta.key(index: q->viewMode())));
2810 settings.setValue(key: "qtVersion", QT_VERSION_STR ""_L1);
2811}
2812
2813bool QFileDialogPrivate::restoreFromSettings()
2814{
2815 Q_Q(QFileDialog);
2816 QSettings settings(QSettings::UserScope, u"QtProject"_s);
2817 if (!settings.childGroups().contains(str: "FileDialog"_L1))
2818 return false;
2819 settings.beginGroup(prefix: "FileDialog");
2820
2821 q->setDirectoryUrl(lastVisitedDir()->isEmpty() ? settings.value(key: "lastVisited").toUrl() : *lastVisitedDir());
2822
2823 QByteArray viewModeStr = settings.value(key: "viewMode").toString().toLatin1();
2824 const QMetaEnum &viewModeMeta = q->metaObject()->enumerator(index: q->metaObject()->indexOfEnumerator(name: "ViewMode"));
2825 bool ok = false;
2826 int viewMode = viewModeMeta.keyToValue(key: viewModeStr.constData(), ok: &ok);
2827 if (!ok)
2828 viewMode = QFileDialog::List;
2829 q->setViewMode(static_cast<QFileDialog::ViewMode>(viewMode));
2830
2831 sidebarUrls = QUrl::fromStringList(uris: settings.value(key: "shortcuts").toStringList());
2832 headerData = settings.value(key: "treeViewHeader").toByteArray();
2833
2834 if (!usingWidgets())
2835 return true;
2836
2837 QStringList history;
2838 const auto urlStrings = settings.value(key: "history").toStringList();
2839 for (const QString &urlStr : urlStrings) {
2840 QUrl url(urlStr);
2841 if (url.isLocalFile())
2842 history << url.toLocalFile();
2843 }
2844
2845 return restoreWidgetState(history, splitterPosition: settings.value(key: "sidebarWidth", defaultValue: -1).toInt());
2846}
2847#endif // settings
2848
2849bool QFileDialogPrivate::restoreWidgetState(QStringList &history, int splitterPosition)
2850{
2851 Q_Q(QFileDialog);
2852 if (splitterPosition >= 0) {
2853 QList<int> splitterSizes;
2854 splitterSizes.append(t: splitterPosition);
2855 splitterSizes.append(qFileDialogUi->splitter->widget(1)->sizeHint().width());
2856 qFileDialogUi->splitter->setSizes(splitterSizes);
2857 } else {
2858 if (!qFileDialogUi->splitter->restoreState(splitterState))
2859 return false;
2860 QList<int> list = qFileDialogUi->splitter->sizes();
2861 if (list.size() >= 2 && (list.at(i: 0) == 0 || list.at(i: 1) == 0)) {
2862 for (int i = 0; i < list.size(); ++i)
2863 list[i] = qFileDialogUi->splitter->widget(i)->sizeHint().width();
2864 qFileDialogUi->splitter->setSizes(list);
2865 }
2866 }
2867
2868 qFileDialogUi->sidebar->setUrls(sidebarUrls);
2869
2870 static const int MaxHistorySize = 5;
2871 if (history.size() > MaxHistorySize)
2872 history.erase(abegin: history.begin(), aend: history.end() - MaxHistorySize);
2873 q->setHistory(history);
2874
2875 QHeaderView *headerView = qFileDialogUi->treeView->header();
2876 if (!headerView->restoreState(state: headerData))
2877 return false;
2878
2879 QList<QAction*> actions = headerView->actions();
2880 QAbstractItemModel *abstractModel = model;
2881#if QT_CONFIG(proxymodel)
2882 if (proxyModel)
2883 abstractModel = proxyModel;
2884#endif
2885 const int total = qMin(a: abstractModel->columnCount(parent: QModelIndex()), b: int(actions.size() + 1));
2886 for (int i = 1; i < total; ++i)
2887 actions.at(i: i - 1)->setChecked(!headerView->isSectionHidden(logicalIndex: i));
2888
2889 return true;
2890}
2891
2892/*!
2893 \internal
2894
2895 Create widgets, layout and set default values
2896*/
2897void QFileDialogPrivate::init(const QFileDialogArgs &args)
2898{
2899 Q_Q(QFileDialog);
2900 if (!args.caption.isEmpty()) {
2901 useDefaultCaption = false;
2902 setWindowTitle = args.caption;
2903 q->setWindowTitle(args.caption);
2904 }
2905
2906 q->setAcceptMode(QFileDialog::AcceptOpen);
2907 nativeDialogInUse = platformFileDialogHelper() != nullptr;
2908 if (!nativeDialogInUse)
2909 createWidgets();
2910 q->setFileMode(QFileDialog::AnyFile);
2911 if (!args.filter.isEmpty())
2912 q->setNameFilter(args.filter);
2913 // QTBUG-70798, prevent the default blocking the restore logic.
2914 const bool dontStoreDir = !args.directory.isValid() && !lastVisitedDir()->isValid();
2915 q->setDirectoryUrl(args.directory);
2916 if (dontStoreDir)
2917 lastVisitedDir()->clear();
2918 if (args.directory.isLocalFile())
2919 q->selectFile(filename: args.selection);
2920 else
2921 q->selectUrl(url: args.directory);
2922
2923#if QT_CONFIG(settings)
2924 // Try to restore from the FileDialog settings group; if it fails, fall back
2925 // to the pre-5.5 QByteArray serialized settings.
2926 if (!restoreFromSettings()) {
2927 const QSettings settings(QSettings::UserScope, u"QtProject"_s);
2928 q->restoreState(state: settings.value(key: "Qt/filedialog").toByteArray());
2929 }
2930#endif
2931
2932#if defined(Q_EMBEDDED_SMALLSCREEN)
2933 qFileDialogUi->lookInLabel->setVisible(false);
2934 qFileDialogUi->fileNameLabel->setVisible(false);
2935 qFileDialogUi->fileTypeLabel->setVisible(false);
2936 qFileDialogUi->sidebar->hide();
2937#endif
2938
2939 const QSize sizeHint = q->sizeHint();
2940 if (sizeHint.isValid())
2941 q->resize(sizeHint);
2942}
2943
2944/*!
2945 \internal
2946
2947 Create the widgets, set properties and connections
2948*/
2949void QFileDialogPrivate::createWidgets()
2950{
2951 if (qFileDialogUi)
2952 return;
2953 Q_Q(QFileDialog);
2954
2955 // This function is sometimes called late (e.g as a fallback from setVisible). In that case we
2956 // need to ensure that the following UI code (setupUI in particular) doesn't reset any explicitly
2957 // set window state or geometry.
2958 QSize preSize = q->testAttribute(attribute: Qt::WA_Resized) ? q->size() : QSize();
2959 Qt::WindowStates preState = q->windowState();
2960
2961 model = new QFileSystemModel(q);
2962 model->setIconProvider(&defaultIconProvider);
2963 model->setFilter(options->filter());
2964 model->setObjectName("qt_filesystem_model"_L1);
2965 if (QPlatformFileDialogHelper *helper = platformFileDialogHelper())
2966 model->setNameFilterDisables(helper->defaultNameFilterDisables());
2967 else
2968 model->setNameFilterDisables(false);
2969 model->d_func()->disableRecursiveSort = true;
2970 QObjectPrivate::connect(sender: model, signal: &QFileSystemModel::fileRenamed,
2971 receiverPrivate: this, slot: &QFileDialogPrivate::fileRenamed);
2972 QObjectPrivate::connect(sender: model, signal: &QFileSystemModel::rootPathChanged,
2973 receiverPrivate: this, slot: &QFileDialogPrivate::pathChanged);
2974 QObjectPrivate::connect(sender: model, signal: &QFileSystemModel::rowsInserted,
2975 receiverPrivate: this, slot: &QFileDialogPrivate::rowsInserted);
2976 model->setReadOnly(false);
2977
2978 qFileDialogUi.reset(new Ui_QFileDialog());
2979 qFileDialogUi->setupUi(q);
2980
2981 QList<QUrl> initialBookmarks;
2982 initialBookmarks << QUrl("file:"_L1)
2983 << QUrl::fromLocalFile(localfile: QDir::homePath());
2984 qFileDialogUi->sidebar->setModelAndUrls(model, initialBookmarks);
2985 QObjectPrivate::connect(qFileDialogUi->sidebar, &QSidebar::goToUrl,
2986 this, &QFileDialogPrivate::goToUrl);
2987
2988 QObject::connect(qFileDialogUi->buttonBox, &QDialogButtonBox::accepted,
2989 q, &QFileDialog::accept);
2990 QObject::connect(qFileDialogUi->buttonBox, &QDialogButtonBox::rejected,
2991 q, &QFileDialog::reject);
2992
2993 qFileDialogUi->lookInCombo->setFileDialogPrivate(this);
2994 QObjectPrivate::connect(qFileDialogUi->lookInCombo, &QComboBox::textActivated,
2995 this, &QFileDialogPrivate::goToDirectory);
2996
2997 qFileDialogUi->lookInCombo->setInsertPolicy(QComboBox::NoInsert);
2998 qFileDialogUi->lookInCombo->setDuplicatesEnabled(false);
2999
3000 // filename
3001 qFileDialogUi->fileNameEdit->setFileDialogPrivate(this);
3002#ifndef QT_NO_SHORTCUT
3003 qFileDialogUi->fileNameLabel->setBuddy(qFileDialogUi->fileNameEdit);
3004#endif
3005#if QT_CONFIG(fscompleter)
3006 completer = new QFSCompleter(model, q);
3007 qFileDialogUi->fileNameEdit->setCompleter(completer);
3008#endif // QT_CONFIG(fscompleter)
3009
3010 qFileDialogUi->fileNameEdit->setInputMethodHints(Qt::ImhNoPredictiveText);
3011
3012 QObjectPrivate::connect(qFileDialogUi->fileNameEdit, &QLineEdit::textChanged,
3013 this, &QFileDialogPrivate::autoCompleteFileName);
3014 QObjectPrivate::connect(qFileDialogUi->fileNameEdit, &QLineEdit::textChanged,
3015 this, &QFileDialogPrivate::updateOkButton);
3016 QObject::connect(qFileDialogUi->fileNameEdit, &QLineEdit::returnPressed,
3017 q, &QFileDialog::accept);
3018
3019 // filetype
3020 qFileDialogUi->fileTypeCombo->setDuplicatesEnabled(false);
3021 qFileDialogUi->fileTypeCombo->setSizeAdjustPolicy(QComboBox::AdjustToContentsOnFirstShow);
3022 qFileDialogUi->fileTypeCombo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
3023 QObjectPrivate::connect(qFileDialogUi->fileTypeCombo, &QComboBox::activated,
3024 this, &QFileDialogPrivate::useNameFilter);
3025 QObject::connect(qFileDialogUi->fileTypeCombo, &QComboBox::textActivated,
3026 q, &QFileDialog::filterSelected);
3027
3028 qFileDialogUi->listView->setFileDialogPrivate(this);
3029 qFileDialogUi->listView->setModel(model);
3030 QObjectPrivate::connect(qFileDialogUi->listView, &QAbstractItemView::activated,
3031 this, &QFileDialogPrivate::enterDirectory);
3032 QObjectPrivate::connect(qFileDialogUi->listView, &QAbstractItemView::customContextMenuRequested,
3033 this, &QFileDialogPrivate::showContextMenu);
3034#ifndef QT_NO_SHORTCUT
3035 QShortcut *shortcut = new QShortcut(QKeySequence::Delete, qFileDialogUi->listView);
3036 QObjectPrivate::connect(sender: shortcut, signal: &QShortcut::activated,
3037 receiverPrivate: this, slot: &QFileDialogPrivate::deleteCurrent);
3038#endif
3039
3040 qFileDialogUi->treeView->setFileDialogPrivate(this);
3041 qFileDialogUi->treeView->setModel(model);
3042 QHeaderView *treeHeader = qFileDialogUi->treeView->header();
3043 QFontMetrics fm(q->font());
3044 treeHeader->resizeSection(logicalIndex: 0, size: fm.horizontalAdvance("wwwwwwwwwwwwwwwwwwwwwwwwww"_L1));
3045 treeHeader->resizeSection(logicalIndex: 1, size: fm.horizontalAdvance("128.88 GB"_L1));
3046 treeHeader->resizeSection(logicalIndex: 2, size: fm.horizontalAdvance("mp3Folder"_L1));
3047 treeHeader->resizeSection(logicalIndex: 3, size: fm.horizontalAdvance("10/29/81 02:02PM"_L1));
3048 treeHeader->setContextMenuPolicy(Qt::ActionsContextMenu);
3049
3050 QActionGroup *showActionGroup = new QActionGroup(q);
3051 showActionGroup->setExclusive(false);
3052 QObjectPrivate::connect(sender: showActionGroup, signal: &QActionGroup::triggered,
3053 receiverPrivate: this, slot: &QFileDialogPrivate::showHeader);
3054
3055 QAbstractItemModel *abstractModel = model;
3056#if QT_CONFIG(proxymodel)
3057 if (proxyModel)
3058 abstractModel = proxyModel;
3059#endif
3060 for (int i = 1; i < abstractModel->columnCount(parent: QModelIndex()); ++i) {
3061 QAction *showHeader = new QAction(showActionGroup);
3062 showHeader->setCheckable(true);
3063 showHeader->setChecked(true);
3064 treeHeader->addAction(action: showHeader);
3065 }
3066
3067 QScopedPointer<QItemSelectionModel> selModel(qFileDialogUi->treeView->selectionModel());
3068 qFileDialogUi->treeView->setSelectionModel(qFileDialogUi->listView->selectionModel());
3069
3070 QObjectPrivate::connect(qFileDialogUi->treeView, &QAbstractItemView::activated,
3071 this, &QFileDialogPrivate::enterDirectory);
3072 QObjectPrivate::connect(qFileDialogUi->treeView, &QAbstractItemView::customContextMenuRequested,
3073 this, &QFileDialogPrivate::showContextMenu);
3074#ifndef QT_NO_SHORTCUT
3075 shortcut = new QShortcut(QKeySequence::Delete, qFileDialogUi->treeView);
3076 QObjectPrivate::connect(sender: shortcut, signal: &QShortcut::activated,
3077 receiverPrivate: this, slot: &QFileDialogPrivate::deleteCurrent);
3078#endif
3079
3080 // Selections
3081 QItemSelectionModel *selections = qFileDialogUi->listView->selectionModel();
3082 QObjectPrivate::connect(sender: selections, signal: &QItemSelectionModel::selectionChanged,
3083 receiverPrivate: this, slot: &QFileDialogPrivate::selectionChanged);
3084 QObjectPrivate::connect(sender: selections, signal: &QItemSelectionModel::currentChanged,
3085 receiverPrivate: this, slot: &QFileDialogPrivate::currentChanged);
3086 qFileDialogUi->splitter->setStretchFactor(qFileDialogUi->splitter->indexOf(qFileDialogUi->splitter->widget(1)), QSizePolicy::Expanding);
3087
3088 createToolButtons();
3089 createMenuActions();
3090
3091#if QT_CONFIG(settings)
3092 // Try to restore from the FileDialog settings group; if it fails, fall back
3093 // to the pre-5.5 QByteArray serialized settings.
3094 if (!restoreFromSettings()) {
3095 const QSettings settings(QSettings::UserScope, u"QtProject"_s);
3096 q->restoreState(state: settings.value(key: "Qt/filedialog").toByteArray());
3097 }
3098#endif
3099
3100 // Initial widget states from options
3101 q->setFileMode(static_cast<QFileDialog::FileMode>(options->fileMode()));
3102 q->setAcceptMode(static_cast<QFileDialog::AcceptMode>(options->acceptMode()));
3103 q->setViewMode(static_cast<QFileDialog::ViewMode>(options->viewMode()));
3104 q->setOptions(static_cast<QFileDialog::Options>(static_cast<int>(options->options())));
3105 if (!options->sidebarUrls().isEmpty())
3106 q->setSidebarUrls(options->sidebarUrls());
3107 q->setDirectoryUrl(options->initialDirectory());
3108#if QT_CONFIG(mimetype)
3109 if (!options->mimeTypeFilters().isEmpty())
3110 q->setMimeTypeFilters(options->mimeTypeFilters());
3111 else
3112#endif
3113 if (!options->nameFilters().isEmpty())
3114 q->setNameFilters(options->nameFilters());
3115 q->selectNameFilter(filter: options->initiallySelectedNameFilter());
3116 q->setDefaultSuffix(options->defaultSuffix());
3117 q->setHistory(options->history());
3118 const auto initiallySelectedFiles = options->initiallySelectedFiles();
3119 if (initiallySelectedFiles.size() == 1)
3120 q->selectFile(filename: initiallySelectedFiles.first().fileName());
3121 for (const QUrl &url : initiallySelectedFiles)
3122 q->selectUrl(url);
3123 lineEdit()->selectAll();
3124 updateOkButton();
3125 retranslateStrings();
3126 q->resize(preSize.isValid() ? preSize : q->sizeHint());
3127 q->setWindowState(preState);
3128}
3129
3130void QFileDialogPrivate::showHeader(QAction *action)
3131{
3132 Q_Q(QFileDialog);
3133 QActionGroup *actionGroup = qobject_cast<QActionGroup*>(object: q->sender());
3134 qFileDialogUi->treeView->header()->setSectionHidden(int(actionGroup->actions().indexOf(t: action) + 1),
3135 !action->isChecked());
3136}
3137
3138#if QT_CONFIG(proxymodel)
3139/*!
3140 Sets the model for the views to the given \a proxyModel. This is useful if you
3141 want to modify the underlying model; for example, to add columns, filter
3142 data or add drives.
3143
3144 Any existing proxy model is removed, but not deleted. The file dialog
3145 takes ownership of the \a proxyModel.
3146
3147 \sa proxyModel()
3148*/
3149void QFileDialog::setProxyModel(QAbstractProxyModel *proxyModel)
3150{
3151 Q_D(QFileDialog);
3152 if (!d->usingWidgets())
3153 return;
3154 if ((!proxyModel && !d->proxyModel)
3155 || (proxyModel == d->proxyModel))
3156 return;
3157
3158 QModelIndex idx = d->rootIndex();
3159 if (d->proxyModel)
3160 QObjectPrivate::disconnect(sender: d->proxyModel, signal: &QAbstractProxyModel::rowsInserted,
3161 receiverPrivate: d, slot: &QFileDialogPrivate::rowsInserted);
3162 else
3163 QObjectPrivate::disconnect(sender: d->model, signal: &QAbstractItemModel::rowsInserted,
3164 receiverPrivate: d, slot: &QFileDialogPrivate::rowsInserted);
3165
3166 if (proxyModel != nullptr) {
3167 proxyModel->setParent(this);
3168 d->proxyModel = proxyModel;
3169 proxyModel->setSourceModel(d->model);
3170 d->qFileDialogUi->listView->setModel(d->proxyModel);
3171 d->qFileDialogUi->treeView->setModel(d->proxyModel);
3172#if QT_CONFIG(fscompleter)
3173 d->completer->setModel(d->proxyModel);
3174 d->completer->proxyModel = d->proxyModel;
3175#endif
3176 QObjectPrivate::connect(sender: d->proxyModel, signal: &QAbstractItemModel::rowsInserted,
3177 receiverPrivate: d, slot: &QFileDialogPrivate::rowsInserted);
3178 } else {
3179 d->proxyModel = nullptr;
3180 d->qFileDialogUi->listView->setModel(d->model);
3181 d->qFileDialogUi->treeView->setModel(d->model);
3182#if QT_CONFIG(fscompleter)
3183 d->completer->setModel(d->model);
3184 d->completer->sourceModel = d->model;
3185 d->completer->proxyModel = nullptr;
3186#endif
3187 QObjectPrivate::connect(sender: d->model, signal: &QAbstractItemModel::rowsInserted,
3188 receiverPrivate: d, slot: &QFileDialogPrivate::rowsInserted);
3189 }
3190 QScopedPointer<QItemSelectionModel> selModel(d->qFileDialogUi->treeView->selectionModel());
3191 d->qFileDialogUi->treeView->setSelectionModel(d->qFileDialogUi->listView->selectionModel());
3192
3193 d->setRootIndex(idx);
3194
3195 // reconnect selection
3196 QItemSelectionModel *selections = d->qFileDialogUi->listView->selectionModel();
3197 QObjectPrivate::connect(sender: selections, signal: &QItemSelectionModel::selectionChanged,
3198 receiverPrivate: d, slot: &QFileDialogPrivate::selectionChanged);
3199 QObjectPrivate::connect(sender: selections, signal: &QItemSelectionModel::currentChanged,
3200 receiverPrivate: d, slot: &QFileDialogPrivate::currentChanged);
3201}
3202
3203/*!
3204 Returns the proxy model used by the file dialog. By default no proxy is set.
3205
3206 \sa setProxyModel()
3207*/
3208QAbstractProxyModel *QFileDialog::proxyModel() const
3209{
3210 Q_D(const QFileDialog);
3211 return d->proxyModel;
3212}
3213#endif // QT_CONFIG(proxymodel)
3214
3215/*!
3216 \internal
3217
3218 Create tool buttons, set properties and connections
3219*/
3220void QFileDialogPrivate::createToolButtons()
3221{
3222 Q_Q(QFileDialog);
3223 qFileDialogUi->backButton->setIcon(q->style()->standardIcon(standardIcon: QStyle::SP_ArrowBack, option: nullptr, widget: q));
3224 qFileDialogUi->backButton->setAutoRaise(true);
3225 qFileDialogUi->backButton->setEnabled(false);
3226 QObjectPrivate::connect(qFileDialogUi->backButton, &QPushButton::clicked,
3227 this, &QFileDialogPrivate::navigateBackward);
3228
3229 qFileDialogUi->forwardButton->setIcon(q->style()->standardIcon(standardIcon: QStyle::SP_ArrowForward, option: nullptr, widget: q));
3230 qFileDialogUi->forwardButton->setAutoRaise(true);
3231 qFileDialogUi->forwardButton->setEnabled(false);
3232 QObjectPrivate::connect(qFileDialogUi->forwardButton, &QPushButton::clicked,
3233 this, &QFileDialogPrivate::navigateForward);
3234
3235 qFileDialogUi->toParentButton->setIcon(q->style()->standardIcon(standardIcon: QStyle::SP_FileDialogToParent, option: nullptr, widget: q));
3236 qFileDialogUi->toParentButton->setAutoRaise(true);
3237 qFileDialogUi->toParentButton->setEnabled(false);
3238 QObjectPrivate::connect(qFileDialogUi->toParentButton, &QPushButton::clicked,
3239 this, &QFileDialogPrivate::navigateToParent);
3240
3241 qFileDialogUi->listModeButton->setIcon(q->style()->standardIcon(standardIcon: QStyle::SP_FileDialogListView, option: nullptr, widget: q));
3242 qFileDialogUi->listModeButton->setAutoRaise(true);
3243 qFileDialogUi->listModeButton->setDown(true);
3244 QObjectPrivate::connect(qFileDialogUi->listModeButton, &QPushButton::clicked,
3245 this, &QFileDialogPrivate::showListView);
3246
3247 qFileDialogUi->detailModeButton->setIcon(q->style()->standardIcon(standardIcon: QStyle::SP_FileDialogDetailedView, option: nullptr, widget: q));
3248 qFileDialogUi->detailModeButton->setAutoRaise(true);
3249 QObjectPrivate::connect(qFileDialogUi->detailModeButton, &QPushButton::clicked,
3250 this, &QFileDialogPrivate::showDetailsView);
3251
3252 QSize toolSize(qFileDialogUi->fileNameEdit->sizeHint().height(), qFileDialogUi->fileNameEdit->sizeHint().height());
3253 qFileDialogUi->backButton->setFixedSize(toolSize);
3254 qFileDialogUi->listModeButton->setFixedSize(toolSize);
3255 qFileDialogUi->detailModeButton->setFixedSize(toolSize);
3256 qFileDialogUi->forwardButton->setFixedSize(toolSize);
3257 qFileDialogUi->toParentButton->setFixedSize(toolSize);
3258
3259 qFileDialogUi->newFolderButton->setIcon(q->style()->standardIcon(standardIcon: QStyle::SP_FileDialogNewFolder, option: nullptr, widget: q));
3260 qFileDialogUi->newFolderButton->setFixedSize(toolSize);
3261 qFileDialogUi->newFolderButton->setAutoRaise(true);
3262 qFileDialogUi->newFolderButton->setEnabled(false);
3263 QObjectPrivate::connect(qFileDialogUi->newFolderButton, &QPushButton::clicked,
3264 this, &QFileDialogPrivate::createDirectory);
3265}
3266
3267/*!
3268 \internal
3269
3270 Create actions which will be used in the right click.
3271*/
3272void QFileDialogPrivate::createMenuActions()
3273{
3274 Q_Q(QFileDialog);
3275
3276 QAction *goHomeAction = new QAction(q);
3277#ifndef QT_NO_SHORTCUT
3278 goHomeAction->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_H);
3279#endif
3280 QObjectPrivate::connect(sender: goHomeAction, signal: &QAction::triggered,
3281 receiverPrivate: this, slot: &QFileDialogPrivate::goHome);
3282 q->addAction(action: goHomeAction);
3283
3284 // ### TODO add Desktop & Computer actions
3285
3286 QAction *goToParent = new QAction(q);
3287 goToParent->setObjectName("qt_goto_parent_action"_L1);
3288#ifndef QT_NO_SHORTCUT
3289 goToParent->setShortcut(Qt::CTRL | Qt::Key_Up);
3290#endif
3291 QObjectPrivate::connect(sender: goToParent, signal: &QAction::triggered,
3292 receiverPrivate: this, slot: &QFileDialogPrivate::navigateToParent);
3293 q->addAction(action: goToParent);
3294
3295 renameAction = new QAction(q);
3296 renameAction->setEnabled(false);
3297 renameAction->setObjectName("qt_rename_action"_L1);
3298 QObjectPrivate::connect(sender: renameAction, signal: &QAction::triggered,
3299 receiverPrivate: this, slot: &QFileDialogPrivate::renameCurrent);
3300
3301 deleteAction = new QAction(q);
3302 deleteAction->setEnabled(false);
3303 deleteAction->setObjectName("qt_delete_action"_L1);
3304 QObjectPrivate::connect(sender: deleteAction, signal: &QAction::triggered,
3305 receiverPrivate: this, slot: &QFileDialogPrivate::deleteCurrent);
3306
3307 showHiddenAction = new QAction(q);
3308 showHiddenAction->setObjectName("qt_show_hidden_action"_L1);
3309 showHiddenAction->setCheckable(true);
3310 QObjectPrivate::connect(sender: showHiddenAction, signal: &QAction::triggered,
3311 receiverPrivate: this, slot: &QFileDialogPrivate::showHidden);
3312
3313 newFolderAction = new QAction(q);
3314 newFolderAction->setObjectName("qt_new_folder_action"_L1);
3315 QObjectPrivate::connect(sender: newFolderAction, signal: &QAction::triggered,
3316 receiverPrivate: this, slot: &QFileDialogPrivate::createDirectory);
3317}
3318
3319void QFileDialogPrivate::goHome()
3320{
3321 Q_Q(QFileDialog);
3322 q->setDirectory(QDir::homePath());
3323}
3324
3325
3326void QFileDialogPrivate::saveHistorySelection()
3327{
3328 if (qFileDialogUi.isNull() || currentHistoryLocation < 0 || currentHistoryLocation >= currentHistory.size())
3329 return;
3330 auto &item = currentHistory[currentHistoryLocation];
3331 item.selection.clear();
3332 const auto selectedIndexes = qFileDialogUi->listView->selectionModel()->selectedRows();
3333 for (const auto &index : selectedIndexes)
3334 item.selection.append(QPersistentModelIndex(index));
3335}
3336
3337/*!
3338 \internal
3339
3340 Update history with new path, buttons, and combo
3341*/
3342void QFileDialogPrivate::pathChanged(const QString &newPath)
3343{
3344 Q_Q(QFileDialog);
3345 qFileDialogUi->toParentButton->setEnabled(QFileInfo::exists(file: model->rootPath()));
3346 qFileDialogUi->sidebar->selectUrl(QUrl::fromLocalFile(localfile: newPath));
3347 q->setHistory(qFileDialogUi->lookInCombo->history());
3348
3349 const QString newNativePath = QDir::toNativeSeparators(pathName: newPath);
3350
3351 // equal paths indicate this was invoked by _q_navigateBack/Forward()
3352 if (currentHistoryLocation < 0 || currentHistory.value(i: currentHistoryLocation).path != newNativePath) {
3353 if (currentHistoryLocation >= 0)
3354 saveHistorySelection();
3355 while (currentHistoryLocation >= 0 && currentHistoryLocation + 1 < currentHistory.size()) {
3356 currentHistory.removeLast();
3357 }
3358 currentHistory.append(t: {.path: newNativePath, .selection: PersistentModelIndexList()});
3359 ++currentHistoryLocation;
3360 }
3361 qFileDialogUi->forwardButton->setEnabled(currentHistory.size() - currentHistoryLocation > 1);
3362 qFileDialogUi->backButton->setEnabled(currentHistoryLocation > 0);
3363}
3364
3365void QFileDialogPrivate::navigate(HistoryItem &historyItem)
3366{
3367 Q_Q(QFileDialog);
3368 q->setDirectory(historyItem.path);
3369 // Restore selection unless something has changed in the file system
3370 if (qFileDialogUi.isNull() || historyItem.selection.isEmpty())
3371 return;
3372 if (std::any_of(first: historyItem.selection.cbegin(), last: historyItem.selection.cend(),
3373 pred: [](const QPersistentModelIndex &i) { return !i.isValid(); })) {
3374 historyItem.selection.clear();
3375 return;
3376 }
3377
3378 QAbstractItemView *view = q->viewMode() == QFileDialog::List
3379 ? static_cast<QAbstractItemView *>(qFileDialogUi->listView)
3380 : static_cast<QAbstractItemView *>(qFileDialogUi->treeView);
3381 auto selectionModel = view->selectionModel();
3382 const QItemSelectionModel::SelectionFlags flags = QItemSelectionModel::Select
3383 | QItemSelectionModel::Rows;
3384 selectionModel->select(historyItem.selection.constFirst(),
3385 flags | QItemSelectionModel::Clear | QItemSelectionModel::Current);
3386 auto it = historyItem.selection.cbegin() + 1;
3387 const auto end = historyItem.selection.cend();
3388 for (; it != end; ++it)
3389 selectionModel->select(*it, flags);
3390
3391 view->scrollTo(index: historyItem.selection.constFirst());
3392}
3393
3394/*!
3395 \internal
3396
3397 Navigates to the last directory viewed in the dialog.
3398*/
3399void QFileDialogPrivate::navigateBackward()
3400{
3401 if (!currentHistory.isEmpty() && currentHistoryLocation > 0) {
3402 saveHistorySelection();
3403 navigate(historyItem&: currentHistory[--currentHistoryLocation]);
3404 }
3405}
3406
3407/*!
3408 \internal
3409
3410 Navigates to the last directory viewed in the dialog.
3411*/
3412void QFileDialogPrivate::navigateForward()
3413{
3414 if (!currentHistory.isEmpty() && currentHistoryLocation < currentHistory.size() - 1) {
3415 saveHistorySelection();
3416 navigate(historyItem&: currentHistory[++currentHistoryLocation]);
3417 }
3418}
3419
3420/*!
3421 \internal
3422
3423 Navigates to the parent directory of the currently displayed directory
3424 in the dialog.
3425*/
3426void QFileDialogPrivate::navigateToParent()
3427{
3428 Q_Q(QFileDialog);
3429 QDir dir(model->rootDirectory());
3430 QString newDirectory;
3431 if (dir.isRoot()) {
3432 newDirectory = model->myComputer().toString();
3433 } else {
3434 dir.cdUp();
3435 newDirectory = dir.absolutePath();
3436 }
3437 q->setDirectory(newDirectory);
3438 emit q->directoryEntered(directory: newDirectory);
3439}
3440
3441/*!
3442 \internal
3443
3444 Creates a new directory, first asking the user for a suitable name.
3445*/
3446void QFileDialogPrivate::createDirectory()
3447{
3448 Q_Q(QFileDialog);
3449 qFileDialogUi->listView->clearSelection();
3450
3451 QString newFolderString = QFileDialog::tr(s: "New Folder");
3452 QString folderName = newFolderString;
3453 QString prefix = q->directory().absolutePath() + QDir::separator();
3454 if (QFile::exists(fileName: prefix + folderName)) {
3455 qlonglong suffix = 2;
3456 while (QFile::exists(fileName: prefix + folderName)) {
3457 folderName = newFolderString + QString::number(suffix++);
3458 }
3459 }
3460
3461 QModelIndex parent = rootIndex();
3462 QModelIndex index = model->mkdir(parent, name: folderName);
3463 if (!index.isValid())
3464 return;
3465
3466 index = select(index);
3467 if (index.isValid()) {
3468 qFileDialogUi->treeView->setCurrentIndex(index);
3469 currentView()->edit(index);
3470 }
3471}
3472
3473void QFileDialogPrivate::showListView()
3474{
3475 qFileDialogUi->listModeButton->setDown(true);
3476 qFileDialogUi->detailModeButton->setDown(false);
3477 qFileDialogUi->treeView->hide();
3478 qFileDialogUi->listView->show();
3479 qFileDialogUi->stackedWidget->setCurrentWidget(qFileDialogUi->listView->parentWidget());
3480 qFileDialogUi->listView->doItemsLayout();
3481}
3482
3483void QFileDialogPrivate::showDetailsView()
3484{
3485 qFileDialogUi->listModeButton->setDown(false);
3486 qFileDialogUi->detailModeButton->setDown(true);
3487 qFileDialogUi->listView->hide();
3488 qFileDialogUi->treeView->show();
3489 qFileDialogUi->stackedWidget->setCurrentWidget(qFileDialogUi->treeView->parentWidget());
3490 qFileDialogUi->treeView->doItemsLayout();
3491}
3492
3493/*!
3494 \internal
3495
3496 Show the context menu for the file/dir under position
3497*/
3498void QFileDialogPrivate::showContextMenu(const QPoint &position)
3499{
3500#if !QT_CONFIG(menu)
3501 Q_UNUSED(position);
3502#else
3503 Q_Q(QFileDialog);
3504 QAbstractItemView *view = nullptr;
3505 if (q->viewMode() == QFileDialog::Detail)
3506 view = qFileDialogUi->treeView;
3507 else
3508 view = qFileDialogUi->listView;
3509 QModelIndex index = view->indexAt(point: position);
3510 index = mapToSource(index: index.sibling(arow: index.row(), acolumn: 0));
3511
3512 QMenu *menu = new QMenu(view);
3513 menu->setAttribute(Qt::WA_DeleteOnClose);
3514
3515 if (index.isValid()) {
3516 // file context menu
3517 const bool ro = model && model->isReadOnly();
3518 QFile::Permissions p(index.parent().data(arole: QFileSystemModel::FilePermissions).toInt());
3519 renameAction->setEnabled(!ro && p & QFile::WriteUser);
3520 menu->addAction(action: renameAction);
3521 deleteAction->setEnabled(!ro && p & QFile::WriteUser);
3522 menu->addAction(action: deleteAction);
3523 menu->addSeparator();
3524 }
3525 menu->addAction(action: showHiddenAction);
3526 if (qFileDialogUi->newFolderButton->isVisible()) {
3527 newFolderAction->setEnabled(qFileDialogUi->newFolderButton->isEnabled());
3528 menu->addAction(action: newFolderAction);
3529 }
3530 menu->popup(pos: view->viewport()->mapToGlobal(position));
3531
3532#endif // QT_CONFIG(menu)
3533}
3534
3535/*!
3536 \internal
3537*/
3538void QFileDialogPrivate::renameCurrent()
3539{
3540 Q_Q(QFileDialog);
3541 QModelIndex index = qFileDialogUi->listView->currentIndex();
3542 index = index.sibling(arow: index.row(), acolumn: 0);
3543 if (q->viewMode() == QFileDialog::List)
3544 qFileDialogUi->listView->edit(index);
3545 else
3546 qFileDialogUi->treeView->edit(index);
3547}
3548
3549bool QFileDialogPrivate::removeDirectory(const QString &path)
3550{
3551 QModelIndex modelIndex = model->index(path);
3552 return model->remove(index: modelIndex);
3553}
3554
3555/*!
3556 \internal
3557
3558 Deletes the currently selected item in the dialog.
3559*/
3560void QFileDialogPrivate::deleteCurrent()
3561{
3562 if (model->isReadOnly())
3563 return;
3564
3565 const QModelIndexList list = qFileDialogUi->listView->selectionModel()->selectedRows();
3566 for (auto it = list.crbegin(), end = list.crend(); it != end; ++it) {
3567 QPersistentModelIndex index = *it;
3568 if (index == qFileDialogUi->listView->rootIndex())
3569 continue;
3570
3571 index = mapToSource(index: index.sibling(row: index.row(), column: 0));
3572 if (!index.isValid())
3573 continue;
3574
3575 QString fileName = index.data(role: QFileSystemModel::FileNameRole).toString();
3576 QString filePath = index.data(role: QFileSystemModel::FilePathRole).toString();
3577
3578 QFile::Permissions p(index.parent().data(arole: QFileSystemModel::FilePermissions).toInt());
3579#if QT_CONFIG(messagebox)
3580 Q_Q(QFileDialog);
3581 if (!(p & QFile::WriteUser) && (QMessageBox::warning(parent: q_func(), title: QFileDialog::tr(s: "Delete"),
3582 text: QFileDialog::tr(s: "'%1' is write protected.\nDo you want to delete it anyway?")
3583 .arg(a: fileName),
3584 buttons: QMessageBox::Yes | QMessageBox::No, defaultButton: QMessageBox::No) == QMessageBox::No))
3585 return;
3586 else if (QMessageBox::warning(parent: q_func(), title: QFileDialog::tr(s: "Delete"),
3587 text: QFileDialog::tr(s: "Are you sure you want to delete '%1'?")
3588 .arg(a: fileName),
3589 buttons: QMessageBox::Yes | QMessageBox::No, defaultButton: QMessageBox::No) == QMessageBox::No)
3590 return;
3591
3592 // the event loop has run, we have to validate if the index is valid because the model might have removed it.
3593 if (!index.isValid())
3594 return;
3595
3596#else
3597 if (!(p & QFile::WriteUser))
3598 return;
3599#endif // QT_CONFIG(messagebox)
3600
3601 if (model->isDir(index) && !model->fileInfo(index).isSymLink()) {
3602 if (!removeDirectory(path: filePath)) {
3603#if QT_CONFIG(messagebox)
3604 QMessageBox::warning(parent: q, title: q->windowTitle(),
3605 text: QFileDialog::tr(s: "Could not delete directory."));
3606#endif
3607 }
3608 } else {
3609 model->remove(index);
3610 }
3611 }
3612}
3613
3614void QFileDialogPrivate::autoCompleteFileName(const QString &text)
3615{
3616 if (text.startsWith(s: "//"_L1) || text.startsWith(c: u'\\')) {
3617 qFileDialogUi->listView->selectionModel()->clearSelection();
3618 return;
3619 }
3620
3621 const QStringList multipleFiles = typedFiles();
3622 if (multipleFiles.size() > 0) {
3623 QModelIndexList oldFiles = qFileDialogUi->listView->selectionModel()->selectedRows();
3624 QList<QModelIndex> newFiles;
3625 for (const auto &file : multipleFiles) {
3626 QModelIndex idx = model->index(path: file);
3627 if (oldFiles.removeAll(t: idx) == 0)
3628 newFiles.append(t: idx);
3629 }
3630 for (const auto &newFile : std::as_const(t&: newFiles))
3631 select(index: newFile);
3632 if (lineEdit()->hasFocus()) {
3633 auto *sm = qFileDialogUi->listView->selectionModel();
3634 for (const auto &oldFile : std::as_const(oldFiles))
3635 sm->select(oldFile, QItemSelectionModel::Toggle | QItemSelectionModel::Rows);
3636 }
3637 }
3638}
3639
3640/*!
3641 \internal
3642*/
3643void QFileDialogPrivate::updateOkButton()
3644{
3645 Q_Q(QFileDialog);
3646 QPushButton *button = qFileDialogUi->buttonBox->button((q->acceptMode() == QFileDialog::AcceptOpen)
3647 ? QDialogButtonBox::Open : QDialogButtonBox::Save);
3648 if (!button)
3649 return;
3650 const QFileDialog::FileMode fileMode = q->fileMode();
3651
3652 bool enableButton = true;
3653 bool isOpenDirectory = false;
3654
3655 const QStringList files = q->selectedFiles();
3656 QString lineEditText = lineEdit()->text();
3657
3658 if (lineEditText.startsWith(s: "//"_L1) || lineEditText.startsWith(c: u'\\')) {
3659 button->setEnabled(true);
3660 updateOkButtonText();
3661 return;
3662 }
3663
3664 if (files.isEmpty()) {
3665 enableButton = false;
3666 } else if (lineEditText == ".."_L1) {
3667 isOpenDirectory = true;
3668 } else {
3669 switch (fileMode) {
3670 case QFileDialog::Directory: {
3671 QString fn = files.first();
3672 QModelIndex idx = model->index(path: fn);
3673 if (!idx.isValid())
3674 idx = model->index(path: getEnvironmentVariable(string: fn));
3675 if (!idx.isValid() || !model->isDir(index: idx))
3676 enableButton = false;
3677 break;
3678 }
3679 case QFileDialog::AnyFile: {
3680 QString fn = files.first();
3681 QFileInfo info(fn);
3682 QModelIndex idx = model->index(path: fn);
3683 QString fileDir;
3684 QString fileName;
3685 if (info.isDir()) {
3686 fileDir = info.canonicalFilePath();
3687 } else {
3688 fileDir = fn.mid(position: 0, n: fn.lastIndexOf(c: u'/'));
3689 fileName = fn.mid(position: fileDir.size() + 1);
3690 }
3691 if (lineEditText.contains(s: ".."_L1)) {
3692 fileDir = info.canonicalFilePath();
3693 fileName = info.fileName();
3694 }
3695
3696 if (fileDir == q->directory().canonicalPath() && fileName.isEmpty()) {
3697 enableButton = false;
3698 break;
3699 }
3700 if (idx.isValid() && model->isDir(index: idx)) {
3701 isOpenDirectory = true;
3702 enableButton = true;
3703 break;
3704 }
3705 if (!idx.isValid()) {
3706 const long maxLength = maxNameLength(path: fileDir);
3707 enableButton = maxLength < 0 || fileName.size() <= maxLength;
3708 }
3709 break;
3710 }
3711 case QFileDialog::ExistingFile:
3712 case QFileDialog::ExistingFiles:
3713 for (const auto &file : files) {
3714 QModelIndex idx = model->index(path: file);
3715 if (!idx.isValid())
3716 idx = model->index(path: getEnvironmentVariable(string: file));
3717 if (!idx.isValid()) {
3718 enableButton = false;
3719 break;
3720 }
3721 if (idx.isValid() && model->isDir(index: idx)) {
3722 isOpenDirectory = true;
3723 break;
3724 }
3725 }
3726 break;
3727 default:
3728 break;
3729 }
3730 }
3731
3732 button->setEnabled(enableButton);
3733 updateOkButtonText(saveAsOnFolder: isOpenDirectory);
3734}
3735
3736/*!
3737 \internal
3738*/
3739void QFileDialogPrivate::currentChanged(const QModelIndex &index)
3740{
3741 updateOkButton();
3742 emit q_func()->currentChanged(path: index.data(arole: QFileSystemModel::FilePathRole).toString());
3743}
3744
3745/*!
3746 \internal
3747
3748 This is called when the user double clicks on a file with the corresponding
3749 model item \a index.
3750*/
3751void QFileDialogPrivate::enterDirectory(const QModelIndex &index)
3752{
3753 Q_Q(QFileDialog);
3754 // My Computer or a directory
3755 QModelIndex sourceIndex = index.model() == proxyModel ? mapToSource(index) : index;
3756 QString path = sourceIndex.data(arole: QFileSystemModel::FilePathRole).toString();
3757 if (path.isEmpty() || model->isDir(index: sourceIndex)) {
3758 const QFileDialog::FileMode fileMode = q->fileMode();
3759 q->setDirectory(path);
3760 emit q->directoryEntered(directory: path);
3761 if (fileMode == QFileDialog::Directory) {
3762 // ### find out why you have to do both of these.
3763 lineEdit()->setText(QString());
3764 lineEdit()->clear();
3765 }
3766 } else {
3767 // Do not accept when shift-clicking to multi-select a file in environments with single-click-activation (KDE)
3768 if ((!q->style()->styleHint(stylehint: QStyle::SH_ItemView_ActivateItemOnSingleClick, opt: nullptr, widget: qFileDialogUi->treeView)
3769 || q->fileMode() != QFileDialog::ExistingFiles || !(QGuiApplication::keyboardModifiers() & Qt::CTRL))
3770 && index.model()->flags(index) & Qt::ItemIsEnabled) {
3771 q->accept();
3772 }
3773 }
3774}
3775
3776/*!
3777 \internal
3778
3779 Changes the file dialog's current directory to the one specified
3780 by \a path.
3781*/
3782void QFileDialogPrivate::goToDirectory(const QString &path)
3783{
3784 enum { UrlRole = Qt::UserRole + 1 };
3785
3786 #if QT_CONFIG(messagebox)
3787 Q_Q(QFileDialog);
3788#endif
3789 QModelIndex index = qFileDialogUi->lookInCombo->model()->index(qFileDialogUi->lookInCombo->currentIndex(),
3790 qFileDialogUi->lookInCombo->modelColumn(),
3791 qFileDialogUi->lookInCombo->rootModelIndex());
3792 QString path2 = path;
3793 if (!index.isValid())
3794 index = mapFromSource(index: model->index(path: getEnvironmentVariable(string: path)));
3795 else {
3796 path2 = index.data(arole: UrlRole).toUrl().toLocalFile();
3797 index = mapFromSource(index: model->index(path: path2));
3798 }
3799 QDir dir(path2);
3800 if (!dir.exists())
3801 dir.setPath(getEnvironmentVariable(string: path2));
3802
3803 if (dir.exists() || path2.isEmpty() || path2 == model->myComputer().toString()) {
3804 enterDirectory(index);
3805#if QT_CONFIG(messagebox)
3806 } else {
3807 QString message = QFileDialog::tr(s: "%1\nDirectory not found.\nPlease verify the "
3808 "correct directory name was given.");
3809 QMessageBox::warning(parent: q, title: q->windowTitle(), text: message.arg(a: path2));
3810#endif // QT_CONFIG(messagebox)
3811 }
3812}
3813
3814/*!
3815 \internal
3816
3817 Sets the current name filter to be nameFilter and
3818 update the qFileDialogUi->fileNameEdit when in AcceptSave mode with the new extension.
3819*/
3820void QFileDialogPrivate::useNameFilter(int index)
3821{
3822 QStringList nameFilters = options->nameFilters();
3823 if (index == nameFilters.size()) {
3824 QAbstractItemModel *comboModel = qFileDialogUi->fileTypeCombo->model();
3825 nameFilters.append(t: comboModel->index(row: comboModel->rowCount() - 1, column: 0).data().toString());
3826 options->setNameFilters(nameFilters);
3827 }
3828
3829 QString nameFilter = nameFilters.at(i: index);
3830 QStringList newNameFilters = QPlatformFileDialogHelper::cleanFilterList(filter: nameFilter);
3831 if (q_func()->acceptMode() == QFileDialog::AcceptSave) {
3832 QString newNameFilterExtension;
3833 if (newNameFilters.size() > 0)
3834 newNameFilterExtension = QFileInfo(newNameFilters.at(i: 0)).suffix();
3835
3836 QString fileName = lineEdit()->text();
3837 const QString fileNameExtension = QFileInfo(fileName).suffix();
3838 if (!fileNameExtension.isEmpty() && !newNameFilterExtension.isEmpty()) {
3839 const qsizetype fileNameExtensionLength = fileNameExtension.size();
3840 fileName.replace(i: fileName.size() - fileNameExtensionLength,
3841 len: fileNameExtensionLength, after: newNameFilterExtension);
3842 qFileDialogUi->listView->clearSelection();
3843 lineEdit()->setText(fileName);
3844 }
3845 }
3846
3847 model->setNameFilters(newNameFilters);
3848}
3849
3850/*!
3851 \internal
3852
3853 This is called when the model index corresponding to the current file is changed
3854 from \a index to \a current.
3855*/
3856void QFileDialogPrivate::selectionChanged()
3857{
3858 const QFileDialog::FileMode fileMode = q_func()->fileMode();
3859 const QModelIndexList indexes = qFileDialogUi->listView->selectionModel()->selectedRows();
3860 bool stripDirs = fileMode != QFileDialog::Directory;
3861
3862 QStringList allFiles;
3863 for (const auto &index : indexes) {
3864 if (stripDirs && model->isDir(mapToSource(index)))
3865 continue;
3866 allFiles.append(index.data().toString());
3867 }
3868 if (allFiles.size() > 1)
3869 for (qsizetype i = 0; i < allFiles.size(); ++i) {
3870 allFiles.replace(i, t: QString(u'"' + allFiles.at(i) + u'"'));
3871 }
3872
3873 QString finalFiles = allFiles.join(sep: u' ');
3874 if (!finalFiles.isEmpty() && !lineEdit()->hasFocus() && lineEdit()->isVisible())
3875 lineEdit()->setText(finalFiles);
3876 else
3877 updateOkButton();
3878}
3879
3880/*!
3881 \internal
3882
3883 Includes hidden files and directories in the items displayed in the dialog.
3884*/
3885void QFileDialogPrivate::showHidden()
3886{
3887 Q_Q(QFileDialog);
3888 QDir::Filters dirFilters = q->filter();
3889 dirFilters.setFlag(flag: QDir::Hidden, on: showHiddenAction->isChecked());
3890 q->setFilter(dirFilters);
3891}
3892
3893/*!
3894 \internal
3895
3896 When parent is root and rows have been inserted when none was there before
3897 then select the first one.
3898*/
3899void QFileDialogPrivate::rowsInserted(const QModelIndex &parent)
3900{
3901 if (!qFileDialogUi->treeView
3902 || parent != qFileDialogUi->treeView->rootIndex()
3903 || !qFileDialogUi->treeView->selectionModel()
3904 || qFileDialogUi->treeView->selectionModel()->hasSelection()
3905 || qFileDialogUi->treeView->model()->rowCount(parent) == 0)
3906 return;
3907}
3908
3909void QFileDialogPrivate::fileRenamed(const QString &path, const QString &oldName, const QString &newName)
3910{
3911 const QFileDialog::FileMode fileMode = q_func()->fileMode();
3912 if (fileMode == QFileDialog::Directory) {
3913 if (path == rootPath() && lineEdit()->text() == oldName)
3914 lineEdit()->setText(newName);
3915 }
3916}
3917
3918void QFileDialogPrivate::emitUrlSelected(const QUrl &file)
3919{
3920 Q_Q(QFileDialog);
3921 emit q->urlSelected(url: file);
3922 if (file.isLocalFile())
3923 emit q->fileSelected(file: file.toLocalFile());
3924}
3925
3926void QFileDialogPrivate::emitUrlsSelected(const QList<QUrl> &files)
3927{
3928 Q_Q(QFileDialog);
3929 emit q->urlsSelected(urls: files);
3930 QStringList localFiles;
3931 for (const QUrl &file : files)
3932 if (file.isLocalFile())
3933 localFiles.append(t: file.toLocalFile());
3934 if (!localFiles.isEmpty())
3935 emit q->filesSelected(files: localFiles);
3936}
3937
3938void QFileDialogPrivate::nativeCurrentChanged(const QUrl &file)
3939{
3940 Q_Q(QFileDialog);
3941 emit q->currentUrlChanged(url: file);
3942 if (file.isLocalFile())
3943 emit q->currentChanged(path: file.toLocalFile());
3944}
3945
3946void QFileDialogPrivate::nativeEnterDirectory(const QUrl &directory)
3947{
3948 Q_Q(QFileDialog);
3949 emit q->directoryUrlEntered(directory);
3950 if (!directory.isEmpty()) { // Windows native dialogs occasionally emit signals with empty strings.
3951 *lastVisitedDir() = directory;
3952 if (directory.isLocalFile())
3953 emit q->directoryEntered(directory: directory.toLocalFile());
3954 }
3955}
3956
3957/*!
3958 \internal
3959
3960 For the list and tree view watch keys to goto parent and back in the history
3961
3962 returns \c true if handled
3963*/
3964bool QFileDialogPrivate::itemViewKeyboardEvent(QKeyEvent *event) {
3965
3966#if QT_CONFIG(shortcut)
3967 Q_Q(QFileDialog);
3968 if (event->matches(key: QKeySequence::Cancel)) {
3969 q->reject();
3970 return true;
3971 }
3972#endif
3973 switch (event->key()) {
3974 case Qt::Key_Backspace:
3975 navigateToParent();
3976 return true;
3977 case Qt::Key_Back:
3978#ifdef QT_KEYPAD_NAVIGATION
3979 if (QApplicationPrivate::keypadNavigationEnabled())
3980 return false;
3981#endif
3982 case Qt::Key_Left:
3983 if (event->key() == Qt::Key_Back || event->modifiers() == Qt::AltModifier) {
3984 navigateBackward();
3985 return true;
3986 }
3987 break;
3988 default:
3989 break;
3990 }
3991 return false;
3992}
3993
3994QString QFileDialogPrivate::getEnvironmentVariable(const QString &string)
3995{
3996#ifdef Q_OS_UNIX
3997 if (string.size() > 1 && string.startsWith(c: u'$')) {
3998 return QString::fromLocal8Bit(ba: qgetenv(varName: QStringView{string}.mid(pos: 1).toLatin1().constData()));
3999 }
4000#else
4001 if (string.size() > 2 && string.startsWith(u'%') && string.endsWith(u'%')) {
4002 return QString::fromLocal8Bit(qgetenv(QStringView{string}.mid(1, string.size() - 2).toLatin1().constData()));
4003 }
4004#endif
4005 return string;
4006}
4007
4008void QFileDialogComboBox::setFileDialogPrivate(QFileDialogPrivate *d_pointer) {
4009 d_ptr = d_pointer;
4010 urlModel = new QUrlModel(this);
4011 urlModel->showFullPath = true;
4012 urlModel->setFileSystemModel(d_ptr->model);
4013 setModel(urlModel);
4014}
4015
4016void QFileDialogComboBox::showPopup()
4017{
4018 if (model()->rowCount() > 1)
4019 QComboBox::showPopup();
4020
4021 urlModel->setUrls(QList<QUrl>());
4022 QList<QUrl> list;
4023 QModelIndex idx = d_ptr->model->index(path: d_ptr->rootPath());
4024 while (idx.isValid()) {
4025 QUrl url = QUrl::fromLocalFile(localfile: idx.data(arole: QFileSystemModel::FilePathRole).toString());
4026 if (url.isValid())
4027 list.append(t: url);
4028 idx = idx.parent();
4029 }
4030 // add "my computer"
4031 list.append(t: QUrl("file:"_L1));
4032 urlModel->addUrls(urls: list, row: 0);
4033 idx = model()->index(row: model()->rowCount() - 1, column: 0);
4034
4035 // append history
4036 QList<QUrl> urls;
4037 for (int i = 0; i < m_history.size(); ++i) {
4038 QUrl path = QUrl::fromLocalFile(localfile: m_history.at(i));
4039 if (!urls.contains(t: path))
4040 urls.prepend(t: path);
4041 }
4042 if (urls.size() > 0) {
4043 model()->insertRow(arow: model()->rowCount());
4044 idx = model()->index(row: model()->rowCount()-1, column: 0);
4045 // ### TODO maybe add a horizontal line before this
4046 model()->setData(index: idx, value: QFileDialog::tr(s: "Recent Places"));
4047 QStandardItemModel *m = qobject_cast<QStandardItemModel*>(object: model());
4048 if (m) {
4049 Qt::ItemFlags flags = m->flags(index: idx);
4050 flags &= ~Qt::ItemIsEnabled;
4051 m->item(row: idx.row(), column: idx.column())->setFlags(flags);
4052 }
4053 urlModel->addUrls(urls, row: -1, move: false);
4054 }
4055 setCurrentIndex(0);
4056
4057 QComboBox::showPopup();
4058}
4059
4060// Exact same as QComboBox::paintEvent(), except we elide the text.
4061void QFileDialogComboBox::paintEvent(QPaintEvent *)
4062{
4063 QStylePainter painter(this);
4064 painter.setPen(palette().color(cr: QPalette::Text));
4065
4066 // draw the combobox frame, focusrect and selected etc.
4067 QStyleOptionComboBox opt;
4068 initStyleOption(option: &opt);
4069
4070 QRect editRect = style()->subControlRect(cc: QStyle::CC_ComboBox, opt: &opt,
4071 sc: QStyle::SC_ComboBoxEditField, widget: this);
4072 int size = editRect.width() - opt.iconSize.width() - 4;
4073 opt.currentText = opt.fontMetrics.elidedText(text: opt.currentText, mode: Qt::ElideMiddle, width: size);
4074 painter.drawComplexControl(cc: QStyle::CC_ComboBox, opt);
4075
4076 // draw the icon and text
4077 painter.drawControl(ce: QStyle::CE_ComboBoxLabel, opt);
4078}
4079
4080void QFileDialogListView::setFileDialogPrivate(QFileDialogPrivate *d_pointer)
4081{
4082 d_ptr = d_pointer;
4083 setSelectionBehavior(QAbstractItemView::SelectRows);
4084 setWrapping(true);
4085 setResizeMode(QListView::Adjust);
4086 setEditTriggers(QAbstractItemView::EditKeyPressed);
4087 setContextMenuPolicy(Qt::CustomContextMenu);
4088#if QT_CONFIG(draganddrop)
4089 setDragDropMode(QAbstractItemView::InternalMove);
4090#endif
4091}
4092
4093QSize QFileDialogListView::sizeHint() const
4094{
4095 int height = qMax(a: 10, b: sizeHintForRow(row: 0));
4096 return QSize(QListView::sizeHint().width() * 2, height * 30);
4097}
4098
4099void QFileDialogListView::keyPressEvent(QKeyEvent *e)
4100{
4101#ifdef QT_KEYPAD_NAVIGATION
4102 if (QApplication::navigationMode() == Qt::NavigationModeKeypadDirectional) {
4103 QListView::keyPressEvent(e);
4104 return;
4105 }
4106#endif // QT_KEYPAD_NAVIGATION
4107
4108 if (!d_ptr->itemViewKeyboardEvent(event: e))
4109 QListView::keyPressEvent(event: e);
4110 e->accept();
4111}
4112
4113void QFileDialogTreeView::setFileDialogPrivate(QFileDialogPrivate *d_pointer)
4114{
4115 d_ptr = d_pointer;
4116 setSelectionBehavior(QAbstractItemView::SelectRows);
4117 setRootIsDecorated(false);
4118 setItemsExpandable(false);
4119 setSortingEnabled(true);
4120 header()->setSortIndicator(logicalIndex: 0, order: Qt::AscendingOrder);
4121 header()->setStretchLastSection(false);
4122 setTextElideMode(Qt::ElideMiddle);
4123 setEditTriggers(QAbstractItemView::EditKeyPressed);
4124 setContextMenuPolicy(Qt::CustomContextMenu);
4125#if QT_CONFIG(draganddrop)
4126 setDragDropMode(QAbstractItemView::InternalMove);
4127#endif
4128}
4129
4130void QFileDialogTreeView::keyPressEvent(QKeyEvent *e)
4131{
4132#ifdef QT_KEYPAD_NAVIGATION
4133 if (QApplication::navigationMode() == Qt::NavigationModeKeypadDirectional) {
4134 QTreeView::keyPressEvent(e);
4135 return;
4136 }
4137#endif // QT_KEYPAD_NAVIGATION
4138
4139 if (!d_ptr->itemViewKeyboardEvent(event: e))
4140 QTreeView::keyPressEvent(event: e);
4141 e->accept();
4142}
4143
4144QSize QFileDialogTreeView::sizeHint() const
4145{
4146 int height = qMax(a: 10, b: sizeHintForRow(row: 0));
4147 QSize sizeHint = header()->sizeHint();
4148 return QSize(sizeHint.width() * 4, height * 30);
4149}
4150
4151/*!
4152 // FIXME: this is a hack to avoid propagating key press events
4153 // to the dialog and from there to the "Ok" button
4154*/
4155void QFileDialogLineEdit::keyPressEvent(QKeyEvent *e)
4156{
4157#ifdef QT_KEYPAD_NAVIGATION
4158 if (QApplication::navigationMode() == Qt::NavigationModeKeypadDirectional) {
4159 QLineEdit::keyPressEvent(e);
4160 return;
4161 }
4162#endif // QT_KEYPAD_NAVIGATION
4163
4164#if QT_CONFIG(shortcut)
4165 int key = e->key();
4166#endif
4167 QLineEdit::keyPressEvent(e);
4168#if QT_CONFIG(shortcut)
4169 if (!e->matches(key: QKeySequence::Cancel) && key != Qt::Key_Back)
4170#endif
4171 e->accept();
4172}
4173
4174#if QT_CONFIG(fscompleter)
4175
4176QString QFSCompleter::pathFromIndex(const QModelIndex &index) const
4177{
4178 const QFileSystemModel *dirModel;
4179 if (proxyModel)
4180 dirModel = qobject_cast<const QFileSystemModel *>(object: proxyModel->sourceModel());
4181 else
4182 dirModel = sourceModel;
4183 QString currentLocation = dirModel->rootPath();
4184 QString path = index.data(arole: QFileSystemModel::FilePathRole).toString();
4185 if (!currentLocation.isEmpty() && path.startsWith(s: currentLocation)) {
4186#if defined(Q_OS_UNIX)
4187 if (currentLocation == QDir::separator())
4188 return path.remove(i: 0, len: currentLocation.size());
4189#endif
4190 if (currentLocation.endsWith(c: u'/'))
4191 return path.remove(i: 0, len: currentLocation.size());
4192 else
4193 return path.remove(i: 0, len: currentLocation.size()+1);
4194 }
4195 return index.data(arole: QFileSystemModel::FilePathRole).toString();
4196}
4197
4198QStringList QFSCompleter::splitPath(const QString &path) const
4199{
4200 if (path.isEmpty())
4201 return QStringList(completionPrefix());
4202
4203 QString pathCopy = QDir::toNativeSeparators(pathName: path);
4204 QChar sep = QDir::separator();
4205#if defined(Q_OS_WIN)
4206 if (pathCopy == "\\"_L1 || pathCopy == "\\\\"_L1)
4207 return QStringList(pathCopy);
4208 QString doubleSlash("\\\\"_L1);
4209 if (pathCopy.startsWith(doubleSlash))
4210 pathCopy = pathCopy.mid(2);
4211 else
4212 doubleSlash.clear();
4213#elif defined(Q_OS_UNIX)
4214 {
4215 QString tildeExpanded = qt_tildeExpansion(path: pathCopy);
4216 if (tildeExpanded != pathCopy) {
4217 QFileSystemModel *dirModel;
4218 if (proxyModel)
4219 dirModel = qobject_cast<QFileSystemModel *>(object: proxyModel->sourceModel());
4220 else
4221 dirModel = sourceModel;
4222 dirModel->fetchMore(parent: dirModel->index(path: tildeExpanded));
4223 }
4224 pathCopy = std::move(tildeExpanded);
4225 }
4226#endif
4227
4228#if defined(Q_OS_WIN)
4229 QStringList parts = pathCopy.split(sep, Qt::SkipEmptyParts);
4230 if (!doubleSlash.isEmpty() && !parts.isEmpty())
4231 parts[0].prepend(doubleSlash);
4232 if (pathCopy.endsWith(sep))
4233 parts.append(QString());
4234#else
4235 QStringList parts = pathCopy.split(sep);
4236 if (pathCopy[0] == sep) // read the "/" at the beginning as the split removed it
4237 parts[0] = sep;
4238#endif
4239
4240#if defined(Q_OS_WIN)
4241 bool startsFromRoot = !parts.isEmpty() && parts[0].endsWith(u':');
4242#else
4243 bool startsFromRoot = pathCopy[0] == sep;
4244#endif
4245 if (parts.size() == 1 || (parts.size() > 1 && !startsFromRoot)) {
4246 const QFileSystemModel *dirModel;
4247 if (proxyModel)
4248 dirModel = qobject_cast<const QFileSystemModel *>(object: proxyModel->sourceModel());
4249 else
4250 dirModel = sourceModel;
4251 QString currentLocation = QDir::toNativeSeparators(pathName: dirModel->rootPath());
4252#if defined(Q_OS_WIN)
4253 if (currentLocation.endsWith(u':'))
4254 currentLocation.append(sep);
4255#endif
4256 if (currentLocation.contains(c: sep) && path != currentLocation) {
4257 QStringList currentLocationList = splitPath(path: currentLocation);
4258 while (!currentLocationList.isEmpty() && parts.size() > 0 && parts.at(i: 0) == ".."_L1) {
4259 parts.removeFirst();
4260 currentLocationList.removeLast();
4261 }
4262 if (!currentLocationList.isEmpty() && currentLocationList.constLast().isEmpty())
4263 currentLocationList.removeLast();
4264 return currentLocationList + parts;
4265 }
4266 }
4267 return parts;
4268}
4269
4270#endif // QT_CONFIG(completer)
4271
4272
4273QT_END_NAMESPACE
4274
4275#include "moc_qfiledialog.cpp"
4276

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of qtbase/src/widgets/dialogs/qfiledialog.cpp