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