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"
18 #include "Logging_p.h"
19 #include "Qt5Qt6Compat_p.h"
21 #include "WidgetResizeHandler_p.h"
23 #include "MDILayoutWidget_p.h"
24 #include "WindowZOrder_x11_p.h"
26 #include <QMouseEvent>
27 #include <QGuiApplication>
31 #include <QScopedValueRollback>
41 class FallbackMouseGrabber :
public QObject
44 FallbackMouseGrabber(
QObject *parent)
49 ~FallbackMouseGrabber()
override;
59 #ifdef KDDOCKWIDGETS_QTQUICK
65 QQuickView *view = m_target ? m_target->quickView()
67 QQuickItem *grabber = view ? view->mouseGrabberItem()
70 grabber->ungrabMouse();
74 qApp->removeEventFilter(
this);
79 if (m_reentrancyGuard || !m_target)
83 m_reentrancyGuard =
true;
84 qApp->sendEvent(m_target, me);
85 m_reentrancyGuard =
false;
92 bool m_reentrancyGuard =
false;
96 FallbackMouseGrabber::~FallbackMouseGrabber()
102 State::State(MinimalStateMachine *parent)
108 State::~State() =
default;
110 bool State::isCurrentState()
const
112 return m_machine->currentState() ==
this;
115 MinimalStateMachine::MinimalStateMachine(
QObject *parent)
120 template<
typename Obj,
typename Signal>
121 void State::addTransition(Obj *obj, Signal signal, State *dest)
123 connect(obj, signal,
this, [
this, dest] {
124 if (isCurrentState()) {
125 m_machine->setCurrentState(dest);
131 State *MinimalStateMachine::currentState()
const
133 return m_currentState;
136 void MinimalStateMachine::setCurrentState(State *state)
138 if (state != m_currentState) {
140 m_currentState->onExit();
142 m_currentState = state;
149 StateBase::StateBase(DragController *parent)
155 bool StateBase::isActiveState()
const
157 return q->activeState() ==
this;
160 StateBase::~StateBase() =
default;
162 StateNone::StateNone(DragController *parent)
167 void StateNone::onEntry()
169 qCDebug(state) <<
"StateNone entered";
172 q->m_draggable =
nullptr;
173 q->m_draggableGuard.clear();
174 q->m_windowBeingDragged.reset();
175 WidgetResizeHandler::s_disableAllHandlers =
false;
177 q->m_nonClientDrag =
false;
178 if (q->m_currentDropArea) {
179 q->m_currentDropArea->removeHover();
180 q->m_currentDropArea =
nullptr;
183 Q_EMIT q->isDraggingChanged();
188 qCDebug(state) <<
"StateNone::handleMouseButtonPress: draggable"
189 << draggable->asWidget() <<
"; globalPos" << globalPos;
191 if (!draggable->isPositionDraggable(pos))
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();
202 StateNone::~StateNone() =
default;
205 StatePreDrag::StatePreDrag(DragController *parent)
210 StatePreDrag::~StatePreDrag() =
default;
212 void StatePreDrag::onEntry()
214 qCDebug(state) <<
"StatePreDrag entered" << q->m_draggableGuard.data();
215 WidgetResizeHandler::s_disableAllHandlers =
true;
218 bool StatePreDrag::handleMouseMove(
QPoint globalPos)
220 if (!q->m_draggableGuard) {
221 qWarning() << Q_FUNC_INFO <<
"Draggable was destroyed, canceling the drag";
222 Q_EMIT q->dragCanceled();
226 if (q->m_draggable->dragCanStart(q->m_pressPos, globalPos)) {
227 if (q->m_draggable->isMDI())
228 Q_EMIT q->manhattanLengthMoveMDI();
230 Q_EMIT q->manhattanLengthMove();
236 bool StatePreDrag::handleMouseButtonRelease(
QPoint)
238 Q_EMIT q->dragCanceled();
242 bool StatePreDrag::handleMouseDoubleClick()
247 Q_EMIT q->dragCanceled();
251 StateDragging::StateDragging(DragController *parent)
254 #if defined(Q_OS_WIN) && !defined(DOCKS_DEVELOPER_MODE)
255 m_maybeCancelDrag.setInterval(100);
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();
270 StateDragging::~StateDragging() =
default;
272 void StateDragging::onEntry()
274 m_maybeCancelDrag.start();
278 if (dw->isFloating())
279 dw->d->saveLastFloatingGeometry();
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()) {
289 FloatingWindow *fw = q->m_windowBeingDragged->floatingWindow();
290 q->m_nonClientDrag =
true;
291 q->m_windowBeingDragged.reset();
292 q->m_windowBeingDragged = fw->makeWindow();
294 QWindow *window = fw->windowHandle();
296 if (needsUndocking) {
306 Q_UNUSED(needsUndocking);
309 qCDebug(state) <<
"StateDragging entered. m_draggable=" << q->m_draggable->asWidget()
310 <<
"; m_windowBeingDragged=" << q->m_windowBeingDragged->floatingWindow();
312 auto fw = q->m_windowBeingDragged->floatingWindow();
313 if (!fw->geometry().contains(q->m_pressPos)) {
317 if (fw->width() < q->m_offset.x()) {
318 q->m_offset.setX(fw->width() / 2);
323 qWarning() << Q_FUNC_INFO <<
"No window being dragged for " << q->m_draggable->asWidget();
324 Q_EMIT q->dragCanceled();
327 Q_EMIT q->isDraggingChanged();
330 void StateDragging::onExit()
332 m_maybeCancelDrag.stop();
335 bool StateDragging::handleMouseButtonRelease(
QPoint globalPos)
337 qCDebug(state) <<
"StateDragging: handleMouseButtonRelease";
339 FloatingWindow *floatingWindow = q->m_windowBeingDragged->floatingWindow();
340 if (!floatingWindow) {
342 qCDebug(state) <<
"StateDragging: Bailling out, deleted externally";
343 Q_EMIT q->dragCanceled();
347 if (floatingWindow->anyNonDockable()) {
348 qCDebug(state) <<
"StateDragging: Ignoring floating window with non dockable widgets";
349 Q_EMIT q->dragCanceled();
353 if (q->m_currentDropArea) {
354 if (q->m_currentDropArea->drop(q->m_windowBeingDragged.get(), globalPos)) {
357 qCDebug(state) <<
"StateDragging: Bailling out, drop not accepted";
358 Q_EMIT q->dragCanceled();
361 qCDebug(state) <<
"StateDragging: Bailling out, not over a drop area";
362 Q_EMIT q->dragCanceled();
367 bool StateDragging::handleMouseMove(
QPoint globalPos)
369 FloatingWindow *fw = q->m_windowBeingDragged->floatingWindow();
371 qCDebug(state) <<
"Canceling drag, window was deleted";
372 Q_EMIT q->dragCanceled();
376 if (fw->beingDeleted()) {
381 if (!q->m_nonClientDrag)
382 fw->windowHandle()->setPosition(globalPos - q->m_offset);
384 if (fw->anyNonDockable()) {
385 qCDebug(state) <<
"StateDragging: Ignoring non dockable floating window";
389 DropArea *dropArea = q->dropAreaUnderCursor();
390 if (q->m_currentDropArea && dropArea != q->m_currentDropArea)
391 q->m_currentDropArea->removeHover();
394 if (FloatingWindow *targetFw = dropArea->floatingWindow()) {
395 if (targetFw->anyNonDockable()) {
396 qCDebug(state) <<
"StateDragging: Ignoring non dockable target floating window";
401 dropArea->hover(q->m_windowBeingDragged.get(), globalPos);
404 q->m_currentDropArea = dropArea;
409 bool StateDragging::handleMouseDoubleClick()
413 Q_EMIT q->dragCanceled();
417 StateInternalMDIDragging::StateInternalMDIDragging(DragController *parent)
422 StateInternalMDIDragging::~StateInternalMDIDragging()
426 void StateInternalMDIDragging::onEntry()
428 qCDebug(state) <<
"StateInternalMDIDragging entered. draggable="
429 << q->m_draggable->asWidget();
432 if (
auto tb = qobject_cast<TitleBar *>(q->m_draggable->asWidget())) {
433 if (Frame *f = tb->frame())
437 Q_EMIT q->isDraggingChanged();
440 bool StateInternalMDIDragging::handleMouseButtonRelease(
QPoint)
442 Q_EMIT q->dragCanceled();
446 bool StateInternalMDIDragging::handleMouseMove(
QPoint globalPos)
449 auto tb = qobject_cast<TitleBar *>(q->m_draggable->asWidget());
451 qWarning() << Q_FUNC_INFO <<
"expected a title bar, not" << q->m_draggable->asWidget();
452 Q_EMIT q->dragCanceled();
456 Frame *frame = tb->frame();
459 qWarning() << Q_FUNC_INFO <<
"null frame.";
460 Q_EMIT q->dragCanceled();
464 const QSize parentSize = frame->QWidgetAdapter::parentWidget()->size();
466 const QPoint delta = globalPos - oldPos;
467 const QPoint newLocalPos = frame->pos() + delta - q->m_offset;
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()));
475 auto layout = frame->mdiLayoutWidget();
477 layout->moveDockWidget(frame, newLocalPosBounded);
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();
491 bool StateInternalMDIDragging::handleMouseDoubleClick()
493 Q_EMIT q->dragCanceled();
497 StateDraggingWayland::StateDraggingWayland(DragController *parent)
498 : StateDragging(parent)
502 StateDraggingWayland::~StateDraggingWayland()
506 void StateDraggingWayland::onEntry()
508 qCDebug(state) <<
"StateDragging entered";
512 qWarning() << Q_FUNC_INFO <<
"Impossible!";
517 q->m_windowBeingDragged = std::unique_ptr<WindowBeingDragged>(
new WindowBeingDraggedWayland(q->m_draggable));
519 auto mimeData =
new WaylandMimeData();
521 drag.setMimeData(mimeData);
522 drag.setPixmap(q->m_windowBeingDragged->pixmap());
524 qApp->installEventFilter(q);
526 qApp->removeEventFilter(q);
528 Q_EMIT q->dragCanceled();
531 bool StateDraggingWayland::handleMouseButtonRelease(
QPoint )
533 qCDebug(state) << Q_FUNC_INFO;
534 Q_EMIT q->dragCanceled();
538 bool StateDraggingWayland::handleDragEnter(
QDragEnterEvent *ev, DropArea *dropArea)
540 auto mimeData = qobject_cast<const WaylandMimeData *>(ev->
mimeData());
541 if (!mimeData || !q->m_windowBeingDragged)
544 if (q->m_windowBeingDragged->contains(dropArea)) {
549 dropArea->hover(q->m_windowBeingDragged.get(), dropArea->mapToGlobal(Qt5Qt6Compat::eventPos(ev)));
555 bool StateDraggingWayland::handleDragLeave(DropArea *dropArea)
557 dropArea->removeHover();
561 bool StateDraggingWayland::handleDrop(
QDropEvent *ev, DropArea *dropArea)
563 auto mimeData = qobject_cast<const WaylandMimeData *>(ev->
mimeData());
564 if (!mimeData || !q->m_windowBeingDragged)
567 if (dropArea->drop(q->m_windowBeingDragged.get(), dropArea->mapToGlobal(Qt5Qt6Compat::eventPos(ev)))) {
572 Q_EMIT q->dragCanceled();
575 dropArea->removeHover();
579 bool StateDraggingWayland::handleDragMove(
QDragMoveEvent *ev, DropArea *dropArea)
581 auto mimeData = qobject_cast<const WaylandMimeData *>(ev->
mimeData());
582 if (!mimeData || !q->m_windowBeingDragged)
585 dropArea->hover(q->m_windowBeingDragged.get(), dropArea->mapToGlobal(Qt5Qt6Compat::eventPos(ev)));
590 DragController::DragController(
QObject *parent)
591 : MinimalStateMachine(parent)
593 qCDebug(creation) <<
"DragController()";
595 auto stateNone =
new StateNone(
this);
596 auto statepreDrag =
new StatePreDrag(
this);
597 auto stateDragging = isWayland() ?
new StateDraggingWayland(
this)
599 m_stateDraggingMDI =
new StateInternalMDIDragging(
this);
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);
608 m_stateDraggingMDI->addTransition(
this, &DragController::dragCanceled, stateNone);
609 m_stateDraggingMDI->addTransition(
this, &DragController::mdiPopOut, stateDragging);
611 if (usesFallbackMouseGrabber())
612 enableFallbackMouseGrabber();
614 setCurrentState(stateNone);
617 DragController *DragController::instance()
619 static DragController dragController;
620 return &dragController;
623 void DragController::registerDraggable(
Draggable *drg)
626 drg->asWidget()->installEventFilter(
this);
629 void DragController::unregisterDraggable(
Draggable *drg)
631 m_draggables.removeOne(drg);
632 drg->asWidget()->removeEventFilter(
this);
635 bool DragController::isDragging()
const
637 return m_windowBeingDragged !=
nullptr || activeState() == m_stateDraggingMDI;
640 bool DragController::isInNonClientDrag()
const
642 return isDragging() && m_nonClientDrag;
645 bool DragController::isInClientDrag()
const
647 return isDragging() && !m_nonClientDrag;
655 if (m_fallbackMouseGrabber) {
656 m_fallbackMouseGrabber->grabMouse(target);
667 if (m_fallbackMouseGrabber) {
668 m_fallbackMouseGrabber->releaseMouse();
674 FloatingWindow *DragController::floatingWindowBeingDragged()
const
676 return m_windowBeingDragged ? m_windowBeingDragged->floatingWindow()
680 void DragController::enableFallbackMouseGrabber()
682 if (!m_fallbackMouseGrabber)
683 m_fallbackMouseGrabber =
new FallbackMouseGrabber(
this);
686 WindowBeingDragged *DragController::windowBeingDragged()
const
688 return m_windowBeingDragged.get();
695 qCDebug(mouseevents) <<
"DragController::eventFilter e=" << e->
type() <<
"; o=" << o;
697 return MinimalStateMachine::eventFilter(o, e);
702 if (
auto dropArea = qobject_cast<DropArea *>(o)) {
703 switch (
int(e->
type())) {
705 if (activeState()->handleDragEnter(
static_cast<QDragEnterEvent *
>(e), dropArea))
709 if (activeState()->handleDragLeave(dropArea))
713 if (activeState()->handleDragMove(
static_cast<QDragMoveEvent *
>(e), dropArea))
717 if (activeState()->handleDrop(
static_cast<QDropEvent *
>(e), dropArea))
726 return MinimalStateMachine::eventFilter(o, e);
728 auto w = qobject_cast<QWidgetOrQuick *>(o);
730 return MinimalStateMachine::eventFilter(o, e);
732 qCDebug(mouseevents) <<
"DragController::eventFilter e=" << e->
type() <<
"; o=" << o
733 <<
"; m_nonClientDrag=" << m_nonClientDrag;
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());
743 return MinimalStateMachine::eventFilter(o, e);
748 if (!KDDockWidgets::usesNativeDraggingAndResizing() || !w->isWindow()) {
749 Q_ASSERT(activeState());
750 return activeState()->handleMouseButtonPress(draggableForQObject(o), Qt5Qt6Compat::eventGlobalPos(me), me->
pos());
755 return activeState()->handleMouseButtonRelease(Qt5Qt6Compat::eventGlobalPos(me));
758 return activeState()->handleMouseMove(Qt5Qt6Compat::eventGlobalPos(me));
761 return activeState()->handleMouseDoubleClick();
766 return MinimalStateMachine::eventFilter(o, e);
769 StateBase *DragController::activeState()
const
771 return static_cast<StateBase *
>(currentState());
774 #if defined(Q_OS_WIN)
778 for (
QWindow *window : windows) {
782 if (hwnd == ( HWND )window->
winId()) {
783 if (
auto result = DockRegistry::self()->topLevelForHandle(window))
785 #ifdef KDDOCKWIDGETS_QTWIDGETS
789 const QWidgetList widgets = qApp->topLevelWidgets();
790 for (
QWidget *widget : widgets) {
791 if (hwnd == ( HWND )widget->winId()) {
799 qCDebug(toplevels) << Q_FUNC_INFO <<
"Couldn't find hwnd for top-level" << hwnd;
805 if (
auto mainWindow = qobject_cast<const MainWindowBase *>(topLevel))
806 return mainWindow->windowGeometry();
816 for (
auto i = windows.
size() - 1; i >= 0; --i) {
818 auto tl = KDDockWidgets::Private::widgetForWindow(window);
823 if (windowBeingDragged && KDDockWidgets::Private::windowForWidget(windowBeingDragged) == KDDockWidgets::Private::windowForWidget(tl))
827 qCDebug(toplevels) << Q_FUNC_INFO <<
"Found top-level" << tl;
835 WidgetType *DragController::qtTopLevelUnderCursor()
const
840 #if defined(Q_OS_WIN)
841 POINT globalNativePos;
842 if (!GetCursorPos(&globalNativePos))
847 HWND hwnd = HWND(m_windowBeingDragged->floatingWindow()->winId());
849 hwnd = GetWindow(hwnd, GW_HWNDNEXT);
851 if (!GetWindowRect(hwnd, &r) || !IsWindowVisible(hwnd))
854 if (!PtInRect(&r, globalNativePos))
857 if (
auto tl = qtTopLevelForHWND(hwnd)) {
858 const QRect windowGeometry = topLevelGeometry(tl);
860 if (windowGeometry.
contains(globalPos) && tl->objectName() != QStringLiteral(
"_docks_IndicatorWindow_Overlay")) {
861 qCDebug(toplevels) << Q_FUNC_INFO <<
"Found top-level" << tl;
865 #ifdef KDDOCKWIDGETS_QTWIDGETS // Maybe it's embedded in a QWinWidget:
866 auto topLevels = qApp->topLevelWidgets();
867 for (
auto topLevel : topLevels) {
870 if (topLevel->
rect().contains(topLevel->
mapFromGlobal(globalPos)) && topLevel->
objectName() != QStringLiteral(
"_docks_IndicatorWindow_Overlay")) {
871 qCDebug(toplevels) << Q_FUNC_INFO <<
"Found top-level" << topLevel;
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;
883 }
else if (linksToXLib() && isXCB()) {
886 FloatingWindow *tlwBeingDragged = m_windowBeingDragged->floatingWindow();
891 qCDebug(toplevels) << Q_FUNC_INFO <<
"No top-level found. Some windows weren't seen by XLib";
901 FloatingWindow *tlwBeingDragged = m_windowBeingDragged->floatingWindow();
905 return qtTopLevelUnderCursor_impl<WidgetType *>(globalPos,
906 DockRegistry::self()->topLevels(
true),
910 qCDebug(toplevels) << Q_FUNC_INFO <<
"No top-level found";
918 auto w = topLevel->
childAt(localPos.x(), localPos.y());
920 if (
auto dt = qobject_cast<DropArea *>(w)) {
921 if (DockRegistry::self()->affinitiesMatch(dt->affinities(), affinities))
924 w = KDDockWidgets::Private::parentWidget(w);
930 DropArea *DragController::dropAreaUnderCursor()
const
932 WidgetType *topLevel = qtTopLevelUnderCursor();
936 const QStringList affinities = m_windowBeingDragged->floatingWindow()->affinities();
938 if (
auto fw = qobject_cast<FloatingWindow *>(topLevel)) {
939 if (DockRegistry::self()->affinitiesMatch(fw->affinities(), affinities))
940 return fw->dropArea();
943 if (topLevel->
objectName() == QStringLiteral(
"_docks_IndicatorWindow")) {
944 qWarning() <<
"Indicator window should be hidden " << topLevel << topLevel->
isVisible();
952 qCDebug(state) <<
"DragController::dropAreaUnderCursor: null2";
958 for (
auto draggable : m_draggables)
959 if (draggable->asWidget() == o) {