/* 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. */ #include "core/DropIndicatorOverlay.h" #include "core/DropIndicatorOverlay_p.h" #include "Config.h" #include "Platform.h" #include "ViewFactory.h" #include "core/DropArea.h" #include "core/Group.h" #include "core/Logging_p.h" #include "core/Controller_p.h" #include "core/WindowBeingDragged_p.h" #include "core/DragController_p.h" #include "core/DockRegistry_p.h" using namespace KDDockWidgets; using namespace KDDockWidgets::Core; DropIndicatorOverlay::DropIndicatorOverlay(DropArea *dropArea, View *view) : Controller(ViewType::DropAreaIndicatorOverlay, view) , d(new Private()) , m_dropArea(dropArea) { setVisible(false); view->setViewName(QStringLiteral("DropIndicatorOverlay")); // Set transparent for mouse events so that topLevel->childAt() never returns the drop indicator // overlay view->enableAttribute(Qt::WA_TransparentForMouseEvents); d->dropIndicatorsInhibitedConnection = DockRegistry::self()->dptr()->dropIndicatorsInhibitedChanged.connect([this](bool inhibited) { if (inhibited) { removeHover(); } else { // Re-add hover. Fastest way is simply faking a mouse move if (auto state = object_cast(DragController::instance()->activeState())) { state->handleMouseMove(Platform::instance()->cursorPos()); } } if (WindowBeingDragged *wbd = DragController::instance()->windowBeingDragged()) { /// Don't use transparency if drop areas are inhibited wbd->updateTransparency(!inhibited); } }); } DropIndicatorOverlay::DropIndicatorOverlay(Core::DropArea *dropArea) : DropIndicatorOverlay(dropArea, Platform::instance()->createView(this, dropArea->view())) { } DropIndicatorOverlay::~DropIndicatorOverlay() { delete d; } void DropIndicatorOverlay::setWindowBeingDragged(bool is) { if (is == m_draggedWindowIsHovering) return; m_draggedWindowIsHovering = is; if (is) { view()->setGeometry(m_dropArea->rect()); view()->raise(); } else { setHoveredGroup(nullptr); } setVisible(is); updateVisibility(); } Rect DropIndicatorOverlay::hoveredGroupRect() const { return m_hoveredGroupRect; } void DropIndicatorOverlay::setHoveredGroup(Core::Group *group) { if (group == m_hoveredGroup) return; // if (WindowBeingDragged *wbd = DragController::instance()->windowBeingDragged()) { // if (wbd->isInWaylandDrag(group)) { // // With wayland, we don't detach the group before the mouse release. // // Instead, we start a QDrag, with this group as cursor QPixmap. // // Here we catch the case where we're dragging the pixmap onto the group // // where're already dragging. That's a no-op. // return; // } // } if (m_hoveredGroup) d->groupConnection = KDBindings::ScopedConnection(); m_hoveredGroup = group; if (m_hoveredGroup) { d->groupConnection = group->Controller::dptr()->aboutToBeDeleted.connect([this] { onGroupDestroyed(); }); setHoveredGroupRect(m_hoveredGroup->view()->geometry()); } else { setHoveredGroupRect(Rect()); } updateVisibility(); d->hoveredGroupChanged.emit(m_hoveredGroup); onHoveredGroupChanged(m_hoveredGroup); } bool DropIndicatorOverlay::isHovered() const { return m_draggedWindowIsHovering; } DropLocation DropIndicatorOverlay::currentDropLocation() const { return m_currentDropLocation; } KDDockWidgets::Location DropIndicatorOverlay::multisplitterLocationFor(DropLocation dropLoc) { switch (dropLoc) { case DropLocation_None: return KDDockWidgets::Location_None; case DropLocation_Left: case DropLocation_OutterLeft: return KDDockWidgets::Location_OnLeft; case DropLocation_OutterTop: case DropLocation_Top: return KDDockWidgets::Location_OnTop; case DropLocation_OutterRight: case DropLocation_Right: return KDDockWidgets::Location_OnRight; case DropLocation_OutterBottom: case DropLocation_Bottom: return KDDockWidgets::Location_OnBottom; case DropLocation_Center: return KDDockWidgets::Location_None; case DropLocation_Inner: case DropLocation_Outter: case DropLocation_Horizontal: case DropLocation_Vertical: KDDW_ERROR("Unexpected drop location={}", dropLoc); break; } return KDDockWidgets::Location_None; } bool DropIndicatorOverlay::dropIndicatorVisible(DropLocation dropLoc) const { if (dropLoc == DropLocation_None) return false; WindowBeingDragged *windowBeingDragged = DragController::instance()->windowBeingDragged(); if (!windowBeingDragged) return false; const Core::DockWidget::List source = windowBeingDragged->dockWidgets(); const Core::DockWidget::List target = m_hoveredGroup ? m_hoveredGroup->dockWidgets() : Core::DockWidget::List(); const bool isInner = dropLoc & DropLocation_Inner; const bool isOutter = dropLoc & DropLocation_Outter; if (isInner) { if (!m_hoveredGroup) return false; } else if (isOutter) { // If there's only 1 group in the layout, the outer indicators are redundant, as they do the // same thing as the internal ones. But there might be another window obscuring our target, // so it's useful to show the outer indicators in this case const bool isTheOnlyGroup = m_hoveredGroup && m_hoveredGroup->isTheOnlyGroup(); if (isTheOnlyGroup && !DockRegistry::self()->isProbablyObscured(m_hoveredGroup->view()->window(), windowBeingDragged)) return false; } else if (dropLoc == DropLocation_Center) { if (!m_hoveredGroup || !m_hoveredGroup->isDockable()) return false; // Only allow to dock to center if the affinities match if (!DockRegistry::self()->affinitiesMatch(m_hoveredGroup->affinities(), windowBeingDragged->affinities())) return false; } else { KDDW_ERROR("Unknown drop indicator location={}", dropLoc); return false; } if (auto dropIndicatorAllowedFunc = Config::self().dropIndicatorAllowedFunc()) { DropArea *dropArea = DragController::instance()->dropAreaUnderCursor(); if (!dropIndicatorAllowedFunc(dropLoc, source, target, dropArea)) return false; } return true; } void DropIndicatorOverlay::onGroupDestroyed() { setHoveredGroup(nullptr); } DropIndicatorOverlay::Private *DropIndicatorOverlay::dptr() const { return d; } void DropIndicatorOverlay::onHoveredGroupChanged(Core::Group *) { } void DropIndicatorOverlay::setCurrentDropLocation(DropLocation location) { if (m_currentDropLocation != location) { m_currentDropLocation = location; d->currentDropLocationChanged.emit(); } } DropLocation DropIndicatorOverlay::hover(Point globalPos) { const DropLocation loc = hover_impl(globalPos); setCurrentDropLocation(loc); return loc; } void DropIndicatorOverlay::setHoveredGroupRect(Rect rect) { if (m_hoveredGroupRect != rect) { m_hoveredGroupRect = rect; d->hoveredGroupRectChanged.emit(); } } void DropIndicatorOverlay::removeHover() { setWindowBeingDragged(false); setCurrentDropLocation(DropLocation_None); } Group *DropIndicatorOverlay::hoveredGroup() const { return m_hoveredGroup; } void DropIndicatorOverlay::updateVisibility() { }