Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquickfiledialogimpl.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
7
8#include <QtCore/qloggingcategory.h>
9#include <QtGui/private/qguiapplication_p.h>
10#include <QtGui/qpa/qplatformtheme.h>
11#include <QtQml/qqmlinfo.h>
12#include <QtQml/qqmlfile.h>
13#if QT_CONFIG(accessibility)
14#include <QtQuick/private/qquickaccessibleattached_p.h>
15#endif
16#include <QtQuick/private/qquickitemview_p_p.h>
17#include <QtQuickTemplates2/private/qquickdialogbuttonbox_p_p.h>
18#include <QtQuickTemplates2/private/qquickpopupitem_p_p.h>
19#include <QtQuickControls2Impl/private/qquickplatformtheme_p.h>
20#include <QtQuickDialogs2Utils/private/qquickfilenamefilter_p.h>
21
24
26
27Q_STATIC_LOGGING_CATEGORY(lcCurrentFolder, "qt.quick.dialogs.quickfiledialogimpl.currentFolder")
28Q_STATIC_LOGGING_CATEGORY(lcSelectedFile, "qt.quick.dialogs.quickfiledialogimpl.selectedFile")
29Q_STATIC_LOGGING_CATEGORY(lcUpdateSelectedFile, "qt.quick.dialogs.quickfiledialogimpl.updateSelectedFile")
30Q_STATIC_LOGGING_CATEGORY(lcOptions, "qt.quick.dialogs.quickfiledialogimpl.options")
31Q_STATIC_LOGGING_CATEGORY(lcNameFilters, "qt.quick.dialogs.quickfiledialogimpl.namefilters")
32Q_STATIC_LOGGING_CATEGORY(lcAttachedNameFilters, "qt.quick.dialogs.quickfiledialogimplattached.namefilters")
33Q_STATIC_LOGGING_CATEGORY(lcAttachedCurrentIndex, "qt.quick.dialogs.quickfiledialogimplattached.currentIndex")
34
35QQuickFileDialogImplPrivate::QQuickFileDialogImplPrivate()
36{
37}
38
39void QQuickFileDialogImplPrivate::setNameFilters(const QStringList &filters)
40{
41 Q_Q(QQuickFileDialogImpl);
42 if (filters == nameFilters)
43 return;
44
45 nameFilters = filters;
46 emit q->nameFiltersChanged();
47}
48
49void QQuickFileDialogImplPrivate::updateEnabled()
50{
51 Q_Q(QQuickFileDialogImpl);
52 QQuickFileDialogImplAttached *attached = attachedOrWarn();
53 if (!attached)
54 return;
55
56 auto openButton = attached->buttonBox()->standardButton(QPlatformDialogHelper::Open);
57 if (!openButton) {
58 qmlWarning(q).nospace() << "Can't update Open button's enabled state because it wasn't found";
59 return;
60 }
61
62 openButton->setEnabled(!selectedFile.isEmpty() && attached->breadcrumbBar()
63 && !attached->breadcrumbBar()->textField()->isVisible());
64}
65
66/*!
67 \internal
68
69 Ensures that a file is always selected after a change in \c folder.
70
71 \a oldFolderPath is the previous value of \c folder.
72*/
73void QQuickFileDialogImplPrivate::updateSelectedFile(const QString &oldFolderPath)
74{
75 Q_Q(QQuickFileDialogImpl);
76 QQuickFileDialogImplAttached *attached = attachedOrWarn();
77 if (!attached || !attached->fileDialogListView())
78 return;
79
80 qCDebug(lcUpdateSelectedFile) << "updateSelectedFile called with oldFolderPath" << oldFolderPath;
81
82 QString newSelectedFilePath;
83 int newSelectedFileIndex = -1;
84 const QString newFolderPath = QQmlFile::urlToLocalFileOrQrc(currentFolder);
85 if (!oldFolderPath.isEmpty() && !newFolderPath.isEmpty()) {
86 // TODO: Add another platform theme hint for this behavior too, as e.g. macOS
87 // doesn't do it this way.
88 // If the user went up a directory (or several), we should set
89 // selectedFile to be the directory that we were in (or
90 // its closest ancestor that is a child of the new directory).
91 // E.g. if oldFolderPath is /foo/bar/baz/abc/xyz, and newFolderPath is /foo/bar,
92 // then we want to set selectedFile to be /foo/bar/baz.
93 const int indexOfFolder = oldFolderPath.indexOf(newFolderPath);
94 if (indexOfFolder != -1) {
95 // [folder]
96 // [ oldFolderPath ]
97 // /foo/bar/baz/abc/xyz
98 // [rel...Paths]
99 QStringList relativePaths = oldFolderPath.mid(indexOfFolder + newFolderPath.size()).split(QLatin1Char('/'), Qt::SkipEmptyParts);
100 newSelectedFilePath = newFolderPath + QLatin1Char('/') + relativePaths.first();
101
102 // Now find the index of that directory so that we can set the ListView's currentIndex to it.
103 const QDir newFolderDir(newFolderPath);
104 // Just to be safe...
105 if (!newFolderDir.exists()) {
106 qmlWarning(q) << "Directory" << newSelectedFilePath << "doesn't exist; can't get a file entry list for it";
107 return;
108 }
109
110 const QFileInfoList filesInNewDir = fileList(newFolderDir);
111 const QFileInfo newSelectedFileInfo(newSelectedFilePath);
112 newSelectedFileIndex = filesInNewDir.indexOf(newSelectedFileInfo);
113 }
114 }
115
116 static const bool preselectFirstFile = []() {
117 const QVariant envVar = qEnvironmentVariable("QT_QUICK_DIALOGS_PRESELECT_FIRST_FILE");
118 if (envVar.isValid() && envVar.canConvert<bool>())
119 return envVar.toBool();
120 return QGuiApplicationPrivate::platformTheme()->themeHint(
121 QPlatformTheme::PreselectFirstFileInDirectory).toBool();
122 }();
123
124 if (preselectFirstFile && newSelectedFilePath.isEmpty()) {
125 // When entering into a directory that isn't a parent of the old one, the first
126 // file delegate should be selected.
127 // TODO: is there a cheaper way to do this? QDirIterator doesn't support sorting,
128 // so we can't use that. QQuickFolderListModel uses threads to fetch its data,
129 // so should be considered asynchronous. We might be able to use it, but it would
130 // complicate the code even more...
131 const QDir newFolderDir(newFolderPath);
132 if (newFolderDir.exists()) {
133 if (!cachedFileList.isEmpty()) {
134 newSelectedFilePath = cachedFileList.first().absoluteFilePath();
135 newSelectedFileIndex = 0;
136 }
137 }
138 }
139
140 const QUrl newSelectedFileUrl = QUrl::fromLocalFile(newSelectedFilePath);
141 qCDebug(lcUpdateSelectedFile).nospace() << "updateSelectedFile is setting selectedFile to " << newSelectedFileUrl
142 << ", newSelectedFileIndex is " << newSelectedFileIndex;
143 q->setSelectedFile(newSelectedFileUrl);
144 updateFileNameTextEdit();
145 // If the index is -1, there are no files in the directory, and so fileDialogListView's
146 // currentIndex will already be -1.
147 if (newSelectedFileIndex != -1)
148 tryUpdateFileDialogListViewCurrentIndex(newSelectedFileIndex);
149}
150
151void QQuickFileDialogImplPrivate::updateFileNameTextEdit()
152{
153 QQuickFileDialogImplAttached *attached = attachedOrWarn();
154 if (Q_UNLIKELY(!attached))
155 return;
156
157 const QFileInfo fileInfo(selectedFile.toLocalFile());
158 if (fileInfo.isFile())
159 attached->fileNameTextField()->setText(fileInfo.fileName());
160}
161
162QDir::SortFlags QQuickFileDialogImplPrivate::fileListSortFlags()
163{
164 QDir::SortFlags sortFlags = QDir::IgnoreCase;
165 if (QQuickPlatformTheme::getThemeHint(QPlatformTheme::ShowDirectoriesFirst).toBool())
166 sortFlags.setFlag(QDir::DirsFirst);
167 return sortFlags;
168}
169
170QFileInfoList QQuickFileDialogImplPrivate::fileList(const QDir &dir)
171{
172 return dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot, fileListSortFlags());
173}
174
175void QQuickFileDialogImplPrivate::setFileDialogListViewCurrentIndex(int newCurrentIndex)
176{
177 qCDebug(lcSelectedFile) << "setting fileDialogListView's currentIndex to" << newCurrentIndex;
178
179 // We block signals from ListView because we don't want fileDialogListViewCurrentIndexChanged
180 // to be called, as the file it gets from the delegate will not be up-to-date (but most
181 // importantly because we already just set the selected file).
182 QQuickFileDialogImplAttached *attached = attachedOrWarn();
183 const QSignalBlocker blocker(attached->fileDialogListView());
184 attached->fileDialogListView()->setCurrentIndex(newCurrentIndex);
185 attached->fileDialogListView()->positionViewAtIndex(newCurrentIndex, QQuickListView::Center);
186 if (QQuickItem *currentItem = attached->fileDialogListView()->currentItem())
187 currentItem->forceActiveFocus();
188}
189
190/*!
191 \internal
192
193 Tries to set the currentIndex of fileDialogListView to \a newCurrentIndex and gives
194 focus to the current item.
195*/
196void QQuickFileDialogImplPrivate::tryUpdateFileDialogListViewCurrentIndex(int newCurrentIndex)
197{
198 qCDebug(lcSelectedFile) << "tryUpdateFileDialogListViewCurrentIndex called with newCurrentIndex" << newCurrentIndex;
199 QQuickFileDialogImplAttached *attached = attachedOrWarn();
200 Q_ASSERT(attached);
201 Q_ASSERT(attached->fileDialogListView());
202
203 // We were likely trying to set an index for a file that the ListView hadn't loaded yet.
204 // We need to wait until the ListView has loaded all expected items, but since we have no
205 // efficient way of verifying that, we just check that the count is as expected.
206 if (newCurrentIndex != -1 && newCurrentIndex >= attached->fileDialogListView()->count()) {
207 qCDebug(lcSelectedFile) << "- trying to set currentIndex to" << newCurrentIndex
208 << "but fileDialogListView only has" << attached->fileDialogListView()->count()
209 << "items; setting pendingCurrentIndexToSet to" << newCurrentIndex;
210 pendingCurrentIndexToSet = newCurrentIndex;
211 QObjectPrivate::connect(attached->fileDialogListView(), &QQuickItemView::countChanged,
212 this, &QQuickFileDialogImplPrivate::fileDialogListViewCountChanged, Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));
213 return;
214 }
215
216 setFileDialogListViewCurrentIndex(newCurrentIndex);
217}
218
219void QQuickFileDialogImplPrivate::fileDialogListViewCountChanged()
220{
221 QQuickFileDialogImplAttached *attached = attachedOrWarn();
222 qCDebug(lcSelectedFile) << "fileDialogListView count changed to" << attached->fileDialogListView()->count();
223
224 if (pendingCurrentIndexToSet != -1 && pendingCurrentIndexToSet < attached->fileDialogListView()->count()) {
225 // The view now has all of the items we expect it to, so we can set
226 // its currentIndex back to the selected file.
227 qCDebug(lcSelectedFile) << "- ListView has expected count;"
228 << "applying pending fileDialogListView currentIndex" << pendingCurrentIndexToSet;
229
230 QObjectPrivate::disconnect(attached->fileDialogListView(), &QQuickItemView::countChanged,
231 this, &QQuickFileDialogImplPrivate::fileDialogListViewCountChanged);
232 setFileDialogListViewCurrentIndex(pendingCurrentIndexToSet);
233 pendingCurrentIndexToSet = -1;
234 qCDebug(lcSelectedFile) << "- reset pendingCurrentIndexToSet to -1";
235 } else {
236 qCDebug(lcSelectedFile) << "- ListView doesn't yet have expected count of" << cachedFileList.size();
237 }
238}
239
240void QQuickFileDialogImplPrivate::handleAccept()
241{
242 // Let handleClick take care of calling accept().
243}
244
245void QQuickFileDialogImplPrivate::handleClick(QQuickAbstractButton *button)
246{
247 Q_Q(QQuickFileDialogImpl);
248 if (buttonRole(button) == QPlatformDialogHelper::AcceptRole && selectedFile.isValid()) {
249 // The "Open" button was clicked, so we need to set the file to the current file, if any.
250 const QFileInfo fileInfo(selectedFile.toLocalFile());
251 if (fileInfo.isDir()) {
252 // If it's a directory, navigate to it.
253 q->setCurrentFolder(selectedFile);
254 // Don't call accept(), because selecting a folder != accepting the dialog.
255 } else {
256 // Otherwise it's a file, so select it and close the dialog.
257
258 lastButtonClicked = button;
259
260 // Unless it already exists...
261 const bool dontConfirmOverride = q->options()->testOption(QFileDialogOptions::DontConfirmOverwrite);
262 const bool isSaveMode = q->options()->fileMode() == QFileDialogOptions::AnyFile;
263 if (QQuickFileDialogImplAttached *attached = attachedOrWarn();
264 attached && fileInfo.exists() && isSaveMode && !dontConfirmOverride) {
265 QQuickDialog *confirmationDialog = attached->overwriteConfirmationDialog();
266 confirmationDialog->open();
267 } else {
268 selectFile();
269 }
270 }
271 }
272}
273
274void QQuickFileDialogImplPrivate::selectFile()
275{
276 Q_Q(QQuickFileDialogImpl);
277 Q_ASSERT(lastButtonClicked);
278 q->setSelectedFile(selectedFile);
279 q->accept();
280 QQuickDialogPrivate::handleClick(lastButtonClicked);
281 emit q->fileSelected(selectedFile);
282}
283
284QQuickFileDialogImpl::QQuickFileDialogImpl(QObject *parent)
285 : QQuickDialog(*(new QQuickFileDialogImplPrivate), parent)
286{
287}
288
289QQuickFileDialogImplAttached *QQuickFileDialogImpl::qmlAttachedProperties(QObject *object)
290{
291 return new QQuickFileDialogImplAttached(object);
292}
293
294QUrl QQuickFileDialogImpl::currentFolder() const
295{
296 Q_D(const QQuickFileDialogImpl);
297 return d->currentFolder;
298}
299
300void QQuickFileDialogImpl::setCurrentFolder(const QUrl &currentFolder, SetReason setReason)
301{
302 Q_D(QQuickFileDialogImpl);
303 qCDebug(lcCurrentFolder).nospace() << "setCurrentFolder called with " << currentFolder
304 << " (old currentFolder is " << d->currentFolder << ")";
305
306 // As we would otherwise get the file list from scratch in a couple of places,
307 // just get it once and cache it.
308 // We need to cache it before the equality check, otherwise opening the dialog
309 // several times in the same directory wouldn't update the cache.
310 if (!currentFolder.isEmpty())
311 d->cachedFileList = d->fileList(QQmlFile::urlToLocalFileOrQrc(currentFolder));
312 else
313 d->cachedFileList.clear();
314 qCDebug(lcCurrentFolder) << "- cachedFileList size is now " << d->cachedFileList.size();
315
316 if (currentFolder == d->currentFolder)
317 return;
318
319 const QString oldFolderPath = QQmlFile::urlToLocalFileOrQrc(d->currentFolder);
320
321 d->currentFolder = currentFolder;
322 // Don't update the selectedFile if it's an Internal set, as that
323 // means that the user just set selectedFile, and we're being called as a result of that.
324 if (setReason == SetReason::External) {
325 // Since the directory changed, the old file can no longer be selected.
326 d->updateSelectedFile(oldFolderPath);
327 }
328 emit currentFolderChanged(d->currentFolder);
329}
330
331QUrl QQuickFileDialogImpl::selectedFile() const
332{
333 Q_D(const QQuickFileDialogImpl);
334 return d->selectedFile;
335}
336
337/*!
338 \internal
339
340 This is mostly called as a result of user interaction, but is also
341 called (indirectly) by QQuickFileDialog::onShow when the user set an initial
342 selectedFile.
343*/
344void QQuickFileDialogImpl::setSelectedFile(const QUrl &selectedFile)
345{
346 qCDebug(lcSelectedFile) << "setSelectedFile called with" << selectedFile;
347 Q_D(QQuickFileDialogImpl);
348 if (selectedFile == d->selectedFile)
349 return;
350
351 d->selectedFile = selectedFile;
352 d->updateEnabled();
353 emit selectedFileChanged(d->selectedFile);
354}
355
356/*!
357 \internal
358
359 Called when showing the FileDialog each time, so long as
360 QFileDialogOptions::initiallySelectedFiles is not empty.
361*/
362void QQuickFileDialogImpl::setInitialCurrentFolderAndSelectedFile(const QUrl &file)
363{
364 Q_D(QQuickFileDialogImpl);
365 const QUrl fileDirUrl = QUrl::fromLocalFile(QFileInfo(file.toLocalFile()).dir().absolutePath());
366 const bool currentFolderChanged = d->currentFolder != fileDirUrl;
367 qCDebug(lcSelectedFile) << "setting initial currentFolder to" << fileDirUrl << "and selectedFile to" << file;
368 setCurrentFolder(fileDirUrl, QQuickFileDialogImpl::SetReason::Internal);
369 setSelectedFile(file);
370 d->updateFileNameTextEdit();
371 d->setCurrentIndexToInitiallySelectedFile = true;
372
373 bool isListViewCurrentIndexNegative = false;
374 if (const auto *attached = d->attachedOrWarn())
375 isListViewCurrentIndexNegative = attached->fileDialogListView()->currentIndex() < 0;
376
377 // If the currentFolder didn't change, the FolderListModel won't change and
378 // neither will the ListView. This means that setFileDialogListViewCurrentIndex
379 // will never get called and the currentIndex will not reflect selectedFile.
380 // We need to account for that here.
381 if (!currentFolderChanged || isListViewCurrentIndexNegative) {
382 const QFileInfo newSelectedFileInfo(d->selectedFile.toLocalFile());
383 const int indexOfSelectedFileInFileDialogListView = d->cachedFileList.indexOf(newSelectedFileInfo);
384 d->tryUpdateFileDialogListViewCurrentIndex(indexOfSelectedFileInFileDialogListView);
385 }
386}
387
388QSharedPointer<QFileDialogOptions> QQuickFileDialogImpl::options() const
389{
390 Q_D(const QQuickFileDialogImpl);
391 return d->options;
392}
393
394void QQuickFileDialogImpl::setOptions(const QSharedPointer<QFileDialogOptions> &options)
395{
396 qCDebug(lcOptions).nospace() << "setOptions called with:"
397 << " acceptMode=" << options->acceptMode()
398 << " fileMode=" << options->fileMode()
399 << " initialDirectory=" << options->initialDirectory()
400 << " nameFilters=" << options->nameFilters()
401 << " initiallySelectedNameFilter=" << options->initiallySelectedNameFilter();
402
403 Q_D(QQuickFileDialogImpl);
404 d->options = options;
405
406 if (d->options) {
407 d->selectedNameFilter->setOptions(options);
408 d->setNameFilters(options->nameFilters());
409
410 if (auto attached = d->attachedOrWarn()) {
411 const bool isSaveMode = d->options->fileMode() == QFileDialogOptions::AnyFile;
412 attached->fileNameLabel()->setVisible(isSaveMode);
413 attached->fileNameTextField()->setVisible(isSaveMode);
414 }
415 }
416}
417
418/*!
419 \internal
420
421 The list of user-facing strings describing the available file filters.
422*/
423QStringList QQuickFileDialogImpl::nameFilters() const
424{
425 Q_D(const QQuickFileDialogImpl);
426 return d->options ? d->options->nameFilters() : QStringList();
427}
428
429void QQuickFileDialogImpl::resetNameFilters()
430{
431 Q_D(QQuickFileDialogImpl);
432 d->setNameFilters(QStringList());
433}
434
435QQuickFileNameFilter *QQuickFileDialogImpl::selectedNameFilter() const
436{
437 Q_D(const QQuickFileDialogImpl);
438 if (!d->selectedNameFilter) {
439 QQuickFileDialogImpl *that = const_cast<QQuickFileDialogImpl *>(this);
440 d->selectedNameFilter = new QQuickFileNameFilter(that);
441 if (d->options)
442 d->selectedNameFilter->setOptions(d->options);
443 }
444 return d->selectedNameFilter;
445}
446
447/*!
448 \internal
449
450 These allow QQuickPlatformFileDialog::show() to set custom labels on the
451 dialog buttons without having to know about/go through QQuickFileDialogImplAttached
452 and QQuickDialogButtonBox.
453*/
454void QQuickFileDialogImpl::setAcceptLabel(const QString &label)
455{
456 Q_D(QQuickFileDialogImpl);
457 d->acceptLabel = label;
458 QQuickFileDialogImplAttached *attached = d->attachedOrWarn();
459 if (!attached)
460 return;
461
462 auto acceptButton = attached->buttonBox()->standardButton(QPlatformDialogHelper::Open);
463 if (!acceptButton) {
464 qmlWarning(this).nospace() << "Can't set accept label to " << label
465 << "; failed to find Open button in DialogButtonBox of " << this;
466 return;
467 }
468
469 auto buttonType = (d->options && d->options->acceptMode() == QFileDialogOptions::AcceptSave)
470 ? QPlatformDialogHelper::Save
471 : QPlatformDialogHelper::Open;
472 acceptButton->setText(!label.isEmpty()
473 ? label : QQuickDialogButtonBoxPrivate::buttonText(buttonType));
474}
475
476void QQuickFileDialogImpl::setRejectLabel(const QString &label)
477{
478 Q_D(QQuickFileDialogImpl);
479 d->rejectLabel = label;
480 QQuickFileDialogImplAttached *attached = d->attachedOrWarn();
481 if (!attached)
482 return;
483
484 auto rejectButton = attached->buttonBox()->standardButton(QPlatformDialogHelper::Cancel);
485 if (!rejectButton) {
486 qmlWarning(this).nospace() << "Can't set reject label to " << label
487 << "; failed to find Open button in DialogButtonBox of " << this;
488 return;
489 }
490
491 rejectButton->setText(!label.isEmpty()
492 ? label : QQuickDialogButtonBoxPrivate::buttonText(QPlatformDialogHelper::Cancel));
493}
494
495void QQuickFileDialogImpl::selectNameFilter(const QString &filter)
496{
497 qCDebug(lcNameFilters) << "selectNameFilter called with" << filter;
498 Q_D(QQuickFileDialogImpl);
499 d->selectedNameFilter->update(filter);
500 emit filterSelected(filter);
501}
502
503QString QQuickFileDialogImpl::fileName() const
504{
505 return selectedFile().fileName();
506}
507void QQuickFileDialogImpl::setFileName(const QString &fileName)
508{
509 const QString previous = selectedFile().fileName();
510 if (previous == fileName)
511 return;
512
513 QUrl newSelectedFile;
514 newSelectedFile.setScheme(currentFolder().scheme());
515 newSelectedFile.setPath(currentFolder().path() + u'/' + fileName);
516 setSelectedFile(newSelectedFile);
517}
518
519QString QQuickFileDialogImpl::currentFolderName() const
520{
521 return QDir(currentFolder().toLocalFile()).dirName();
522}
523
524void QQuickFileDialogImpl::componentComplete()
525{
526 Q_D(QQuickFileDialogImpl);
527 QQuickDialog::componentComplete();
528
529 // Find the right-most button and set its key navigation so that
530 // tab moves focus to the breadcrumb bar's up button. I tried
531 // doing this via KeyNavigation on the DialogButtonBox in QML,
532 // but it didn't work (probably because it's not the right item).
533 QQuickFileDialogImplAttached *attached = d->attachedOrWarn();
534 if (!attached)
535 return;
536
537 const int buttonCount = attached->buttonBox()->count();
538 if (buttonCount == 0)
539 return;
540
541 QQuickAbstractButton *rightMostButton = qobject_cast<QQuickAbstractButton *>(
542 attached->buttonBox()->itemAt(buttonCount - 1));
543 if (!rightMostButton) {
544 qmlWarning(this) << "Can't find right-most button in DialogButtonBox";
545 return;
546 }
547
548 auto keyNavigationAttached = QQuickKeyNavigationAttached::qmlAttachedProperties(rightMostButton);
549 if (!keyNavigationAttached) {
550 qmlWarning(this) << "Can't create attached KeyNavigation object on" << QDebug::toString(rightMostButton);
551 return;
552 }
553
554 keyNavigationAttached->setTab(attached->breadcrumbBar()->upButton());
555
556#if QT_CONFIG(accessibility)
557 auto *label = attached->filterLabel();
558 auto *comboBox = attached->nameFiltersComboBox();
559 if (label && comboBox)
560 if (QQuickAccessibleAttached *accessibleAttached = QQuickControlPrivate::accessibleAttached(label))
561 accessibleAttached->setLabelFor(comboBox);
562#endif
563}
564
565void QQuickFileDialogImpl::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data)
566{
567 Q_D(QQuickFileDialogImpl);
568 QQuickDialog::itemChange(change, data);
569
570 if (change != QQuickItem::ItemVisibleHasChanged || !isComponentComplete() || !data.boolValue)
571 return;
572
573 QQuickFileDialogImplAttached *attached = d->attachedOrWarn();
574 if (!attached)
575 return;
576
577 attached->fileDialogListView()->forceActiveFocus();
578 d->updateEnabled();
579}
580
581QQuickFileDialogImplAttached *QQuickFileDialogImplPrivate::attachedOrWarn()
582{
583 Q_Q(QQuickFileDialogImpl);
584 QQuickFileDialogImplAttached *attached = static_cast<QQuickFileDialogImplAttached*>(
585 qmlAttachedPropertiesObject<QQuickFileDialogImpl>(q, false));
586 if (!attached)
587 qmlWarning(q) << "Expected FileDialogImpl attached object to be present on" << this;
588 return attached;
589}
590
591void QQuickFileDialogImplAttachedPrivate::nameFiltersComboBoxItemActivated(int index)
592{
593 qCDebug(lcAttachedNameFilters) << "nameFiltersComboBoxItemActivated called with" << index;
594 auto fileDialogImpl = qobject_cast<QQuickFileDialogImpl*>(parent);
595 if (!fileDialogImpl)
596 return;
597
598 fileDialogImpl->selectNameFilter(nameFiltersComboBox->textAt(index));
599}
600
601void QQuickFileDialogImplAttachedPrivate::fileDialogListViewCurrentIndexChanged()
602{
603 auto fileDialogImpl = qobject_cast<QQuickFileDialogImpl*>(parent);
604 if (!fileDialogImpl)
605 return;
606
607 auto fileDialogDelegate = qobject_cast<QQuickFileDialogDelegate*>(fileDialogListView->currentItem());
608 if (!fileDialogDelegate)
609 return;
610
611 const QQuickItemViewPrivate::MovementReason moveReason = QQuickItemViewPrivate::get(fileDialogListView)->moveReason;
612 qCDebug(lcAttachedCurrentIndex).nospace() << "fileDialogListView currentIndex changed to " << fileDialogListView->currentIndex()
613 << " with moveReason " << moveReason
614 << "; the file at that index is " << fileDialogDelegate->file();
615
616 // Only update selectedFile if the currentIndex changed as a result of user interaction;
617 // things like model changes (i.e. QQuickItemViewPrivate::applyModelChanges() calling
618 // QQuickItemViewPrivate::updateCurrent as a result of us changing the directory on the FolderListModel)
619 // shouldn't cause the selectedFile to change.
620 auto fileDialogImplPrivate = QQuickFileDialogImplPrivate::get(fileDialogImpl);
621 if (moveReason != QQuickItemViewPrivate::Other) {
622 fileDialogImpl->setSelectedFile(fileDialogDelegate->file());
623 fileDialogImplPrivate->updateFileNameTextEdit();
624 } else if (fileDialogImplPrivate->setCurrentIndexToInitiallySelectedFile) {
625 // When setting selectedFile before opening the FileDialog,
626 // we need to ensure that the currentIndex is correct, because the initial change
627 // in directory will cause the underyling FolderListModel to change its folder property,
628 // which in turn resets the fileDialogListView's currentIndex to 0.
629 const QFileInfo newSelectedFileInfo(fileDialogImplPrivate->selectedFile.toLocalFile());
630 const int indexOfSelectedFileInFileDialogListView = fileDialogImplPrivate->cachedFileList.indexOf(newSelectedFileInfo);
631 fileDialogImplPrivate->tryUpdateFileDialogListViewCurrentIndex(indexOfSelectedFileInFileDialogListView);
632 fileDialogImplPrivate->setCurrentIndexToInitiallySelectedFile = false;
633 }
634}
635
636void QQuickFileDialogImplAttachedPrivate::fileNameEditedByUser()
637{
638 if (!buttonBox)
639 return;
640 auto openButton = buttonBox->standardButton(QPlatformDialogHelper::Open);
641 if (!openButton || !fileNameTextField)
642 return;
643 openButton->setEnabled(!fileNameTextField->text().isEmpty());
644}
645
646void QQuickFileDialogImplAttachedPrivate::fileNameEditingByUserFinished()
647{
648 auto fileDialogImpl = qobject_cast<QQuickFileDialogImpl *>(parent);
649 if (!fileDialogImpl)
650 return;
651
652 fileDialogImpl->setFileName(fileNameTextField->text());
653}
654
655QQuickFileDialogImplAttached::QQuickFileDialogImplAttached(QObject *parent)
656 : QObject(*(new QQuickFileDialogImplAttachedPrivate), parent)
657{
658 if (!qobject_cast<QQuickFileDialogImpl*>(parent)) {
659 qmlWarning(this) << "FileDialogImpl attached properties should only be "
660 << "accessed through the root FileDialogImpl instance";
661 }
662}
663
664QQuickDialogButtonBox *QQuickFileDialogImplAttached::buttonBox() const
665{
666 Q_D(const QQuickFileDialogImplAttached);
667 return d->buttonBox;
668}
669
670void QQuickFileDialogImplAttached::setButtonBox(QQuickDialogButtonBox *buttonBox)
671{
672 Q_D(QQuickFileDialogImplAttached);
673 if (buttonBox == d->buttonBox)
674 return;
675
676 if (d->buttonBox) {
677 QQuickFileDialogImpl *fileDialogImpl = qobject_cast<QQuickFileDialogImpl*>(parent());
678 if (fileDialogImpl) {
679 auto dialogPrivate = QQuickDialogPrivate::get(fileDialogImpl);
680 QObjectPrivate::disconnect(d->buttonBox, &QQuickDialogButtonBox::accepted,
681 dialogPrivate, &QQuickDialogPrivate::handleAccept);
682 QObjectPrivate::disconnect(d->buttonBox, &QQuickDialogButtonBox::rejected,
683 dialogPrivate, &QQuickDialogPrivate::handleReject);
684 QObjectPrivate::disconnect(d->buttonBox, &QQuickDialogButtonBox::clicked,
685 dialogPrivate, &QQuickDialogPrivate::handleClick);
686 }
687 }
688
689 d->buttonBox = buttonBox;
690
691 if (buttonBox) {
692 QQuickFileDialogImpl *fileDialogImpl = qobject_cast<QQuickFileDialogImpl*>(parent());
693 if (fileDialogImpl) {
694 auto dialogPrivate = QQuickDialogPrivate::get(fileDialogImpl);
695 QObjectPrivate::connect(d->buttonBox, &QQuickDialogButtonBox::accepted,
696 dialogPrivate, &QQuickDialogPrivate::handleAccept);
697 QObjectPrivate::connect(d->buttonBox, &QQuickDialogButtonBox::rejected,
698 dialogPrivate, &QQuickDialogPrivate::handleReject);
699 QObjectPrivate::connect(d->buttonBox, &QQuickDialogButtonBox::clicked,
700 dialogPrivate, &QQuickDialogPrivate::handleClick);
701 }
702 }
703
704 emit buttonBoxChanged();
705}
706
707QQuickComboBox *QQuickFileDialogImplAttached::nameFiltersComboBox() const
708{
709 Q_D(const QQuickFileDialogImplAttached);
710 return d->nameFiltersComboBox;
711}
712
713void QQuickFileDialogImplAttached::setNameFiltersComboBox(QQuickComboBox *nameFiltersComboBox)
714{
715 Q_D(QQuickFileDialogImplAttached);
716 if (nameFiltersComboBox == d->nameFiltersComboBox)
717 return;
718
719 d->nameFiltersComboBox = nameFiltersComboBox;
720
721 QObjectPrivate::connect(d->nameFiltersComboBox, &QQuickComboBox::activated,
722 d, &QQuickFileDialogImplAttachedPrivate::nameFiltersComboBoxItemActivated);
723
724 emit nameFiltersComboBoxChanged();
725}
726
727QQuickLabel *QQuickFileDialogImplAttached::filterLabel() const
728{
729 Q_D(const QQuickFileDialogImplAttached);
730 return d->filterLabel;
731}
732
733void QQuickFileDialogImplAttached::setFilterLabel(QQuickLabel *label)
734{
735 Q_D(QQuickFileDialogImplAttached);
736
737 if (d->filterLabel == label)
738 return;
739
740 d->filterLabel = label;
741
742 emit filterLabelChanged();
743}
744
745QString QQuickFileDialogImplAttached::selectedNameFilter() const
746{
747 Q_D(const QQuickFileDialogImplAttached);
748 return d->nameFiltersComboBox ? d->nameFiltersComboBox->currentText() : QString();
749}
750
751void QQuickFileDialogImplAttached::selectNameFilter(const QString &filter)
752{
753 Q_D(QQuickFileDialogImplAttached);
754 qCDebug(lcAttachedNameFilters) << "selectNameFilter called with" << filter;
755 if (!d->nameFiltersComboBox)
756 return;
757
758 const int indexInComboBox = d->nameFiltersComboBox->find(filter);
759 if (indexInComboBox == -1)
760 return;
761
762 qCDebug(lcAttachedNameFilters) << "setting ComboBox's currentIndex to" << indexInComboBox;
763 d->nameFiltersComboBox->setCurrentIndex(indexInComboBox);
764}
765
766QQuickListView *QQuickFileDialogImplAttached::fileDialogListView() const
767{
768 Q_D(const QQuickFileDialogImplAttached);
769 return d->fileDialogListView;
770}
771
772void QQuickFileDialogImplAttached::setFileDialogListView(QQuickListView *fileDialogListView)
773{
774 Q_D(QQuickFileDialogImplAttached);
775 if (fileDialogListView == d->fileDialogListView)
776 return;
777
778 if (d->fileDialogListView)
779 QObjectPrivate::disconnect(d->fileDialogListView, &QQuickListView::currentIndexChanged,
780 d, &QQuickFileDialogImplAttachedPrivate::fileDialogListViewCurrentIndexChanged);
781
782 d->fileDialogListView = fileDialogListView;
783
784 if (d->fileDialogListView)
785 QObjectPrivate::connect(d->fileDialogListView, &QQuickListView::currentIndexChanged,
786 d, &QQuickFileDialogImplAttachedPrivate::fileDialogListViewCurrentIndexChanged);
787
788 emit fileDialogListViewChanged();
789}
790
791QQuickFolderBreadcrumbBar *QQuickFileDialogImplAttached::breadcrumbBar() const
792{
793 Q_D(const QQuickFileDialogImplAttached);
794 return d->breadcrumbBar;
795}
796
797void QQuickFileDialogImplAttached::setBreadcrumbBar(QQuickFolderBreadcrumbBar *breadcrumbBar)
798{
799 Q_D(QQuickFileDialogImplAttached);
800 if (breadcrumbBar == d->breadcrumbBar)
801 return;
802
803 d->breadcrumbBar = breadcrumbBar;
804 emit breadcrumbBarChanged();
805}
806
807QQuickLabel *QQuickFileDialogImplAttached::fileNameLabel() const
808{
809 Q_D(const QQuickFileDialogImplAttached);
810 return d->fileNameLabel;
811}
812
813void QQuickFileDialogImplAttached::setFileNameLabel(QQuickLabel *fileNameLabel)
814{
815 Q_D(QQuickFileDialogImplAttached);
816 if (fileNameLabel == d->fileNameLabel)
817 return;
818
819 d->fileNameLabel = fileNameLabel;
820
821 emit fileNameLabelChanged();
822}
823
824QQuickTextField *QQuickFileDialogImplAttached::fileNameTextField() const
825{
826 Q_D(const QQuickFileDialogImplAttached);
827 return d->fileNameTextField;
828}
829
830void QQuickFileDialogImplAttached::setFileNameTextField(QQuickTextField *fileNameTextField)
831{
832 Q_D(QQuickFileDialogImplAttached);
833 if (fileNameTextField == d->fileNameTextField)
834 return;
835
836 if (d->fileNameTextField) {
837 QObjectPrivate::disconnect(d->fileNameTextField, &QQuickTextField::editingFinished,
838 d, &QQuickFileDialogImplAttachedPrivate::fileNameEditingByUserFinished);
839 QObjectPrivate::disconnect(d->fileNameTextField, &QQuickTextField::textEdited,
840 d, &QQuickFileDialogImplAttachedPrivate::fileNameEditedByUser);
841 }
842
843 d->fileNameTextField = fileNameTextField;
844
845 if (d->fileNameTextField) {
846 QObjectPrivate::connect(d->fileNameTextField, &QQuickTextField::editingFinished,
847 d, &QQuickFileDialogImplAttachedPrivate::fileNameEditingByUserFinished);
848 QObjectPrivate::connect(d->fileNameTextField, &QQuickTextField::textEdited,
849 d, &QQuickFileDialogImplAttachedPrivate::fileNameEditedByUser);
850 }
851 emit fileNameTextFieldChanged();
852}
853
854QQuickDialog *QQuickFileDialogImplAttached::overwriteConfirmationDialog() const
855{
856 Q_D(const QQuickFileDialogImplAttached);
857 return d->overwriteConfirmationDialog;
858}
859
860void QQuickFileDialogImplAttached::setOverwriteConfirmationDialog(QQuickDialog *dialog)
861{
862 Q_D(QQuickFileDialogImplAttached);
863 if (dialog == d->overwriteConfirmationDialog)
864 return;
865
866 QQuickFileDialogImpl *fileDialogImpl = qobject_cast<QQuickFileDialogImpl*>(parent());
867 if (d->overwriteConfirmationDialog && fileDialogImpl)
868 QObjectPrivate::disconnect(d->overwriteConfirmationDialog, &QQuickDialog::accepted,
869 QQuickFileDialogImplPrivate::get(fileDialogImpl), &QQuickFileDialogImplPrivate::selectFile);
870
871 d->overwriteConfirmationDialog = dialog;
872
873 if (d->overwriteConfirmationDialog && fileDialogImpl)
874 QObjectPrivate::connect(d->overwriteConfirmationDialog, &QQuickDialog::accepted,
875 QQuickFileDialogImplPrivate::get(fileDialogImpl), &QQuickFileDialogImplPrivate::selectFile, Qt::QueuedConnection);
876
877 emit overwriteConfirmationDialogChanged();
878}
879
880QQuickSideBar *QQuickFileDialogImplAttached::sideBar() const
881{
882 Q_D(const QQuickFileDialogImplAttached);
883 return d->sideBar;
884}
885
886void QQuickFileDialogImplAttached::setSideBar(QQuickSideBar *sideBar)
887{
888 Q_D(QQuickFileDialogImplAttached);
889 if (sideBar == d->sideBar)
890 return;
891
892 d->sideBar = sideBar;
893
894 emit sideBarChanged();
895}
896
897QT_END_NAMESPACE
898
899#include "moc_qquickfiledialogimpl_p.cpp"
Combined button and popup list for selecting options.