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

source code of qtdeclarative/src/quickdialogs/quickdialogsquickimpl/qquickfiledialogimpl.cpp