File indexing completed on 2024-05-19 15:09:21

0001 /*
0002     SPDX-FileCopyrightText: 2010 BetterInbox <contact@betterinbox.com>
0003     SPDX-FileContributor: Gregory Schlomoff <greg@betterinbox.com>
0004 
0005     SPDX-License-Identifier: MIT
0006 */
0007 
0008 #include "DeclarativeDragArea.h"
0009 
0010 #include <QDrag>
0011 #include <QGuiApplication>
0012 #include <QIcon>
0013 #include <QMimeData>
0014 #include <QMouseEvent>
0015 #include <QPainter>
0016 #include <QQuickItemGrabResult>
0017 #include <QQuickWindow>
0018 #include <QStyleHints>
0019 
0020 #include <QDebug>
0021 
0022 /*!
0023     A DragArea is used to make an item draggable.
0024 */
0025 
0026 DeclarativeDragArea::DeclarativeDragArea(QQuickItem *parent)
0027     : QQuickItem(parent)
0028     , m_delegate(nullptr)
0029     , m_source(parent)
0030     , m_target(nullptr)
0031     , m_enabled(true)
0032     , m_draggingJustStarted(false)
0033     , m_dragActive(false)
0034     , m_supportedActions(Qt::MoveAction)
0035     , m_defaultAction(Qt::MoveAction)
0036     , m_data(new DeclarativeMimeData()) // m_data is owned by us, and we shouldn't pass it to Qt directly
0037                                         // as it will automatically delete it after the drag and drop.
0038     , m_pressAndHoldTimerId(0)
0039 {
0040     m_startDragDistance = QGuiApplication::styleHints()->startDragDistance();
0041     setAcceptedMouseButtons(Qt::LeftButton);
0042     //     setFiltersChildEvents(true);
0043     setFlag(ItemAcceptsDrops, m_enabled);
0044     setFiltersChildMouseEvents(true);
0045 }
0046 
0047 DeclarativeDragArea::~DeclarativeDragArea()
0048 {
0049     if (m_data) {
0050         delete m_data;
0051     }
0052 }
0053 
0054 /*!
0055   The delegate is the item that will be displayed next to the mouse cursor during the drag and drop operation.
0056   It usually consists of a large, semi-transparent icon representing the data being dragged.
0057 */
0058 QQuickItem *DeclarativeDragArea::delegate() const
0059 {
0060     return m_delegate;
0061 }
0062 
0063 void DeclarativeDragArea::setDelegate(QQuickItem *delegate)
0064 {
0065     if (m_delegate != delegate) {
0066         // qDebug() << " ______________________________________________ " << delegate;
0067         m_delegate = delegate;
0068         Q_EMIT delegateChanged();
0069     }
0070 }
0071 void DeclarativeDragArea::resetDelegate()
0072 {
0073     setDelegate(nullptr);
0074 }
0075 
0076 /*!
0077   The QML element that is the source of this drag and drop operation. This can be defined to any item, and will
0078   be available to the DropArea as event.data.source
0079 */
0080 QQuickItem *DeclarativeDragArea::source() const
0081 {
0082     return m_source;
0083 }
0084 
0085 void DeclarativeDragArea::setSource(QQuickItem *source)
0086 {
0087     if (m_source != source) {
0088         m_source = source;
0089         Q_EMIT sourceChanged();
0090     }
0091 }
0092 
0093 void DeclarativeDragArea::resetSource()
0094 {
0095     setSource(nullptr);
0096 }
0097 
0098 bool DeclarativeDragArea::dragActive() const
0099 {
0100     return m_dragActive;
0101 }
0102 
0103 // target
0104 QQuickItem *DeclarativeDragArea::target() const
0105 {
0106     // TODO: implement me
0107     return nullptr;
0108 }
0109 
0110 // data
0111 DeclarativeMimeData *DeclarativeDragArea::mimeData() const
0112 {
0113     return m_data;
0114 }
0115 
0116 // startDragDistance
0117 int DeclarativeDragArea::startDragDistance() const
0118 {
0119     return m_startDragDistance;
0120 }
0121 
0122 void DeclarativeDragArea::setStartDragDistance(int distance)
0123 {
0124     if (distance == m_startDragDistance) {
0125         return;
0126     }
0127 
0128     m_startDragDistance = distance;
0129     Q_EMIT startDragDistanceChanged();
0130 }
0131 
0132 // delegateImage
0133 QVariant DeclarativeDragArea::delegateImage() const
0134 {
0135     return m_delegateImage;
0136 }
0137 
0138 void DeclarativeDragArea::setDelegateImage(const QVariant &image)
0139 {
0140     if (image.canConvert<QImage>() && image.value<QImage>() == m_delegateImage) {
0141         return;
0142     }
0143 
0144     if (image.canConvert<QImage>()) {
0145         m_delegateImage = image.value<QImage>();
0146     } else if (image.canConvert<QString>()) {
0147         m_delegateImage = QIcon::fromTheme(image.toString()).pixmap(QSize(48, 48)).toImage();
0148     } else {
0149         m_delegateImage = image.value<QIcon>().pixmap(QSize(48, 48)).toImage();
0150     }
0151 
0152     Q_EMIT delegateImageChanged();
0153 }
0154 
0155 // enabled
0156 bool DeclarativeDragArea::isEnabled() const
0157 {
0158     return m_enabled;
0159 }
0160 void DeclarativeDragArea::setEnabled(bool enabled)
0161 {
0162     if (enabled != m_enabled) {
0163         m_enabled = enabled;
0164         Q_EMIT enabledChanged();
0165     }
0166 }
0167 
0168 // supported actions
0169 Qt::DropActions DeclarativeDragArea::supportedActions() const
0170 {
0171     return m_supportedActions;
0172 }
0173 void DeclarativeDragArea::setSupportedActions(Qt::DropActions actions)
0174 {
0175     if (actions != m_supportedActions) {
0176         m_supportedActions = actions;
0177         Q_EMIT supportedActionsChanged();
0178     }
0179 }
0180 
0181 // default action
0182 Qt::DropAction DeclarativeDragArea::defaultAction() const
0183 {
0184     return m_defaultAction;
0185 }
0186 void DeclarativeDragArea::setDefaultAction(Qt::DropAction action)
0187 {
0188     if (action != m_defaultAction) {
0189         m_defaultAction = action;
0190         Q_EMIT defaultActionChanged();
0191     }
0192 }
0193 
0194 void DeclarativeDragArea::mousePressEvent(QMouseEvent *event)
0195 {
0196     m_pressAndHoldTimerId = startTimer(QGuiApplication::styleHints()->mousePressAndHoldInterval());
0197     m_buttonDownPos = event->screenPos();
0198     m_draggingJustStarted = true;
0199     setKeepMouseGrab(true);
0200 }
0201 
0202 void DeclarativeDragArea::mouseReleaseEvent(QMouseEvent *event)
0203 {
0204     Q_UNUSED(event);
0205     killTimer(m_pressAndHoldTimerId);
0206     m_pressAndHoldTimerId = 0;
0207     m_draggingJustStarted = false;
0208     setKeepMouseGrab(false);
0209     ungrabMouse();
0210 }
0211 
0212 void DeclarativeDragArea::timerEvent(QTimerEvent *event)
0213 {
0214     if (event->timerId() == m_pressAndHoldTimerId && m_draggingJustStarted && m_enabled) {
0215         // Grab delegate before starting drag
0216         if (m_delegate) {
0217             // Another grab is already in progress
0218             if (m_grabResult) {
0219                 return;
0220             }
0221             m_grabResult = m_delegate->grabToImage();
0222             if (m_grabResult) {
0223                 connect(m_grabResult.data(), &QQuickItemGrabResult::ready, this, [this]() {
0224                     startDrag(m_grabResult->image());
0225                     m_grabResult.reset();
0226                 });
0227                 return;
0228             }
0229         }
0230 
0231         // No delegate or grab failed, start drag immediately
0232         startDrag(m_delegateImage);
0233     }
0234 }
0235 
0236 void DeclarativeDragArea::mouseMoveEvent(QMouseEvent *event)
0237 {
0238     if (!m_enabled || QLineF(event->screenPos(), m_buttonDownPos).length() < m_startDragDistance) {
0239         return;
0240     }
0241 
0242     // don't start drags on move for touch events, they'll be handled only by press and hold
0243     // reset timer if moved more than m_startDragDistance
0244     if (event->source() == Qt::MouseEventSynthesizedByQt) {
0245         killTimer(m_pressAndHoldTimerId);
0246         m_pressAndHoldTimerId = 0;
0247         return;
0248     }
0249 
0250     if (m_draggingJustStarted) {
0251         // Grab delegate before starting drag
0252         if (m_delegate) {
0253             // Another grab is already in progress
0254             if (m_grabResult) {
0255                 return;
0256             }
0257             m_grabResult = m_delegate->grabToImage();
0258             if (m_grabResult) {
0259                 connect(m_grabResult.data(), &QQuickItemGrabResult::ready, this, [this]() {
0260                     startDrag(m_grabResult->image());
0261                     m_grabResult.reset();
0262                 });
0263                 return;
0264             }
0265         }
0266 
0267         // No delegate or grab failed, start drag immediately
0268         startDrag(m_delegateImage);
0269     }
0270 }
0271 
0272 bool DeclarativeDragArea::childMouseEventFilter(QQuickItem *item, QEvent *event)
0273 {
0274     if (!isEnabled()) {
0275         return false;
0276     }
0277 
0278     switch (event->type()) {
0279     case QEvent::MouseButtonPress: {
0280         QMouseEvent *me = static_cast<QMouseEvent *>(event);
0281         // qDebug() << "press in dragarea";
0282         mousePressEvent(me);
0283         break;
0284     }
0285     case QEvent::MouseMove: {
0286         QMouseEvent *me = static_cast<QMouseEvent *>(event);
0287         // qDebug() << "move in dragarea";
0288         mouseMoveEvent(me);
0289         break;
0290     }
0291     case QEvent::MouseButtonRelease: {
0292         QMouseEvent *me = static_cast<QMouseEvent *>(event);
0293         // qDebug() << "release in dragarea";
0294         mouseReleaseEvent(me);
0295         break;
0296     }
0297     default:
0298         break;
0299     }
0300 
0301     return QQuickItem::childMouseEventFilter(item, event);
0302 }
0303 
0304 void DeclarativeDragArea::startDrag(const QImage &image)
0305 {
0306     grabMouse();
0307     m_draggingJustStarted = false;
0308 
0309     QDrag *drag = new QDrag(parent());
0310     DeclarativeMimeData *dataCopy = new DeclarativeMimeData(m_data); // Qt will take ownership of this copy and delete it.
0311     drag->setMimeData(dataCopy);
0312 
0313     const qreal devicePixelRatio = window() ? window()->devicePixelRatio() : 1;
0314     const int imageSize = 48 * devicePixelRatio;
0315 
0316     if (!image.isNull()) {
0317         drag->setPixmap(QPixmap::fromImage(image));
0318     } else if (mimeData()->hasImage()) {
0319         const QImage im = qvariant_cast<QImage>(mimeData()->imageData());
0320         drag->setPixmap(QPixmap::fromImage(im));
0321     } else if (mimeData()->hasColor()) {
0322         QPixmap px(imageSize, imageSize);
0323         px.fill(mimeData()->color());
0324         drag->setPixmap(px);
0325     } else {
0326         // Icons otherwise
0327         QStringList icons;
0328         if (mimeData()->hasText()) {
0329             icons << QStringLiteral("text-plain");
0330         }
0331         if (mimeData()->hasHtml()) {
0332             icons << QStringLiteral("text-html");
0333         }
0334         if (mimeData()->hasUrls()) {
0335             for (int i = 0; i < std::min<int>(4, mimeData()->urls().size()); ++i) {
0336                 icons << QStringLiteral("text-html");
0337             }
0338         }
0339         if (!icons.isEmpty()) {
0340             QPixmap pm(imageSize * icons.count(), imageSize);
0341             pm.fill(Qt::transparent);
0342             QPainter p(&pm);
0343             int i = 0;
0344             for (const QString &ic : std::as_const(icons)) {
0345                 p.drawPixmap(QPoint(i * imageSize, 0), QIcon::fromTheme(ic).pixmap(imageSize));
0346                 i++;
0347             }
0348             p.end();
0349             drag->setPixmap(pm);
0350         }
0351     }
0352 
0353     // drag->setHotSpot(QPoint(drag->pixmap().width()/2, drag->pixmap().height()/2)); // TODO: Make a property for that
0354     // setCursor(Qt::OpenHandCursor);    //TODO? Make a property for the cursor
0355 
0356     m_dragActive = true;
0357     Q_EMIT dragActiveChanged();
0358     Q_EMIT dragStarted();
0359 
0360     Qt::DropAction action = drag->exec(m_supportedActions, m_defaultAction);
0361     setKeepMouseGrab(false);
0362 
0363     m_dragActive = false;
0364     Q_EMIT dragActiveChanged();
0365     Q_EMIT drop(action);
0366 
0367     ungrabMouse();
0368 }
0369 
0370 #include "moc_DeclarativeDragArea.cpp"