MuseScore/src/appshell/view/dockwindow/internal/dockbase.cpp

647 lines
14 KiB
C++

/*
* 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 <https://www.gnu.org/licenses/>.
*/
#include "dockbase.h"
#include <QRect>
#include <QTimer>
#include <QAction>
#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<int>(m_properties.location);
}
Location DockBase::location() const
{
return m_properties.location;
}
QVariantList DockBase::dropDestinationsProperty() const
{
return m_dropDestinations;
}
QList<DropDestination> DockBase::dropDestinations() const
{
QList<DropDestination> result;
for (const QVariant& obj : m_dropDestinations) {
QVariantMap map = obj.toMap();
DropDestination destination;
destination.dock = map["dock"].value<DockBase*>();
if (map.contains("dropLocation")) {
destination.dropLocation = static_cast<Location>(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>(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<const KDDockWidgets::FrameQuick*>(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<QMetaObject::Connection>();
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;
}