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

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