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