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