KDDockWidgets API Documentation  1.4
DragController.cpp
Go to the documentation of this file.
1 /*
2  This file is part of KDDockWidgets.
3 
4  SPDX-FileCopyrightText: 2019-2021 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
5  Author: SĂ©rgio Martins <sergio.martins@kdab.com>
6 
7  SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
8 
9  Contact KDAB at <info@kdab.com> for commercial licensing options.
10 */
11 
12 #include "DragController_p.h"
13 #include "DockRegistry_p.h"
14 #include "DockWidgetBase_p.h"
15 #include "DropArea_p.h"
16 #include "FloatingWindow_p.h"
17 #include "Frame_p.h"
18 #include "Logging_p.h"
19 #include "Qt5Qt6Compat_p.h"
20 #include "Utils_p.h"
21 #include "WidgetResizeHandler_p.h"
22 #include "Config.h"
23 #include "MDILayoutWidget_p.h"
24 
25 #include <QMouseEvent>
26 #include <QGuiApplication>
27 #include <QCursor>
28 #include <QWindow>
29 #include <QDrag>
30 #include <QScopedValueRollback>
31 
32 #if defined(Q_OS_WIN)
33 #include <windows.h>
34 #endif
35 
36 using namespace KDDockWidgets;
37 
38 namespace KDDockWidgets {
40 class FallbackMouseGrabber : public QObject
41 {
42 public:
43  FallbackMouseGrabber(QObject *parent)
44  : QObject(parent)
45  {
46  }
47 
48  ~FallbackMouseGrabber() override;
49 
50  void grabMouse(QWidgetOrQuick *target)
51  {
52  m_target = target;
53  qApp->installEventFilter(this);
54  }
55 
56  void releaseMouse()
57  {
58 #ifdef KDDOCKWIDGETS_QTQUICK
59  // Ungrab harder if QtQuick.
60  // QtQuick has the habit og grabbing the MouseArea internally, then doesn't ungrab it since
61  // we're consuming the events. So explicitly ungrab if any QQuickWindow::mouseGrabberItem()
62  // is still set.
63 
64  QQuickView *view = m_target ? m_target->quickView()
65  : nullptr;
66  QQuickItem *grabber = view ? view->mouseGrabberItem()
67  : nullptr;
68  if (grabber)
69  grabber->ungrabMouse();
70 #endif
71 
72  m_target = nullptr;
73  qApp->removeEventFilter(this);
74  }
75 
76  bool eventFilter(QObject *, QEvent *ev) override
77  {
78  if (m_reentrancyGuard || !m_target)
79  return false;
80 
81  if (QMouseEvent *me = mouseEvent(ev)) {
82  m_reentrancyGuard = true;
83  qApp->sendEvent(m_target, me);
84  m_reentrancyGuard = false;
85  return true;
86  }
87 
88  return false;
89  }
90 
91  bool m_reentrancyGuard = false;
92  QPointer<QWidgetOrQuick> m_target;
93 };
94 
95 FallbackMouseGrabber::~FallbackMouseGrabber()
96 {
97 }
98 
99 }
100 
101 State::State(MinimalStateMachine *parent)
102  : QObject(parent)
103  , m_machine(parent)
104 {
105 }
106 
107 State::~State() = default;
108 
109 bool State::isCurrentState() const
110 {
111  return m_machine->currentState() == this;
112 }
113 
114 MinimalStateMachine::MinimalStateMachine(QObject *parent)
115  : QObject(parent)
116 {
117 }
118 
119 template<typename Obj, typename Signal>
120 void State::addTransition(Obj *obj, Signal signal, State *dest)
121 {
122  connect(obj, signal, this, [this, dest] {
123  if (isCurrentState()) {
124  m_machine->setCurrentState(dest);
125  }
126  });
127 }
128 
129 
130 State *MinimalStateMachine::currentState() const
131 {
132  return m_currentState;
133 }
134 
135 void MinimalStateMachine::setCurrentState(State *state)
136 {
137  if (state != m_currentState) {
138  if (m_currentState)
139  m_currentState->onExit();
140 
141  m_currentState = state;
142 
143  if (state)
144  state->onEntry();
145  }
146 }
147 
148 StateBase::StateBase(DragController *parent)
149  : State(parent)
150  , q(parent)
151 {
152 }
153 
154 bool StateBase::isActiveState() const
155 {
156  return q->activeState() == this;
157 }
158 
159 StateBase::~StateBase() = default;
160 
161 StateNone::StateNone(DragController *parent)
162  : StateBase(parent)
163 {
164 }
165 
166 void StateNone::onEntry()
167 {
168  qCDebug(state) << "StateNone entered";
169  q->m_pressPos = QPoint();
170  q->m_offset = QPoint();
171  q->m_draggable = nullptr;
172  q->m_draggableGuard.clear();
173  q->m_windowBeingDragged.reset();
174  WidgetResizeHandler::s_disableAllHandlers = false; // Re-enable resize handlers
175 
176  q->m_nonClientDrag = false;
177  if (q->m_currentDropArea) {
178  q->m_currentDropArea->removeHover();
179  q->m_currentDropArea = nullptr;
180  }
181 
182  Q_EMIT q->isDraggingChanged();
183 }
184 
185 bool StateNone::handleMouseButtonPress(Draggable *draggable, QPoint globalPos, QPoint pos)
186 {
187  qCDebug(state) << "StateNone::handleMouseButtonPress: draggable"
188  << draggable->asWidget() << "; globalPos" << globalPos;
189 
190  if (!draggable->isPositionDraggable(pos))
191  return false;
192 
193  q->m_draggable = draggable;
194  q->m_draggableGuard = draggable->asWidget();
195  q->m_pressPos = globalPos;
196  q->m_offset = draggable->mapToWindow(pos);
197  Q_EMIT q->mousePressed();
198  return false;
199 }
200 
201 StateNone::~StateNone() = default;
202 
203 
204 StatePreDrag::StatePreDrag(DragController *parent)
205  : StateBase(parent)
206 {
207 }
208 
209 StatePreDrag::~StatePreDrag() = default;
210 
211 void StatePreDrag::onEntry()
212 {
213  qCDebug(state) << "StatePreDrag entered" << q->m_draggableGuard.data();
214  WidgetResizeHandler::s_disableAllHandlers = true; // Disable the resize handler during dragging
215 }
216 
217 bool StatePreDrag::handleMouseMove(QPoint globalPos)
218 {
219  if (!q->m_draggableGuard) {
220  qWarning() << Q_FUNC_INFO << "Draggable was destroyed, canceling the drag";
221  Q_EMIT q->dragCanceled();
222  return false;
223  }
224 
225  if (q->m_draggable->dragCanStart(q->m_pressPos, globalPos)) {
226  if (q->m_draggable->isMDI())
227  Q_EMIT q->manhattanLengthMoveMDI();
228  else
229  Q_EMIT q->manhattanLengthMove();
230  return true;
231  }
232  return false;
233 }
234 
235 bool StatePreDrag::handleMouseButtonRelease(QPoint)
236 {
237  Q_EMIT q->dragCanceled();
238  return false;
239 }
240 
241 bool StatePreDrag::handleMouseDoubleClick()
242 {
243  // This is only needed for QtQuick.
244  // With QtQuick, when double clicking, we get: Press, Release, Press, Double-click. and never
245  // receive the last Release event.
246  Q_EMIT q->dragCanceled();
247  return false;
248 }
249 
250 StateDragging::StateDragging(DragController *parent)
251  : StateBase(parent)
252 {
253 #if defined(Q_OS_WIN) && !defined(DOCKS_DEVELOPER_MODE)
254  m_maybeCancelDrag.setInterval(100);
255  QObject::connect(&m_maybeCancelDrag, &QTimer::timeout, this, [this] {
256  // Workaround bug #166 , where Qt doesn't agree with Window's mouse button state.
257  // Looking in the Qt bug tracker there's many hits, so do a quick workaround here:
258 
259  const bool mouseButtonIsReallyDown = (GetKeyState(VK_LBUTTON) & 0x8000);
260  if (!mouseButtonIsReallyDown && isLeftButtonPressed()) {
261  qCDebug(state) << "Canceling drag, Qt thinks mouse button is pressed"
262  << "but Windows knows it's not";
263  Q_EMIT q->dragCanceled();
264  }
265  });
266 #endif
267 }
268 
269 StateDragging::~StateDragging() = default;
270 
271 void StateDragging::onEntry()
272 {
273  m_maybeCancelDrag.start();
274 
275  if (DockWidgetBase *dw = q->m_draggable->singleDockWidget()) {
276  // When we start to drag a floating window which has a single dock widget, we save the position
277  if (dw->isFloating())
278  dw->d->saveLastFloatingGeometry();
279  }
280 
281  const bool needsUndocking = !q->m_draggable->isWindow();
282  q->m_windowBeingDragged = q->m_draggable->makeWindow();
283  if (q->m_windowBeingDragged) {
284 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) && defined(Q_OS_WIN)
285  if (!q->m_nonClientDrag && KDDockWidgets::usesNativeDraggingAndResizing()) {
286  // Started as a client move, as the dock widget was docked,
287  // but now that we're dragging it as a floating window, switch to native drag, so we can still get aero-snap
288  FloatingWindow *fw = q->m_windowBeingDragged->floatingWindow();
289  q->m_nonClientDrag = true;
290  q->m_windowBeingDragged.reset();
291  q->m_windowBeingDragged = fw->makeWindow();
292 
293  QWindow *window = fw->windowHandle();
294 
295  if (needsUndocking) {
296  // Position the window before the drag start, otherwise if you move mouse too fast there will be an offset
297  // Only required when we've undocked/detached a window.
298  window->setPosition(QCursor::pos() - q->m_offset);
299  }
300 
301  // Start the native move
302  window->startSystemMove();
303  }
304 #else
305  Q_UNUSED(needsUndocking);
306 #endif
307 
308  qCDebug(state) << "StateDragging entered. m_draggable=" << q->m_draggable->asWidget()
309  << "; m_windowBeingDragged=" << q->m_windowBeingDragged->floatingWindow();
310 
311  auto fw = q->m_windowBeingDragged->floatingWindow();
312  if (!fw->geometry().contains(q->m_pressPos)) {
313  // The window shrunk when the drag started, this can happen if it has max-size constraints
314  // we make the floating window smaller. Has the downside that it might not be under the mouse
315  // cursor anymore, so make the change
316  if (fw->width() < q->m_offset.x()) { // make sure it shrunk
317  q->m_offset.setX(fw->width() / 2);
318  }
319  }
320  } else {
321  // Shouldn't happen
322  qWarning() << Q_FUNC_INFO << "No window being dragged for " << q->m_draggable->asWidget();
323  Q_EMIT q->dragCanceled();
324  }
325 
326  Q_EMIT q->isDraggingChanged();
327 }
328 
329 void StateDragging::onExit()
330 {
331  m_maybeCancelDrag.stop();
332 }
333 
334 bool StateDragging::handleMouseButtonRelease(QPoint globalPos)
335 {
336  qCDebug(state) << "StateDragging: handleMouseButtonRelease";
337 
338  FloatingWindow *floatingWindow = q->m_windowBeingDragged->floatingWindow();
339  if (!floatingWindow) {
340  // It was deleted externally
341  qCDebug(state) << "StateDragging: Bailling out, deleted externally";
342  Q_EMIT q->dragCanceled();
343  return true;
344  }
345 
346  if (floatingWindow->anyNonDockable()) {
347  qCDebug(state) << "StateDragging: Ignoring floating window with non dockable widgets";
348  Q_EMIT q->dragCanceled();
349  return true;
350  }
351 
352  if (q->m_currentDropArea) {
353  if (q->m_currentDropArea->drop(q->m_windowBeingDragged.get(), globalPos)) {
354  Q_EMIT q->dropped();
355  } else {
356  qCDebug(state) << "StateDragging: Bailling out, drop not accepted";
357  Q_EMIT q->dragCanceled();
358  }
359  } else {
360  qCDebug(state) << "StateDragging: Bailling out, not over a drop area";
361  Q_EMIT q->dragCanceled();
362  }
363  return true;
364 }
365 
366 bool StateDragging::handleMouseMove(QPoint globalPos)
367 {
368  FloatingWindow *fw = q->m_windowBeingDragged->floatingWindow();
369  if (!fw) {
370  qCDebug(state) << "Canceling drag, window was deleted";
371  Q_EMIT q->dragCanceled();
372  return true;
373  }
374 
375  if (fw->beingDeleted()) {
376  // Ignore, we're in the middle of recurrency. We're inside StateDragging::handleMouseButtonRelease too
377  return true;
378  }
379 
380  if (!q->m_nonClientDrag)
381  fw->windowHandle()->setPosition(globalPos - q->m_offset);
382 
383  if (fw->anyNonDockable()) {
384  qCDebug(state) << "StateDragging: Ignoring non dockable floating window";
385  return true;
386  }
387 
388  DropArea *dropArea = q->dropAreaUnderCursor();
389  if (q->m_currentDropArea && dropArea != q->m_currentDropArea)
390  q->m_currentDropArea->removeHover();
391 
392  if (dropArea) {
393  if (FloatingWindow *targetFw = dropArea->floatingWindow()) {
394  if (targetFw->anyNonDockable()) {
395  qCDebug(state) << "StateDragging: Ignoring non dockable target floating window";
396  return false;
397  }
398  }
399 
400  dropArea->hover(q->m_windowBeingDragged.get(), globalPos);
401  }
402 
403  q->m_currentDropArea = dropArea;
404 
405  return true;
406 }
407 
408 bool StateDragging::handleMouseDoubleClick()
409 {
410  // See comment in StatePreDrag::handleMouseDoubleClick().
411  // Very unlikely that we're in this state though, due to manhattan length
412  Q_EMIT q->dragCanceled();
413  return false;
414 }
415 
416 StateInternalMDIDragging::StateInternalMDIDragging(DragController *parent)
417  : StateBase(parent)
418 {
419 }
420 
421 StateInternalMDIDragging::~StateInternalMDIDragging()
422 {
423 }
424 
425 void StateInternalMDIDragging::onEntry()
426 {
427  qCDebug(state) << "StateInternalMDIDragging entered. draggable="
428  << q->m_draggable->asWidget();
429 
430  // Raise the dock widget being dragged
431  if (auto tb = qobject_cast<TitleBar *>(q->m_draggable->asWidget())) {
432  if (Frame *f = tb->frame())
433  f->raise();
434  }
435 
436  Q_EMIT q->isDraggingChanged();
437 }
438 
439 bool StateInternalMDIDragging::handleMouseButtonRelease(QPoint)
440 {
441  Q_EMIT q->dragCanceled();
442  return false;
443 }
444 
445 bool StateInternalMDIDragging::handleMouseMove(QPoint globalPos)
446 {
447  // for MDI we only support dragging via the title bar, other cases don't make sense conceptually
448  auto tb = qobject_cast<TitleBar *>(q->m_draggable->asWidget());
449  if (!tb) {
450  qWarning() << Q_FUNC_INFO << "expected a title bar, not" << q->m_draggable->asWidget();
451  Q_EMIT q->dragCanceled();
452  return false;
453  }
454 
455  Frame *frame = tb->frame();
456  if (!frame) {
457  // Doesn't happen.
458  qWarning() << Q_FUNC_INFO << "null frame.";
459  Q_EMIT q->dragCanceled();
460  return false;
461  }
462 
463  const QSize parentSize = frame->QWidgetAdapter::parentWidget()->size();
464  const QPoint oldPos = frame->mapToGlobal(QPoint(0, 0));
465  const QPoint delta = globalPos - oldPos;
466  const QPoint newLocalPos = frame->pos() + delta - q->m_offset;
467 
468  // Let's not allow the MDI window to go outside of its parent
469 
470  QPoint newLocalPosBounded = { qMax(0, newLocalPos.x()), qMax(0, newLocalPos.y()) };
471  newLocalPosBounded.setX(qMin(newLocalPosBounded.x(), parentSize.width() - frame->width()));
472  newLocalPosBounded.setY(qMin(newLocalPosBounded.y(), parentSize.height() - frame->height()));
473 
474  auto layout = frame->mdiLayoutWidget();
475  Q_ASSERT(layout);
476  layout->moveDockWidget(frame, newLocalPosBounded);
477 
478  // Check if we need to pop out the MDI window (make it float)
479  // If we drag the window against an edge, and move behind the edge some threshold, we float it
480  const int threshold = Config::self().mdiPopupThreshold();
481  if (threshold != -1) {
482  const QPoint overflow = newLocalPosBounded - newLocalPos;
483  if (qAbs(overflow.x()) > threshold || qAbs(overflow.y()) > threshold)
484  Q_EMIT q->mdiPopOut();
485  }
486 
487  return false;
488 }
489 
490 bool StateInternalMDIDragging::handleMouseDoubleClick()
491 {
492  Q_EMIT q->dragCanceled();
493  return false;
494 }
495 
496 StateDraggingWayland::StateDraggingWayland(DragController *parent)
497  : StateDragging(parent)
498 {
499 }
500 
501 StateDraggingWayland::~StateDraggingWayland()
502 {
503 }
504 
505 void StateDraggingWayland::onEntry()
506 {
507  qCDebug(state) << "StateDragging entered";
508 
509  if (m_inQDrag) {
510  // Maybe we can exit the state due to the nested event loop of QDrag::Exec();
511  qWarning() << Q_FUNC_INFO << "Impossible!";
512  return;
513  }
514 
515  QScopedValueRollback<bool> guard(m_inQDrag, true);
516  q->m_windowBeingDragged = std::unique_ptr<WindowBeingDragged>(new WindowBeingDraggedWayland(q->m_draggable));
517 
518  auto mimeData = new WaylandMimeData();
519  QDrag drag(this);
520  drag.setMimeData(mimeData);
521  drag.setPixmap(q->m_windowBeingDragged->pixmap());
522 
523  qApp->installEventFilter(q);
524  const Qt::DropAction result = drag.exec();
525  qApp->removeEventFilter(q);
526  if (result == Qt::IgnoreAction)
527  Q_EMIT q->dragCanceled();
528 }
529 
530 bool StateDraggingWayland::handleMouseButtonRelease(QPoint /*globalPos*/)
531 {
532  qCDebug(state) << Q_FUNC_INFO;
533  Q_EMIT q->dragCanceled();
534  return true;
535 }
536 
537 bool StateDraggingWayland::handleDragEnter(QDragEnterEvent *ev, DropArea *dropArea)
538 {
539  auto mimeData = qobject_cast<const WaylandMimeData *>(ev->mimeData());
540  if (!mimeData || !q->m_windowBeingDragged)
541  return false; // Not for us, some other user drag.
542 
543  if (q->m_windowBeingDragged->contains(dropArea)) {
544  ev->ignore();
545  return true;
546  }
547 
548  dropArea->hover(q->m_windowBeingDragged.get(), dropArea->mapToGlobal(Qt5Qt6Compat::eventPos(ev)));
549 
550  ev->accept();
551  return true;
552 }
553 
554 bool StateDraggingWayland::handleDragLeave(DropArea *dropArea)
555 {
556  dropArea->removeHover();
557  return true;
558 }
559 
560 bool StateDraggingWayland::handleDrop(QDropEvent *ev, DropArea *dropArea)
561 {
562  auto mimeData = qobject_cast<const WaylandMimeData *>(ev->mimeData());
563  if (!mimeData || !q->m_windowBeingDragged)
564  return false; // Not for us, some other user drag.
565 
566  if (dropArea->drop(q->m_windowBeingDragged.get(), dropArea->mapToGlobal(Qt5Qt6Compat::eventPos(ev)))) {
568  ev->accept();
569  Q_EMIT q->dropped();
570  } else {
571  Q_EMIT q->dragCanceled();
572  }
573 
574  dropArea->removeHover();
575  return true;
576 }
577 
578 bool StateDraggingWayland::handleDragMove(QDragMoveEvent *ev, DropArea *dropArea)
579 {
580  auto mimeData = qobject_cast<const WaylandMimeData *>(ev->mimeData());
581  if (!mimeData || !q->m_windowBeingDragged)
582  return false; // Not for us, some other user drag.
583 
584  dropArea->hover(q->m_windowBeingDragged.get(), dropArea->mapToGlobal(Qt5Qt6Compat::eventPos(ev)));
585 
586  return true;
587 }
588 
589 DragController::DragController(QObject *parent)
590  : MinimalStateMachine(parent)
591 {
592  qCDebug(creation) << "DragController()";
593 
594  auto stateNone = new StateNone(this);
595  auto statepreDrag = new StatePreDrag(this);
596  auto stateDragging = isWayland() ? new StateDraggingWayland(this)
597  : new StateDragging(this);
598  m_stateDraggingMDI = new StateInternalMDIDragging(this);
599 
600  stateNone->addTransition(this, &DragController::mousePressed, statepreDrag);
601  statepreDrag->addTransition(this, &DragController::dragCanceled, stateNone);
602  statepreDrag->addTransition(this, &DragController::manhattanLengthMove, stateDragging);
603  statepreDrag->addTransition(this, &DragController::manhattanLengthMoveMDI, m_stateDraggingMDI);
604  stateDragging->addTransition(this, &DragController::dragCanceled, stateNone);
605  stateDragging->addTransition(this, &DragController::dropped, stateNone);
606 
607  m_stateDraggingMDI->addTransition(this, &DragController::dragCanceled, stateNone);
608  m_stateDraggingMDI->addTransition(this, &DragController::mdiPopOut, stateDragging);
609 
610  if (usesFallbackMouseGrabber())
611  enableFallbackMouseGrabber();
612 
613  setCurrentState(stateNone);
614 }
615 
616 DragController *DragController::instance()
617 {
618  static DragController dragController;
619  return &dragController;
620 }
621 
622 void DragController::registerDraggable(Draggable *drg)
623 {
624  m_draggables << drg;
625  drg->asWidget()->installEventFilter(this);
626 }
627 
628 void DragController::unregisterDraggable(Draggable *drg)
629 {
630  m_draggables.removeOne(drg);
631  drg->asWidget()->removeEventFilter(this);
632 }
633 
634 bool DragController::isDragging() const
635 {
636  return m_windowBeingDragged != nullptr || activeState() == m_stateDraggingMDI;
637 }
638 
639 bool DragController::isInNonClientDrag() const
640 {
641  return isDragging() && m_nonClientDrag;
642 }
643 
644 bool DragController::isInClientDrag() const
645 {
646  return isDragging() && !m_nonClientDrag;
647 }
648 
649 void DragController::grabMouseFor(QWidgetOrQuick *target)
650 {
651  if (isWayland())
652  return; // No grabbing supported on wayland
653 
654  if (m_fallbackMouseGrabber) {
655  m_fallbackMouseGrabber->grabMouse(target);
656  } else {
657  target->grabMouse();
658  }
659 }
660 
661 void DragController::releaseMouse(QWidgetOrQuick *target)
662 {
663  if (isWayland())
664  return; // No grabbing supported on wayland
665 
666  if (m_fallbackMouseGrabber) {
667  m_fallbackMouseGrabber->releaseMouse();
668  } else {
669  target->releaseMouse();
670  }
671 }
672 
673 FloatingWindow *DragController::floatingWindowBeingDragged() const
674 {
675  return m_windowBeingDragged ? m_windowBeingDragged->floatingWindow()
676  : nullptr;
677 }
678 
679 void DragController::enableFallbackMouseGrabber()
680 {
681  if (!m_fallbackMouseGrabber)
682  m_fallbackMouseGrabber = new FallbackMouseGrabber(this);
683 }
684 
685 WindowBeingDragged *DragController::windowBeingDragged() const
686 {
687  return m_windowBeingDragged.get();
688 }
689 
690 bool DragController::eventFilter(QObject *o, QEvent *e)
691 {
692  if (m_nonClientDrag && e->type() == QEvent::Move) {
693  // On Windows, non-client mouse moves are only sent at the end, so we must fake it:
694  qCDebug(mouseevents) << "DragController::eventFilter e=" << e->type() << "; o=" << o;
695  activeState()->handleMouseMove(QCursor::pos());
696  return MinimalStateMachine::eventFilter(o, e);
697  }
698 
699  if (isWayland()) {
700  // Wayland is very different. It uses QDrag for the dragging of a window.
701  if (auto dropArea = qobject_cast<DropArea *>(o)) {
702  switch (int(e->type())) {
703  case QEvent::DragEnter:
704  if (activeState()->handleDragEnter(static_cast<QDragEnterEvent *>(e), dropArea))
705  return true;
706  break;
707  case QEvent::DragLeave:
708  if (activeState()->handleDragLeave(dropArea))
709  return true;
710  break;
711  case QEvent::DragMove:
712  if (activeState()->handleDragMove(static_cast<QDragMoveEvent *>(e), dropArea))
713  return true;
714  break;
715  case QEvent::Drop:
716  if (activeState()->handleDrop(static_cast<QDropEvent *>(e), dropArea))
717  return true;
718  break;
719  }
720  }
721  }
722 
723  QMouseEvent *me = mouseEvent(e);
724  if (!me)
725  return MinimalStateMachine::eventFilter(o, e);
726 
727  auto w = qobject_cast<QWidgetOrQuick *>(o);
728  if (!w)
729  return MinimalStateMachine::eventFilter(o, e);
730 
731  qCDebug(mouseevents) << "DragController::eventFilter e=" << e->type() << "; o=" << o
732  << "; m_nonClientDrag=" << m_nonClientDrag;
733 
734  switch (e->type()) {
736  if (auto fw = qobject_cast<FloatingWindow *>(o)) {
737  if (KDDockWidgets::usesNativeTitleBar() || fw->isInDragArea(Qt5Qt6Compat::eventGlobalPos(me))) {
738  m_nonClientDrag = true;
739  return activeState()->handleMouseButtonPress(draggableForQObject(o), Qt5Qt6Compat::eventGlobalPos(me), me->pos());
740  }
741  }
742  return MinimalStateMachine::eventFilter(o, e);
743  }
745  // For top-level windows that support native dragging all goes through the NonClient* events.
746  // This also forbids dragging a FloatingWindow simply by pressing outside of the title area, in the background
747  if (!KDDockWidgets::usesNativeDraggingAndResizing() || !w->isWindow()) {
748  Q_ASSERT(activeState());
749  return activeState()->handleMouseButtonPress(draggableForQObject(o), Qt5Qt6Compat::eventGlobalPos(me), me->pos());
750  } else
751  break;
754  return activeState()->handleMouseButtonRelease(Qt5Qt6Compat::eventGlobalPos(me));
756  case QEvent::MouseMove:
757  return activeState()->handleMouseMove(Qt5Qt6Compat::eventGlobalPos(me));
760  return activeState()->handleMouseDoubleClick();
761  default:
762  break;
763  }
764 
765  return MinimalStateMachine::eventFilter(o, e);
766 }
767 
768 StateBase *DragController::activeState() const
769 {
770  return static_cast<StateBase *>(currentState());
771 }
772 
773 #if defined(Q_OS_WIN)
774 static QWidgetOrQuick *qtTopLevelForHWND(HWND hwnd)
775 {
776  const QList<QWindow *> windows = qApp->topLevelWindows();
777  for (QWindow *window : windows) {
778  if (!window->isVisible())
779  continue;
780 
781  if (hwnd == ( HWND )window->winId()) {
782  if (auto result = DockRegistry::self()->topLevelForHandle(window))
783  return result;
784 #ifdef KDDOCKWIDGETS_QTWIDGETS
785  // It's not a KDDW window, but we still return something, as the KDDW main window
786  // might be embedded into another non-kddw QMainWindow
787  // Case not supported for QtQuick.
788  const QWidgetList widgets = qApp->topLevelWidgets();
789  for (QWidget *widget : widgets) {
790  if (hwnd == ( HWND )widget->winId()) {
791  return widget;
792  }
793  }
794 #endif
795  }
796  }
797 
798  qCDebug(toplevels) << Q_FUNC_INFO << "Couldn't find hwnd for top-level" << hwnd;
799  return nullptr;
800 }
801 
802 static QRect topLevelGeometry(const QWidgetOrQuick *topLevel)
803 {
804  if (auto mainWindow = qobject_cast<const MainWindowBase *>(topLevel))
805  return mainWindow->windowGeometry();
806 
807  return topLevel->geometry();
808 }
809 
810 #endif
811 
812 template<typename T>
813 static WidgetType *qtTopLevelUnderCursor_impl(QPoint globalPos, const QVector<QWindow *> &windows, T windowBeingDragged)
814 {
815  for (auto i = windows.size() - 1; i >= 0; --i) {
816  QWindow *window = windows.at(i);
817  auto tl = KDDockWidgets::Private::widgetForWindow(window);
818 
819  if (!tl->isVisible() || tl == windowBeingDragged || KDDockWidgets::Private::isMinimized(tl))
820  continue;
821 
822  if (windowBeingDragged && KDDockWidgets::Private::windowForWidget(windowBeingDragged) == KDDockWidgets::Private::windowForWidget(tl))
823  continue;
824 
825  if (window->geometry().contains(globalPos)) {
826  qCDebug(toplevels) << Q_FUNC_INFO << "Found top-level" << tl;
827  return tl;
828  }
829  }
830 
831  return nullptr;
832 }
833 
834 WidgetType *DragController::qtTopLevelUnderCursor() const
835 {
836  QPoint globalPos = QCursor::pos();
837 
838  if (qApp->platformName() == QLatin1String("windows")) { // So -platform offscreen on Windows doesn't use this
839 #if defined(Q_OS_WIN)
840  POINT globalNativePos;
841  if (!GetCursorPos(&globalNativePos))
842  return nullptr;
843 
844  // There might be windows that don't belong to our app in between, so use win32 to travel by z-order.
845  // Another solution is to set a parent on all top-levels. But this code is orthogonal.
846  HWND hwnd = HWND(m_windowBeingDragged->floatingWindow()->winId());
847  while (hwnd) {
848  hwnd = GetWindow(hwnd, GW_HWNDNEXT);
849  RECT r;
850  if (!GetWindowRect(hwnd, &r) || !IsWindowVisible(hwnd))
851  continue;
852 
853  if (!PtInRect(&r, globalNativePos)) // Check if window is under cursor
854  continue;
855 
856  if (auto tl = qtTopLevelForHWND(hwnd)) {
857  const QRect windowGeometry = topLevelGeometry(tl);
858 
859  if (windowGeometry.contains(globalPos) && tl->objectName() != QStringLiteral("_docks_IndicatorWindow_Overlay")) {
860  qCDebug(toplevels) << Q_FUNC_INFO << "Found top-level" << tl;
861  return tl;
862  }
863  } else {
864 #ifdef KDDOCKWIDGETS_QTWIDGETS // Maybe it's embedded in a QWinWidget:
865  auto topLevels = qApp->topLevelWidgets();
866  for (auto topLevel : topLevels) {
867  if (QLatin1String(topLevel->metaObject()->className()) == QLatin1String("QWinWidget")) {
868  if (hwnd == GetParent(HWND(topLevel->windowHandle()->winId()))) {
869  if (topLevel->rect().contains(topLevel->mapFromGlobal(globalPos)) && topLevel->objectName() != QStringLiteral("_docks_IndicatorWindow_Overlay")) {
870  qCDebug(toplevels) << Q_FUNC_INFO << "Found top-level" << topLevel;
871  return topLevel;
872  }
873  }
874  }
875  }
876 #endif // QtWidgets A window belonging to another app is below the cursor
877  qCDebug(toplevels) << Q_FUNC_INFO << "Window from another app is under cursor" << hwnd;
878  return nullptr;
879  }
880  }
881 #endif // Q_OS_WIN
882  } else {
883  // !Windows: Linux, macOS, offscreen (offscreen on Windows too), etc.
884 
885  // On Linux we don't have API to check the z-order of top-levels. So first check the floating windows
886  // and check the MainWindow last, as the MainWindow will have lower z-order as it's a parent (TODO: How will it work with multiple MainWindows ?)
887  // The floating window list is sorted by z-order, as we catch QEvent::Expose and move it to last of the list
888 
889  FloatingWindow *tlwBeingDragged = m_windowBeingDragged->floatingWindow();
890  if (auto tl = qtTopLevelUnderCursor_impl(globalPos, DockRegistry::self()->floatingQWindows(), tlwBeingDragged))
891  return tl;
892 
893  return qtTopLevelUnderCursor_impl<WidgetType *>(globalPos,
894  DockRegistry::self()->topLevels(/*excludeFloating=*/true),
895  tlwBeingDragged);
896  }
897 
898  qCDebug(toplevels) << Q_FUNC_INFO << "No top-level found";
899  return nullptr;
900 }
901 
902 static DropArea *deepestDropAreaInTopLevel(WidgetType *topLevel, QPoint globalPos,
903  const QStringList &affinities)
904 {
905  const auto localPos = topLevel->mapFromGlobal(globalPos);
906  auto w = topLevel->childAt(localPos.x(), localPos.y());
907  while (w) {
908  if (auto dt = qobject_cast<DropArea *>(w)) {
909  if (DockRegistry::self()->affinitiesMatch(dt->affinities(), affinities))
910  return dt;
911  }
912  w = KDDockWidgets::Private::parentWidget(w);
913  }
914 
915  return nullptr;
916 }
917 
918 DropArea *DragController::dropAreaUnderCursor() const
919 {
920  WidgetType *topLevel = qtTopLevelUnderCursor();
921  if (!topLevel)
922  return nullptr;
923 
924  const QStringList affinities = m_windowBeingDragged->floatingWindow()->affinities();
925 
926  if (auto fw = qobject_cast<FloatingWindow *>(topLevel)) {
927  if (DockRegistry::self()->affinitiesMatch(fw->affinities(), affinities))
928  return fw->dropArea();
929  }
930 
931  if (topLevel->objectName() == QStringLiteral("_docks_IndicatorWindow")) {
932  qWarning() << "Indicator window should be hidden " << topLevel << topLevel->isVisible();
933  Q_ASSERT(false);
934  }
935 
936  if (auto dt = deepestDropAreaInTopLevel(topLevel, QCursor::pos(), affinities)) {
937  return dt;
938  }
939 
940  qCDebug(state) << "DragController::dropAreaUnderCursor: null2";
941  return nullptr;
942 }
943 
944 Draggable *DragController::draggableForQObject(QObject *o) const
945 {
946  for (auto draggable : m_draggables)
947  if (draggable->asWidget() == o) {
948  return draggable;
949  }
950 
951  return nullptr;
952 }
QMouseEvent::pos
QPoint pos() const const
QPointer
QDragEnterEvent
QEvent::Move
Move
KDDockWidgets::Config::mdiPopupThreshold
int mdiPopupThreshold() const
Definition: Config.cpp:329
QRect
QWindow::geometry
QRect geometry() const const
QWidget::rect
rect
QWidget::releaseMouse
void releaseMouse()
QWindow
QWidget::mapFromGlobal
QPoint mapFromGlobal(const QPoint &pos) const const
QDragMoveEvent
QWidget
QWindow::isVisible
bool isVisible() const const
QSize
qtTopLevelUnderCursor_impl
static WidgetType * qtTopLevelUnderCursor_impl(QPoint globalPos, const QVector< QWindow * > &windows, T windowBeingDragged)
Definition: DragController.cpp:813
QPoint::x
int x() const const
QPoint::y
int y() const const
QDropEvent::mimeData
const QMimeData * mimeData() const const
deepestDropAreaInTopLevel
static DropArea * deepestDropAreaInTopLevel(WidgetType *topLevel, QPoint globalPos, const QStringList &affinities)
Definition: DragController.cpp:902
QSize::width
int width() const const
QRect::contains
bool contains(const QRect &rectangle, bool proper) const const
QList
QWindow::setPosition
void setPosition(const QPoint &pt)
QObject::connect
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QMouseEvent
QWidget::isVisible
bool isVisible() const const
QObject
QDropEvent
QSize::height
int height() const const
QDragMoveEvent::accept
void accept()
QDropEvent::setDropAction
void setDropAction(Qt::DropAction action)
QVector::at
const T & at(int i) const const
QObject::installEventFilter
void installEventFilter(QObject *filterObj)
QPoint::setX
void setX(int x)
QPoint::setY
void setY(int y)
QTimer::timeout
void timeout()
QWindow::startSystemMove
bool startSystemMove()
QWindow::winId
WId winId() const const
KDDockWidgets::DockWidgetBase::StateDragging
friend class StateDragging
Definition: DockWidgetBase.h:530
QCursor::pos
QPoint pos()
QLatin1String
QDragMoveEvent::ignore
void ignore()
QObject::metaObject
virtual const QMetaObject * metaObject() const const
QWidget::childAt
QWidget * childAt(int x, int y) const const
QMetaObject::className
const char * className() const const
Config.h
Application-wide config to tune certain behaviours of the framework.
Qt::DropAction
DropAction
QWidget::windowHandle
QWindow * windowHandle() const const
QEvent::type
QEvent::Type type() const const
QEvent
QObject::objectName
objectName
KDDockWidgets::DockWidgetBase
The DockWidget base-class. DockWidget and DockWidgetBase are only split in two so we can share some c...
Definition: DockWidgetBase.h:61
KDDockWidgets
Definition: Config.cpp:36
QWidget::grabMouse
void grabMouse()
QScopedValueRollback
QWidget::geometry
geometry
QVector::size
int size() const const
Draggable
KDDockWidgets::Private::isMinimized
bool isMinimized(QWindow *window)
Definition: QWidgetAdapter.h:31
QVector
QDrag
QPoint
QEvent::accept
void accept()
KDDockWidgets::Config::self
static Config & self()
returns the singleton Config instance
Definition: Config.cpp:82
QStringList

© 2019-2021 Klarälvdalens Datakonsult AB (KDAB)
"The Qt, C++ and OpenGL Experts"
https://www.kdab.com/
KDDockWidgets
Advanced Dock Widget Framework for Qt
https://www.kdab.com/development-resources/qt-tools/kddockwidgets/
Generated on Mon Nov 15 2021 00:17:19 for KDDockWidgets API Documentation by doxygen 1.8.20