Files
KDDockWidgets/src/private/multisplitter/MultiSplitterLayout.cpp
Sergio Martins 7198bdf826 wip
2020-04-22 20:31:54 +01:00

456 lines
13 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
using namespace KDDockWidgets;
using namespace Layouting;
MultiSplitterLayout::MultiSplitterLayout(MultiSplitter *parent)
: QObject(parent)
, m_multiSplitter(parent)
, m_rootItem(new ItemContainer(parent))
{
Q_ASSERT(parent);
DockRegistry::self()->registerLayout(this);
setSize(parent->size());
qCDebug(multisplittercreation()) << "MultiSplitter";
connect(m_rootItem, &ItemContainer::numItemsChanged, this, &MultiSplitterLayout::widgetCountChanged);
connect(m_rootItem, &ItemContainer::numVisibleItemsChanged, this, &MultiSplitterLayout::visibleWidgetCountChanged);
connect(m_rootItem, &ItemContainer::minSizeChanged, this, [this] {
Q_EMIT minimumSizeChanged(minimumSize());
});
clear();
// Initialize min size
updateSizeConstraints();
m_inCtor = false;
}
MultiSplitterLayout::~MultiSplitterLayout()
{
qCDebug(multisplittercreation) << "~MultiSplitter" << this;
m_inDestructor = true;
const auto anchors = m_anchors;
qDeleteAll(anchors);
if (m_rootItem->hostWidget() == multiSplitter())
delete m_rootItem;
DockRegistry::self()->unregisterLayout(this);
}
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=" << Layouting::widgetMinSize(w)
<< "; 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;
Item *newItem = nullptr;
Frame::List frames = framesFrom(w);
unrefOldPlaceholders(frames);
if (frame) {
newItem = new Item(multiSplitter());
newItem->setFrame(frame);
} else if (auto dw = qobject_cast<DockWidgetBase*>(w)) {
newItem = new Item(multiSplitter());
frame = new Frame();
frame->addWidget(dw);
newItem->setFrame(frame);
} else if (auto ms = qobject_cast<MultiSplitter*>(w)) {
newItem = ms->multiSplitterLayout()->rootItem();
Q_ASSERT(newItem->hostWidget() != multiSplitter());
newItem->setHostWidget(multiSplitter());
delete ms;
}
Q_ASSERT(!newItem->geometry().isEmpty());
relativeTo->insertItem(newItem, Layouting::Location(location));
if (option & AddingOption_StartHidden) {
// TODO: Make the layouting engine support this out of the box, to reduce possible flicker
newItem->parentContainer()->removeItem(newItem, /*hard delete=*/ false);
}
}
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;
item->parentContainer()->removeItem(item);
Q_EMIT widgetRemoved(item); // TODO Remove.
}
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
{
return m_rootItem->itemAt_recursive(p);
}
void MultiSplitterLayout::clear()
{
m_rootItem->clear();
}
int MultiSplitterLayout::visibleCount() const
{
return m_rootItem->visibleCount_recursive();
}
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" << minimumSize()
<< "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
{
const Item::List items = m_rootItem->items_recursive();
Frame::List result;
result.reserve(items.size());
for (Item *item : 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(DockWidgetBase *dw, Item *item, int tabIndex)
{
if (item->isPlaceholder()) {
Frame *newFrame = Config::self().frameworkWidgetFactory()->createFrame(multiSplitter());
item->restore(newFrame);
}
Frame *frame = qobject_cast<Frame*>(item->frame());
Q_ASSERT(frame);
if (tabIndex != -1 && frame->dockWidgetCount() >= tabIndex) {
frame->insertWidget(dw, tabIndex);
} else {
frame->addWidget(dw);
}
frame->setVisible(true);
}
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});
}
}
QSize MultiSplitterLayout::minimumSize() const
{
return m_rootItem->minSize();
}
int MultiSplitterLayout::length(Qt::Orientation o) const
{
return o == Qt::Vertical ? width()
: height();
}
void MultiSplitterLayout::setMinimumSize(QSize sz)
{
if (sz != m_rootItem->minSize()) {
setSize(size().expandedTo(m_rootItem->minSize())); // Increase size in case we need to
m_rootItem->setMinSize(sz);
}
qCDebug(sizing) << Q_FUNC_INFO << "minSize = " << m_rootItem->minSize();
}
const ItemList MultiSplitterLayout::items() const
{
return m_rootItem->items_recursive();
}
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);
const QSize min = Layouting::widgetMinSize(widget);
if (relativeTo) {
ItemContainer *container = relativeTo->parentContainer();
QRect rect = container->suggestedDropRect(min, relativeTo, Layouting::Location(location));
return container->mapToRoot(rect);
} else {
return m_rootItem->suggestedDropRect(min, nullptr, Layouting::Location(location));
}
}
bool MultiSplitterLayout::deserialize(const LayoutSaver::MultiSplitterLayout &)
{
return true;
}
LayoutSaver::MultiSplitterLayout MultiSplitterLayout::serialize() const
{
LayoutSaver::MultiSplitterLayout l;
return l;
}