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