415 lines
10 KiB
C++
415 lines
10 KiB
C++
/*
|
|
This file is part of KDDockWidgets.
|
|
|
|
SPDX-FileCopyrightText: 2020 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
|
Author: Sérgio Martins <sergio.martins@kdab.com>
|
|
|
|
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
|
|
|
|
Contact KDAB at <info@kdab.com> for commercial licensing options.
|
|
*/
|
|
|
|
#include "Layout.h"
|
|
#include "Layout_p.h"
|
|
#include "LayoutSaver_p.h"
|
|
#include "Position_p.h"
|
|
#include "Config.h"
|
|
#include "Platform.h"
|
|
#include "ViewFactory.h"
|
|
#include "Utils_p.h"
|
|
#include "View_p.h"
|
|
#include "Logging_p.h"
|
|
#include "ScopedValueRollback_p.h"
|
|
#include "DropArea.h"
|
|
#include "DockWidget_p.h"
|
|
#include "Group.h"
|
|
#include "FloatingWindow.h"
|
|
#include "MainWindow.h"
|
|
#include "layouting/Item_p.h"
|
|
|
|
#include <unordered_map>
|
|
|
|
using namespace KDDockWidgets;
|
|
using namespace KDDockWidgets::Core;
|
|
|
|
|
|
Layout::Layout(ViewType type, View *view)
|
|
: Controller(type, view)
|
|
, d(new Private(this))
|
|
{
|
|
assert(view);
|
|
view->d->layoutInvalidated.connect([this] { updateSizeConstraints(); });
|
|
|
|
view->d->resized.connect(&Layout::onResize, this);
|
|
}
|
|
|
|
Layout::~Layout()
|
|
{
|
|
d->m_minSizeChangedHandler.disconnect();
|
|
|
|
if (d->m_rootItem && !d->m_viewDeleted)
|
|
viewAboutToBeDeleted();
|
|
|
|
delete d;
|
|
}
|
|
|
|
void Layout::viewAboutToBeDeleted()
|
|
{
|
|
if (view()) {
|
|
if (d == d->m_rootItem->host()) {
|
|
delete d->m_rootItem;
|
|
d->m_rootItem = nullptr;
|
|
}
|
|
|
|
d->m_viewDeleted = true;
|
|
}
|
|
}
|
|
|
|
bool Layout::isInMainWindow(bool honourNesting) const
|
|
{
|
|
return mainWindow(honourNesting) != nullptr;
|
|
}
|
|
|
|
Core::MainWindow *Layout::mainWindow(bool honourNesting) const
|
|
{
|
|
if (honourNesting) {
|
|
// This layout might be a MDIArea, nested in DropArea, which is main window.
|
|
if (Controller *c = view()->d->firstParentOfType(ViewType::MainWindow))
|
|
return static_cast<Core::MainWindow *>(c);
|
|
return nullptr;
|
|
} else {
|
|
if (auto pw = view()->parentView()) {
|
|
// Note that if pw is a FloatingWindow then pw->parentWidget() can be a MainWindow too,
|
|
// as it's parented
|
|
if (pw->viewName() == QLatin1String("MyCentralWidget"))
|
|
return pw->parentView()->asMainWindowController();
|
|
|
|
if (auto mw = pw->asMainWindowController())
|
|
return mw;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
Core::FloatingWindow *Layout::floatingWindow() const
|
|
{
|
|
auto parent = view()->rootView();
|
|
return parent ? parent->asFloatingWindowController() : nullptr;
|
|
}
|
|
|
|
void Layout::setRootItem(Core::ItemContainer *root)
|
|
{
|
|
delete d->m_rootItem;
|
|
d->m_rootItem = root;
|
|
d->m_rootItem->numVisibleItemsChanged.connect(
|
|
[this](int count) { d->visibleWidgetCountChanged.emit(count); });
|
|
|
|
d->m_minSizeChangedHandler =
|
|
d->m_rootItem->minSizeChanged.connect([this] { view()->setMinimumSize(layoutMinimumSize()); });
|
|
}
|
|
|
|
Size Layout::layoutMinimumSize() const
|
|
{
|
|
return d->m_rootItem->minSize();
|
|
}
|
|
|
|
Size Layout::layoutMaximumSizeHint() const
|
|
{
|
|
return d->m_rootItem->maxSizeHint();
|
|
}
|
|
|
|
void Layout::setLayoutMinimumSize(Size sz)
|
|
{
|
|
if (sz != d->m_rootItem->minSize()) {
|
|
setLayoutSize(layoutSize().expandedTo(d->m_rootItem->minSize())); // Increase size in case we
|
|
// need to
|
|
d->m_rootItem->setMinSize(sz);
|
|
}
|
|
}
|
|
|
|
Size Layout::layoutSize() const
|
|
{
|
|
return d->m_rootItem->size();
|
|
}
|
|
|
|
void Layout::clearLayout()
|
|
{
|
|
d->m_rootItem->clear();
|
|
}
|
|
|
|
bool Layout::checkSanity() const
|
|
{
|
|
return d->m_rootItem->checkSanity();
|
|
}
|
|
|
|
void Layout::dumpLayout() const
|
|
{
|
|
d->m_rootItem->dumpLayout();
|
|
}
|
|
|
|
void Layout::restorePlaceholder(Core::DockWidget *dw, Core::Item *item, int tabIndex)
|
|
{
|
|
if (item->isPlaceholder()) {
|
|
auto newGroup = new Core::Group(view());
|
|
item->restore(newGroup->asLayoutingGuest());
|
|
}
|
|
|
|
auto group = Group::fromItem(item);
|
|
assert(group);
|
|
if (group->inDtor() || group->beingDeletedLater()) {
|
|
// Known bug. Let's print diagnostics early, as this is usually difficult to debug
|
|
// further up the stack. Will also fatal the tests.
|
|
KDDW_ERROR("Layout::restorePlaceholder: Trying to use a group that's being deleted");
|
|
}
|
|
|
|
if (auto tabIndexFunc = Config::self().dockWidgetTabIndexOverrideFunc()) {
|
|
// The user wishes to control the tabIndex himself
|
|
group->insertWidget(dw, tabIndexFunc(dw, group, tabIndex));
|
|
} else {
|
|
if (tabIndex != -1 && group->dockWidgetCount() >= tabIndex) {
|
|
group->insertWidget(dw, tabIndex);
|
|
} else {
|
|
group->addTab(dw);
|
|
}
|
|
}
|
|
|
|
group->Controller::setVisible(true);
|
|
}
|
|
|
|
void Layout::unrefOldPlaceholders(const Core::Group::List &groupsBeingAdded) const
|
|
{
|
|
for (Core::Group *group : groupsBeingAdded) {
|
|
const auto dws = group->dockWidgets();
|
|
for (Core::DockWidget *dw : dws) {
|
|
dw->d->lastPosition()->removePlaceholders(d);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Layout::setLayoutSize(Size size)
|
|
{
|
|
if (size != layoutSize()) {
|
|
const bool restoreInProgress = LayoutSaver::restoreInProgress();
|
|
ChildrenResizeStrategy strategy = ChildrenResizeStrategy::Percentage;
|
|
const auto *main = mainWindow();
|
|
if (!restoreInProgress && main && main->options() & MainWindowOption_CentralWidgetGetsAllExtraSpace)
|
|
strategy = ChildrenResizeStrategy::GiveDropAreaWithCentralFrameAllExtra;
|
|
|
|
d->m_rootItem->setSize_recursive(size, strategy);
|
|
|
|
if (!d->m_inResizeEvent && !restoreInProgress)
|
|
view()->resize(size);
|
|
}
|
|
}
|
|
|
|
Core::Item::List Layout::items() const
|
|
{
|
|
return d->m_rootItem->items_recursive();
|
|
}
|
|
|
|
bool Layout::containsItem(const Core::Item *item) const
|
|
{
|
|
return d->m_rootItem->contains_recursive(item);
|
|
}
|
|
|
|
bool Layout::containsGroup(const Core::Group *group) const
|
|
{
|
|
return itemForGroup(group) != nullptr;
|
|
}
|
|
|
|
int Layout::count() const
|
|
{
|
|
return d->m_rootItem->count_recursive();
|
|
}
|
|
|
|
int Layout::visibleCount() const
|
|
{
|
|
return d->m_rootItem->visibleCount_recursive();
|
|
}
|
|
|
|
int Layout::placeholderCount() const
|
|
{
|
|
return count() - visibleCount();
|
|
}
|
|
|
|
Core::Item *Layout::itemForGroup(const Core::Group *group) const
|
|
{
|
|
if (!group)
|
|
return nullptr;
|
|
|
|
return d->m_rootItem->itemForView(group->asLayoutingGuest());
|
|
}
|
|
|
|
Core::DockWidget::List Layout::dockWidgets() const
|
|
{
|
|
Core::DockWidget::List dockWidgets;
|
|
const Core::Group::List groups = this->groups();
|
|
for (Core::Group *group : groups)
|
|
dockWidgets.append(group->dockWidgets());
|
|
|
|
return dockWidgets;
|
|
}
|
|
|
|
Core::Group::List Layout::groupsFrom(View *groupOrMultiSplitter) const
|
|
{
|
|
if (auto group = groupOrMultiSplitter->asGroupController())
|
|
return { group };
|
|
|
|
if (auto msw = groupOrMultiSplitter->asDropAreaController())
|
|
return msw->groups();
|
|
|
|
return {};
|
|
}
|
|
|
|
Core::Group::List Layout::groups() const
|
|
{
|
|
const Core::Item::List items = d->m_rootItem->items_recursive();
|
|
|
|
Core::Group::List result;
|
|
result.reserve(items.size());
|
|
|
|
for (Core::Item *item : items) {
|
|
if (auto group = Group::fromItem(item)) {
|
|
result.push_back(group);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void Layout::removeItem(Core::Item *item)
|
|
{
|
|
if (!item) {
|
|
KDDW_ERROR("nullptr item");
|
|
return;
|
|
}
|
|
|
|
item->parentContainer()->removeItem(item);
|
|
}
|
|
|
|
void Layout::updateSizeConstraints()
|
|
{
|
|
const Size newMinSize = d->m_rootItem->minSize();
|
|
setLayoutMinimumSize(newMinSize);
|
|
}
|
|
|
|
bool Layout::deserialize(const LayoutSaver::MultiSplitter &l)
|
|
{
|
|
std::unordered_map<QString, LayoutingGuest *> groups;
|
|
for (const auto &it : l.groups) {
|
|
const LayoutSaver::Group &group = it.second;
|
|
Core::Group *f = Core::Group::deserialize(group);
|
|
if (!f)
|
|
return false;
|
|
|
|
assert(!group.id.isEmpty());
|
|
groups[group.id] = f->asLayoutingGuest();
|
|
}
|
|
|
|
d->m_rootItem->fillFromJson(l.layout, groups);
|
|
updateSizeConstraints();
|
|
|
|
const Size newLayoutSize = view()->size().expandedTo(d->m_rootItem->minSize());
|
|
|
|
d->m_rootItem->setSize_recursive(newLayoutSize);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Layout::onResize(Size newSize)
|
|
{
|
|
ScopedValueRollback resizeGuard(d->m_inResizeEvent, true); // to avoid re-entrancy
|
|
|
|
if (!LayoutSaver::restoreInProgress()) {
|
|
// don't resize anything while we're restoring the layout
|
|
setLayoutSize(newSize);
|
|
}
|
|
|
|
return false; // So QWidget::resizeEvent is called
|
|
}
|
|
|
|
LayoutSaver::MultiSplitter Layout::serialize() const
|
|
{
|
|
LayoutSaver::MultiSplitter l;
|
|
d->m_rootItem->to_json(l.layout);
|
|
const Core::Item::List items = d->m_rootItem->items_recursive();
|
|
l.groups.reserve(size_t(items.size()));
|
|
for (Core::Item *item : items) {
|
|
if (!item->isContainer()) {
|
|
if (auto group = Group::fromItem(item)) {
|
|
l.groups[group->view()->d->id()] = group->serialize();
|
|
}
|
|
}
|
|
}
|
|
|
|
return l;
|
|
}
|
|
|
|
Core::DropArea *Layout::asDropArea() const
|
|
{
|
|
return view()->asDropAreaController();
|
|
}
|
|
|
|
MDILayout *Layout::asMDILayout() const
|
|
{
|
|
if (auto v = view())
|
|
return v->asMDILayoutController();
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
Core::ItemContainer *Layout::rootItem() const
|
|
{
|
|
return d->m_rootItem;
|
|
}
|
|
|
|
void Layout::onCloseEvent(CloseEvent *e)
|
|
{
|
|
e->accept(); // Accepted by default (will close unless ignored)
|
|
|
|
const Core::Group::List groups = this->groups();
|
|
for (Core::Group *group : groups) {
|
|
group->view()->d->requestClose(e);
|
|
if (!e->isAccepted())
|
|
break; // Stop when the first group prevents closing
|
|
}
|
|
}
|
|
|
|
LayoutingHost *Layout::asLayoutingHost() const
|
|
{
|
|
return d;
|
|
}
|
|
|
|
Layout::Private *Layout::d_ptr()
|
|
{
|
|
return d;
|
|
}
|
|
|
|
bool Layout::Private::supportsHonouringLayoutMinSize() const
|
|
{
|
|
if (auto window = q->view()->window()) {
|
|
return window->supportsHonouringLayoutMinSize();
|
|
} else {
|
|
// Corner case. No reason not to honour min-size
|
|
return true;
|
|
}
|
|
}
|
|
|
|
Layout::Private::Private(Layout *qq)
|
|
: q(qq)
|
|
{
|
|
}
|
|
|
|
Layout::Private::~Private() = default;
|
|
|
|
|
|
/** static */
|
|
Layout *Layout::fromLayoutingHost(LayoutingHost *host)
|
|
{
|
|
if (auto layoutPriv = dynamic_cast<Core::Layout::Private *>(host))
|
|
return layoutPriv->q;
|
|
|
|
return nullptr;
|
|
}
|