##// END OF EJS Templates
fix some wrong drag&drop behavior appearing on mac
trabillard -
r979:a300a0876631
parent child
Show More
@@ -1,470 +1,472
1 1 #include "Visualization/VisualizationDragDropContainer.h"
2 2 #include "DragAndDrop/DragDropHelper.h"
3 3 #include "SqpApplication.h"
4 4 #include "Visualization/VisualizationDragWidget.h"
5 5
6 6 #include "Common/VisualizationDef.h"
7 7
8 8 #include <QDrag>
9 9 #include <QDragEnterEvent>
10 10 #include <QVBoxLayout>
11 11
12 12 #include <cmath>
13 13 #include <memory>
14 14
15 15 Q_LOGGING_CATEGORY(LOG_VisualizationDragDropContainer, "VisualizationDragDropContainer")
16 16
17 17 auto DRAGGED_MINIATURE_WIDTH = 200; // in pixels
18 18
19 19 struct VisualizationDragDropContainer::VisualizationDragDropContainerPrivate {
20 20
21 21 QVBoxLayout *m_Layout;
22 22 QHash<QString, VisualizationDragDropContainer::DropBehavior> m_AcceptedMimeTypes;
23 23 QString m_PlaceHolderText;
24 24 DragDropHelper::PlaceHolderType m_PlaceHolderType = DragDropHelper::PlaceHolderType::Graph;
25 25
26 26 VisualizationDragDropContainer::AcceptMimeDataFunction m_AcceptMimeDataFun
27 27 = [](auto mimeData) { return true; };
28 28
29 29 int m_MinContainerHeight = 0;
30 30
31 31 explicit VisualizationDragDropContainerPrivate(QWidget *widget)
32 32 {
33 33 m_Layout = new QVBoxLayout(widget);
34 34 m_Layout->setContentsMargins(0, 0, 0, 0);
35 35 }
36 36
37 37 bool acceptMimeData(const QMimeData *data) const
38 38 {
39 39 auto accepted = false;
40 40 for (auto it = m_AcceptedMimeTypes.constBegin(); it != m_AcceptedMimeTypes.constEnd();
41 41 ++it) {
42 42 const auto &type = it.key();
43 43 const auto &behavior = it.value();
44 44
45 45 if (data->hasFormat(type)) {
46 46 if (behavior != DropBehavior::Forbidden) {
47 47 accepted = true;
48 48 }
49 49 else {
50 50 accepted = false;
51 51 break;
52 52 }
53 53 }
54 54 }
55 55
56 56 if (accepted) {
57 57 accepted = m_AcceptMimeDataFun(data);
58 58 }
59 59
60 60 return accepted;
61 61 }
62 62
63 63 bool allowMergeForMimeData(const QMimeData *data) const
64 64 {
65 65 bool result = false;
66 66 for (auto it = m_AcceptedMimeTypes.constBegin(); it != m_AcceptedMimeTypes.constEnd();
67 67 ++it) {
68 68
69 69 if (data->hasFormat(it.key())
70 70 && (it.value() == VisualizationDragDropContainer::DropBehavior::Merged
71 71 || it.value()
72 72 == VisualizationDragDropContainer::DropBehavior::InsertedAndMerged)) {
73 73 result = true;
74 74 }
75 75 else if (data->hasFormat(it.key())
76 76 && it.value() == VisualizationDragDropContainer::DropBehavior::Inserted) {
77 77 // Merge is forbidden if the mime data contain an acceptable type which cannot be
78 78 // merged
79 79 result = false;
80 80 break;
81 81 }
82 82 }
83 83
84 84 return result;
85 85 }
86 86
87 87 bool allowInsertForMimeData(const QMimeData *data) const
88 88 {
89 89 for (auto it = m_AcceptedMimeTypes.constBegin(); it != m_AcceptedMimeTypes.constEnd();
90 90 ++it) {
91 91 if (data->hasFormat(it.key())
92 92 && (it.value() == VisualizationDragDropContainer::DropBehavior::Inserted
93 93 || it.value()
94 94 == VisualizationDragDropContainer::DropBehavior::InsertedAndMerged)) {
95 95 return true;
96 96 }
97 97 }
98 98
99 99 return false;
100 100 }
101 101
102 102 bool hasPlaceHolder() const
103 103 {
104 104 return sqpApp->dragDropHelper().placeHolder().parentWidget() == m_Layout->parentWidget();
105 105 }
106 106
107 107 VisualizationDragWidget *getChildDragWidgetAt(const QWidget *parent, const QPoint &pos) const
108 108 {
109 109 VisualizationDragWidget *dragWidget = nullptr;
110 110
111 111 for (auto child : parent->children()) {
112 112 auto widget = qobject_cast<VisualizationDragWidget *>(child);
113 113 if (widget && widget->isVisible()) {
114 114 if (widget->frameGeometry().contains(pos)) {
115 115 dragWidget = widget;
116 116 break;
117 117 }
118 118 }
119 119 }
120 120
121 121 return dragWidget;
122 122 }
123 123
124 124 bool cursorIsInContainer(QWidget *container) const
125 125 {
126 return container->isAncestorOf(sqpApp->widgetAt(QCursor::pos()));
126 auto widgetUnderMouse = sqpApp->widgetAt(QCursor::pos());
127 return container->isAncestorOf(widgetUnderMouse) && widgetUnderMouse != container
128 && sqpApp->dragDropHelper().placeHolder().isAncestorOf(widgetUnderMouse);
127 129 }
128 130
129 131 int countDragWidget(const QWidget *parent, bool onlyVisible = false) const
130 132 {
131 133 auto nbGraph = 0;
132 134 for (auto child : parent->children()) {
133 135 if (qobject_cast<VisualizationDragWidget *>(child)) {
134 136 if (!onlyVisible || qobject_cast<VisualizationDragWidget *>(child)->isVisible()) {
135 137 nbGraph += 1;
136 138 }
137 139 }
138 140 }
139 141
140 142 return nbGraph;
141 143 }
142 144
143 145 void findPlaceHolderPosition(const QPoint &pos, bool canInsert, bool canMerge,
144 146 const VisualizationDragDropContainer *container);
145 147 };
146 148
147 149 VisualizationDragDropContainer::VisualizationDragDropContainer(QWidget *parent)
148 150 : QFrame{parent},
149 151 impl{spimpl::make_unique_impl<VisualizationDragDropContainerPrivate>(this)}
150 152 {
151 153 setAcceptDrops(true);
152 154 }
153 155
154 156 void VisualizationDragDropContainer::addDragWidget(VisualizationDragWidget *dragWidget)
155 157 {
156 158 impl->m_Layout->addWidget(dragWidget);
157 159 disconnect(dragWidget, &VisualizationDragWidget::dragDetected, nullptr, nullptr);
158 160 connect(dragWidget, &VisualizationDragWidget::dragDetected, this,
159 161 &VisualizationDragDropContainer::startDrag);
160 162 }
161 163
162 164 void VisualizationDragDropContainer::insertDragWidget(int index,
163 165 VisualizationDragWidget *dragWidget)
164 166 {
165 167 impl->m_Layout->insertWidget(index, dragWidget);
166 168 disconnect(dragWidget, &VisualizationDragWidget::dragDetected, nullptr, nullptr);
167 169 connect(dragWidget, &VisualizationDragWidget::dragDetected, this,
168 170 &VisualizationDragDropContainer::startDrag);
169 171 }
170 172
171 173 void VisualizationDragDropContainer::setMimeType(
172 174 const QString &mimeType, VisualizationDragDropContainer::DropBehavior behavior)
173 175 {
174 176 impl->m_AcceptedMimeTypes[mimeType] = behavior;
175 177 }
176 178
177 179 int VisualizationDragDropContainer::countDragWidget() const
178 180 {
179 181 return impl->countDragWidget(this);
180 182 }
181 183
182 184 void VisualizationDragDropContainer::setAcceptMimeDataFunction(
183 185 VisualizationDragDropContainer::AcceptMimeDataFunction fun)
184 186 {
185 187 impl->m_AcceptMimeDataFun = fun;
186 188 }
187 189
188 190 void VisualizationDragDropContainer::setPlaceHolderType(DragDropHelper::PlaceHolderType type,
189 191 const QString &placeHolderText)
190 192 {
191 193 impl->m_PlaceHolderType = type;
192 194 impl->m_PlaceHolderText = placeHolderText;
193 195 }
194 196
195 197 void VisualizationDragDropContainer::startDrag(VisualizationDragWidget *dragWidget,
196 198 const QPoint &dragPosition)
197 199 {
198 200 auto &helper = sqpApp->dragDropHelper();
199 201 helper.resetDragAndDrop();
200 202
201 203 // Note: The management of the drag object is done by Qt
202 204 auto drag = new QDrag{dragWidget};
203 205
204 206 auto mimeData = dragWidget->mimeData();
205 207 drag->setMimeData(mimeData);
206 208
207 209 auto pixmap = QPixmap(dragWidget->size());
208 210 dragWidget->render(&pixmap);
209 211 drag->setPixmap(pixmap.scaled(DRAGGED_MINIATURE_WIDTH, DRAGGED_MINIATURE_WIDTH,
210 212 Qt::KeepAspectRatio, Qt::SmoothTransformation));
211 213
212 214 auto image = pixmap.toImage();
213 215 mimeData->setImageData(image);
214 216 mimeData->setUrls({helper.imageTemporaryUrl(image)});
215 217
216 218 if (impl->m_Layout->indexOf(dragWidget) >= 0) {
217 219 helper.setCurrentDragWidget(dragWidget);
218 220
219 221 if (impl->cursorIsInContainer(this)) {
220 222 auto dragWidgetIndex = impl->m_Layout->indexOf(dragWidget);
221 223 helper.insertPlaceHolder(impl->m_Layout, dragWidgetIndex, impl->m_PlaceHolderType,
222 224 impl->m_PlaceHolderText);
223 225 dragWidget->setVisible(false);
224 226 }
225 227 else {
226 228 // The drag starts directly outside the drop zone
227 229 // do not add the placeHolder
228 230 }
229 231
230 232 drag->exec(Qt::MoveAction | Qt::CopyAction, Qt::MoveAction);
231 233
232 234 helper.doCloseWidgets();
233 235 }
234 236 else {
235 237 qCWarning(LOG_VisualizationDragDropContainer())
236 238 << tr("VisualizationDragDropContainer::startDrag, drag aborted, the specified "
237 239 "VisualizationDragWidget is not found in this container.");
238 240 }
239 241 }
240 242
241 243 void VisualizationDragDropContainer::dragEnterEvent(QDragEnterEvent *event)
242 244 {
243 245 if (impl->acceptMimeData(event->mimeData())) {
244 246 event->acceptProposedAction();
245 247
246 248 auto &helper = sqpApp->dragDropHelper();
247 249
248 250 if (!impl->hasPlaceHolder()) {
249 251 auto dragWidget = helper.getCurrentDragWidget();
250 252
251 253 if (dragWidget) {
252 254 // If the drag&drop is internal to the visualization, entering the container hide
253 255 // the dragWidget which was made visible by the dragLeaveEvent
254 256 auto parentWidget
255 257 = qobject_cast<VisualizationDragDropContainer *>(dragWidget->parentWidget());
256 258 if (parentWidget) {
257 259 dragWidget->setVisible(false);
258 260 }
259 261 }
260 262
261 263 auto canMerge = impl->allowMergeForMimeData(event->mimeData());
262 264 auto canInsert = impl->allowInsertForMimeData(event->mimeData());
263 265 impl->findPlaceHolderPosition(event->pos(), canInsert, canMerge, this);
264 266 }
265 267 else {
266 268 // do nothing
267 269 }
268 270 }
269 271 else {
270 272 event->ignore();
271 273 }
272 274
273 275 QWidget::dragEnterEvent(event);
274 276 }
275 277
276 278 void VisualizationDragDropContainer::dragLeaveEvent(QDragLeaveEvent *event)
277 279 {
278 280 Q_UNUSED(event);
279 281
280 282 auto &helper = sqpApp->dragDropHelper();
281 283
282 284 if (!impl->cursorIsInContainer(this)) {
283 285 helper.removePlaceHolder();
284 286 helper.setHightlightedDragWidget(nullptr);
285 287 impl->m_MinContainerHeight = 0;
286 288
287 289 auto dragWidget = helper.getCurrentDragWidget();
288 290 if (dragWidget) {
289 291 // dragWidget has a value only if the drag is started from the visualization
290 292 // In that case, shows the drag widget at its original place
291 293 // So the drag widget doesn't stay hidden if the drop occurs outside the visualization
292 294 // drop zone (It is not possible to catch a drop event outside of the application)
293 295
294 296 if (dragWidget) {
295 297 dragWidget->setVisible(true);
296 298 }
297 299 }
298 300 }
299 301 else {
300 302 // Leave event probably received for a child widget.
301 303 // Do nothing.
302 304 // Note: The DragLeave event, doesn't have any mean to determine who sent it.
303 305 }
304 306
305 307 QWidget::dragLeaveEvent(event);
306 308 }
307 309
308 310 void VisualizationDragDropContainer::dragMoveEvent(QDragMoveEvent *event)
309 311 {
310 312 if (impl->acceptMimeData(event->mimeData())) {
311 313 auto canMerge = impl->allowMergeForMimeData(event->mimeData());
312 314 auto canInsert = impl->allowInsertForMimeData(event->mimeData());
313 315 impl->findPlaceHolderPosition(event->pos(), canInsert, canMerge, this);
314 316 }
315 317 else {
316 318 event->ignore();
317 319 }
318 320
319 321 QWidget::dragMoveEvent(event);
320 322 }
321 323
322 324 void VisualizationDragDropContainer::dropEvent(QDropEvent *event)
323 325 {
324 326 auto &helper = sqpApp->dragDropHelper();
325 327
326 328 if (impl->acceptMimeData(event->mimeData())) {
327 329 auto dragWidget = helper.getCurrentDragWidget();
328 330 if (impl->hasPlaceHolder()) {
329 331 // drop where the placeHolder is located
330 332
331 333 auto canInsert = impl->allowInsertForMimeData(event->mimeData());
332 334 if (canInsert) {
333 335 auto droppedIndex = impl->m_Layout->indexOf(&helper.placeHolder());
334 336
335 337 if (dragWidget) {
336 338 auto dragWidgetIndex = impl->m_Layout->indexOf(dragWidget);
337 339 if (dragWidgetIndex >= 0 && dragWidgetIndex < droppedIndex) {
338 340 // Correction of the index if the drop occurs in the same container
339 341 // and if the drag is started from the visualization (in that case, the
340 342 // dragWidget is hidden)
341 343 droppedIndex -= 1;
342 344 }
343 345
344 346 dragWidget->setVisible(true);
345 347 }
346 348
347 349 event->acceptProposedAction();
348 350
349 351 helper.removePlaceHolder();
350 352
351 353 emit dropOccuredInContainer(droppedIndex, event->mimeData());
352 354 }
353 355 else {
354 356 qCWarning(LOG_VisualizationDragDropContainer()) << tr(
355 357 "VisualizationDragDropContainer::dropEvent, dropping on the placeHolder, but "
356 358 "the insertion is forbidden.");
357 359 Q_ASSERT(false);
358 360 }
359 361 }
360 362 else if (helper.getHightlightedDragWidget()) {
361 363 // drop on the highlighted widget
362 364
363 365 auto canMerge = impl->allowMergeForMimeData(event->mimeData());
364 366 if (canMerge) {
365 367 event->acceptProposedAction();
366 368 emit dropOccuredOnWidget(helper.getHightlightedDragWidget(), event->mimeData());
367 369 }
368 370 else {
369 371 qCWarning(LOG_VisualizationDragDropContainer())
370 372 << tr("VisualizationDragDropContainer::dropEvent, dropping on a widget, but "
371 373 "the merge is forbidden.");
372 374 Q_ASSERT(false);
373 375 }
374 376 }
375 377 }
376 378 else {
377 379 event->ignore();
378 380 }
379 381
380 382 sqpApp->dragDropHelper().setHightlightedDragWidget(nullptr);
381 383 impl->m_MinContainerHeight = 0;
382 384
383 385 QWidget::dropEvent(event);
384 386 }
385 387
386 388
387 389 void VisualizationDragDropContainer::VisualizationDragDropContainerPrivate::findPlaceHolderPosition(
388 390 const QPoint &pos, bool canInsert, bool canMerge,
389 391 const VisualizationDragDropContainer *container)
390 392 {
391 393 auto &helper = sqpApp->dragDropHelper();
392 394
393 395 auto absPos = container->mapToGlobal(pos);
394 auto isOnPlaceHolder = sqpApp->widgetAt(absPos) == &(helper.placeHolder());
396 auto isOnPlaceHolder = helper.placeHolder().isAncestorOf(sqpApp->widgetAt(absPos));
395 397
396 398 if (countDragWidget(container, true) == 0) {
397 399 // Drop on an empty container, just add the placeHolder at the top
398 400 helper.insertPlaceHolder(m_Layout, 0, m_PlaceHolderType, m_PlaceHolderText);
399 401 }
400 402 else if (!isOnPlaceHolder) {
401 403 auto nbDragWidget = countDragWidget(container);
402 404 if (nbDragWidget > 0) {
403 405
404 406 if (m_MinContainerHeight == 0) {
405 407 m_MinContainerHeight = container->size().height();
406 408 }
407 409
408 410 m_MinContainerHeight = qMin(m_MinContainerHeight, container->size().height());
409 411 auto graphHeight = qMax(m_MinContainerHeight / nbDragWidget, GRAPH_MINIMUM_HEIGHT);
410 412
411 413 auto posY = pos.y();
412 414 auto dropIndex = floor(posY / graphHeight);
413 415 auto zoneSize = qMin(graphHeight / 4.0, 75.0);
414 416
415 417
416 418 auto isOnTop = posY < dropIndex * graphHeight + zoneSize;
417 419 auto isOnBottom = posY > (dropIndex + 1) * graphHeight - zoneSize;
418 420
419 421 auto placeHolderIndex = m_Layout->indexOf(&(helper.placeHolder()));
420 422
421 423 auto dragWidgetHovered = getChildDragWidgetAt(container, pos);
422 424
423 425 if (canInsert && (isOnTop || isOnBottom || !canMerge)) {
424 426 if (isOnBottom) {
425 427 dropIndex += 1;
426 428 }
427 429
428 430 if (helper.getCurrentDragWidget()) {
429 431 auto dragWidgetIndex = m_Layout->indexOf(helper.getCurrentDragWidget());
430 432 if (dragWidgetIndex >= 0 && dragWidgetIndex <= dropIndex) {
431 433 // Correction of the index if the drop occurs in the same container
432 434 // and if the drag is started from the visualization (in that case, the
433 435 // dragWidget is hidden)
434 436 dropIndex += 1;
435 437 }
436 438 }
437 439
438 440 if (dropIndex != placeHolderIndex) {
439 441 helper.insertPlaceHolder(m_Layout, dropIndex, m_PlaceHolderType,
440 442 m_PlaceHolderText);
441 443 }
442 444
443 445 helper.setHightlightedDragWidget(nullptr);
444 446 }
445 447 else if (canMerge && dragWidgetHovered) {
446 448 // drop on the middle -> merge
447 449 if (hasPlaceHolder()) {
448 450 helper.removePlaceHolder();
449 451 }
450 452
451 453 helper.setHightlightedDragWidget(dragWidgetHovered);
452 454 }
453 455 else {
454 456 qCWarning(LOG_VisualizationDragDropContainer())
455 457 << tr("VisualizationDragDropContainer::findPlaceHolderPosition, no valid drop "
456 458 "action.");
457 459 }
458 460 }
459 461 else {
460 462 qCWarning(LOG_VisualizationDragDropContainer())
461 463 << tr("VisualizationDragDropContainer::findPlaceHolderPosition, no widget "
462 464 "found in the "
463 465 "container");
464 466 }
465 467 }
466 468 else {
467 469 // the mouse is hover the placeHolder
468 470 // Do nothing
469 471 }
470 472 }
General Comments 1
Under Review
author

Auto status change to "Under Review"

You need to be logged in to leave comments. Login now