aboutsummaryrefslogtreecommitdiffstats
path: root/src/quickdialogs/quickdialogsquickimpl/qquickfiledialogdelegate.cpp
diff options
context:
space:
mode:
authorEd Cooke <ed.cooke@qt.io>2024-11-07 15:42:27 +0100
committerJan Arve Sæther <jan-arve.saether@qt.io>2025-01-03 08:49:38 +0100
commit0b1e5c45de264bf7b62e074c50aba0acb3f157f8 (patch)
tree64d58a70606c7b900ff49b10c58c2892b7ec1042 /src/quickdialogs/quickdialogsquickimpl/qquickfiledialogdelegate.cpp
parent7d36867bfe0e45954294a219550b9808b1806a05 (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.cpp283
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"