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