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"