Files
KDDockWidgets/src/private/multisplitter/MultiSplitterLayout.cpp
Sergio Martins 1c16eff3e1 wip
2020-04-03 20:03:24 +01:00

503 lines
14 KiB
C++

/*
This file is part of KDDockWidgets.
Copyright (C) 2018-2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Author: Sérgio Martins <sergio.martins@kdab.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
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 <http://www.gnu.org/licenses/>.
*/
#include "MultiSplitterLayout_p.h"
#include "Logging_p.h"
#include "MultiSplitter_p.h"
#include "Frame_p.h"
#include "FloatingWindow_p.h"
#include "DockWidgetBase.h"
#include "LastPosition_p.h"
#include "DockRegistry_p.h"
#include "Config.h"
#include "Separator_p.h"
#include "FrameworkWidgetFactory.h"
#include "LayoutSaver.h"
#include <QAction>
#include <QEvent>
#include <QtMath>
#include <QScopedValueRollback>
#define INDICATOR_MINIMUM_LENGTH 100
#define KDDOCKWIDGETS_MIN_WIDTH 80
#define KDDOCKWIDGETS_MIN_HEIGHT 90
using namespace KDDockWidgets;
using namespace Layouting;
const QString MultiSplitterLayout::s_magicMarker = QStringLiteral("bac9948e-5f1b-4271-acc5-07f1708e2611");
MultiSplitterLayout::MultiSplitterLayout(MultiSplitter *parent)
: QObject(parent)
, m_multiSplitter(parent)
, m_rootItem(new ItemContainer())
{
Q_ASSERT(parent);
DockRegistry::self()->registerLayout(this);
setSize(parent->size());
qCDebug(multisplittercreation()) << "MultiSplitter";
connect(this, &MultiSplitterLayout::widgetCountChanged, this, [this] {
Q_EMIT visibleWidgetCountChanged(visibleCount());
});
clear();
// Initialize min size
updateSizeConstraints();
m_inCtor = false;
}
MultiSplitterLayout::~MultiSplitterLayout()
{
qCDebug(multisplittercreation) << "~MultiSplitter" << this;
m_inDestructor = true;
const auto anchors = m_anchors;
qDeleteAll(anchors);
delete m_rootItem;
DockRegistry::self()->unregisterLayout(this);
}
/**static*/
QSize MultiSplitterLayout::hardcodedMinimumSize()
{
return QSize(KDDOCKWIDGETS_MIN_WIDTH, KDDOCKWIDGETS_MIN_HEIGHT);
}
MultiSplitter *MultiSplitterLayout::multiSplitter() const
{
return m_multiSplitter;
}
bool MultiSplitterLayout::validateInputs(QWidgetOrQuick *widget,
Location location,
const Frame *relativeToFrame, AddingOption option) const
{
if (!widget) {
qWarning() << Q_FUNC_INFO << "Widget is null";
return false;
}
const bool isDockWidget = qobject_cast<DockWidgetBase*>(widget);
const bool isStartHidden = option & AddingOption_StartHidden;
if (!qobject_cast<Frame*>(widget) && !qobject_cast<MultiSplitter*>(widget) && !isDockWidget) {
qWarning() << "Unknown widget type" << widget;
return false;
}
if (isDockWidget != isStartHidden) {
qWarning() << "Wrong parameters" << isDockWidget << isStartHidden;
return false;
}
if (relativeToFrame && relativeToFrame == widget) {
qWarning() << "widget can't be relative to itself";
return false;
}
Item *item = itemForFrame(qobject_cast<Frame*>(widget));
if (contains(item)) {
qWarning() << "MultiSplitterLayout::addWidget: Already contains" << widget;
return false;
}// TODO: check for widget changing parent
if (location == Location_None) {
qWarning() << "MultiSplitterLayout::addWidget: not adding to location None";
return false;
}
const bool relativeToThis = relativeToFrame == nullptr;
Item *relativeToItem = itemForFrame(relativeToFrame);
if (!relativeToThis && !contains(relativeToItem)) {
qWarning() << "MultiSplitterLayout::addWidget: Doesn't contain relativeTo:"
<< "; relativeToFrame=" << relativeToFrame
<< "; relativeToItem=" << relativeToItem
<< "; options=" << option;
return false;
}
return true;
}
void MultiSplitterLayout::addWidget(QWidgetOrQuick *w, Location location, Frame *relativeToWidget, AddingOption option)
{
auto frame = qobject_cast<Frame*>(w);
qCDebug(addwidget) << Q_FUNC_INFO << w
<< "; location=" << locationStr(location)
<< "; relativeTo=" << relativeToWidget
<< "; size=" << size()
<< "; w.size=" << w->size()
<< "; w.min=" << widgetMinLength(w, Layouting::orientationForLocation(Layouting::Location(location)))
<< "; frame=" << frame
<< "; option=" << option;
if (itemForFrame(frame) != nullptr) {
// Item already exists, remove it.
// Changing the frame parent will make the item clean itself up. It turns into a placeholder and is removed by unrefOldPlaceholders
frame->setParent(nullptr); // so ~Item doesn't delete it
frame->setLayoutItem(nullptr); // so Item is destroyed, as there's no refs to it
}
// Make some sanity checks:
if (!validateInputs(w, location, relativeToWidget, option))
return;
Item *relativeTo = itemForFrame(relativeToWidget);
if (!relativeTo)
relativeTo = m_rootItem;
auto newItem = new Item();
newItem->setFrame(w);
w->setParent(multiSplitter());
relativeTo->insertItem(newItem, Layouting::Location(location));
unrefOldPlaceholders(framesFrom(w));
//Item *relativeToItem = itemForFrame(relativeToWidget);
}
void MultiSplitterLayout::addItems_internal(const ItemList &items, bool updateConstraints, bool emitSignal)
{
m_items << items;
if (updateConstraints)
updateSizeConstraints();
for (auto item : items) {
if (item->frame()) {
item->setIsVisible(true);
item->frame()->installEventFilter(this);
Q_EMIT widgetAdded(item);
}
}
if (emitSignal)
Q_EMIT widgetCountChanged(m_items.size());
}
QString MultiSplitterLayout::affinityName() const
{
if (auto ms = multiSplitter()) {
if (auto mainWindow = ms->mainWindow()) {
return mainWindow->affinityName();
} else if (auto fw = ms->floatingWindow()) {
return fw->affinityName();
}
}
return QString();
}
void MultiSplitterLayout::addMultiSplitter(MultiSplitter *sourceMultiSplitter,
Location location,
Frame *relativeTo)
{
qCDebug(addwidget) << Q_FUNC_INFO << sourceMultiSplitter << location << relativeTo;
addWidget(sourceMultiSplitter, location, relativeTo);
}
void MultiSplitterLayout::removeItem(Item *item)
{
if (!item || m_inDestructor)
return;
Q_ASSERT(item != m_rootItem);
if (!item->isPlaceholder())
item->frame()->removeEventFilter(this);
item->parentContainer()->removeItem(item);
Q_EMIT widgetRemoved(item);
Q_EMIT widgetCountChanged(m_items.size());
}
bool MultiSplitterLayout::contains(const Item *item) const
{
return m_rootItem->contains_recursive(item);
}
bool MultiSplitterLayout::contains(const Frame *frame) const
{
return itemForFrame(frame) != nullptr;
}
Item *MultiSplitterLayout::itemAt(QPoint p) const
{
for (Layouting::Item *item : m_items) {
if (!item->isPlaceholder() && item->geometry().contains(p))
return item;
}
return nullptr;
}
void MultiSplitterLayout::clear()
{
const int oldCount = count();
const int oldVisibleCount = visibleCount();
m_rootItem->clear();
if (oldCount > 0)
Q_EMIT widgetCountChanged(0);
if (oldVisibleCount > 0)
Q_EMIT visibleWidgetCountChanged(0);
}
int MultiSplitterLayout::visibleCount() const
{
return m_rootItem->visibleCount();
}
int MultiSplitterLayout::placeholderCount() const
{
return count() - visibleCount();
}
void MultiSplitterLayout::setAnchorBeingDragged(Anchor *anchor)
{
m_anchorBeingDragged = anchor;
}
int MultiSplitterLayout::numVisibleAnchors() const
{
int count = 0;
for (Anchor *a : m_anchors) {
if (a->separatorWidget()->isVisible())
count++;
}
return count;
}
void MultiSplitterLayout::updateSizeConstraints()
{
const QSize newMinSize = m_rootItem->minSize();
qCDebug(sizing) << Q_FUNC_INFO << "Updating size constraints from" << m_minSize
<< "to" << newMinSize;
setMinimumSize(newMinSize);
}
void MultiSplitterLayout::emitVisibleWidgetCountChanged()
{
if (!m_inDestructor)
Q_EMIT visibleWidgetCountChanged(visibleCount());
}
Item *MultiSplitterLayout::itemForFrame(const Frame *frame) const
{
if (!frame)
return nullptr;
return m_rootItem->itemForFrame(frame);
}
Frame::List MultiSplitterLayout::framesFrom(QWidgetOrQuick *frameOrMultiSplitter) const
{
if (auto frame = qobject_cast<Frame*>(frameOrMultiSplitter))
return { frame };
if (auto msw = qobject_cast<MultiSplitter*>(frameOrMultiSplitter))
return msw->multiSplitterLayout()->frames();
return {};
}
Frame::List MultiSplitterLayout::frames() const
{
Frame::List result;
for (Item *item : m_items) {
if (auto f = static_cast<Frame*>(item->frame()))
result.push_back(f);
}
return result;
}
QVector<DockWidgetBase *> MultiSplitterLayout::dockWidgets() const
{
DockWidgetBase::List result;
const Frame::List frames = this->frames();
for (Frame *frame : frames)
result << frame->dockWidgets();
return result;
}
void MultiSplitterLayout::restorePlaceholder(Item *, int /*tabIndex*/)
{
// TODO
}
bool MultiSplitterLayout::checkSanity() const
{
return m_rootItem->checkSanity();
}
void MultiSplitterLayout::unrefOldPlaceholders(const Frame::List &framesBeingAdded) const
{
for (Frame *frame : framesBeingAdded) {
for (DockWidgetBase *dw : frame->dockWidgets()) {
if (Item *existingItem = dw->lastPosition()->layoutItem()) {
if (contains(existingItem)) { // We're only interested in placeholders from this layout
dw->lastPosition()->removePlaceholders(this);
}
}
}
}
}
void MultiSplitterLayout::dumpDebug() const
{
m_rootItem->dumpLayout();
}
void MultiSplitterLayout::setSize(QSize size)
{
if (size != this->size()) {
m_rootItem->resize(size);
m_resizing = true;
Q_EMIT sizeChanged(size);
m_resizing = false; // TODO: m_resizing needed ?
}
}
void MultiSplitterLayout::setContentLength(int value, Qt::Orientation o)
{
if (o == Qt::Vertical) {
// Setting the width
setSize({value, size().height()});
} else {
// Setting the height
setSize({size().width(), value});
}
}
int MultiSplitterLayout::length(Qt::Orientation o) const
{
return o == Qt::Vertical ? width()
: height();
}
void MultiSplitterLayout::setMinimumSize(QSize sz)
{
if (sz != m_rootItem->minSize()) {
m_rootItem->setMinSize(sz);
setSize(size().expandedTo(m_rootItem->minSize())); // Increase size incase we need to
Q_EMIT minimumSizeChanged(sz);
}
qCDebug(sizing) << Q_FUNC_INFO << "minSize = " << m_minSize;
}
const ItemList MultiSplitterLayout::items() const
{
return m_items;
}
Item *MultiSplitterLayout::rootItem() const
{
return m_rootItem;
}
QRect MultiSplitterLayout::rectForDrop(const QWidgetOrQuick *widget, Location location, const Item *relativeTo) const
{
// TODO
Q_UNUSED(widget);
Q_UNUSED(location);
Q_UNUSED(relativeTo);
return QRect();
}
bool MultiSplitterLayout::eventFilter(QObject *o, QEvent *e)
{
if (m_inDestructor || e->spontaneous() || !m_multiSplitter)
return false;
if (!m_multiSplitter->isVisible()) {
// The whole MultiSplitter isn't visible, don't bother. It probably even is being hidden by ~QMainWindow().
return false;
}
QWidget *w = qobject_cast<QWidget*>(o);
if (!w || !w->testAttribute(Qt::WA_WState_ExplicitShowHide)) {
// We only care about explicit show/hide by the developer
return false;
}
return false;
}
bool MultiSplitterLayout::deserialize(const LayoutSaver::MultiSplitterLayout &msl)
{
clear();
ItemList items;
items.reserve(msl.items.size());
for (const auto &i : qAsConst(msl.items)) {
Q_UNUSED(i);
//Item *item = deserialize(); TODO
//items.push_back(item);
}
m_items = items; // Set the items, so Anchor::deserialize() can set the side1 and side2 items
m_items.clear(); // Now properly set the items, which installs needed event filters, etc.
addItems_internal(items, false, false); // Add the items only after we have the static anchors set
setSize(msl.size);
setMinimumSize(msl.minSize);
if (!m_items.isEmpty())
Q_EMIT widgetCountChanged(m_items.size());
// The main window that we're restoring can have more stuff now (other-toolbars etc), so by
// having restored its geometry it can mean our dockwidget layout is now different, so update
// its content size if needed
Q_EMIT minimumSizeChanged(m_minSize);
if (size() != multiSplitter()->size()) {
setSize(multiSplitter()->size());
}
return true;
}
LayoutSaver::MultiSplitterLayout MultiSplitterLayout::serialize() const
{
LayoutSaver::MultiSplitterLayout l;
l.size = size();
l.minSize = minimumSize();
//for (Item *item : m_items) TODO
//l.items.push_back(item->serialize());
return l;
}