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"
25 #include <QMouseEvent>
26 #include <QGuiApplication>
30 #include <QScopedValueRollback>
40 class FallbackMouseGrabber :
public QObject
43 FallbackMouseGrabber(
QObject *parent)
48 ~FallbackMouseGrabber()
override;
58 #ifdef KDDOCKWIDGETS_QTQUICK
64 QQuickView *view = m_target ? m_target->quickView()
66 QQuickItem *grabber = view ? view->mouseGrabberItem()
69 grabber->ungrabMouse();
73 qApp->removeEventFilter(
this);
78 if (m_reentrancyGuard || !m_target)
82 m_reentrancyGuard =
true;
83 qApp->sendEvent(m_target, me);
84 m_reentrancyGuard =
false;
91 bool m_reentrancyGuard =
false;
95 FallbackMouseGrabber::~FallbackMouseGrabber()
101 State::State(MinimalStateMachine *parent)
107 State::~State() =
default;
109 bool State::isCurrentState()
const
111 return m_machine->currentState() ==
this;
114 MinimalStateMachine::MinimalStateMachine(
QObject *parent)
119 template<
typename Obj,
typename Signal>
120 void State::addTransition(Obj *obj, Signal signal, State *dest)
122 connect(obj, signal,
this, [
this, dest] {
123 if (isCurrentState()) {
124 m_machine->setCurrentState(dest);
130 State *MinimalStateMachine::currentState()
const
132 return m_currentState;
135 void MinimalStateMachine::setCurrentState(State *state)
137 if (state != m_currentState) {
139 m_currentState->onExit();
141 m_currentState = state;
148 StateBase::StateBase(DragController *parent)
154 bool StateBase::isActiveState()
const
156 return q->activeState() ==
this;
159 StateBase::~StateBase() =
default;
161 StateNone::StateNone(DragController *parent)
166 void StateNone::onEntry()
168 qCDebug(state) <<
"StateNone entered";
171 q->m_draggable =
nullptr;
172 q->m_draggableGuard.clear();
173 q->m_windowBeingDragged.reset();
174 WidgetResizeHandler::s_disableAllHandlers =
false;
176 q->m_nonClientDrag =
false;
177 if (q->m_currentDropArea) {
178 q->m_currentDropArea->removeHover();
179 q->m_currentDropArea =
nullptr;
182 Q_EMIT q->isDraggingChanged();
187 qCDebug(state) <<
"StateNone::handleMouseButtonPress: draggable"
188 << draggable->asWidget() <<
"; globalPos" << globalPos;
190 if (!draggable->isPositionDraggable(pos))
193 q->m_draggable = draggable;
194 q->m_draggableGuard = draggable->asWidget();
195 q->m_pressPos = globalPos;
196 q->m_offset = draggable->mapToWindow(pos);
197 Q_EMIT q->mousePressed();
201 StateNone::~StateNone() =
default;
204 StatePreDrag::StatePreDrag(DragController *parent)
209 StatePreDrag::~StatePreDrag() =
default;
211 void StatePreDrag::onEntry()
213 qCDebug(state) <<
"StatePreDrag entered" << q->m_draggableGuard.data();
214 WidgetResizeHandler::s_disableAllHandlers =
true;
217 bool StatePreDrag::handleMouseMove(
QPoint globalPos)
219 if (!q->m_draggableGuard) {
220 qWarning() << Q_FUNC_INFO <<
"Draggable was destroyed, canceling the drag";
221 Q_EMIT q->dragCanceled();
225 if (q->m_draggable->dragCanStart(q->m_pressPos, globalPos)) {
226 if (q->m_draggable->isMDI())
227 Q_EMIT q->manhattanLengthMoveMDI();
229 Q_EMIT q->manhattanLengthMove();
235 bool StatePreDrag::handleMouseButtonRelease(
QPoint)
237 Q_EMIT q->dragCanceled();
241 bool StatePreDrag::handleMouseDoubleClick()
246 Q_EMIT q->dragCanceled();
250 StateDragging::StateDragging(DragController *parent)
253 #if defined(Q_OS_WIN) && !defined(DOCKS_DEVELOPER_MODE)
254 m_maybeCancelDrag.setInterval(100);
259 const bool mouseButtonIsReallyDown = (GetKeyState(VK_LBUTTON) & 0x8000);
260 if (!mouseButtonIsReallyDown && isLeftButtonPressed()) {
261 qCDebug(state) <<
"Canceling drag, Qt thinks mouse button is pressed"
262 <<
"but Windows knows it's not";
263 Q_EMIT q->dragCanceled();
269 StateDragging::~StateDragging() =
default;
271 void StateDragging::onEntry()
273 m_maybeCancelDrag.start();
277 if (dw->isFloating())
278 dw->d->saveLastFloatingGeometry();
281 const bool needsUndocking = !q->m_draggable->isWindow();
282 q->m_windowBeingDragged = q->m_draggable->makeWindow();
283 if (q->m_windowBeingDragged) {
284 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) && defined(Q_OS_WIN)
285 if (!q->m_nonClientDrag && KDDockWidgets::usesNativeDraggingAndResizing()) {
288 FloatingWindow *fw = q->m_windowBeingDragged->floatingWindow();
289 q->m_nonClientDrag =
true;
290 q->m_windowBeingDragged.reset();
291 q->m_windowBeingDragged = fw->makeWindow();
293 QWindow *window = fw->windowHandle();
295 if (needsUndocking) {
305 Q_UNUSED(needsUndocking);
308 qCDebug(state) <<
"StateDragging entered. m_draggable=" << q->m_draggable->asWidget()
309 <<
"; m_windowBeingDragged=" << q->m_windowBeingDragged->floatingWindow();
311 auto fw = q->m_windowBeingDragged->floatingWindow();
312 if (!fw->geometry().contains(q->m_pressPos)) {
316 if (fw->width() < q->m_offset.x()) {
317 q->m_offset.setX(fw->width() / 2);
322 qWarning() << Q_FUNC_INFO <<
"No window being dragged for " << q->m_draggable->asWidget();
323 Q_EMIT q->dragCanceled();
326 Q_EMIT q->isDraggingChanged();
329 void StateDragging::onExit()
331 m_maybeCancelDrag.stop();
334 bool StateDragging::handleMouseButtonRelease(
QPoint globalPos)
336 qCDebug(state) <<
"StateDragging: handleMouseButtonRelease";
338 FloatingWindow *floatingWindow = q->m_windowBeingDragged->floatingWindow();
339 if (!floatingWindow) {
341 qCDebug(state) <<
"StateDragging: Bailling out, deleted externally";
342 Q_EMIT q->dragCanceled();
346 if (floatingWindow->anyNonDockable()) {
347 qCDebug(state) <<
"StateDragging: Ignoring floating window with non dockable widgets";
348 Q_EMIT q->dragCanceled();
352 if (q->m_currentDropArea) {
353 if (q->m_currentDropArea->drop(q->m_windowBeingDragged.get(), globalPos)) {
356 qCDebug(state) <<
"StateDragging: Bailling out, drop not accepted";
357 Q_EMIT q->dragCanceled();
360 qCDebug(state) <<
"StateDragging: Bailling out, not over a drop area";
361 Q_EMIT q->dragCanceled();
366 bool StateDragging::handleMouseMove(
QPoint globalPos)
368 FloatingWindow *fw = q->m_windowBeingDragged->floatingWindow();
370 qCDebug(state) <<
"Canceling drag, window was deleted";
371 Q_EMIT q->dragCanceled();
375 if (fw->beingDeleted()) {
380 if (!q->m_nonClientDrag)
381 fw->windowHandle()->setPosition(globalPos - q->m_offset);
383 if (fw->anyNonDockable()) {
384 qCDebug(state) <<
"StateDragging: Ignoring non dockable floating window";
388 DropArea *dropArea = q->dropAreaUnderCursor();
389 if (q->m_currentDropArea && dropArea != q->m_currentDropArea)
390 q->m_currentDropArea->removeHover();
393 if (FloatingWindow *targetFw = dropArea->floatingWindow()) {
394 if (targetFw->anyNonDockable()) {
395 qCDebug(state) <<
"StateDragging: Ignoring non dockable target floating window";
400 dropArea->hover(q->m_windowBeingDragged.get(), globalPos);
403 q->m_currentDropArea = dropArea;
408 bool StateDragging::handleMouseDoubleClick()
412 Q_EMIT q->dragCanceled();
416 StateInternalMDIDragging::StateInternalMDIDragging(DragController *parent)
421 StateInternalMDIDragging::~StateInternalMDIDragging()
425 void StateInternalMDIDragging::onEntry()
427 qCDebug(state) <<
"StateInternalMDIDragging entered. draggable="
428 << q->m_draggable->asWidget();
431 if (
auto tb = qobject_cast<TitleBar *>(q->m_draggable->asWidget())) {
432 if (Frame *f = tb->frame())
436 Q_EMIT q->isDraggingChanged();
439 bool StateInternalMDIDragging::handleMouseButtonRelease(
QPoint)
441 Q_EMIT q->dragCanceled();
445 bool StateInternalMDIDragging::handleMouseMove(
QPoint globalPos)
448 auto tb = qobject_cast<TitleBar *>(q->m_draggable->asWidget());
450 qWarning() << Q_FUNC_INFO <<
"expected a title bar, not" << q->m_draggable->asWidget();
451 Q_EMIT q->dragCanceled();
455 Frame *frame = tb->frame();
458 qWarning() << Q_FUNC_INFO <<
"null frame.";
459 Q_EMIT q->dragCanceled();
463 const QSize parentSize = frame->QWidgetAdapter::parentWidget()->size();
465 const QPoint delta = globalPos - oldPos;
466 const QPoint newLocalPos = frame->pos() + delta - q->m_offset;
470 QPoint newLocalPosBounded = { qMax(0, newLocalPos.
x()), qMax(0, newLocalPos.
y()) };
471 newLocalPosBounded.
setX(qMin(newLocalPosBounded.
x(), parentSize.
width() - frame->width()));
472 newLocalPosBounded.
setY(qMin(newLocalPosBounded.
y(), parentSize.
height() - frame->height()));
474 auto layout = frame->mdiLayoutWidget();
476 layout->moveDockWidget(frame, newLocalPosBounded);
481 if (threshold != -1) {
482 const QPoint overflow = newLocalPosBounded - newLocalPos;
483 if (qAbs(overflow.
x()) > threshold || qAbs(overflow.
y()) > threshold)
484 Q_EMIT q->mdiPopOut();
490 bool StateInternalMDIDragging::handleMouseDoubleClick()
492 Q_EMIT q->dragCanceled();
496 StateDraggingWayland::StateDraggingWayland(DragController *parent)
497 : StateDragging(parent)
501 StateDraggingWayland::~StateDraggingWayland()
505 void StateDraggingWayland::onEntry()
507 qCDebug(state) <<
"StateDragging entered";
511 qWarning() << Q_FUNC_INFO <<
"Impossible!";
516 q->m_windowBeingDragged = std::unique_ptr<WindowBeingDragged>(
new WindowBeingDraggedWayland(q->m_draggable));
518 auto mimeData =
new WaylandMimeData();
520 drag.setMimeData(mimeData);
521 drag.setPixmap(q->m_windowBeingDragged->pixmap());
523 qApp->installEventFilter(q);
525 qApp->removeEventFilter(q);
527 Q_EMIT q->dragCanceled();
530 bool StateDraggingWayland::handleMouseButtonRelease(
QPoint )
532 qCDebug(state) << Q_FUNC_INFO;
533 Q_EMIT q->dragCanceled();
537 bool StateDraggingWayland::handleDragEnter(
QDragEnterEvent *ev, DropArea *dropArea)
539 auto mimeData = qobject_cast<const WaylandMimeData *>(ev->
mimeData());
540 if (!mimeData || !q->m_windowBeingDragged)
543 if (q->m_windowBeingDragged->contains(dropArea)) {
548 dropArea->hover(q->m_windowBeingDragged.get(), dropArea->mapToGlobal(Qt5Qt6Compat::eventPos(ev)));
554 bool StateDraggingWayland::handleDragLeave(DropArea *dropArea)
556 dropArea->removeHover();
560 bool StateDraggingWayland::handleDrop(
QDropEvent *ev, DropArea *dropArea)
562 auto mimeData = qobject_cast<const WaylandMimeData *>(ev->
mimeData());
563 if (!mimeData || !q->m_windowBeingDragged)
566 if (dropArea->drop(q->m_windowBeingDragged.get(), dropArea->mapToGlobal(Qt5Qt6Compat::eventPos(ev)))) {
571 Q_EMIT q->dragCanceled();
574 dropArea->removeHover();
578 bool StateDraggingWayland::handleDragMove(
QDragMoveEvent *ev, DropArea *dropArea)
580 auto mimeData = qobject_cast<const WaylandMimeData *>(ev->
mimeData());
581 if (!mimeData || !q->m_windowBeingDragged)
584 dropArea->hover(q->m_windowBeingDragged.get(), dropArea->mapToGlobal(Qt5Qt6Compat::eventPos(ev)));
589 DragController::DragController(
QObject *parent)
590 : MinimalStateMachine(parent)
592 qCDebug(creation) <<
"DragController()";
594 auto stateNone =
new StateNone(
this);
595 auto statepreDrag =
new StatePreDrag(
this);
596 auto stateDragging = isWayland() ?
new StateDraggingWayland(
this)
598 m_stateDraggingMDI =
new StateInternalMDIDragging(
this);
600 stateNone->addTransition(
this, &DragController::mousePressed, statepreDrag);
601 statepreDrag->addTransition(
this, &DragController::dragCanceled, stateNone);
602 statepreDrag->addTransition(
this, &DragController::manhattanLengthMove, stateDragging);
603 statepreDrag->addTransition(
this, &DragController::manhattanLengthMoveMDI, m_stateDraggingMDI);
604 stateDragging->addTransition(
this, &DragController::dragCanceled, stateNone);
605 stateDragging->addTransition(
this, &DragController::dropped, stateNone);
607 m_stateDraggingMDI->addTransition(
this, &DragController::dragCanceled, stateNone);
608 m_stateDraggingMDI->addTransition(
this, &DragController::mdiPopOut, stateDragging);
610 if (usesFallbackMouseGrabber())
611 enableFallbackMouseGrabber();
613 setCurrentState(stateNone);
616 DragController *DragController::instance()
618 static DragController dragController;
619 return &dragController;
622 void DragController::registerDraggable(
Draggable *drg)
625 drg->asWidget()->installEventFilter(
this);
628 void DragController::unregisterDraggable(
Draggable *drg)
630 m_draggables.removeOne(drg);
631 drg->asWidget()->removeEventFilter(
this);
634 bool DragController::isDragging()
const
636 return m_windowBeingDragged !=
nullptr || activeState() == m_stateDraggingMDI;
639 bool DragController::isInNonClientDrag()
const
641 return isDragging() && m_nonClientDrag;
644 bool DragController::isInClientDrag()
const
646 return isDragging() && !m_nonClientDrag;
654 if (m_fallbackMouseGrabber) {
655 m_fallbackMouseGrabber->grabMouse(target);
666 if (m_fallbackMouseGrabber) {
667 m_fallbackMouseGrabber->releaseMouse();
673 FloatingWindow *DragController::floatingWindowBeingDragged()
const
675 return m_windowBeingDragged ? m_windowBeingDragged->floatingWindow()
679 void DragController::enableFallbackMouseGrabber()
681 if (!m_fallbackMouseGrabber)
682 m_fallbackMouseGrabber =
new FallbackMouseGrabber(
this);
685 WindowBeingDragged *DragController::windowBeingDragged()
const
687 return m_windowBeingDragged.get();
694 qCDebug(mouseevents) <<
"DragController::eventFilter e=" << e->
type() <<
"; o=" << o;
696 return MinimalStateMachine::eventFilter(o, e);
701 if (
auto dropArea = qobject_cast<DropArea *>(o)) {
702 switch (
int(e->
type())) {
704 if (activeState()->handleDragEnter(
static_cast<QDragEnterEvent *
>(e), dropArea))
708 if (activeState()->handleDragLeave(dropArea))
712 if (activeState()->handleDragMove(
static_cast<QDragMoveEvent *
>(e), dropArea))
716 if (activeState()->handleDrop(
static_cast<QDropEvent *
>(e), dropArea))
725 return MinimalStateMachine::eventFilter(o, e);
727 auto w = qobject_cast<QWidgetOrQuick *>(o);
729 return MinimalStateMachine::eventFilter(o, e);
731 qCDebug(mouseevents) <<
"DragController::eventFilter e=" << e->
type() <<
"; o=" << o
732 <<
"; m_nonClientDrag=" << m_nonClientDrag;
736 if (
auto fw = qobject_cast<FloatingWindow *>(o)) {
737 if (KDDockWidgets::usesNativeTitleBar() || fw->isInDragArea(Qt5Qt6Compat::eventGlobalPos(me))) {
738 m_nonClientDrag =
true;
739 return activeState()->handleMouseButtonPress(draggableForQObject(o), Qt5Qt6Compat::eventGlobalPos(me), me->
pos());
742 return MinimalStateMachine::eventFilter(o, e);
747 if (!KDDockWidgets::usesNativeDraggingAndResizing() || !w->isWindow()) {
748 Q_ASSERT(activeState());
749 return activeState()->handleMouseButtonPress(draggableForQObject(o), Qt5Qt6Compat::eventGlobalPos(me), me->
pos());
754 return activeState()->handleMouseButtonRelease(Qt5Qt6Compat::eventGlobalPos(me));
757 return activeState()->handleMouseMove(Qt5Qt6Compat::eventGlobalPos(me));
760 return activeState()->handleMouseDoubleClick();
765 return MinimalStateMachine::eventFilter(o, e);
768 StateBase *DragController::activeState()
const
770 return static_cast<StateBase *
>(currentState());
773 #if defined(Q_OS_WIN)
777 for (
QWindow *window : windows) {
781 if (hwnd == ( HWND )window->
winId()) {
782 if (
auto result = DockRegistry::self()->topLevelForHandle(window))
784 #ifdef KDDOCKWIDGETS_QTWIDGETS
788 const QWidgetList widgets = qApp->topLevelWidgets();
789 for (
QWidget *widget : widgets) {
790 if (hwnd == ( HWND )widget->winId()) {
798 qCDebug(toplevels) << Q_FUNC_INFO <<
"Couldn't find hwnd for top-level" << hwnd;
804 if (
auto mainWindow = qobject_cast<const MainWindowBase *>(topLevel))
805 return mainWindow->windowGeometry();
815 for (
auto i = windows.
size() - 1; i >= 0; --i) {
817 auto tl = KDDockWidgets::Private::widgetForWindow(window);
822 if (windowBeingDragged && KDDockWidgets::Private::windowForWidget(windowBeingDragged) == KDDockWidgets::Private::windowForWidget(tl))
826 qCDebug(toplevels) << Q_FUNC_INFO <<
"Found top-level" << tl;
834 WidgetType *DragController::qtTopLevelUnderCursor()
const
839 #if defined(Q_OS_WIN)
840 POINT globalNativePos;
841 if (!GetCursorPos(&globalNativePos))
846 HWND hwnd = HWND(m_windowBeingDragged->floatingWindow()->winId());
848 hwnd = GetWindow(hwnd, GW_HWNDNEXT);
850 if (!GetWindowRect(hwnd, &r) || !IsWindowVisible(hwnd))
853 if (!PtInRect(&r, globalNativePos))
856 if (
auto tl = qtTopLevelForHWND(hwnd)) {
857 const QRect windowGeometry = topLevelGeometry(tl);
859 if (windowGeometry.
contains(globalPos) && tl->objectName() != QStringLiteral(
"_docks_IndicatorWindow_Overlay")) {
860 qCDebug(toplevels) << Q_FUNC_INFO <<
"Found top-level" << tl;
864 #ifdef KDDOCKWIDGETS_QTWIDGETS // Maybe it's embedded in a QWinWidget:
865 auto topLevels = qApp->topLevelWidgets();
866 for (
auto topLevel : topLevels) {
869 if (topLevel->
rect().contains(topLevel->
mapFromGlobal(globalPos)) && topLevel->
objectName() != QStringLiteral(
"_docks_IndicatorWindow_Overlay")) {
870 qCDebug(toplevels) << Q_FUNC_INFO <<
"Found top-level" << topLevel;
876 #endif // QtWidgets A window belonging to another app is below the cursor
877 qCDebug(toplevels) << Q_FUNC_INFO <<
"Window from another app is under cursor" << hwnd;
889 FloatingWindow *tlwBeingDragged = m_windowBeingDragged->floatingWindow();
893 return qtTopLevelUnderCursor_impl<WidgetType *>(globalPos,
894 DockRegistry::self()->topLevels(
true),
898 qCDebug(toplevels) << Q_FUNC_INFO <<
"No top-level found";
906 auto w = topLevel->
childAt(localPos.x(), localPos.y());
908 if (
auto dt = qobject_cast<DropArea *>(w)) {
909 if (DockRegistry::self()->affinitiesMatch(dt->affinities(), affinities))
912 w = KDDockWidgets::Private::parentWidget(w);
918 DropArea *DragController::dropAreaUnderCursor()
const
920 WidgetType *topLevel = qtTopLevelUnderCursor();
924 const QStringList affinities = m_windowBeingDragged->floatingWindow()->affinities();
926 if (
auto fw = qobject_cast<FloatingWindow *>(topLevel)) {
927 if (DockRegistry::self()->affinitiesMatch(fw->affinities(), affinities))
928 return fw->dropArea();
931 if (topLevel->
objectName() == QStringLiteral(
"_docks_IndicatorWindow")) {
932 qWarning() <<
"Indicator window should be hidden " << topLevel << topLevel->
isVisible();
940 qCDebug(state) <<
"DragController::dropAreaUnderCursor: null2";
946 for (
auto draggable : m_draggables)
947 if (draggable->asWidget() == o) {