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