diff options
Diffstat (limited to 'src/quickdialogs/quickdialogsquickimpl/qquickfolderdialogimpl.cpp')
-rw-r--r-- | src/quickdialogs/quickdialogsquickimpl/qquickfolderdialogimpl.cpp | 374 |
1 files changed, 374 insertions, 0 deletions
diff --git a/src/quickdialogs/quickdialogsquickimpl/qquickfolderdialogimpl.cpp b/src/quickdialogs/quickdialogsquickimpl/qquickfolderdialogimpl.cpp new file mode 100644 index 0000000000..25d82a17aa --- /dev/null +++ b/src/quickdialogs/quickdialogsquickimpl/qquickfolderdialogimpl.cpp @@ -0,0 +1,374 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qquickfolderdialogimpl_p.h" +#include "qquickfolderdialogimpl_p_p.h" + +#include <QtCore/qloggingcategory.h> +#include <QtQuickTemplates2/private/qquickdialogbuttonbox_p_p.h> + +#include "qquickfiledialogdelegate_p.h" +#include "qquickfolderbreadcrumbbar_p.h" + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcFolderDialogCurrentFolder, "qt.quick.dialogs.quickfolderdialogimpl.currentFolder") +Q_LOGGING_CATEGORY(lcFolderDialogSelectedFolder, "qt.quick.dialogs.quickfolderdialogimpl.selectedFolder") +Q_LOGGING_CATEGORY(lcFolderDialogOptions, "qt.quick.dialogs.quickfolderdialogimpl.options") + +QQuickFolderDialogImplPrivate::QQuickFolderDialogImplPrivate() +{ +} + +void QQuickFolderDialogImplPrivate::updateEnabled() +{ + Q_Q(QQuickFolderDialogImpl); + if (!buttonBox) + return; + + QQuickFolderDialogImplAttached *attached = attachedOrWarn(); + if (!attached) + return; + + auto openButton = buttonBox->standardButton(QPlatformDialogHelper::Open); + if (!openButton) { + qmlWarning(q).nospace() << "Can't update Open button's enabled state because it wasn't found"; + return; + } + + openButton->setEnabled(!selectedFolder.isEmpty() && attached->breadcrumbBar() + && !attached->breadcrumbBar()->textField()->isVisible()); +} + +/*! + \internal + + Ensures that a folder is always selected after a change in \c currentFolder. + + \a oldFolderPath is the previous value of \c currentFolder. +*/ +void QQuickFolderDialogImplPrivate::updateSelectedFolder(const QString &oldFolderPath) +{ + Q_Q(QQuickFolderDialogImpl); + QQuickFolderDialogImplAttached *attached = attachedOrWarn(); + if (!attached || !attached->folderDialogListView()) + return; + + QString newSelectedFolderPath; + int newSelectedFolderIndex = 0; + const QString newFolderPath = QQmlFile::urlToLocalFileOrQrc(currentFolder); + if (!oldFolderPath.isEmpty() && !newFolderPath.isEmpty()) { + // If the user went up a directory (or several), we should set + // selectedFolder to be the directory that we were in (or + // its closest ancestor that is a child of the new directory). + // E.g. if oldFolderPath is /foo/bar/baz/abc/xyz, and newFolderPath is /foo/bar, + // then we want to set selectedFolder to be /foo/bar/baz. + const int indexOfFolder = oldFolderPath.indexOf(newFolderPath); + if (indexOfFolder != -1) { + // [folder] + // [ oldFolderPath ] + // /foo/bar/baz/abc/xyz + // [rel...Paths] + QStringList relativePaths = oldFolderPath.mid(indexOfFolder + newFolderPath.size()).split(QLatin1Char('/'), Qt::SkipEmptyParts); + newSelectedFolderPath = newFolderPath + QLatin1Char('/') + relativePaths.first(); + + // Now find the index of that directory so that we can set the ListView's currentIndex to it. + const QDir newFolderDir(newFolderPath); + // Just to be safe... + if (!newFolderDir.exists()) { + qmlWarning(q) << "Directory" << newSelectedFolderPath << "doesn't exist; can't get a file entry list for it"; + return; + } + + const QFileInfoList dirs = newFolderDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::DirsFirst); + const QFileInfo newSelectedFileInfo(newSelectedFolderPath); + // The directory can contain files, but since we put dirs first, that should never affect the indices. + newSelectedFolderIndex = dirs.indexOf(newSelectedFileInfo); + } + } + + if (newSelectedFolderPath.isEmpty()) { + // When entering into a directory that isn't a parent of the old one, the first + // file delegate should be selected. + // TODO: is there a cheaper way to do this? QDirIterator doesn't support sorting, + // so we can't use that. QQuickFolderListModel uses threads to fetch its data, + // so should be considered asynchronous. We might be able to use it, but it would + // complicate the code even more... + QDir newFolderDir(newFolderPath); + if (newFolderDir.exists()) { + const QFileInfoList files = newFolderDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::DirsFirst); + if (!files.isEmpty()) + newSelectedFolderPath = files.first().absoluteFilePath(); + } + } + + const bool folderSelected = !newSelectedFolderPath.isEmpty(); + q->setSelectedFolder(folderSelected ? QUrl::fromLocalFile(newSelectedFolderPath) : QUrl()); + { + // Set the appropriate currentIndex for the selected folder. We block signals from ListView + // because we don't want folderDialogListViewCurrentIndexChanged to be called, as the file + // it gets from the delegate will not be up-to-date (but most importantly because we already + // just set the selected folder). + QSignalBlocker blocker(attached->folderDialogListView()); + attached->folderDialogListView()->setCurrentIndex(folderSelected ? newSelectedFolderIndex : -1); + } + if (folderSelected) { + if (QQuickItem *currentItem = attached->folderDialogListView()->currentItem()) + currentItem->forceActiveFocus(); + } +} + +void QQuickFolderDialogImplPrivate::handleAccept() +{ + // Let handleClick take care of calling accept(). +} + +void QQuickFolderDialogImplPrivate::handleClick(QQuickAbstractButton *button) +{ + Q_Q(QQuickFolderDialogImpl); + if (buttonRole(button) == QPlatformDialogHelper::AcceptRole && selectedFolder.isValid()) { + q->setSelectedFolder(selectedFolder); + q->accept(); + } +} + +/*! + \class QQuickFolderDialogImpl + \internal + + An interface that QQuickFolderDialog can use to access the non-native Qt Quick FolderDialog. + + Both this and the native implementations are created in QQuickAbstractDialog::create(). +*/ + +QQuickFolderDialogImpl::QQuickFolderDialogImpl(QObject *parent) + : QQuickDialog(*(new QQuickFolderDialogImplPrivate), parent) +{ +} + +QQuickFolderDialogImplAttached *QQuickFolderDialogImpl::qmlAttachedProperties(QObject *object) +{ + return new QQuickFolderDialogImplAttached(object); +} + +QUrl QQuickFolderDialogImpl::currentFolder() const +{ + Q_D(const QQuickFolderDialogImpl); + return d->currentFolder; +} + +void QQuickFolderDialogImpl::setCurrentFolder(const QUrl ¤tFolder) +{ + qCDebug(lcFolderDialogCurrentFolder) << "setCurrentFolder called with" << currentFolder; + Q_D(QQuickFolderDialogImpl); + if (currentFolder == d->currentFolder) + return; + + const QString oldFolderPath = QQmlFile::urlToLocalFileOrQrc(d->currentFolder); + + d->currentFolder = currentFolder; + d->updateSelectedFolder(oldFolderPath); + emit currentFolderChanged(d->currentFolder); +} + +QUrl QQuickFolderDialogImpl::selectedFolder() const +{ + Q_D(const QQuickFolderDialogImpl); + return d->selectedFolder; +} + +void QQuickFolderDialogImpl::setSelectedFolder(const QUrl &selectedFolder) +{ + Q_D(QQuickFolderDialogImpl); + qCDebug(lcFolderDialogSelectedFolder).nospace() << "setSelectedFolder called with selectedFolder " + << selectedFolder << " (d->selectedFolder is " << d->selectedFolder << ")"; + if (selectedFolder == d->selectedFolder) + return; + + d->selectedFolder = selectedFolder; + d->updateEnabled(); + emit selectedFolderChanged(selectedFolder); +} + +QSharedPointer<QFileDialogOptions> QQuickFolderDialogImpl::options() const +{ + Q_D(const QQuickFolderDialogImpl); + return d->options; +} + +void QQuickFolderDialogImpl::setOptions(const QSharedPointer<QFileDialogOptions> &options) +{ + qCDebug(lcFolderDialogOptions).nospace() << "setOptions called with:" + << " acceptMode=" << options->acceptMode() + << " fileMode=" << options->fileMode() + << " initialDirectory=" << options->initialDirectory(); + + Q_D(QQuickFolderDialogImpl); + d->options = options; +} + +/*! + \internal + + These allow QQuickPlatformFileDialog::show() to set custom labels on the + dialog buttons without having to know about/go through QQuickFolderDialogImplAttached + and QQuickDialogButtonBox. +*/ +void QQuickFolderDialogImpl::setAcceptLabel(const QString &label) +{ + Q_D(QQuickFolderDialogImpl); + d->acceptLabel = label; + QQuickFolderDialogImplAttached *attached = d->attachedOrWarn(); + if (!attached) + return; + + auto acceptButton = d->buttonBox->standardButton(QPlatformDialogHelper::Open); + if (!acceptButton) { + qmlWarning(this).nospace() << "Can't set accept label to " << label + << "; failed to find Open button in DialogButtonBox of " << this; + return; + } + + acceptButton->setText(!label.isEmpty() + ? label : QQuickDialogButtonBoxPrivate::buttonText(QPlatformDialogHelper::Open)); +} + +void QQuickFolderDialogImpl::setRejectLabel(const QString &label) +{ + Q_D(QQuickFolderDialogImpl); + d->rejectLabel = label; + if (!d->buttonBox) + return; + + auto rejectButton = d->buttonBox->standardButton(QPlatformDialogHelper::Cancel); + if (!rejectButton) { + qmlWarning(this).nospace() << "Can't set reject label to " << label + << "; failed to find Open button in DialogButtonBox of " << this; + return; + } + + rejectButton->setText(!label.isEmpty() + ? label : QQuickDialogButtonBoxPrivate::buttonText(QPlatformDialogHelper::Cancel)); +} + +void QQuickFolderDialogImpl::componentComplete() +{ + Q_D(QQuickFolderDialogImpl); + QQuickDialog::componentComplete(); + + // Find the right-most button and set its key navigation so that + // tab moves focus to the breadcrumb bar's up button. I tried + // doing this via KeyNavigation on the DialogButtonBox in QML, + // but it didn't work (probably because it's not the right item). + QQuickFolderDialogImplAttached *attached = d->attachedOrWarn(); + if (!attached) + return; + + Q_ASSERT(d->buttonBox); + const int buttonCount = d->buttonBox->count(); + if (buttonCount == 0) + return; + + QQuickAbstractButton *rightMostButton = qobject_cast<QQuickAbstractButton *>( + d->buttonBox->itemAt(buttonCount - 1)); + if (!rightMostButton) { + qmlWarning(this) << "Can't find right-most button in DialogButtonBox"; + return; + } + + auto keyNavigationAttached = QQuickKeyNavigationAttached::qmlAttachedProperties(rightMostButton); + if (!keyNavigationAttached) { + qmlWarning(this) << "Can't create attached KeyNavigation object on" << QDebug::toString(rightMostButton); + return; + } + + keyNavigationAttached->setTab(attached->breadcrumbBar()->upButton()); +} + +void QQuickFolderDialogImpl::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data) +{ + Q_D(QQuickFolderDialogImpl); + QQuickDialog::itemChange(change, data); + + if (change != QQuickItem::ItemVisibleHasChanged || !isComponentComplete() || !data.boolValue) + return; + + QQuickFolderDialogImplAttached *attached = d->attachedOrWarn(); + if (!attached) + return; + + attached->folderDialogListView()->forceActiveFocus(); + d->updateEnabled(); +} + +QQuickFolderDialogImplAttached *QQuickFolderDialogImplPrivate::attachedOrWarn() +{ + Q_Q(QQuickFolderDialogImpl); + QQuickFolderDialogImplAttached *attached = static_cast<QQuickFolderDialogImplAttached*>( + qmlAttachedPropertiesObject<QQuickFolderDialogImpl>(q)); + if (!attached) + qmlWarning(q) << "Expected FileDialogImpl attached object to be present on" << this; + return attached; +} + +void QQuickFolderDialogImplAttachedPrivate::folderDialogListViewCurrentIndexChanged() +{ + auto folderDialogImpl = qobject_cast<QQuickFolderDialogImpl*>(parent); + if (!folderDialogImpl) + return; + + auto folderDialogDelegate = qobject_cast<QQuickFileDialogDelegate*>(folderDialogListView->currentItem()); + if (!folderDialogDelegate) + return; + + folderDialogImpl->setSelectedFolder(folderDialogDelegate->file()); +} + +QQuickFolderDialogImplAttached::QQuickFolderDialogImplAttached(QObject *parent) + : QObject(*(new QQuickFolderDialogImplAttachedPrivate), parent) +{ + if (!qobject_cast<QQuickFolderDialogImpl*>(parent)) { + qmlWarning(this) << "FolderDialogImpl attached properties should only be " + << "accessed through the root FileDialogImpl instance"; + } +} + +QQuickListView *QQuickFolderDialogImplAttached::folderDialogListView() const +{ + Q_D(const QQuickFolderDialogImplAttached); + return d->folderDialogListView; +} + +void QQuickFolderDialogImplAttached::setFolderDialogListView(QQuickListView *folderDialogListView) +{ + Q_D(QQuickFolderDialogImplAttached); + if (folderDialogListView == d->folderDialogListView) + return; + + d->folderDialogListView = folderDialogListView; + + QObjectPrivate::connect(d->folderDialogListView, &QQuickListView::currentIndexChanged, + d, &QQuickFolderDialogImplAttachedPrivate::folderDialogListViewCurrentIndexChanged); + + emit folderDialogListViewChanged(); +} + +QQuickFolderBreadcrumbBar *QQuickFolderDialogImplAttached::breadcrumbBar() const +{ + Q_D(const QQuickFolderDialogImplAttached); + return d->breadcrumbBar; +} + +void QQuickFolderDialogImplAttached::setBreadcrumbBar(QQuickFolderBreadcrumbBar *breadcrumbBar) +{ + Q_D(QQuickFolderDialogImplAttached); + if (breadcrumbBar == d->breadcrumbBar) + return; + + d->breadcrumbBar = breadcrumbBar; + emit breadcrumbBarChanged(); +} + +QT_END_NAMESPACE + +#include "moc_qquickfolderdialogimpl_p.cpp" |