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

source code of qtbase/src/widgets/dialogs/qfiledialog.cpp