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",
2840 QUrl::toStringList(qFileDialogUi->sidebar->urls(), QUrl::FullyEncoded));
2841 settings.setValue("treeViewHeader", qFileDialogUi->treeView->header()->saveState());
2842 }
2843 QStringList historyUrls;
2844 const QStringList history = q->history();
2845 historyUrls.reserve(history.size());
2846 for (const QString &path : history)
2847 historyUrls << QUrl::fromLocalFile(path).toString(QUrl::FullyEncoded);
2848 settings.setValue("history", historyUrls);
2849 settings.setValue("lastVisited", lastVisitedDir()->toString(QUrl::FullyEncoded));
2850 const QMetaEnum &viewModeMeta = q->metaObject()->enumerator(q->metaObject()->indexOfEnumerator("ViewMode"));
2851 settings.setValue("viewMode", QLatin1StringView(viewModeMeta.key(q->viewMode())));
2852 settings.setValue("qtVersion", QT_VERSION_STR ""_L1);
2853}
2854
2855bool QFileDialogPrivate::restoreFromSettings()
2856{
2857 Q_Q(QFileDialog);
2858 QSettings settings(QSettings::UserScope, u"QtProject"_s);
2859 if (!settings.childGroups().contains("FileDialog"_L1))
2860 return false;
2861 settings.beginGroup("FileDialog");
2862
2863 q->setDirectoryUrl(lastVisitedDir()->isEmpty() ? settings.value("lastVisited").toUrl() : *lastVisitedDir());
2864
2865 QByteArray viewModeStr = settings.value("viewMode").toString().toLatin1();
2866 const QMetaEnum &viewModeMeta = q->metaObject()->enumerator(q->metaObject()->indexOfEnumerator("ViewMode"));
2867 bool ok = false;
2868 int viewMode = viewModeMeta.keyToValue(viewModeStr.constData(), &ok);
2869 if (!ok)
2870 viewMode = QFileDialog::List;
2871 q->setViewMode(static_cast<QFileDialog::ViewMode>(viewMode));
2872
2873 sidebarUrls = QUrl::fromStringList(settings.value("shortcuts").toStringList());
2874 headerData = settings.value("treeViewHeader").toByteArray();
2875
2876 if (!usingWidgets())
2877 return true;
2878
2879 QStringList history;
2880 const auto urlStrings = settings.value("history").toStringList();
2881 for (const QString &urlStr : urlStrings) {
2882 QUrl url(urlStr);
2883 if (url.isLocalFile())
2884 history << url.toLocalFile();
2885 }
2886
2887 return restoreWidgetState(history, settings.value("sidebarWidth", -1).toInt());
2888}
2889#endif // settings
2890
2891bool QFileDialogPrivate::restoreWidgetState(QStringList &history, int splitterPosition)
2892{
2893 Q_Q(QFileDialog);
2894 if (splitterPosition >= 0) {
2895 QList<int> splitterSizes;
2896 splitterSizes.append(splitterPosition);
2897 splitterSizes.append(qFileDialogUi->splitter->widget(1)->sizeHint().width());
2898 qFileDialogUi->splitter->setSizes(splitterSizes);
2899 } else {
2900 if (!qFileDialogUi->splitter->restoreState(splitterState))
2901 return false;
2902 QList<int> list = qFileDialogUi->splitter->sizes();
2903 if (list.size() >= 2 && (list.at(0) == 0 || list.at(1) == 0)) {
2904 for (int i = 0; i < list.size(); ++i)
2905 list[i] = qFileDialogUi->splitter->widget(i)->sizeHint().width();
2906 qFileDialogUi->splitter->setSizes(list);
2907 }
2908 }
2909
2910 qFileDialogUi->sidebar->setUrls(sidebarUrls);
2911
2912 static const int MaxHistorySize = 5;
2913 if (history.size() > MaxHistorySize)
2914 history.erase(history.begin(), history.end() - MaxHistorySize);
2915 q->setHistory(history);
2916
2917 QHeaderView *headerView = qFileDialogUi->treeView->header();
2918 if (!headerView->restoreState(headerData))
2919 return false;
2920
2921 QList<QAction*> actions = headerView->actions();
2922 QAbstractItemModel *abstractModel = model;
2923#if QT_CONFIG(proxymodel)
2924 if (proxyModel)
2925 abstractModel = proxyModel;
2926#endif
2927 const int total = qMin(abstractModel->columnCount(QModelIndex()), int(actions.size() + 1));
2928 for (int i = 1; i < total; ++i)
2929 actions.at(i - 1)->setChecked(!headerView->isSectionHidden(i));
2930
2931 return true;
2932}
2933
2934/*!
2935 \internal
2936
2937 Create widgets, layout and set default values
2938*/
2939void QFileDialogPrivate::init(const QFileDialogArgs &args)
2940{
2941 Q_Q(QFileDialog);
2942 if (!args.caption.isEmpty()) {
2943 useDefaultCaption = false;
2944 setWindowTitle = args.caption;
2945 q->setWindowTitle(args.caption);
2946 }
2947
2948 q->setAcceptMode(QFileDialog::AcceptOpen);
2949 nativeDialogInUse = platformFileDialogHelper() != nullptr;
2950 if (!nativeDialogInUse)
2951 createWidgets();
2952 q->setFileMode(QFileDialog::AnyFile);
2953 if (!args.filter.isEmpty())
2954 q->setNameFilter(args.filter);
2955 q->setDirectoryUrl(args.directory);
2956 if (args.directory.isLocalFile())
2957 q->selectFile(args.selection);
2958 else
2959 q->selectUrl(args.directory);
2960
2961#if QT_CONFIG(settings)
2962 // Try to restore from the FileDialog settings group; if it fails, fall back
2963 // to the pre-5.5 QByteArray serialized settings.
2964 if (!restoreFromSettings()) {
2965 const QSettings settings(QSettings::UserScope, u"QtProject"_s);
2966 q->restoreState(settings.value("Qt/filedialog").toByteArray());
2967 }
2968#endif
2969
2970#if defined(Q_EMBEDDED_SMALLSCREEN)
2971 qFileDialogUi->lookInLabel->setVisible(false);
2972 qFileDialogUi->fileNameLabel->setVisible(false);
2973 qFileDialogUi->fileTypeLabel->setVisible(false);
2974 qFileDialogUi->sidebar->hide();
2975#endif
2976
2977 const QSize sizeHint = q->sizeHint();
2978 if (sizeHint.isValid())
2979 q->resize(sizeHint);
2980}
2981
2982/*!
2983 \internal
2984
2985 Create the widgets, set properties and connections
2986*/
2987void QFileDialogPrivate::createWidgets()
2988{
2989 if (qFileDialogUi)
2990 return;
2991 Q_Q(QFileDialog);
2992
2993 // This function is sometimes called late (e.g as a fallback from setVisible). In that case we
2994 // need to ensure that the following UI code (setupUI in particular) doesn't reset any explicitly
2995 // set window state or geometry.
2996 QSize preSize = q->testAttribute(Qt::WA_Resized) ? q->size() : QSize();
2997 Qt::WindowStates preState = q->windowState();
2998
2999 model = new QFileSystemModel(q);
3000 model->setIconProvider(&defaultIconProvider);
3001 model->setFilter(options->filter());
3002 model->setObjectName("qt_filesystem_model"_L1);
3003 if (QPlatformFileDialogHelper *helper = platformFileDialogHelper())
3004 model->setNameFilterDisables(helper->defaultNameFilterDisables());
3005 else
3006 model->setNameFilterDisables(false);
3007 model->d_func()->disableRecursiveSort = true;
3008 QObjectPrivate::connect(model, &QFileSystemModel::fileRenamed,
3009 this, &QFileDialogPrivate::fileRenamed);
3010 QObjectPrivate::connect(model, &QFileSystemModel::rootPathChanged,
3011 this, &QFileDialogPrivate::pathChanged);
3012 QObjectPrivate::connect(model, &QFileSystemModel::rowsInserted,
3013 this, &QFileDialogPrivate::rowsInserted);
3014 model->setReadOnly(false);
3015
3016 qFileDialogUi.reset(new Ui_QFileDialog());
3017 qFileDialogUi->setupUi(q);
3018
3019 QList<QUrl> initialBookmarks;
3020 initialBookmarks << QUrl("file:"_L1)
3021 << QUrl::fromLocalFile(QDir::homePath());
3022 qFileDialogUi->sidebar->setModelAndUrls(model, initialBookmarks);
3023 QObjectPrivate::connect(qFileDialogUi->sidebar, &QSidebar::goToUrl,
3024 this, &QFileDialogPrivate::goToUrl);
3025
3026 QObject::connect(qFileDialogUi->buttonBox, &QDialogButtonBox::accepted,
3027 q, &QFileDialog::accept);
3028 QObject::connect(qFileDialogUi->buttonBox, &QDialogButtonBox::rejected,
3029 q, &QFileDialog::reject);
3030
3031 qFileDialogUi->lookInCombo->setFileDialogPrivate(this);
3032 QObjectPrivate::connect(qFileDialogUi->lookInCombo, &QComboBox::textActivated,
3033 this, &QFileDialogPrivate::goToDirectory);
3034
3035 qFileDialogUi->lookInCombo->setInsertPolicy(QComboBox::NoInsert);
3036 qFileDialogUi->lookInCombo->setDuplicatesEnabled(false);
3037
3038 // filename
3039#ifndef QT_NO_SHORTCUT
3040 qFileDialogUi->fileNameLabel->setBuddy(qFileDialogUi->fileNameEdit);
3041#endif
3042#if QT_CONFIG(fscompleter)
3043 completer = new QFSCompleter(model, q);
3044 qFileDialogUi->fileNameEdit->setCompleter(completer);
3045#endif // QT_CONFIG(fscompleter)
3046
3047 qFileDialogUi->fileNameEdit->setInputMethodHints(Qt::ImhNoPredictiveText);
3048
3049 QObjectPrivate::connect(qFileDialogUi->fileNameEdit, &QLineEdit::textChanged,
3050 this, &QFileDialogPrivate::autoCompleteFileName);
3051 QObjectPrivate::connect(qFileDialogUi->fileNameEdit, &QLineEdit::textChanged,
3052 this, &QFileDialogPrivate::updateOkButton);
3053 QObject::connect(qFileDialogUi->fileNameEdit, &QLineEdit::returnPressed,
3054 q, &QFileDialog::accept);
3055
3056 // filetype
3057 qFileDialogUi->fileTypeCombo->setDuplicatesEnabled(false);
3058 qFileDialogUi->fileTypeCombo->setSizeAdjustPolicy(QComboBox::AdjustToContentsOnFirstShow);
3059 qFileDialogUi->fileTypeCombo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
3060 QObjectPrivate::connect(qFileDialogUi->fileTypeCombo, &QComboBox::activated,
3061 this, &QFileDialogPrivate::useNameFilter);
3062 QObject::connect(qFileDialogUi->fileTypeCombo, &QComboBox::textActivated,
3063 q, &QFileDialog::filterSelected);
3064
3065 qFileDialogUi->listView->setFileDialogPrivate(this);
3066 qFileDialogUi->listView->setModel(model);
3067 QObjectPrivate::connect(qFileDialogUi->listView, &QAbstractItemView::activated,
3068 this, &QFileDialogPrivate::enterDirectory);
3069 QObjectPrivate::connect(qFileDialogUi->listView, &QAbstractItemView::customContextMenuRequested,
3070 this, &QFileDialogPrivate::showContextMenu);
3071#ifndef QT_NO_SHORTCUT
3072 QShortcut *shortcut = new QShortcut(QKeySequence::Delete, qFileDialogUi->listView);
3073 QObjectPrivate::connect(shortcut, &QShortcut::activated,
3074 this, &QFileDialogPrivate::deleteCurrent);
3075#endif
3076
3077 qFileDialogUi->treeView->setFileDialogPrivate(this);
3078 qFileDialogUi->treeView->setModel(model);
3079 QHeaderView *treeHeader = qFileDialogUi->treeView->header();
3080 QFontMetrics fm(q->font());
3081 treeHeader->resizeSection(0, fm.horizontalAdvance("wwwwwwwwwwwwwwwwwwwwwwwwww"_L1));
3082 treeHeader->resizeSection(1, fm.horizontalAdvance("128.88 GB"_L1));
3083 treeHeader->resizeSection(2, fm.horizontalAdvance("mp3Folder"_L1));
3084 treeHeader->resizeSection(3, fm.horizontalAdvance("10/29/81 02:02PM"_L1));
3085 treeHeader->setContextMenuPolicy(Qt::ActionsContextMenu);
3086
3087 QActionGroup *showActionGroup = new QActionGroup(q);
3088 showActionGroup->setExclusive(false);
3089 QObjectPrivate::connect(showActionGroup, &QActionGroup::triggered,
3090 this, &QFileDialogPrivate::showHeader);
3091
3092 QAbstractItemModel *abstractModel = model;
3093#if QT_CONFIG(proxymodel)
3094 if (proxyModel)
3095 abstractModel = proxyModel;
3096#endif
3097 for (int i = 1; i < abstractModel->columnCount(QModelIndex()); ++i) {
3098 QAction *showHeader = new QAction(showActionGroup);
3099 showHeader->setCheckable(true);
3100 showHeader->setChecked(true);
3101 treeHeader->addAction(showHeader);
3102 }
3103
3104 QScopedPointer<QItemSelectionModel> selModel(qFileDialogUi->treeView->selectionModel());
3105 qFileDialogUi->treeView->setSelectionModel(qFileDialogUi->listView->selectionModel());
3106
3107 QObjectPrivate::connect(qFileDialogUi->treeView, &QAbstractItemView::activated,
3108 this, &QFileDialogPrivate::enterDirectory);
3109 QObjectPrivate::connect(qFileDialogUi->treeView, &QAbstractItemView::customContextMenuRequested,
3110 this, &QFileDialogPrivate::showContextMenu);
3111#ifndef QT_NO_SHORTCUT
3112 shortcut = new QShortcut(QKeySequence::Delete, qFileDialogUi->treeView);
3113 QObjectPrivate::connect(shortcut, &QShortcut::activated,
3114 this, &QFileDialogPrivate::deleteCurrent);
3115#endif
3116
3117 // Selections
3118 QItemSelectionModel *selections = qFileDialogUi->listView->selectionModel();
3119 QObjectPrivate::connect(selections, &QItemSelectionModel::selectionChanged,
3120 this, &QFileDialogPrivate::selectionChanged);
3121 QObjectPrivate::connect(selections, &QItemSelectionModel::currentChanged,
3122 this, &QFileDialogPrivate::currentChanged);
3123 qFileDialogUi->splitter->setStretchFactor(qFileDialogUi->splitter->indexOf(qFileDialogUi->splitter->widget(1)), QSizePolicy::Expanding);
3124
3125 createToolButtons();
3126 createMenuActions();
3127
3128#if QT_CONFIG(settings)
3129 // Try to restore from the FileDialog settings group; if it fails, fall back
3130 // to the pre-5.5 QByteArray serialized settings.
3131 if (!restoreFromSettings()) {
3132 const QSettings settings(QSettings::UserScope, u"QtProject"_s);
3133 q->restoreState(settings.value("Qt/filedialog").toByteArray());
3134 }
3135#endif
3136
3137 // Initial widget states from options
3138 q->setFileMode(static_cast<QFileDialog::FileMode>(options->fileMode()));
3139 q->setAcceptMode(static_cast<QFileDialog::AcceptMode>(options->acceptMode()));
3140 q->setViewMode(static_cast<QFileDialog::ViewMode>(options->viewMode()));
3141 q->setOptions(static_cast<QFileDialog::Options>(static_cast<int>(options->options())));
3142 if (!options->sidebarUrls().isEmpty())
3143 q->setSidebarUrls(options->sidebarUrls());
3144 q->setDirectoryUrl(options->initialDirectory());
3145#if QT_CONFIG(mimetype)
3146 if (!options->mimeTypeFilters().isEmpty())
3147 q->setMimeTypeFilters(options->mimeTypeFilters());
3148 else
3149#endif
3150 if (!options->nameFilters().isEmpty())
3151 q->setNameFilters(options->nameFilters());
3152 q->selectNameFilter(options->initiallySelectedNameFilter());
3153 q->setDefaultSuffix(options->defaultSuffix());
3154 q->setHistory(options->history());
3155 const auto initiallySelectedFiles = options->initiallySelectedFiles();
3156 if (initiallySelectedFiles.size() == 1)
3157 q->selectFile(initiallySelectedFiles.first().fileName());
3158 for (const QUrl &url : initiallySelectedFiles)
3159 q->selectUrl(url);
3160 lineEdit()->selectAll();
3161 updateOkButton();
3162 retranslateStrings();
3163 q->resize(preSize.isValid() ? preSize : q->sizeHint());
3164 q->setWindowState(preState);
3165}
3166
3167void QFileDialogPrivate::showHeader(QAction *action)
3168{
3169 Q_Q(QFileDialog);
3170 QActionGroup *actionGroup = qobject_cast<QActionGroup*>(q->sender());
3171 qFileDialogUi->treeView->header()->setSectionHidden(int(actionGroup->actions().indexOf(action) + 1),
3172 !action->isChecked());
3173}
3174
3175#if QT_CONFIG(proxymodel)
3176/*!
3177 Sets the model for the views to the given \a proxyModel. This is useful if you
3178 want to modify the underlying model; for example, to add columns, filter
3179 data or add drives.
3180
3181 Any existing proxy model is removed, but not deleted. The file dialog
3182 takes ownership of the \a proxyModel.
3183
3184 \sa proxyModel()
3185*/
3186void QFileDialog::setProxyModel(QAbstractProxyModel *proxyModel)
3187{
3188 Q_D(QFileDialog);
3189 if (!d->usingWidgets())
3190 return;
3191 if ((!proxyModel && !d->proxyModel)
3192 || (proxyModel == d->proxyModel))
3193 return;
3194
3195 QModelIndex idx = d->rootIndex();
3196 if (d->proxyModel)
3197 QObjectPrivate::disconnect(d->proxyModel, &QAbstractProxyModel::rowsInserted,
3198 d, &QFileDialogPrivate::rowsInserted);
3199 else
3200 QObjectPrivate::disconnect(d->model, &QAbstractItemModel::rowsInserted,
3201 d, &QFileDialogPrivate::rowsInserted);
3202
3203 if (proxyModel != nullptr) {
3204 proxyModel->setParent(this);
3205 d->proxyModel = proxyModel;
3206 proxyModel->setSourceModel(d->model);
3207 d->qFileDialogUi->listView->setModel(d->proxyModel);
3208 d->qFileDialogUi->treeView->setModel(d->proxyModel);
3209#if QT_CONFIG(fscompleter)
3210 d->completer->setModel(d->proxyModel);
3211 d->completer->proxyModel = d->proxyModel;
3212#endif
3213 QObjectPrivate::connect(d->proxyModel, &QAbstractItemModel::rowsInserted,
3214 d, &QFileDialogPrivate::rowsInserted);
3215 } else {
3216 d->proxyModel = nullptr;
3217 d->qFileDialogUi->listView->setModel(d->model);
3218 d->qFileDialogUi->treeView->setModel(d->model);
3219#if QT_CONFIG(fscompleter)
3220 d->completer->setModel(d->model);
3221 d->completer->sourceModel = d->model;
3222 d->completer->proxyModel = nullptr;
3223#endif
3224 QObjectPrivate::connect(d->model, &QAbstractItemModel::rowsInserted,
3225 d, &QFileDialogPrivate::rowsInserted);
3226 }
3227 QScopedPointer<QItemSelectionModel> selModel(d->qFileDialogUi->treeView->selectionModel());
3228 d->qFileDialogUi->treeView->setSelectionModel(d->qFileDialogUi->listView->selectionModel());
3229
3230 d->setRootIndex(idx);
3231
3232 // reconnect selection
3233 QItemSelectionModel *selections = d->qFileDialogUi->listView->selectionModel();
3234 QObjectPrivate::connect(selections, &QItemSelectionModel::selectionChanged,
3235 d, &QFileDialogPrivate::selectionChanged);
3236 QObjectPrivate::connect(selections, &QItemSelectionModel::currentChanged,
3237 d, &QFileDialogPrivate::currentChanged);
3238}
3239
3240/*!
3241 Returns the proxy model used by the file dialog. By default no proxy is set.
3242
3243 \sa setProxyModel()
3244*/
3245QAbstractProxyModel *QFileDialog::proxyModel() const
3246{
3247 Q_D(const QFileDialog);
3248 return d->proxyModel;
3249}
3250#endif // QT_CONFIG(proxymodel)
3251
3252/*!
3253 \internal
3254
3255 Create tool buttons, set properties and connections
3256*/
3257void QFileDialogPrivate::createToolButtons()
3258{
3259 Q_Q(QFileDialog);
3260 qFileDialogUi->backButton->setIcon(q->style()->standardIcon(QStyle::SP_ArrowBack, nullptr, q));
3261 qFileDialogUi->backButton->setAutoRaise(true);
3262 qFileDialogUi->backButton->setEnabled(false);
3263 QObjectPrivate::connect(qFileDialogUi->backButton, &QPushButton::clicked,
3264 this, &QFileDialogPrivate::navigateBackward);
3265
3266 qFileDialogUi->forwardButton->setIcon(q->style()->standardIcon(QStyle::SP_ArrowForward, nullptr, q));
3267 qFileDialogUi->forwardButton->setAutoRaise(true);
3268 qFileDialogUi->forwardButton->setEnabled(false);
3269 QObjectPrivate::connect(qFileDialogUi->forwardButton, &QPushButton::clicked,
3270 this, &QFileDialogPrivate::navigateForward);
3271
3272 qFileDialogUi->toParentButton->setIcon(q->style()->standardIcon(QStyle::SP_FileDialogToParent, nullptr, q));
3273 qFileDialogUi->toParentButton->setAutoRaise(true);
3274 qFileDialogUi->toParentButton->setEnabled(false);
3275 QObjectPrivate::connect(qFileDialogUi->toParentButton, &QPushButton::clicked,
3276 this, &QFileDialogPrivate::navigateToParent);
3277
3278 qFileDialogUi->listModeButton->setIcon(q->style()->standardIcon(QStyle::SP_FileDialogListView, nullptr, q));
3279 qFileDialogUi->listModeButton->setAutoRaise(true);
3280 qFileDialogUi->listModeButton->setDown(true);
3281 QObjectPrivate::connect(qFileDialogUi->listModeButton, &QPushButton::clicked,
3282 this, &QFileDialogPrivate::showListView);
3283
3284 qFileDialogUi->detailModeButton->setIcon(q->style()->standardIcon(QStyle::SP_FileDialogDetailedView, nullptr, q));
3285 qFileDialogUi->detailModeButton->setAutoRaise(true);
3286 QObjectPrivate::connect(qFileDialogUi->detailModeButton, &QPushButton::clicked,
3287 this, &QFileDialogPrivate::showDetailsView);
3288
3289 QSize toolSize(qFileDialogUi->fileNameEdit->sizeHint().height(), qFileDialogUi->fileNameEdit->sizeHint().height());
3290 qFileDialogUi->backButton->setFixedSize(toolSize);
3291 qFileDialogUi->listModeButton->setFixedSize(toolSize);
3292 qFileDialogUi->detailModeButton->setFixedSize(toolSize);
3293 qFileDialogUi->forwardButton->setFixedSize(toolSize);
3294 qFileDialogUi->toParentButton->setFixedSize(toolSize);
3295
3296 qFileDialogUi->newFolderButton->setIcon(q->style()->standardIcon(QStyle::SP_FileDialogNewFolder, nullptr, q));
3297 qFileDialogUi->newFolderButton->setFixedSize(toolSize);
3298 qFileDialogUi->newFolderButton->setAutoRaise(true);
3299 qFileDialogUi->newFolderButton->setEnabled(false);
3300 QObjectPrivate::connect(qFileDialogUi->newFolderButton, &QPushButton::clicked,
3301 this, &QFileDialogPrivate::createDirectory);
3302}
3303
3304/*!
3305 \internal
3306
3307 Create actions which will be used in the right click.
3308*/
3309void QFileDialogPrivate::createMenuActions()
3310{
3311 Q_Q(QFileDialog);
3312
3313 QAction *goHomeAction = new QAction(q);
3314#ifndef QT_NO_SHORTCUT
3315 goHomeAction->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_H);
3316#endif
3317 QObjectPrivate::connect(goHomeAction, &QAction::triggered,
3318 this, &QFileDialogPrivate::goHome);
3319 q->addAction(goHomeAction);
3320
3321 // ### TODO add Desktop & Computer actions
3322
3323 QAction *goToParent = new QAction(q);
3324 goToParent->setObjectName("qt_goto_parent_action"_L1);
3325#ifndef QT_NO_SHORTCUT
3326 goToParent->setShortcut(Qt::CTRL | Qt::Key_Up);
3327#endif
3328 QObjectPrivate::connect(goToParent, &QAction::triggered,
3329 this, &QFileDialogPrivate::navigateToParent);
3330 q->addAction(goToParent);
3331
3332 renameAction = new QAction(q);
3333 renameAction->setEnabled(false);
3334 renameAction->setObjectName("qt_rename_action"_L1);
3335 QObjectPrivate::connect(renameAction, &QAction::triggered,
3336 this, &QFileDialogPrivate::renameCurrent);
3337
3338 deleteAction = new QAction(q);
3339 deleteAction->setEnabled(false);
3340 deleteAction->setObjectName("qt_delete_action"_L1);
3341 QObjectPrivate::connect(deleteAction, &QAction::triggered,
3342 this, &QFileDialogPrivate::deleteCurrent);
3343
3344 showHiddenAction = new QAction(q);
3345 showHiddenAction->setObjectName("qt_show_hidden_action"_L1);
3346 showHiddenAction->setCheckable(true);
3347 QObjectPrivate::connect(showHiddenAction, &QAction::triggered,
3348 this, &QFileDialogPrivate::showHidden);
3349
3350 newFolderAction = new QAction(q);
3351 newFolderAction->setObjectName("qt_new_folder_action"_L1);
3352 QObjectPrivate::connect(newFolderAction, &QAction::triggered,
3353 this, &QFileDialogPrivate::createDirectory);
3354}
3355
3356void QFileDialogPrivate::goHome()
3357{
3358 Q_Q(QFileDialog);
3359 q->setDirectory(QDir::homePath());
3360}
3361
3362
3363void QFileDialogPrivate::saveHistorySelection()
3364{
3365 if (qFileDialogUi.isNull() || currentHistoryLocation < 0 || currentHistoryLocation >= currentHistory.size())
3366 return;
3367 auto &item = currentHistory[currentHistoryLocation];
3368 item.selection.clear();
3369 const auto selectedIndexes = qFileDialogUi->listView->selectionModel()->selectedRows();
3370 for (const auto &index : selectedIndexes)
3371 item.selection.append(QPersistentModelIndex(index));
3372}
3373
3374/*!
3375 \internal
3376
3377 Update history with new path, buttons, and combo
3378*/
3379void QFileDialogPrivate::pathChanged(const QString &newPath)
3380{
3381 Q_Q(QFileDialog);
3382 qFileDialogUi->toParentButton->setEnabled(QFileInfo::exists(model->rootPath()));
3383 qFileDialogUi->sidebar->selectUrl(QUrl::fromLocalFile(newPath));
3384 q->setHistory(qFileDialogUi->lookInCombo->history());
3385
3386 const QString newNativePath = QDir::toNativeSeparators(newPath);
3387
3388 // equal paths indicate this was invoked by _q_navigateBack/Forward()
3389 if (currentHistoryLocation < 0 || currentHistory.value(currentHistoryLocation).path != newNativePath) {
3390 if (currentHistoryLocation >= 0)
3391 saveHistorySelection();
3392 while (currentHistoryLocation >= 0 && currentHistoryLocation + 1 < currentHistory.size()) {
3393 currentHistory.removeLast();
3394 }
3395 currentHistory.append({newNativePath, PersistentModelIndexList()});
3396 ++currentHistoryLocation;
3397 }
3398 qFileDialogUi->forwardButton->setEnabled(currentHistory.size() - currentHistoryLocation > 1);
3399 qFileDialogUi->backButton->setEnabled(currentHistoryLocation > 0);
3400}
3401
3402void QFileDialogPrivate::navigate(HistoryItem &historyItem)
3403{
3404 Q_Q(QFileDialog);
3405 q->setDirectory(historyItem.path);
3406 // Restore selection unless something has changed in the file system
3407 if (qFileDialogUi.isNull() || historyItem.selection.isEmpty())
3408 return;
3409 if (std::any_of(historyItem.selection.cbegin(), historyItem.selection.cend(),
3410 [](const QPersistentModelIndex &i) { return !i.isValid(); })) {
3411 historyItem.selection.clear();
3412 return;
3413 }
3414
3415 QAbstractItemView *view = q->viewMode() == QFileDialog::List
3416 ? static_cast<QAbstractItemView *>(qFileDialogUi->listView)
3417 : static_cast<QAbstractItemView *>(qFileDialogUi->treeView);
3418 auto selectionModel = view->selectionModel();
3419 const QItemSelectionModel::SelectionFlags flags = QItemSelectionModel::Select
3420 | QItemSelectionModel::Rows;
3421 selectionModel->select(historyItem.selection.constFirst(),
3422 flags | QItemSelectionModel::Clear | QItemSelectionModel::Current);
3423 auto it = historyItem.selection.cbegin() + 1;
3424 const auto end = historyItem.selection.cend();
3425 for (; it != end; ++it)
3426 selectionModel->select(*it, flags);
3427
3428 view->scrollTo(historyItem.selection.constFirst());
3429}
3430
3431/*!
3432 \internal
3433
3434 Navigates to the last directory viewed in the dialog.
3435*/
3436void QFileDialogPrivate::navigateBackward()
3437{
3438 if (!currentHistory.isEmpty() && currentHistoryLocation > 0) {
3439 saveHistorySelection();
3440 navigate(currentHistory[--currentHistoryLocation]);
3441 }
3442}
3443
3444/*!
3445 \internal
3446
3447 Navigates to the last directory viewed in the dialog.
3448*/
3449void QFileDialogPrivate::navigateForward()
3450{
3451 if (!currentHistory.isEmpty() && currentHistoryLocation < currentHistory.size() - 1) {
3452 saveHistorySelection();
3453 navigate(currentHistory[++currentHistoryLocation]);
3454 }
3455}
3456
3457/*!
3458 \internal
3459
3460 Navigates to the parent directory of the currently displayed directory
3461 in the dialog.
3462*/
3463void QFileDialogPrivate::navigateToParent()
3464{
3465 Q_Q(QFileDialog);
3466 QDir dir(model->rootDirectory());
3467 QString newDirectory;
3468 if (dir.isRoot()) {
3469 newDirectory = model->myComputer().toString();
3470 } else {
3471 dir.cdUp();
3472 newDirectory = dir.absolutePath();
3473 }
3474 q->setDirectory(newDirectory);
3475 emit q->directoryEntered(newDirectory);
3476}
3477
3478/*!
3479 \internal
3480
3481 Creates a new directory, first asking the user for a suitable name.
3482*/
3483void QFileDialogPrivate::createDirectory()
3484{
3485 Q_Q(QFileDialog);
3486 qFileDialogUi->listView->clearSelection();
3487
3488 QString newFolderString = QFileDialog::tr("New Folder");
3489 QString folderName = newFolderString;
3490 QString prefix = q->directory().absolutePath() + QDir::separator();
3491 if (QFile::exists(prefix + folderName)) {
3492 qlonglong suffix = 2;
3493 while (QFile::exists(prefix + folderName)) {
3494 folderName = newFolderString + QString::number(suffix++);
3495 }
3496 }
3497
3498 QModelIndex parent = rootIndex();
3499 QModelIndex index = model->mkdir(parent, folderName);
3500 if (!index.isValid())
3501 return;
3502
3503 index = select(index);
3504 if (index.isValid()) {
3505 qFileDialogUi->treeView->setCurrentIndex(index);
3506 currentView()->edit(index);
3507 }
3508}
3509
3510void QFileDialogPrivate::showListView()
3511{
3512 qFileDialogUi->listModeButton->setDown(true);
3513 qFileDialogUi->detailModeButton->setDown(false);
3514 qFileDialogUi->treeView->hide();
3515 qFileDialogUi->listView->show();
3516 qFileDialogUi->stackedWidget->setCurrentWidget(qFileDialogUi->listView->parentWidget());
3517 qFileDialogUi->listView->doItemsLayout();
3518}
3519
3520void QFileDialogPrivate::showDetailsView()
3521{
3522 qFileDialogUi->listModeButton->setDown(false);
3523 qFileDialogUi->detailModeButton->setDown(true);
3524 qFileDialogUi->listView->hide();
3525 qFileDialogUi->treeView->show();
3526 qFileDialogUi->stackedWidget->setCurrentWidget(qFileDialogUi->treeView->parentWidget());
3527 qFileDialogUi->treeView->doItemsLayout();
3528}
3529
3530/*!
3531 \internal
3532
3533 Show the context menu for the file/dir under position
3534*/
3535void QFileDialogPrivate::showContextMenu(const QPoint &position)
3536{
3537#if !QT_CONFIG(menu)
3538 Q_UNUSED(position);
3539#else
3540 Q_Q(QFileDialog);
3541 QAbstractItemView *view = nullptr;
3542 if (q->viewMode() == QFileDialog::Detail)
3543 view = qFileDialogUi->treeView;
3544 else
3545 view = qFileDialogUi->listView;
3546 QModelIndex index = view->indexAt(position);
3547 index = mapToSource(index.sibling(index.row(), 0));
3548
3549 QMenu *menu = new QMenu(view);
3550 menu->setAttribute(Qt::WA_DeleteOnClose);
3551
3552 if (index.isValid()) {
3553 // file context menu
3554 const bool ro = model && model->isReadOnly();
3555 QFile::Permissions p(index.parent().data(QFileSystemModel::FilePermissions).toInt());
3556 renameAction->setEnabled(!ro && p & QFile::WriteUser);
3557 menu->addAction(renameAction);
3558 deleteAction->setEnabled(!ro && p & QFile::WriteUser);
3559 menu->addAction(deleteAction);
3560 menu->addSeparator();
3561 }
3562 menu->addAction(showHiddenAction);
3563 if (qFileDialogUi->newFolderButton->isVisible()) {
3564 newFolderAction->setEnabled(qFileDialogUi->newFolderButton->isEnabled());
3565 menu->addAction(newFolderAction);
3566 }
3567 menu->popup(view->viewport()->mapToGlobal(position));
3568
3569#endif // QT_CONFIG(menu)
3570}
3571
3572/*!
3573 \internal
3574*/
3575void QFileDialogPrivate::renameCurrent()
3576{
3577 Q_Q(QFileDialog);
3578 QModelIndex index = qFileDialogUi->listView->currentIndex();
3579 index = index.sibling(index.row(), 0);
3580 if (q->viewMode() == QFileDialog::List)
3581 qFileDialogUi->listView->edit(index);
3582 else
3583 qFileDialogUi->treeView->edit(index);
3584}
3585
3586bool QFileDialogPrivate::removeDirectory(const QString &path)
3587{
3588 QModelIndex modelIndex = model->index(path);
3589 return model->remove(modelIndex);
3590}
3591
3592/*!
3593 \internal
3594
3595 Deletes the currently selected item in the dialog.
3596*/
3597void QFileDialogPrivate::deleteCurrent()
3598{
3599 if (model->isReadOnly())
3600 return;
3601
3602 const QModelIndexList list = qFileDialogUi->listView->selectionModel()->selectedRows();
3603 for (auto it = list.crbegin(), end = list.crend(); it != end; ++it) {
3604 QPersistentModelIndex index = *it;
3605 if (index == qFileDialogUi->listView->rootIndex())
3606 continue;
3607
3608 index = mapToSource(index.sibling(index.row(), 0));
3609 if (!index.isValid())
3610 continue;
3611
3612 QString fileName = index.data(QFileSystemModel::FileNameRole).toString();
3613 QString filePath = index.data(QFileSystemModel::FilePathRole).toString();
3614
3615 QFile::Permissions p(index.parent().data(QFileSystemModel::FilePermissions).toInt());
3616#if QT_CONFIG(messagebox)
3617 Q_Q(QFileDialog);
3618 if (!(p & QFile::WriteUser) && (QMessageBox::warning(q_func(), QFileDialog::tr("Delete"),
3619 QFileDialog::tr("'%1' is write protected.\nDo you want to delete it anyway?")
3620 .arg(fileName),
3621 QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No))
3622 return;
3623 else if (QMessageBox::warning(q_func(), QFileDialog::tr("Delete"),
3624 QFileDialog::tr("Are you sure you want to delete '%1'?")
3625 .arg(fileName),
3626 QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No)
3627 return;
3628
3629 // the event loop has run, we have to validate if the index is valid because the model might have removed it.
3630 if (!index.isValid())
3631 return;
3632
3633#else
3634 if (!(p & QFile::WriteUser))
3635 return;
3636#endif // QT_CONFIG(messagebox)
3637
3638 if (model->isDir(index) && !model->fileInfo(index).isSymLink()) {
3639 if (!removeDirectory(filePath)) {
3640#if QT_CONFIG(messagebox)
3641 QMessageBox::warning(q, q->windowTitle(),
3642 QFileDialog::tr("Could not delete directory."));
3643#endif
3644 }
3645 } else {
3646 model->remove(index);
3647 }
3648 }
3649}
3650
3651void QFileDialogPrivate::autoCompleteFileName(const QString &text)
3652{
3653 if (text.startsWith("//"_L1) || text.startsWith(u'\\')) {
3654 qFileDialogUi->listView->selectionModel()->clearSelection();
3655 return;
3656 }
3657
3658 const QStringList multipleFiles = typedFiles();
3659 if (multipleFiles.size() > 0) {
3660 QModelIndexList oldFiles = qFileDialogUi->listView->selectionModel()->selectedRows();
3661 QList<QModelIndex> newFiles;
3662 for (const auto &file : multipleFiles) {
3663 QModelIndex idx = model->index(file);
3664 if (oldFiles.removeAll(idx) == 0)
3665 newFiles.append(idx);
3666 }
3667 for (const auto &newFile : std::as_const(newFiles))
3668 select(newFile);
3669 if (lineEdit()->hasFocus()) {
3670 auto *sm = qFileDialogUi->listView->selectionModel();
3671 for (const auto &oldFile : std::as_const(oldFiles))
3672 sm->select(oldFile, QItemSelectionModel::Toggle | QItemSelectionModel::Rows);
3673 }
3674 }
3675}
3676
3677/*!
3678 \internal
3679*/
3680void QFileDialogPrivate::updateOkButton()
3681{
3682 Q_Q(QFileDialog);
3683 QPushButton *button = qFileDialogUi->buttonBox->button((q->acceptMode() == QFileDialog::AcceptOpen)
3684 ? QDialogButtonBox::Open : QDialogButtonBox::Save);
3685 if (!button)
3686 return;
3687 const QFileDialog::FileMode fileMode = q->fileMode();
3688
3689 bool enableButton = true;
3690 bool isOpenDirectory = false;
3691
3692 const QStringList files = q->selectedFiles();
3693 QString lineEditText = lineEdit()->text();
3694
3695 if (lineEditText.startsWith("//"_L1) || lineEditText.startsWith(u'\\')) {
3696 button->setEnabled(true);
3697 updateOkButtonText();
3698 return;
3699 }
3700
3701 if (files.isEmpty()) {
3702 enableButton = false;
3703 } else if (lineEditText == ".."_L1) {
3704 isOpenDirectory = true;
3705 } else {
3706 switch (fileMode) {
3707 case QFileDialog::Directory: {
3708 QString fn = files.first();
3709 QModelIndex idx = model->index(fn);
3710 if (!idx.isValid())
3711 idx = model->index(getEnvironmentVariable(fn));
3712 if (!idx.isValid() || !model->isDir(idx))
3713 enableButton = false;
3714 break;
3715 }
3716 case QFileDialog::AnyFile: {
3717 QString fn = files.first();
3718 QFileInfo info(fn);
3719 QModelIndex idx = model->index(fn);
3720 QString fileDir;
3721 QString fileName;
3722 if (info.isDir()) {
3723 fileDir = info.canonicalFilePath();
3724 } else {
3725 fileDir = fn.mid(0, fn.lastIndexOf(u'/'));
3726 fileName = fn.mid(fileDir.size() + 1);
3727 }
3728 if (lineEditText.contains(".."_L1)) {
3729 fileDir = info.canonicalFilePath();
3730 fileName = info.fileName();
3731 }
3732
3733 if (fileDir == q->directory().canonicalPath() && fileName.isEmpty()) {
3734 enableButton = false;
3735 break;
3736 }
3737 if (idx.isValid() && model->isDir(idx)) {
3738 isOpenDirectory = true;
3739 enableButton = true;
3740 break;
3741 }
3742 if (!idx.isValid()) {
3743 const long maxLength = maxNameLength(fileDir);
3744 enableButton = maxLength < 0 || fileName.size() <= maxLength;
3745 }
3746 break;
3747 }
3748 case QFileDialog::ExistingFile:
3749 case QFileDialog::ExistingFiles:
3750 for (const auto &file : files) {
3751 QModelIndex idx = model->index(file);
3752 if (!idx.isValid())
3753 idx = model->index(getEnvironmentVariable(file));
3754 if (!idx.isValid()) {
3755 enableButton = false;
3756 break;
3757 }
3758 if (idx.isValid() && model->isDir(idx)) {
3759 isOpenDirectory = true;
3760 break;
3761 }
3762 }
3763 break;
3764 default:
3765 break;
3766 }
3767 }
3768
3769 button->setEnabled(enableButton);
3770 updateOkButtonText(isOpenDirectory);
3771}
3772
3773/*!
3774 \internal
3775*/
3776void QFileDialogPrivate::currentChanged(const QModelIndex &index)
3777{
3778 updateOkButton();
3779 emit q_func()->currentChanged(index.data(QFileSystemModel::FilePathRole).toString());
3780}
3781
3782/*!
3783 \internal
3784
3785 This is called when the user double clicks on a file with the corresponding
3786 model item \a index.
3787*/
3788void QFileDialogPrivate::enterDirectory(const QModelIndex &index)
3789{
3790 Q_Q(QFileDialog);
3791 // My Computer or a directory
3792 QModelIndex sourceIndex = index.model() == proxyModel ? mapToSource(index) : index;
3793 QString path = sourceIndex.data(QFileSystemModel::FilePathRole).toString();
3794 if (path.isEmpty() || model->isDir(sourceIndex)) {
3795 if (q->directory().path() == path)
3796 return;
3797
3798 const QFileDialog::FileMode fileMode = q->fileMode();
3799 q->setDirectory(path);
3800 emit q->directoryEntered(path);
3801 if (fileMode == QFileDialog::Directory) {
3802 // ### find out why you have to do both of these.
3803 lineEdit()->setText(QString());
3804 lineEdit()->clear();
3805 }
3806 } else {
3807 // Do not accept when shift-clicking to multi-select a file in environments with single-click-activation (KDE)
3808 if ((!q->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, qFileDialogUi->treeView)
3809 || q->fileMode() != QFileDialog::ExistingFiles || !(QGuiApplication::keyboardModifiers() & Qt::CTRL))
3810 && index.model()->flags(index) & Qt::ItemIsEnabled) {
3811 q->accept();
3812 }
3813 }
3814}
3815
3816/*!
3817 \internal
3818
3819 Changes the file dialog's current directory to the one specified
3820 by \a path.
3821*/
3822void QFileDialogPrivate::goToDirectory(const QString &path)
3823{
3824 enum { UrlRole = Qt::UserRole + 1 };
3825
3826 #if QT_CONFIG(messagebox)
3827 Q_Q(QFileDialog);
3828#endif
3829 QModelIndex index = qFileDialogUi->lookInCombo->model()->index(qFileDialogUi->lookInCombo->currentIndex(),
3830 qFileDialogUi->lookInCombo->modelColumn(),
3831 qFileDialogUi->lookInCombo->rootModelIndex());
3832 QString path2 = path;
3833 if (!index.isValid())
3834 index = mapFromSource(model->index(getEnvironmentVariable(path)));
3835 else {
3836 path2 = index.data(UrlRole).toUrl().toLocalFile();
3837 index = mapFromSource(model->index(path2));
3838 }
3839 QDir dir(path2);
3840 if (!dir.exists())
3841 dir.setPath(getEnvironmentVariable(path2));
3842
3843 if (dir.exists() || path2.isEmpty() || path2 == model->myComputer().toString()) {
3844 enterDirectory(index);
3845#if QT_CONFIG(messagebox)
3846 } else {
3847 QString message = QFileDialog::tr("%1\nDirectory not found.\nPlease verify the "
3848 "correct directory name was given.");
3849 QMessageBox::warning(q, q->windowTitle(), message.arg(path2));
3850#endif // QT_CONFIG(messagebox)
3851 }
3852}
3853
3854/*!
3855 \internal
3856
3857 Sets the current name filter to be nameFilter and
3858 update the qFileDialogUi->fileNameEdit when in AcceptSave mode with the new extension.
3859*/
3860void QFileDialogPrivate::useNameFilter(int index)
3861{
3862 QStringList nameFilters = options->nameFilters();
3863 if (index == nameFilters.size()) {
3864 QAbstractItemModel *comboModel = qFileDialogUi->fileTypeCombo->model();
3865 nameFilters.append(comboModel->index(comboModel->rowCount() - 1, 0).data().toString());
3866 options->setNameFilters(nameFilters);
3867 }
3868
3869 QString nameFilter = nameFilters.at(index);
3870 QStringList newNameFilters = QPlatformFileDialogHelper::cleanFilterList(nameFilter);
3871 if (q_func()->acceptMode() == QFileDialog::AcceptSave) {
3872 QString newNameFilterExtension;
3873 if (newNameFilters.size() > 0)
3874 newNameFilterExtension = QFileInfo(newNameFilters.at(0)).suffix();
3875
3876 QString fileName = lineEdit()->text();
3877 const QString fileNameExtension = QFileInfo(fileName).suffix();
3878 if (!fileNameExtension.isEmpty() && !newNameFilterExtension.isEmpty()) {
3879 const qsizetype fileNameExtensionLength = fileNameExtension.size();
3880 fileName.replace(fileName.size() - fileNameExtensionLength,
3881 fileNameExtensionLength, newNameFilterExtension);
3882 qFileDialogUi->listView->clearSelection();
3883 lineEdit()->setText(fileName);
3884 }
3885 }
3886
3887 model->setNameFilters(newNameFilters);
3888}
3889
3890/*!
3891 \internal
3892
3893 This is called when the model index corresponding to the current file is changed
3894 from \a index to \a current.
3895*/
3896void QFileDialogPrivate::selectionChanged()
3897{
3898 const QFileDialog::FileMode fileMode = q_func()->fileMode();
3899 const QModelIndexList indexes = qFileDialogUi->listView->selectionModel()->selectedRows();
3900 bool stripDirs = fileMode != QFileDialog::Directory;
3901
3902 QStringList allFiles;
3903 for (const auto &index : indexes) {
3904 if (stripDirs && model->isDir(mapToSource(index)))
3905 continue;
3906 allFiles.append(index.data().toString());
3907 }
3908 if (allFiles.size() > 1)
3909 for (qsizetype i = 0; i < allFiles.size(); ++i) {
3910 allFiles.replace(i, QString(u'"' + allFiles.at(i) + u'"'));
3911 }
3912
3913 QString finalFiles = allFiles.join(u' ');
3914 if (!finalFiles.isEmpty() && !lineEdit()->hasFocus() && lineEdit()->isVisible())
3915 lineEdit()->setText(finalFiles);
3916 else
3917 updateOkButton();
3918}
3919
3920/*!
3921 \internal
3922
3923 Includes hidden files and directories in the items displayed in the dialog.
3924*/
3925void QFileDialogPrivate::showHidden()
3926{
3927 Q_Q(QFileDialog);
3928 QDir::Filters dirFilters = q->filter();
3929 dirFilters.setFlag(QDir::Hidden, showHiddenAction->isChecked());
3930 q->setFilter(dirFilters);
3931}
3932
3933/*!
3934 \internal
3935
3936 When parent is root and rows have been inserted when none was there before
3937 then select the first one.
3938*/
3939void QFileDialogPrivate::rowsInserted(const QModelIndex &parent)
3940{
3941 if (!qFileDialogUi->treeView
3942 || parent != qFileDialogUi->treeView->rootIndex()
3943 || !qFileDialogUi->treeView->selectionModel()
3944 || qFileDialogUi->treeView->selectionModel()->hasSelection()
3945 || qFileDialogUi->treeView->model()->rowCount(parent) == 0)
3946 return;
3947}
3948
3949void QFileDialogPrivate::fileRenamed(const QString &path, const QString &oldName, const QString &newName)
3950{
3951 const QFileDialog::FileMode fileMode = q_func()->fileMode();
3952 if (fileMode == QFileDialog::Directory) {
3953 if (path == rootPath() && lineEdit()->text() == oldName)
3954 lineEdit()->setText(newName);
3955 }
3956}
3957
3958void QFileDialogPrivate::emitUrlSelected(const QUrl &file)
3959{
3960 Q_Q(QFileDialog);
3961 emit q->urlSelected(file);
3962 if (file.isLocalFile())
3963 emit q->fileSelected(file.toLocalFile());
3964}
3965
3966void QFileDialogPrivate::emitUrlsSelected(const QList<QUrl> &files)
3967{
3968 Q_Q(QFileDialog);
3969 emit q->urlsSelected(files);
3970 QStringList localFiles;
3971 for (const QUrl &file : files)
3972 if (file.isLocalFile())
3973 localFiles.append(file.toLocalFile());
3974 if (!localFiles.isEmpty())
3975 emit q->filesSelected(localFiles);
3976}
3977
3978void QFileDialogPrivate::nativeCurrentChanged(const QUrl &file)
3979{
3980 Q_Q(QFileDialog);
3981 emit q->currentUrlChanged(file);
3982 if (file.isLocalFile())
3983 emit q->currentChanged(file.toLocalFile());
3984}
3985
3986void QFileDialogPrivate::nativeEnterDirectory(const QUrl &directory)
3987{
3988 Q_Q(QFileDialog);
3989 emit q->directoryUrlEntered(directory);
3990 if (!directory.isEmpty()) { // Windows native dialogs occasionally emit signals with empty strings.
3991 *lastVisitedDir() = directory;
3992 if (directory.isLocalFile())
3993 emit q->directoryEntered(directory.toLocalFile());
3994 }
3995}
3996
3997/*!
3998 \internal
3999
4000 For the list and tree view watch keys to goto parent and back in the history
4001
4002 returns \c true if handled
4003*/
4004bool QFileDialogPrivate::itemViewKeyboardEvent(QKeyEvent *event) {
4005
4006#if QT_CONFIG(shortcut)
4007 Q_Q(QFileDialog);
4008 if (event->matches(QKeySequence::Cancel)) {
4009 q->reject();
4010 return true;
4011 }
4012#endif
4013 switch (event->key()) {
4014 case Qt::Key_Backspace:
4015 navigateToParent();
4016 return true;
4017 case Qt::Key_Back:
4018 case Qt::Key_Left:
4019 if (event->key() == Qt::Key_Back || event->modifiers() == Qt::AltModifier) {
4020 navigateBackward();
4021 return true;
4022 }
4023 break;
4024 default:
4025 break;
4026 }
4027 return false;
4028}
4029
4030QString QFileDialogPrivate::getEnvironmentVariable(const QString &string)
4031{
4032#ifdef Q_OS_UNIX
4033 if (string.size() > 1 && string.startsWith(u'$')) {
4034 return qEnvironmentVariable(QStringView{string}.mid(1).toLatin1().constData());
4035 }
4036#else
4037 if (string.size() > 2 && string.startsWith(u'%') && string.endsWith(u'%')) {
4038 return qEnvironmentVariable(QStringView{string}.mid(1, string.size() - 2).toLatin1().constData());
4039 }
4040#endif
4041 return string;
4042}
4043
4044void QFileDialogComboBox::setFileDialogPrivate(QFileDialogPrivate *d_pointer) {
4045 d_ptr = d_pointer;
4046 urlModel = new QUrlModel(this);
4047 urlModel->showFullPath = true;
4048 urlModel->setFileSystemModel(d_ptr->model);
4049 setModel(urlModel);
4050}
4051
4053{
4054 if (model()->rowCount() > 1)
4055 QComboBox::showPopup();
4056
4057 urlModel->setUrls(QList<QUrl>());
4058 QList<QUrl> list;
4059 QModelIndex idx = d_ptr->model->index(d_ptr->rootPath());
4060 while (idx.isValid()) {
4061 QUrl url = QUrl::fromLocalFile(idx.data(QFileSystemModel::FilePathRole).toString());
4062 if (url.isValid())
4063 list.append(url);
4064 idx = idx.parent();
4065 }
4066 // add "my computer"
4067 list.append(QUrl("file:"_L1));
4068 urlModel->addUrls(list, 0);
4069 idx = model()->index(model()->rowCount() - 1, 0);
4070
4071 // append history
4072 QList<QUrl> urls;
4073 for (int i = 0; i < m_history.size(); ++i) {
4074 QUrl path = QUrl::fromLocalFile(m_history.at(i));
4075 if (!urls.contains(path))
4076 urls.prepend(path);
4077 }
4078 if (urls.size() > 0) {
4079 model()->insertRow(model()->rowCount());
4080 idx = model()->index(model()->rowCount()-1, 0);
4081 // ### TODO maybe add a horizontal line before this
4082 model()->setData(idx, QFileDialog::tr("Recent Places"));
4083 QStandardItemModel *m = qobject_cast<QStandardItemModel*>(model());
4084 if (m) {
4085 Qt::ItemFlags flags = m->flags(idx);
4086 flags &= ~Qt::ItemIsEnabled;
4087 m->item(idx.row(), idx.column())->setFlags(flags);
4088 }
4089 urlModel->addUrls(urls, -1, false);
4090 }
4091 setCurrentIndex(0);
4092
4093 QComboBox::showPopup();
4094}
4095
4096// Exact same as QComboBox::paintEvent(), except we elide the text.
4097void QFileDialogComboBox::paintEvent(QPaintEvent *)
4098{
4099 QStylePainter painter(this);
4100 painter.setPen(palette().color(QPalette::Text));
4101
4102 // draw the combobox frame, focusrect and selected etc.
4103 QStyleOptionComboBox opt;
4104 initStyleOption(&opt);
4105
4106 QRect editRect = style()->subControlRect(QStyle::CC_ComboBox, &opt,
4107 QStyle::SC_ComboBoxEditField, this);
4108 int size = editRect.width() - opt.iconSize.width() - 4;
4109 opt.currentText = opt.fontMetrics.elidedText(opt.currentText, Qt::ElideMiddle, size);
4110 painter.drawComplexControl(QStyle::CC_ComboBox, opt);
4111
4112 // draw the icon and text
4113 painter.drawControl(QStyle::CE_ComboBoxLabel, opt);
4114}
4115
4116void QFileDialogListView::setFileDialogPrivate(QFileDialogPrivate *d_pointer)
4117{
4118 d_ptr = d_pointer;
4119 setSelectionBehavior(QAbstractItemView::SelectRows);
4120 setWrapping(true);
4121 setResizeMode(QListView::Adjust);
4122 setEditTriggers(QAbstractItemView::EditKeyPressed);
4123 setContextMenuPolicy(Qt::CustomContextMenu);
4124#if QT_CONFIG(draganddrop)
4125 setDragDropMode(QAbstractItemView::InternalMove);
4126#endif
4127}
4128
4130{
4131 int height = qMax(10, sizeHintForRow(0));
4132 return QSize(QListView::sizeHint().width() * 2, height * 30);
4133}
4134
4135void QFileDialogListView::keyPressEvent(QKeyEvent *e)
4136{
4137 if (!d_ptr->itemViewKeyboardEvent(e))
4138 QListView::keyPressEvent(e);
4139 e->accept();
4140}
4141
4142void QFileDialogTreeView::setFileDialogPrivate(QFileDialogPrivate *d_pointer)
4143{
4144 d_ptr = d_pointer;
4145 setSelectionBehavior(QAbstractItemView::SelectRows);
4146 setRootIsDecorated(false);
4147 setItemsExpandable(false);
4148 setSortingEnabled(true);
4149 header()->setSortIndicator(0, Qt::AscendingOrder);
4150 header()->setStretchLastSection(false);
4151 setTextElideMode(Qt::ElideMiddle);
4152 setEditTriggers(QAbstractItemView::EditKeyPressed);
4153 setContextMenuPolicy(Qt::CustomContextMenu);
4154#if QT_CONFIG(draganddrop)
4155 setDragDropMode(QAbstractItemView::InternalMove);
4156#endif
4157}
4158
4159void QFileDialogTreeView::keyPressEvent(QKeyEvent *e)
4160{
4161 if (!d_ptr->itemViewKeyboardEvent(e))
4162 QTreeView::keyPressEvent(e);
4163 e->accept();
4164}
4165
4167{
4168 int height = qMax(10, sizeHintForRow(0));
4169 QSize sizeHint = header()->sizeHint();
4170 return QSize(sizeHint.width() * 4, height * 30);
4171}
4172
4173/*!
4174 \class QFileDialogLineEdit
4175 \inmodule QtWidgets
4176 \internal
4177*/
4178
4179/*!
4180 // FIXME: this is a hack to avoid propagating key press events
4181 // to the dialog and from there to the "Ok" button
4182*/
4183void QFileDialogLineEdit::keyPressEvent(QKeyEvent *e)
4184{
4185#if QT_CONFIG(shortcut)
4186 int key = e->key();
4187#endif
4188 QLineEdit::keyPressEvent(e);
4189#if QT_CONFIG(shortcut)
4190 if (!e->matches(QKeySequence::Cancel) && key != Qt::Key_Back)
4191#endif
4192 e->accept();
4193}
4194
4195#if QT_CONFIG(fscompleter)
4196
4197QString QFSCompleter::pathFromIndex(const QModelIndex &index) const
4198{
4199 const QFileSystemModel *dirModel;
4200 if (proxyModel)
4201 dirModel = qobject_cast<const QFileSystemModel *>(proxyModel->sourceModel());
4202 else
4203 dirModel = sourceModel;
4204 QString currentLocation = dirModel->rootPath();
4205 QString path = index.data(QFileSystemModel::FilePathRole).toString();
4206 if (!currentLocation.isEmpty() && path.startsWith(currentLocation)) {
4207#if defined(Q_OS_UNIX)
4208 if (currentLocation == QDir::separator())
4209 return path.remove(0, currentLocation.size());
4210#endif
4211 if (currentLocation.endsWith(u'/'))
4212 return path.remove(0, currentLocation.size());
4213 else
4214 return path.remove(0, currentLocation.size()+1);
4215 }
4216 return index.data(QFileSystemModel::FilePathRole).toString();
4217}
4218
4219QStringList QFSCompleter::splitPath(const QString &path) const
4220{
4221 if (path.isEmpty())
4222 return QStringList(completionPrefix());
4223
4224 QString pathCopy = QDir::toNativeSeparators(path);
4225 QChar sep = QDir::separator();
4226#if defined(Q_OS_WIN)
4227 if (pathCopy == "\\"_L1 || pathCopy == "\\\\"_L1)
4228 return QStringList(pathCopy);
4229 QString doubleSlash("\\\\"_L1);
4230 if (pathCopy.startsWith(doubleSlash))
4231 pathCopy = pathCopy.mid(2);
4232 else
4233 doubleSlash.clear();
4234#elif defined(Q_OS_UNIX)
4235 {
4236 QString tildeExpanded = qt_tildeExpansion(pathCopy);
4237 if (tildeExpanded != pathCopy) {
4238 QFileSystemModel *dirModel;
4239 if (proxyModel)
4240 dirModel = qobject_cast<QFileSystemModel *>(proxyModel->sourceModel());
4241 else
4242 dirModel = sourceModel;
4243 dirModel->fetchMore(dirModel->index(tildeExpanded));
4244 }
4245 pathCopy = std::move(tildeExpanded);
4246 }
4247#endif
4248
4249#if defined(Q_OS_WIN)
4250 QStringList parts = pathCopy.split(sep, Qt::SkipEmptyParts);
4251 if (!doubleSlash.isEmpty() && !parts.isEmpty())
4252 parts[0].prepend(doubleSlash);
4253 if (pathCopy.endsWith(sep))
4254 parts.append(QString());
4255#else
4256 QStringList parts = pathCopy.split(sep);
4257 if (pathCopy[0] == sep) // read the "/" at the beginning as the split removed it
4258 parts[0] = sep;
4259#endif
4260
4261#if defined(Q_OS_WIN)
4262 bool startsFromRoot = !parts.isEmpty() && parts[0].endsWith(u':');
4263#else
4264 bool startsFromRoot = pathCopy[0] == sep;
4265#endif
4266 if (parts.size() == 1 || (parts.size() > 1 && !startsFromRoot)) {
4267 const QFileSystemModel *dirModel;
4268 if (proxyModel)
4269 dirModel = qobject_cast<const QFileSystemModel *>(proxyModel->sourceModel());
4270 else
4271 dirModel = sourceModel;
4272 QString currentLocation = QDir::toNativeSeparators(dirModel->rootPath());
4273#if defined(Q_OS_WIN)
4274 if (currentLocation.endsWith(u':'))
4275 currentLocation.append(sep);
4276#endif
4277 if (currentLocation.contains(sep) && path != currentLocation) {
4278 QStringList currentLocationList = splitPath(currentLocation);
4279 while (!currentLocationList.isEmpty() && parts.size() > 0 && parts.at(0) == ".."_L1) {
4280 parts.removeFirst();
4281 currentLocationList.removeLast();
4282 }
4283 if (!currentLocationList.isEmpty() && currentLocationList.constLast().isEmpty())
4284 currentLocationList.removeLast();
4285 return currentLocationList + parts;
4286 }
4287 }
4288 return parts;
4289}
4290
4291#endif // QT_CONFIG(completer)
4292
4293
4294QT_END_NAMESPACE
4295
4296#include "moc_qfiledialog.cpp"
void setHistory(const QStringList &paths)
void setFileDialogPrivate(QFileDialogPrivate *d_pointer)
void showPopup() override
Displays the list of items in the combobox.
\inmodule QtWidgets
void setFileDialogPrivate(QFileDialogPrivate *d_pointer)
QSize sizeHint() const override
QSize sizeHint() const override
void setFileDialogPrivate(QFileDialogPrivate *d_pointer)
Definition qlist.h:82
\inmodule QtCore
static QUrl _qt_get_directory(const QUrl &url, const QFileInfo &local)
static void _qt_init_lastVisited()
QStringList qt_strip_filters(const QStringList &filters)
QStringList qt_make_filter_list(const QString &filter)
static QString fileFromPath(const QString &rootPath, QString path)
static bool isCaseSensitiveFileSystem(const QString &path)
QFileDialogArgs(const QUrl &url={})