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"
19#include "Qt5Qt6Compat_p.h"
21#include "WidgetResizeHandler_p.h"
23#include "MDILayoutWidget_p.h"
24#include "WindowZOrder_x11_p.h"
27#include <QGuiApplication>
31#include <QScopedValueRollback>
41class FallbackMouseGrabber :
public QObject
49 ~FallbackMouseGrabber()
override;
59#ifdef KDDOCKWIDGETS_QTQUICK
65 QQuickView *view = m_target ? m_target->quickView()
67 QQuickItem *
grabber = view ? view->mouseGrabberItem()
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;
96FallbackMouseGrabber::~FallbackMouseGrabber()
102State::State(MinimalStateMachine *parent)
108State::~State() =
default;
110bool State::isCurrentState()
const
112 return m_machine->currentState() ==
this;
115MinimalStateMachine::MinimalStateMachine(
QObject *parent)
120template<
typename Obj,
typename Signal>
121void State::addTransition(Obj *obj, Signal signal, State *dest)
123 connect(obj, signal,
this, [
this, dest] {
124 if (isCurrentState()) {
125 m_machine->setCurrentState(dest);
131State *MinimalStateMachine::currentState()
const
133 return m_currentState;
136void MinimalStateMachine::setCurrentState(State *state)
138 if (state != m_currentState) {
140 m_currentState->onExit();
142 m_currentState = state;
147 Q_EMIT currentStateChanged();
151StateBase::StateBase(DragController *parent)
157bool StateBase::isActiveState()
const
159 return q->activeState() ==
this;
162StateBase::~StateBase() =
default;
164StateNone::StateNone(DragController *parent)
169void StateNone::onEntry()
171 qCDebug(state) <<
"StateNone entered";
174 q->m_draggable =
nullptr;
175 q->m_draggableGuard.clear();
176 q->m_windowBeingDragged.reset();
177 WidgetResizeHandler::s_disableAllHandlers =
false;
179 q->m_nonClientDrag =
false;
180 if (q->m_currentDropArea) {
181 q->m_currentDropArea->removeHover();
182 q->m_currentDropArea =
nullptr;
185 Q_EMIT q->isDraggingChanged();
188bool StateNone::handleMouseButtonPress(Draggable *draggable,
QPoint globalPos,
QPoint pos)
190 qCDebug(state) <<
"StateNone::handleMouseButtonPress: draggable"
191 << draggable->asWidget() <<
"; globalPos" << globalPos;
193 if (!draggable->isPositionDraggable(pos))
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();
204StateNone::~StateNone() =
default;
207StatePreDrag::StatePreDrag(DragController *parent)
212StatePreDrag::~StatePreDrag() =
default;
214void StatePreDrag::onEntry()
216 qCDebug(state) <<
"StatePreDrag entered" << q->m_draggableGuard.data();
217 WidgetResizeHandler::s_disableAllHandlers =
true;
220bool StatePreDrag::handleMouseMove(
QPoint globalPos)
222 if (!q->m_draggableGuard) {
223 qWarning() << Q_FUNC_INFO <<
"Draggable was destroyed, canceling the drag";
224 Q_EMIT q->dragCanceled();
228 if (q->m_draggable->dragCanStart(q->m_pressPos, globalPos)) {
229 if (q->m_draggable->isMDI())
230 Q_EMIT q->manhattanLengthMoveMDI();
232 Q_EMIT q->manhattanLengthMove();
238bool StatePreDrag::handleMouseButtonRelease(
QPoint)
240 Q_EMIT q->dragCanceled();
244bool StatePreDrag::handleMouseDoubleClick()
249 Q_EMIT q->dragCanceled();
253StateDragging::StateDragging(DragController *parent)
256#if defined(Q_OS_WIN) && !defined(DOCKS_DEVELOPER_MODE)
257 m_maybeCancelDrag.setInterval(100);
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";
267 Q_EMIT q->dragCanceled();
273StateDragging::~StateDragging() =
default;
275void StateDragging::onEntry()
277 m_maybeCancelDrag.start();
281 if (dw->isFloating())
282 dw->d->saveLastFloatingGeometry();
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()) {
292 FloatingWindow *fw = q->m_windowBeingDragged->floatingWindow();
293 q->m_nonClientDrag =
true;
294 q->m_windowBeingDragged.reset();
295 q->m_windowBeingDragged = fw->makeWindow();
297 QWindow *window = fw->windowHandle();
299 if (needsUndocking) {
309 Q_UNUSED(needsUndocking);
312 qCDebug(state) <<
"StateDragging entered. m_draggable=" << q->m_draggable->asWidget()
313 <<
"; m_windowBeingDragged=" << q->m_windowBeingDragged->floatingWindow();
315 auto fw = q->m_windowBeingDragged->floatingWindow();
317 if (fw->isMaximizedOverride()) {
321 const QRect normalGeometry = fw->normalGeometry();
324 const int leftOffset = q->m_offset.
x();
327 const int rightOffset = fw->width() - q->m_offset.x();
329 const bool leftEdgeIsNearest = leftOffset <= rightOffset;
333 if (!normalGeometry.
contains(q->m_pressPos)) {
334 if ((leftEdgeIsNearest && leftOffset > normalGeometry.
width()) || (!leftEdgeIsNearest && rightOffset > normalGeometry.
width())) {
337 q->m_offset.setX(normalGeometry.
width() / 2);
338 }
else if (!leftEdgeIsNearest) {
341 q->m_offset.setX(normalGeometry.
width() - rightOffset);
346 if (!fw->geometry().contains(q->m_pressPos)) {
350 if (fw->width() < q->m_offset.x()) {
351 q->m_offset.setX(fw->width() / 2);
356 qWarning() << Q_FUNC_INFO <<
"No window being dragged for " << q->m_draggable->asWidget();
357 Q_EMIT q->dragCanceled();
360 Q_EMIT q->isDraggingChanged();
363void StateDragging::onExit()
365 m_maybeCancelDrag.stop();
368bool StateDragging::handleMouseButtonRelease(
QPoint globalPos)
370 qCDebug(state) <<
"StateDragging: handleMouseButtonRelease";
372 FloatingWindow *floatingWindow = q->m_windowBeingDragged->floatingWindow();
373 if (!floatingWindow) {
375 qCDebug(state) <<
"StateDragging: Bailling out, deleted externally";
376 Q_EMIT q->dragCanceled();
380 if (floatingWindow->anyNonDockable()) {
381 qCDebug(state) <<
"StateDragging: Ignoring floating window with non dockable widgets";
382 Q_EMIT q->dragCanceled();
386 if (q->m_currentDropArea) {
387 if (q->m_currentDropArea->drop(q->m_windowBeingDragged.get(), globalPos)) {
390 qCDebug(state) <<
"StateDragging: Bailling out, drop not accepted";
391 Q_EMIT q->dragCanceled();
394 qCDebug(state) <<
"StateDragging: Bailling out, not over a drop area";
395 Q_EMIT q->dragCanceled();
400bool StateDragging::handleMouseMove(
QPoint globalPos)
402 FloatingWindow *fw = q->m_windowBeingDragged->floatingWindow();
404 qCDebug(state) <<
"Canceling drag, window was deleted";
405 Q_EMIT q->dragCanceled();
409 if (fw->beingDeleted()) {
426 if (!q->m_nonClientDrag)
427 fw->windowHandle()->setPosition(globalPos - q->m_offset);
429 if (fw->anyNonDockable()) {
430 qCDebug(state) <<
"StateDragging: Ignoring non dockable floating window";
434 DropArea *dropArea = q->dropAreaUnderCursor();
435 if (q->m_currentDropArea && dropArea != q->m_currentDropArea)
436 q->m_currentDropArea->removeHover();
439 if (FloatingWindow *targetFw = dropArea->floatingWindow()) {
440 if (targetFw->anyNonDockable()) {
441 qCDebug(state) <<
"StateDragging: Ignoring non dockable target floating window";
446 dropArea->hover(q->m_windowBeingDragged.get(), globalPos);
449 q->m_currentDropArea = dropArea;
454bool StateDragging::handleMouseDoubleClick()
458 Q_EMIT q->dragCanceled();
462StateInternalMDIDragging::StateInternalMDIDragging(DragController *parent)
467StateInternalMDIDragging::~StateInternalMDIDragging()
471void StateInternalMDIDragging::onEntry()
473 qCDebug(state) <<
"StateInternalMDIDragging entered. draggable="
474 << q->m_draggable->asWidget();
477 if (
auto tb = qobject_cast<TitleBar *>(q->m_draggable->asWidget())) {
478 if (Frame *f = tb->frame())
482 Q_EMIT q->isDraggingChanged();
485bool StateInternalMDIDragging::handleMouseButtonRelease(
QPoint)
487 Q_EMIT q->dragCanceled();
491bool StateInternalMDIDragging::handleMouseMove(
QPoint globalPos)
494 auto tb = qobject_cast<TitleBar *>(q->m_draggable->asWidget());
496 qWarning() << Q_FUNC_INFO <<
"expected a title bar, not" << q->m_draggable->asWidget();
497 Q_EMIT q->dragCanceled();
501 Frame *frame = tb->frame();
504 qWarning() << Q_FUNC_INFO <<
"null frame.";
505 Q_EMIT q->dragCanceled();
509 const QSize parentSize = frame->QWidgetAdapter::parentWidget()->size();
511 const QPoint delta = globalPos - oldPos;
512 const QPoint newLocalPos = frame->pos() + delta - q->m_offset;
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()));
520 auto layout = frame->mdiLayoutWidget();
522 layout->moveDockWidget(frame, newLocalPosBounded);
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();
536bool StateInternalMDIDragging::handleMouseDoubleClick()
538 Q_EMIT q->dragCanceled();
542StateDraggingWayland::StateDraggingWayland(DragController *parent)
543 : StateDragging(parent)
547StateDraggingWayland::~StateDraggingWayland()
551void StateDraggingWayland::onEntry()
553 qCDebug(state) <<
"StateDragging entered";
557 qWarning() << Q_FUNC_INFO <<
"Impossible!";
562 q->m_windowBeingDragged = std::unique_ptr<WindowBeingDragged>(
new WindowBeingDraggedWayland(q->m_draggable));
564 auto mimeData =
new WaylandMimeData();
566 drag.setMimeData(mimeData);
567 drag.setPixmap(q->m_windowBeingDragged->pixmap());
569 qApp->installEventFilter(q);
571 qApp->removeEventFilter(q);
573 Q_EMIT q->dragCanceled();
576bool StateDraggingWayland::handleMouseButtonRelease(
QPoint )
578 qCDebug(state) << Q_FUNC_INFO;
579 Q_EMIT q->dragCanceled();
583bool StateDraggingWayland::handleMouseMove(
QPoint)
591bool StateDraggingWayland::handleDragEnter(
QDragEnterEvent *ev, DropArea *dropArea)
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)
598 if (q->m_windowBeingDragged->contains(dropArea)) {
603 dropArea->hover(q->m_windowBeingDragged.get(), dropArea->mapToGlobal(Qt5Qt6Compat::eventPos(ev)));
609bool StateDraggingWayland::handleDragLeave(DropArea *dropArea)
611 qCDebug(state) << Q_FUNC_INFO;
612 dropArea->removeHover();
616bool StateDraggingWayland::handleDrop(
QDropEvent *ev, DropArea *dropArea)
618 qCDebug(state) << Q_FUNC_INFO;
619 auto mimeData = qobject_cast<const WaylandMimeData *>(ev->
mimeData());
620 if (!mimeData || !q->m_windowBeingDragged)
623 if (dropArea->drop(q->m_windowBeingDragged.get(), dropArea->mapToGlobal(Qt5Qt6Compat::eventPos(ev)))) {
628 Q_EMIT q->dragCanceled();
631 dropArea->removeHover();
635bool StateDraggingWayland::handleDragMove(
QDragMoveEvent *ev, DropArea *dropArea)
637 auto mimeData = qobject_cast<const WaylandMimeData *>(ev->
mimeData());
638 if (!mimeData || !q->m_windowBeingDragged)
641 dropArea->hover(q->m_windowBeingDragged.get(), dropArea->mapToGlobal(Qt5Qt6Compat::eventPos(ev)));
646DragController::DragController(
QObject *parent)
647 : MinimalStateMachine(parent)
649 qCDebug(creation) <<
"DragController()";
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);
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);
664 m_stateDraggingMDI->addTransition(
this, &DragController::dragCanceled, m_stateNone);
665 m_stateDraggingMDI->addTransition(
this, &DragController::mdiPopOut, stateDragging);
667 if (usesFallbackMouseGrabber())
668 enableFallbackMouseGrabber();
670 setCurrentState(m_stateNone);
673DragController *DragController::instance()
675 static DragController dragController;
676 return &dragController;
679void DragController::registerDraggable(Draggable *drg)
682 drg->asWidget()->installEventFilter(
this);
685void DragController::unregisterDraggable(Draggable *drg)
687 m_draggables.removeOne(drg);
688 drg->asWidget()->removeEventFilter(
this);
691bool DragController::isDragging()
const
693 return m_windowBeingDragged !=
nullptr || activeState() == m_stateDraggingMDI;
696bool DragController::isInNonClientDrag()
const
698 return isDragging() && m_nonClientDrag;
701bool DragController::isInClientDrag()
const
703 return isDragging() && !m_nonClientDrag;
706bool DragController::isIdle()
const
708 return activeState() == m_stateNone;
716 if (m_fallbackMouseGrabber) {
717 m_fallbackMouseGrabber->grabMouse(target);
728 if (m_fallbackMouseGrabber) {
729 m_fallbackMouseGrabber->releaseMouse();
735FloatingWindow *DragController::floatingWindowBeingDragged()
const
737 return m_windowBeingDragged ? m_windowBeingDragged->floatingWindow()
741void DragController::enableFallbackMouseGrabber()
743 if (!m_fallbackMouseGrabber)
744 m_fallbackMouseGrabber =
new FallbackMouseGrabber(
this);
747WindowBeingDragged *DragController::windowBeingDragged()
const
749 return m_windowBeingDragged.get();
756 qCDebug(mouseevents) <<
"DragController::eventFilter e=" << e->
type() <<
"; o=" << o;
758 return MinimalStateMachine::eventFilter(o, e);
763 if (
auto dropArea = qobject_cast<DropArea *>(o)) {
764 switch (
int(e->
type())) {
766 if (activeState()->handleDragEnter(
static_cast<QDragEnterEvent *
>(e), dropArea))
770 if (activeState()->handleDragLeave(dropArea))
774 if (activeState()->handleDragMove(
static_cast<QDragMoveEvent *
>(e), dropArea))
778 if (activeState()->handleDrop(
static_cast<QDropEvent *
>(e), dropArea))
790 return MinimalStateMachine::eventFilter(o, e);
792 auto w = qobject_cast<QWidgetOrQuick *>(o);
794 return MinimalStateMachine::eventFilter(o, e);
796 qCDebug(mouseevents) <<
"DragController::eventFilter e=" << e->
type() <<
"; o=" << o
797 <<
"; m_nonClientDrag=" << m_nonClientDrag;
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());
807 return MinimalStateMachine::eventFilter(o, e);
812 if (!KDDockWidgets::usesNativeDraggingAndResizing() || !w->isWindow()) {
813 Q_ASSERT(activeState());
814 return activeState()->handleMouseButtonPress(draggableForQObject(o), Qt5Qt6Compat::eventGlobalPos(me), me->
pos());
819 return activeState()->handleMouseButtonRelease(Qt5Qt6Compat::eventGlobalPos(me));
822 return activeState()->handleMouseMove(Qt5Qt6Compat::eventGlobalPos(me));
825 return activeState()->handleMouseDoubleClick();
830 return MinimalStateMachine::eventFilter(o, e);
833StateBase *DragController::activeState()
const
835 return static_cast<StateBase *
>(currentState());
842 for (
QWindow *window : windows) {
846 if (hwnd == ( HWND )window->
winId()) {
847 if (
auto result = DockRegistry::self()->topLevelForHandle(window))
849#ifdef KDDOCKWIDGETS_QTWIDGETS
853 const QWidgetList widgets = qApp->topLevelWidgets();
854 for (
QWidget *widget : widgets) {
855 if (!widget->windowHandle()) {
861 if (hwnd == ( HWND )widget->winId()) {
869 qCDebug(toplevels) << Q_FUNC_INFO <<
"Couldn't find hwnd for top-level" << hwnd;
875 if (
auto mainWindow = qobject_cast<const MainWindowBase *>(topLevel))
876 return mainWindow->windowGeometry();
886 for (
auto i = windows.
size() - 1; i >= 0; --i) {
888 auto tl = KDDockWidgets::Private::widgetForWindow(window);
893 if (windowBeingDragged && KDDockWidgets::Private::windowForWidget(windowBeingDragged) == KDDockWidgets::Private::windowForWidget(tl))
897 qCDebug(toplevels) << Q_FUNC_INFO <<
"Found top-level" << tl;
905WidgetType *DragController::qtTopLevelUnderCursor()
const
911 POINT globalNativePos;
912 if (!GetCursorPos(&globalNativePos))
917 HWND hwnd = HWND(m_windowBeingDragged->floatingWindow()->winId());
919 hwnd = GetWindow(hwnd, GW_HWNDNEXT);
921 if (!GetWindowRect(hwnd, &r) || !IsWindowVisible(hwnd))
924 if (!PtInRect(&r, globalNativePos))
927 if (
auto tl = qtTopLevelForHWND(hwnd)) {
928 const QRect windowGeometry = topLevelGeometry(tl);
930 if (windowGeometry.
contains(globalPos) && tl->objectName() != QStringLiteral(
"_docks_IndicatorWindow_Overlay")) {
931 qCDebug(toplevels) << Q_FUNC_INFO <<
"Found top-level" << tl;
935#ifdef KDDOCKWIDGETS_QTWIDGETS
936 auto topLevels = qApp->topLevelWidgets();
937 for (
auto topLevel : topLevels) {
940 if (topLevel->
rect().contains(topLevel->
mapFromGlobal(globalPos)) && topLevel->
objectName() != QStringLiteral(
"_docks_IndicatorWindow_Overlay")) {
941 qCDebug(toplevels) << Q_FUNC_INFO <<
"Found top-level" << topLevel;
948 qCDebug(toplevels) << Q_FUNC_INFO <<
"Window from another app is under cursor" << hwnd;
953 }
else if (linksToXLib() && isXCB()) {
956 FloatingWindow *tlwBeingDragged = m_windowBeingDragged->floatingWindow();
961 qCDebug(toplevels) << Q_FUNC_INFO <<
"No top-level found. Some windows weren't seen by XLib";
971 FloatingWindow *tlwBeingDragged = m_windowBeingDragged->floatingWindow();
975 return qtTopLevelUnderCursor_impl<WidgetType *>(globalPos,
976 DockRegistry::self()->topLevels(
true),
980 qCDebug(toplevels) << Q_FUNC_INFO <<
"No top-level found";
988 auto w = topLevel->
childAt(localPos.x(), localPos.y());
990 if (
auto dt = qobject_cast<DropArea *>(w)) {
991 if (DockRegistry::self()->affinitiesMatch(dt->affinities(), affinities))
994 w = KDDockWidgets::Private::parentWidget(w);
1000DropArea *DragController::dropAreaUnderCursor()
const
1002 WidgetType *topLevel = qtTopLevelUnderCursor();
1004 qCDebug(state) << Q_FUNC_INFO <<
"No drop area under cursor";
1008 const QStringList affinities = m_windowBeingDragged->floatingWindow()->affinities();
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();
1017 if (topLevel->
objectName() == QStringLiteral(
"_docks_IndicatorWindow")) {
1018 qWarning() <<
"Indicator window should be hidden " << topLevel << topLevel->
isVisible();
1023 qCDebug(state) << Q_FUNC_INFO <<
"Found drop area" << dt << dt->window();
1027 qCDebug(state) <<
"DragController::dropAreaUnderCursor: null2";
1031Draggable *DragController::draggableForQObject(
QObject *o)
const
1033 for (
auto draggable : m_draggables)
1034 if (draggable->asWidget() == o) {
Application-wide config to tune certain behaviours of the framework.
static WidgetType * qtTopLevelUnderCursor_impl(QPoint globalPos, const QVector< QWindow * > &windows, T windowBeingDragged)
static DropArea * deepestDropAreaInTopLevel(WidgetType *topLevel, QPoint globalPos, const QStringList &affinities)
const QMimeData * mimeData() const const
void setDropAction(Qt::DropAction action)
QEvent::Type type() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void installEventFilter(QObject *filterObj)
QObject * parent() const const
T qobject_cast(QObject *object)
bool contains(const QRect &rectangle, bool proper) const const
const T & at(int i) const const
QRect geometry() const const
void setPosition(const QPoint &pt)
bool isVisible() const const