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

Auto status change to "Under Review"

Approved

Status change > Approved

Approved

Status change > Approved

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