/* This file is part of KDDockWidgets. SPDX-FileCopyrightText: 2019 Klarälvdalens Datakonsult AB, a KDAB Group company Author: Sérgio Martins SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only Contact KDAB at for commercial licensing options. */ #ifndef KD_DRAGCONTROLLER_P_H #define KD_DRAGCONTROLLER_P_H #include "kddockwidgets/docks_export.h" #include "WindowBeingDragged_p.h" #include "core/EventFilterInterface.h" #include "kddockwidgets/QtCompat_p.h" #include #include #ifdef KDDW_FRONTEND_QT_WINDOWS #include #endif class TestQtWidgets; namespace KDDockWidgets { namespace Core { class StateBase; class StatePreDrag; class StateDragging; class StateNone; class StateInternalMDIDragging; class FallbackMouseGrabber; class MinimalStateMachine; class DropArea; class Draggable; class State : public Core::Object { Q_OBJECT public: explicit State(MinimalStateMachine *parent); ~State() override; template void addTransition(Signal &, State *dest); bool isCurrentState() const; virtual void onEntry() = 0; virtual void onExit() { } private: MinimalStateMachine *const m_machine; }; class MinimalStateMachine : public Core::Object { Q_OBJECT public: explicit MinimalStateMachine(Core::Object *parent = nullptr); State *currentState() const; void setCurrentState(State *); KDBindings::Signal<> currentStateChanged; private: State *m_currentState = nullptr; }; class DOCKS_EXPORT_FOR_UNIT_TESTS DragController : public MinimalStateMachine, public EventFilterInterface { Q_OBJECT public: enum State { State_None = 0, State_PreDrag, State_Dragging }; Q_ENUM(State) static DragController *instance(); // Registers something that wants to be able to be dragged void registerDraggable(Draggable *); void unregisterDraggable(Draggable *); bool isDragging() const; bool isInNonClientDrag() const; bool isInClientDrag() const; bool isInProgrammaticDrag() const; bool isIdle() const; void grabMouseFor(View *); void releaseMouse(View *); FloatingWindow *floatingWindowBeingDragged() const; /// @brief Returns the current drop area under the mouse DropArea *dropAreaUnderCursor() const; ///@brief Returns the window being dragged WindowBeingDragged *windowBeingDragged() const; /// Experimental, internal, not for general use. void enableFallbackMouseGrabber(); // Returns the active state StateBase *activeState() const; /// Returns the current drop location /// The user needs to be dragging a window and be over a drop indicator, otherwise DropLocation_None is returned DropLocation currentDropLocation() const; /// Triggers a drag start without user interaction /// /// This method is unneeded for almost everyone as state machine changes states based /// on mouse events from user interaction. It's currently used for users who are developing for touch interfaces /// and want other means of starting a drag. /// /// Do not rely on it. We're still trying to understand the touch use case. /// @internal /// @param draggable The draggable we want to move (a title-bar, tab-bar or floating window) bool programmaticStartDrag(Draggable *draggable, Point globalPos, Point offset); void programmaticStopDrag(); KDBindings::Signal<> mousePressed; KDBindings::Signal<> manhattanLengthMove; KDBindings::Signal<> manhattanLengthMoveMDI; KDBindings::Signal<> mdiPopOut; KDBindings::Signal<> dragCanceled; KDBindings::Signal<> dropped; KDBindings::Signal<> isDraggingChanged; /// Wayland only bool isInQDrag() const; private: friend class StateBase; friend class StateNone; friend class StatePreDrag; friend class StateDragging; friend class StateInternalMDIDragging; friend class StateDropped; friend class StateDraggingWayland; friend class ::TestQtWidgets; explicit DragController(Core::Object * = nullptr); std::shared_ptr qtTopLevelUnderCursor() const; Core::Draggable *draggableForView(Core::View *) const; bool onDnDEvent(Core::View *, Event *) override; bool onMoveEvent(Core::View *) override; bool onMouseEvent(Core::View *, MouseEvent *) override; Point m_pressPos; Point m_offset; Vector m_draggables; Core::Draggable *m_draggable = nullptr; Core::ViewGuard m_draggableGuard = nullptr; // Just so we know if the draggable was destroyed for some reason std::unique_ptr m_windowBeingDragged; Core::DropArea *m_currentDropArea = nullptr; FallbackMouseGrabber *m_fallbackMouseGrabber = nullptr; StateNone *const m_stateNone; StatePreDrag *const m_statePreDrag; StateDragging *const m_stateDragging; StateInternalMDIDragging *m_stateDraggingMDI = nullptr; bool m_nonClientDrag = false; // native title bar drag bool m_inQDrag = false; // wayland drag bool m_inProgrammaticDrag = false; // via DockWidget::startDrag() }; class StateBase : public State { Q_OBJECT public: explicit StateBase(DragController *parent); ~StateBase(); // Not using QEvent here, to abstract platform differences regarding production of such events virtual bool handleMouseButtonPress(Core::Draggable * /*receiver*/, Point /*globalPos*/, Point /*pos*/) { return false; } virtual bool handleMouseMove(Point /*globalPos*/) { return false; } virtual bool handleMouseButtonRelease(Point /*globalPos*/) { return false; } virtual bool handleMouseDoubleClick() { return false; } // Only interesting for Wayland virtual bool handleDragEnter(DragMoveEvent *, DropArea *) { return false; } virtual bool handleDragLeave(DropArea *) { return false; } virtual bool handleDragMove(DragMoveEvent *, DropArea *) { return false; } virtual bool handleDrop(DropEvent *, DropArea *) { return false; } // Returns whether this is the current state bool isActiveState() const; DragController *const q; }; class StateNone : public StateBase { Q_OBJECT public: explicit StateNone(DragController *parent); ~StateNone() override; void onEntry() override; bool handleMouseButtonPress(Draggable *draggable, Point globalPos, Point pos) override; }; class StatePreDrag : public StateBase { Q_OBJECT public: explicit StatePreDrag(DragController *parent); ~StatePreDrag() override; void onEntry() override; bool handleMouseMove(Point globalPos) override; bool handleMouseButtonRelease(Point) override; bool handleMouseDoubleClick() override; }; // Used on all platforms except Wayland. @see StateDraggingWayland class StateDragging : public StateBase { Q_OBJECT public: explicit StateDragging(DragController *parent); ~StateDragging() override; void onEntry() override; void onExit() override; bool handleMouseButtonRelease(Point globalPos) override; bool handleMouseMove(Point globalPos) override; bool handleMouseDoubleClick() override; #if defined(KDDW_FRONTEND_QT_WINDOWS) private: QTimer m_maybeCancelDrag; #endif }; /// @brief State when we're moving an MDI dock widget around the main window /// without it becoming floating class StateInternalMDIDragging : public StateBase { Q_OBJECT public: explicit StateInternalMDIDragging(DragController *parent); ~StateInternalMDIDragging() override; void onEntry() override; bool handleMouseButtonRelease(Point globalPos) override; bool handleMouseMove(Point globalPos) override; bool handleMouseDoubleClick() override; }; } } #endif