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