diff options
| author | Ed Cooke <ed.cooke@qt.io> | 2024-11-07 15:42:27 +0100 |
|---|---|---|
| committer | Jan Arve Sæther <jan-arve.saether@qt.io> | 2025-01-03 08:49:38 +0100 |
| commit | 0b1e5c45de264bf7b62e074c50aba0acb3f157f8 (patch) | |
| tree | 64d58a70606c7b900ff49b10c58c2892b7ec1042 /src/quickdialogs/quickdialogsquickimpl/qquickfiledialogdelegate.cpp | |
| parent | 7d36867bfe0e45954294a219550b9808b1806a05 (diff) | |
Add drag and drop to the QtQuick Dialogs SideBar
The current SideBar implementation for the QtQuick Dialogs non-native
FileDialog has a SideBar containing supported standard paths. This patch
adds support for adding favorites to the SideBar, that can be added by
dragging a directory into the SideBar.
Each FileDialogDelegate contains a QQuickTapHandler which listens for a
long press. When long pressed, the delegate can be dragged. A long press
is used to initiate the drag as the delegates are part of a Flickable.
The drag and drop is a system drag and drop. If the dragged delegate is
a directory, the drag is started.
When the dragging enters the SideBar, a delegate appears with 'Add
Favorite' text, to suggest to the user that this is the drop area.
The context menu used for removing a favorite is a QQuickContextMenu,
which handles the context menu event internally.
Pick-to: 6.9
Change-Id: Icf60f3785522df607ed1f7420486da4318118f0f
Reviewed-by: Oliver Eftevaag <oliver.eftevaag@qt.io>
Diffstat (limited to 'src/quickdialogs/quickdialogsquickimpl/qquickfiledialogdelegate.cpp')
| -rw-r--r-- | src/quickdialogs/quickdialogsquickimpl/qquickfiledialogdelegate.cpp | 283 |
1 files changed, 263 insertions, 20 deletions
diff --git a/src/quickdialogs/quickdialogsquickimpl/qquickfiledialogdelegate.cpp b/src/quickdialogs/quickdialogsquickimpl/qquickfiledialogdelegate.cpp index 369176f844..daf65d749f 100644 --- a/src/quickdialogs/quickdialogsquickimpl/qquickfiledialogdelegate.cpp +++ b/src/quickdialogs/quickdialogsquickimpl/qquickfiledialogdelegate.cpp @@ -2,35 +2,21 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qquickfiledialogdelegate_p.h" +#include "qquickfiledialogdelegate_p_p.h" #include <QtCore/qfileinfo.h> +#include <QtCore/qmimedata.h> #include <QtGui/qpa/qplatformtheme.h> #include <QtQml/QQmlFile> #include <QtQml/qqmlexpression.h> #include <QtQuick/private/qquicklistview_p.h> -#include <QtQuickTemplates2/private/qquickitemdelegate_p_p.h> - -#include "qquickfiledialogimpl_p.h" -#include "qquickfiledialogimpl_p_p.h" -#include "qquickfolderdialogimpl_p.h" +#include <QtQuick/private/qquickitemview_p_p.h> +#include "qquicksidebar_p.h" +#include "qquicksidebar_p_p.h" QT_BEGIN_NAMESPACE -class QQuickFileDialogDelegatePrivate : public QQuickItemDelegatePrivate -{ -public: - Q_DECLARE_PUBLIC(QQuickFileDialogDelegate) - - void highlightFile(); - void chooseFile(); - - bool acceptKeyClick(Qt::Key key) const override; - - QQuickDialog *dialog = nullptr; - QQuickFileDialogImpl *fileDialog = nullptr; - QQuickFolderDialogImpl *folderDialog = nullptr; - QUrl file; -}; +using namespace Qt::Literals::StringLiterals; void QQuickFileDialogDelegatePrivate::highlightFile() { @@ -117,6 +103,13 @@ void QQuickFileDialogDelegate::setDialog(QQuickDialog *dialog) d->fileDialog = qobject_cast<QQuickFileDialogImpl*>(dialog); d->folderDialog = qobject_cast<QQuickFolderDialogImpl*>(dialog); emit dialogChanged(); + + if (d->tapHandler) + d->destroyTapHandler(); + if (d->fileDialog) + d->initTapHandler(); + + } QUrl QQuickFileDialogDelegate::file() const @@ -158,6 +151,256 @@ void QQuickFileDialogDelegate::keyReleaseEvent(QKeyEvent *event) disconnect(connection); } +void QQuickFileDialogDelegatePrivate::initTapHandler() +{ + Q_Q(QQuickFileDialogDelegate); + if (!tapHandler) { + tapHandler = new QQuickFileDialogTapHandler(q); + + connect(tapHandler, &QQuickTapHandler::longPressed, this, + &QQuickFileDialogDelegatePrivate::handleLongPress); + } +} + +void QQuickFileDialogDelegatePrivate::destroyTapHandler() +{ + if (tapHandler) + delete tapHandler; +} + +void QQuickFileDialogDelegatePrivate::handleLongPress() +{ + if (!tapHandler) + return; + + tapHandler->m_state = QQuickFileDialogTapHandler::Tracking; + tapHandler->m_longPressed = true; +} + +// ---------------------------------------------- + +QQuickFileDialogTapHandler::QQuickFileDialogTapHandler(QQuickItem *parent) + : QQuickTapHandler(parent) +{ + // Set a grab permission that stops the flickable from stealing the drag. + setGrabPermissions(QQuickPointerHandler::CanTakeOverFromAnything); + + // The drag threshold is used by the handler to know when to start a drag. + // We handle the drag inpendently, so set the threshold to a big number. + // This will guarantee that QQuickTapHandler::wantsEventPoint always returns + // true. + setDragThreshold(1000); +} + +QQuickFileDialogImpl *QQuickFileDialogTapHandler::getFileDialogImpl() const +{ + auto *delegate = qobject_cast<QQuickFileDialogDelegate*>(parent()); + auto *delegatePrivate = QQuickFileDialogDelegatePrivate::get(delegate); + return delegatePrivate->fileDialog; +} + +void QQuickFileDialogTapHandler::grabFolder() +{ + if (m_drag.isNull()) + return; + + QPixmap pixmap(":/qt-project.org/imports/QtQuick/Dialogs/quickimpl/images/sidebar-folder.png"_L1); + auto *mimeData = new QMimeData(); + mimeData->setImageData(pixmap); + m_drag->setMimeData(mimeData); + m_drag->setPixmap(pixmap); +} + +QUrl QQuickFileDialogTapHandler::getFolderUrlAtPress() const +{ + return qobject_cast<QQuickFileDialogDelegate*>(parent())->file().toLocalFile(); +} + +void QQuickFileDialogTapHandler::handleDrag(QQuickDragEvent *event) +{ + if (m_state == Dragging) { + auto *fileDialogImpl = getFileDialogImpl(); + auto *attached = QQuickFileDialogImplPrivate::get(fileDialogImpl)->attachedOrWarn(); + if (Q_LIKELY(attached)) { + auto *sideBar = attached->sideBar(); + if (Q_LIKELY(sideBar)) { + auto *sideBarPrivate = QQuickSideBarPrivate::get(sideBar); + const auto pos = QPoint(event->x(), event->y()); + sideBarPrivate->setShowAddFavoriteDelegate(sideBar->contains(pos)); + if (sideBarPrivate->showAddFavoriteDelegate()) { + const QList<QQuickItem *> items = sideBar->contentChildren().toList<QList<QQuickItem *>>(); + // iterate through the children until we have counted all the folder paths. The next button will + // be the addFavoriteDelegate + const int addFavoritePos = sideBar->effectiveFolderPaths().size(); + int currentPos = 0; + for (int i = 0; i < items.length(); i++) { + if (auto *button = qobject_cast<QQuickAbstractButton *>(items.at(i))) { + if (currentPos == addFavoritePos) { + // check if the pointer position is within the add favorite button + const QRectF bBox = button->mapRectToItem(sideBar, button->boundingRect()); + sideBarPrivate->setAddFavoriteDelegateHovered(bBox.contains(pos)); + break; + } else { + currentPos++; + } + } + } + } + } + } + } +} + +void QQuickFileDialogTapHandler::handleDrop(QQuickDragEvent *event) +{ + Q_UNUSED(event); + if (m_state == Dragging) { + if (!m_sourceUrl.isEmpty()) { + auto *fileDialogImpl = getFileDialogImpl(); + auto *attached = QQuickFileDialogImplPrivate::get(fileDialogImpl)->attachedOrWarn(); + if (Q_LIKELY(attached)) { + auto *sideBar = attached->sideBar(); + if (Q_LIKELY(sideBar)) { + auto *sideBarPrivate = QQuickSideBarPrivate::get(sideBar); + // this cannot be handled in handleDrag because handleDrag is connected to the drop + // area, and so won't run when the cursor is outside of it + if (sideBarPrivate->addFavoriteDelegateHovered()) + sideBarPrivate->addFavorite(m_sourceUrl); + sideBarPrivate->setShowAddFavoriteDelegate(false); + } + } + } + m_state = DraggingFinished; + } +} + +void QQuickFileDialogTapHandler::handleContainsDragChanged() +{ + if (m_state == Dragging) { + auto *fileDialogImpl = getFileDialogImpl(); + auto *attached = QQuickFileDialogImplPrivate::get(fileDialogImpl)->attachedOrWarn(); + if (Q_LIKELY(attached)) { + auto *sideBar = attached->sideBar(); + if (Q_LIKELY(sideBar && m_dropArea)) { + auto *sideBarPrivate = QQuickSideBarPrivate::get(sideBar); + if (m_dropArea->containsDrag()) { + sideBarPrivate->setShowAddFavoriteDelegate(true); + } else { + sideBarPrivate->setShowAddFavoriteDelegate(false); + sideBarPrivate->setAddFavoriteDelegateHovered(false); + } + } + } + } +} + +void QQuickFileDialogTapHandler::resetDragData() +{ + if (m_state != Listening) { + m_state = Listening; + m_sourceUrl = QUrl(); + if (!m_drag.isNull()) { + m_drag->disconnect(); + delete m_drag; + } + if (!m_dropArea.isNull()) { + m_dropArea->disconnect(); + delete m_dropArea; + } + + m_longPressed = false; + } +} + +void QQuickFileDialogTapHandler::handleEventPoint(QPointerEvent *event, QEventPoint &point) +{ + QQuickTapHandler::handleEventPoint(event, point); + + auto *fileDialogDelegate = qobject_cast<QQuickFileDialogDelegate *>(parent()); + auto *fileDialogImpl = getFileDialogImpl(); + if (Q_UNLIKELY(!fileDialogImpl)) + return; + auto *fileDialogImplPrivate = QQuickFileDialogImplPrivate::get(fileDialogImpl); + QQuickFileDialogImplAttached *attached = fileDialogImplPrivate->attachedOrWarn(); + if (Q_UNLIKELY(!attached || !attached->sideBar())) + return; + + if (m_state == DraggingFinished) + resetDragData(); + + if (point.state() == QEventPoint::Pressed) { + resetDragData(); + // Activate the passive grab to get further move updates + setPassiveGrab(event, point, true); + } else if (point.state() == QEventPoint::Released) { + resetDragData(); + } else if (point.state() == QEventPoint::Updated && m_longPressed) { + // Check to see that the movement can be considered as dragging + const qreal distX = point.position().x() - point.pressPosition().x(); + const qreal distY = point.position().y() - point.pressPosition().y(); + // consider the squared distance to be optimal + const qreal dragDistSq = distX * distX + distY * distY; + if (dragDistSq > qPow(qApp->styleHints()->startDragDistance(), 2)) { + switch (m_state) { + case Tracking: { + // set the drag + if (m_drag.isNull()) + m_drag = new QDrag(fileDialogDelegate); + // Set the drop area + if (m_dropArea.isNull()) { + auto *sideBar = attached->sideBar(); + m_dropArea = new QQuickDropArea(sideBar); + m_dropArea->setSize(sideBar->size()); + connect(m_dropArea, &QQuickDropArea::positionChanged, this, + &QQuickFileDialogTapHandler::handleDrag); + connect(m_dropArea, &QQuickDropArea::dropped, this, + &QQuickFileDialogTapHandler::handleDrop); + connect(m_dropArea, &QQuickDropArea::containsDragChanged, this, + &QQuickFileDialogTapHandler::handleContainsDragChanged); + } + + m_sourceUrl = fileDialogDelegate->file().toLocalFile(); + // set up the grab + grabFolder(); + m_state = DraggingStarted; + } break; + + case DraggingStarted: { + if (m_drag && m_drag->mimeData()) { + if (auto *item = qobject_cast<QQuickItem *>(m_drag->source())) { + Q_UNUSED(item); + m_state = Dragging; + // start the drag + m_drag->exec(); + // If the state still remains dragging, then the drop happened outside + // the drop area + if (m_state == Dragging) + resetDragData(); + } + } + } break; + + default: + break; + } + } + } +} + +bool QQuickFileDialogTapHandler::wantsEventPoint(const QPointerEvent *event, const QEventPoint &point) +{ + if (!QQuickTapHandler::wantsEventPoint(event, point) || event->type() == QEvent::Type::Wheel) + return false; + + // we only want the event point if the delegate contains a directory and not a file + auto *fileDialogDelegate = qobject_cast<QQuickFileDialogDelegate *>(parent()); + QFileInfo info(fileDialogDelegate->file().toLocalFile()); + if (info.isDir()) + return true; + + return false; +} + QT_END_NAMESPACE #include "moc_qquickfiledialogdelegate_p.cpp" |
