/* * SPDX-License-Identifier: GPL-3.0-only * MuseScore-CLA-applies * * MuseScore * Music Composition & Notation * * Copyright (C) 2021 MuseScore BVBA and others * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "dockbase.h" #include #include #include #include "log.h" #include "thirdparty/KDDockWidgets/src/DockWidgetQuick.h" #include "thirdparty/KDDockWidgets/src/private/quick/FrameQuick_p.h" #include "thirdparty/KDDockWidgets/src/private/FloatingWindow_p.h" namespace mu::dock { static QSize adjustSizeByConstraints(const QSize& size, const QSize& min, const QSize& max) { return size.expandedTo(min).boundedTo(max); } class DockWidgetImpl : public KDDockWidgets::DockWidgetQuick { public: DockWidgetImpl(const QString& uniqueName) : KDDockWidgets::DockWidgetQuick(uniqueName) { setObjectName(uniqueName); } QSize minimumSize() const override { return DockWidgetBase::minimumSize(); } QSize maximumSize() const override { return DockWidgetBase::maximumSize(); } }; } using namespace mu::dock; DockBase::DockBase(DockType type, QQuickItem* parent) : QQuickItem(parent) { Q_ASSERT(type != DockType::Undefined); m_properties.type = type; m_properties.floatable = true; m_properties.closable = true; m_properties.resizable = true; m_properties.separatorsVisible = true; } QString DockBase::title() const { return m_title; } int DockBase::minimumWidth() const { return m_minimumWidth; } int DockBase::minimumHeight() const { return m_minimumHeight; } int DockBase::maximumWidth() const { return m_maximumWidth; } int DockBase::maximumHeight() const { return m_maximumHeight; } int DockBase::contentWidth() const { return m_contentWidth; } int DockBase::contentHeight() const { return m_contentHeight; } QSize DockBase::preferredSize() const { return QSize(width(), height()); } int DockBase::locationProperty() const { return static_cast(m_properties.location); } Location DockBase::location() const { return m_properties.location; } QVariantList DockBase::dropDestinationsProperty() const { return m_dropDestinations; } QList DockBase::dropDestinations() const { QList result; for (const QVariant& obj : m_dropDestinations) { QVariantMap map = obj.toMap(); DropDestination destination; destination.dock = map["dock"].value(); if (map.contains("dropLocation")) { destination.dropLocation = static_cast(map["dropLocation"].toInt()); } else { destination.dropLocation = Location::Left; } if (map.contains("dropDistance")) { destination.dropDistance = map["dropDistance"].toInt(); } result << destination; } return result; } bool DockBase::closable() const { return m_properties.closable; } bool DockBase::resizable() const { return m_properties.resizable; } bool DockBase::separatorsVisible() const { return m_properties.separatorsVisible; } bool DockBase::floatable() const { return m_properties.floatable; } bool DockBase::floating() const { return m_floating; } bool DockBase::inited() const { return m_inited; } DockType DockBase::type() const { return m_properties.type; } KDDockWidgets::DockWidgetQuick* DockBase::dockWidget() const { return m_dockWidget; } void DockBase::setTitle(const QString& title) { if (title == m_title) { return; } m_title = title; emit titleChanged(); } void DockBase::setMinimumWidth(int width) { if (width == minimumWidth()) { return; } m_minimumWidth = width; emit minimumSizeChanged(); } void DockBase::setMinimumHeight(int height) { if (height == minimumHeight()) { return; } m_minimumHeight = height; emit minimumSizeChanged(); } void DockBase::setMaximumWidth(int width) { if (width == maximumWidth()) { return; } m_maximumWidth = width; emit maximumSizeChanged(); } void DockBase::setMaximumHeight(int height) { if (height == maximumHeight()) { return; } m_maximumHeight = height; emit maximumSizeChanged(); } void DockBase::setContentWidth(int width) { if (m_contentWidth == width) { return; } m_contentWidth = width; emit contentSizeChanged(); } void DockBase::setContentHeight(int height) { if (m_contentHeight == height) { return; } m_contentHeight = height; emit contentSizeChanged(); } void DockBase::setLocation(int location) { if (location == m_properties.location) { return; } m_properties.location = static_cast(location); emit locationChanged(); } void DockBase::setDropDestinations(const QVariantList& destinations) { if (m_dropDestinations == destinations) { return; } m_dropDestinations = destinations; emit dropDestinationsChanged(); } void DockBase::setFloatable(bool floatable) { if (floatable == m_properties.floatable) { return; } m_properties.floatable = floatable; emit floatableChanged(); } void DockBase::setClosable(bool closable) { if (closable == m_properties.closable) { return; } m_properties.closable = closable; emit closableChanged(); } void DockBase::setResizable(bool resizable) { if (resizable == m_properties.resizable) { return; } m_properties.resizable = resizable; emit resizableChanged(); } void DockBase::setSeparatorsVisible(bool visible) { if (visible == m_properties.separatorsVisible) { return; } m_properties.separatorsVisible = visible; emit separatorsVisibleChanged(); } void DockBase::setFloating(bool floating) { IF_ASSERT_FAILED(m_dockWidget) { return; } m_dockWidget->setFloating(floating); } void DockBase::init() { IF_ASSERT_FAILED(m_dockWidget) { return; } setVisible(m_dockWidget->isOpen()); setInited(true); } void DockBase::deinit() { setInited(false); } bool DockBase::isOpen() const { IF_ASSERT_FAILED(m_dockWidget) { return false; } return m_dockWidget->isOpen(); } void DockBase::open() { TRACEFUNC; IF_ASSERT_FAILED(m_dockWidget) { return; } m_dockWidget->show(); setVisible(true); } void DockBase::close() { TRACEFUNC; IF_ASSERT_FAILED(m_dockWidget) { return; } m_dockWidget->forceClose(); setVisible(false); } void DockBase::showHighlighting(const QRect& highlightingRect) { if (highlightingRect == m_properties.highlightingRect) { return; } m_properties.highlightingRect = highlightingRect; writeProperties(); } void DockBase::hideHighlighting() { showHighlighting(QRect()); } QRect DockBase::frameGeometry() const { if (m_dockWidget && m_dockWidget->isVisible()) { return m_dockWidget->frameGeometry(); } return QRect(); } void DockBase::resetToDefault() { setVisible(m_defaultVisibility); } void DockBase::resize(int width, int height) { TRACEFUNC; if (width == this->width() && height == this->height()) { return; } if (!m_dockWidget) { return; } auto frame = static_cast(m_dockWidget->frame()); if (!frame) { return; } const Layouting::Item* item = frame->layoutItem(); if (!item) { return; } Layouting::ItemBoxContainer* parentContainer = item->parentBoxContainer(); if (!parentContainer) { return; } width = qBound(m_minimumWidth, width, m_maximumWidth); height = qBound(m_minimumHeight, height, m_maximumHeight); QSize minSizeBackup = QSize(m_minimumWidth, m_minimumHeight); QSize maxSizeBackup = QSize(m_maximumWidth, m_maximumHeight); m_minimumWidth = width; m_maximumWidth = width; const QQuickItem* visualItem = frame->visualItem(); int extraHeight = visualItem ? visualItem->property("nonContentsHeight").toInt() : 0; height += extraHeight; m_minimumHeight = height; m_maximumHeight = height; applySizeConstraints(); parentContainer->layoutEqually(); m_minimumWidth = minSizeBackup.width(); m_maximumWidth = maxSizeBackup.width(); m_minimumHeight = minSizeBackup.height(); m_maximumHeight = maxSizeBackup.height(); applySizeConstraints(); } void DockBase::componentComplete() { TRACEFUNC; QQuickItem::componentComplete(); auto children = childItems(); IF_ASSERT_FAILED_X(children.size() == 1, "Dock must have only one child as its content!") { return; } QQuickItem* content = children.first(); IF_ASSERT_FAILED(content) { return; } if (content->objectName().isEmpty()) { content->setObjectName(objectName() + "_content"); } m_dockWidget = new DockWidgetImpl(objectName()); m_dockWidget->setWidget(content); m_dockWidget->setTitle(m_title); writeProperties(); listenFloatingChanges(); connect(m_dockWidget, &KDDockWidgets::DockWidgetQuick::widthChanged, this, [this]() { if (m_dockWidget) { setWidth(m_dockWidget->width()); } }); connect(m_dockWidget, &KDDockWidgets::DockWidgetQuick::heightChanged, this, [this]() { if (m_dockWidget) { setHeight(m_dockWidget->height()); } }); connect(this, &DockBase::minimumSizeChanged, this, &DockBase::applySizeConstraints); connect(this, &DockBase::maximumSizeChanged, this, &DockBase::applySizeConstraints); m_defaultVisibility = isVisible(); } void DockBase::applySizeConstraints() { if (!m_dockWidget) { return; } TRACEFUNC; int minWidth = m_minimumWidth > 0 ? m_minimumWidth : m_dockWidget->minimumWidth(); int minHeight = m_minimumHeight > 0 ? m_minimumHeight : m_dockWidget->minimumHeight(); int maxWidth = m_maximumWidth > 0 ? m_maximumWidth : m_dockWidget->maximumWidth(); int maxHeight = m_maximumHeight > 0 ? m_maximumHeight : m_dockWidget->maximumHeight(); QSize minimumSize(minWidth, minHeight); QSize maximumSize(maxWidth, maxHeight); if (!m_properties.resizable) { maximumSize = minimumSize; } if (auto frame = m_dockWidget->frame()) { frame->setMinimumSize(minimumSize); frame->setMaximumSize(maximumSize); } m_dockWidget->setMinimumSize(minimumSize); m_dockWidget->setMaximumSize(maximumSize); if (auto window = m_dockWidget->floatingWindow()) { window->setMinimumSize(minimumSize); window->setMaximumSize(maximumSize); QSize winSize = adjustSizeByConstraints(window->frameGeometry().size(), minimumSize, maximumSize); QRect winRect(window->dragRect().topLeft(), winSize); window->setGeometry(winRect); if (auto layout = window->layoutWidget()) { layout->setLayoutSize(winSize); } } } void DockBase::listenFloatingChanges() { IF_ASSERT_FAILED(m_dockWidget) { return; } auto frameConn = std::make_shared(); connect(m_dockWidget, &KDDockWidgets::DockWidgetQuick::parentChanged, this, [this, frameConn]() { if (frameConn) { disconnect(*frameConn); doSetFloating(false); } if (!m_dockWidget || !m_dockWidget->parentItem()) { return; } const KDDockWidgets::Frame* frame = m_dockWidget->frame(); if (!frame) { return; } //! NOTE: window will be available later //! So it is important to apply size constraints //! and emit floatingChanged() after that QTimer::singleShot(0, this, [this]() { updateFloatingStatus(); }); *frameConn = connect(frame, &KDDockWidgets::Frame::isInMainWindowChanged, this, &DockBase::updateFloatingStatus, Qt::UniqueConnection); }); connect(m_dockWidget->toggleAction(), &QAction::toggled, this, [this]() { if (!isOpen()) { doSetFloating(false); } }); } void DockBase::updateFloatingStatus() { bool floating = m_dockWidget && m_dockWidget->floatingWindow(); doSetFloating(floating); applySizeConstraints(); } void DockBase::doSetFloating(bool floating) { if (m_floating == floating) { return; } m_floating = floating; emit floatingChanged(); } void DockBase::writeProperties() { if (m_dockWidget) { writePropertiesToObject(m_properties, *m_dockWidget); } } void DockBase::setInited(bool inited) { if (m_inited == inited) { return; } m_inited = inited; emit initedChanged(); } bool DropDestination::operator==(const DropDestination& dest) const { return dock == dest.dock && dropLocation == dest.dropLocation && dropDistance == dest.dropDistance; } bool DropDestination::isValid() const { return dock != nullptr; } void DropDestination::clear() { dock = nullptr; dropLocation = Location::Undefined; dropDistance = 0; }