1107 lines
29 KiB
C++
1107 lines
29 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 "Group.h"
|
|
#include "Group_p.h"
|
|
|
|
#include "kddockwidgets/Config.h"
|
|
#include "core/ViewFactory.h"
|
|
|
|
#include "Controller.h"
|
|
#include "View.h"
|
|
#include "Layout_p.h"
|
|
#include "FloatingWindow_p.h"
|
|
#include "ScopedValueRollback_p.h"
|
|
#include "Platform.h"
|
|
#include "views/GroupViewInterface.h"
|
|
|
|
#include "core/TitleBar.h"
|
|
#include "core/Stack.h"
|
|
#include "core/FloatingWindow.h"
|
|
#include "core/MDILayout.h"
|
|
#include "core/DropArea.h"
|
|
#include "core/Layout.h"
|
|
#include "core/MainWindow.h"
|
|
#include "core/TabBar_p.h"
|
|
|
|
#include "DockRegistry.h"
|
|
#include "DockWidget_p.h"
|
|
#include "ObjectGuard_p.h"
|
|
|
|
#include "core/Logging_p.h"
|
|
#include "core/Utils_p.h"
|
|
#include "core/View_p.h"
|
|
#include "core/LayoutSaver_p.h"
|
|
#include "core/Position_p.h"
|
|
#include "core/WidgetResizeHandler_p.h"
|
|
#include "core/DelayedCall_p.h"
|
|
#include "core/layouting/Item_p.h"
|
|
|
|
#include "kdbindings/signal.h"
|
|
|
|
#include <utility>
|
|
|
|
static int s_dbg_numFrames = 0;
|
|
|
|
using namespace KDDockWidgets;
|
|
using namespace KDDockWidgets::Core;
|
|
|
|
Core::Item *Core::Group::s_inFloatHack = nullptr;
|
|
|
|
namespace KDDockWidgets {
|
|
|
|
static FrameOptions actualOptions(FrameOptions options)
|
|
{
|
|
// Center group has custom logic for showing tabs or not
|
|
const bool isCentralGroup = options & FrameOption_IsCentralFrame;
|
|
|
|
if (!isCentralGroup) {
|
|
if (Config::self().flags() & Config::Flag_AlwaysShowTabs) {
|
|
options |= FrameOption_AlwaysShowsTabs;
|
|
} else {
|
|
// options could have came from a JSON layout which was saved from a Config with Flag_AlwaysShowTabs
|
|
// If current Config doesn't have this flag then remove it here as well
|
|
options &= ~FrameOption_AlwaysShowsTabs;
|
|
}
|
|
}
|
|
|
|
return options;
|
|
}
|
|
|
|
static StackOptions tabWidgetOptions(FrameOptions options)
|
|
{
|
|
if (options & FrameOption_NonDockable) {
|
|
/// If we can't tab things into this Frame then let's not draw the QTabWidget group either
|
|
return StackOption_DocumentMode;
|
|
}
|
|
|
|
return StackOption_None;
|
|
}
|
|
|
|
}
|
|
|
|
Group::Group(View *parent, FrameOptions options, int userType)
|
|
: Controller(ViewType::Group, Config::self().viewFactory()->createGroup(this, parent))
|
|
, FocusScope(view())
|
|
, d(new Private(this, userType, actualOptions(options)))
|
|
, m_stack(new Core::Stack(this, tabWidgetOptions(options)))
|
|
, m_tabBar(m_stack->tabBar())
|
|
, m_titleBar(new Core::TitleBar(this))
|
|
{
|
|
s_dbg_numFrames++;
|
|
DockRegistry::self()->registerGroup(this);
|
|
|
|
m_tabBar->dptr()->currentDockWidgetChanged.connect([this] {
|
|
updateTitleAndIcon();
|
|
});
|
|
|
|
setLayout(parent ? parent->asLayout() : nullptr);
|
|
m_stack->setTabBarAutoHide(!alwaysShowsTabs());
|
|
view()->init();
|
|
view()->d->closeRequested.connect([this](CloseEvent *ev) { onCloseEvent(ev); });
|
|
|
|
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
|
|
m_inCtor = false;
|
|
}
|
|
|
|
Group::~Group()
|
|
{
|
|
m_inDtor = true;
|
|
s_dbg_numFrames--;
|
|
if (d->m_layoutItem)
|
|
d->m_layoutItem->unref();
|
|
|
|
delete m_resizeHandler;
|
|
m_resizeHandler = nullptr;
|
|
|
|
DockRegistry::self()->unregisterGroup(this);
|
|
|
|
// Run some disconnects() too, so we don't receive signals during destruction:
|
|
setLayout(nullptr);
|
|
delete m_titleBar;
|
|
delete m_stack;
|
|
delete d;
|
|
}
|
|
|
|
void Group::onCloseEvent(CloseEvent *e)
|
|
{
|
|
e->accept(); // Accepted by default (will close unless ignored)
|
|
const DockWidget::List docks = dockWidgets();
|
|
for (DockWidget *dock : docks) {
|
|
dock->view()->d->requestClose(e);
|
|
if (!e->isAccepted())
|
|
break; // Stop when the first dockwidget prevents closing
|
|
}
|
|
}
|
|
|
|
void Group::setLayout(Layout *dt)
|
|
{
|
|
if (dt == m_layout)
|
|
return;
|
|
|
|
const bool wasInMainWindow = dt && isInMainWindow();
|
|
|
|
m_layout = dt;
|
|
delete m_resizeHandler;
|
|
m_resizeHandler = nullptr;
|
|
|
|
if (m_layout) {
|
|
if (isMDI())
|
|
createMDIResizeHandler();
|
|
|
|
// We keep the connect result so we don't dereference m_layout at shutdown
|
|
d->m_visibleWidgetCountChangedConnection =
|
|
m_layout->d_ptr()->visibleWidgetCountChanged.connect(&Group::updateTitleBarVisibility, this);
|
|
updateTitleBarVisibility();
|
|
if (wasInMainWindow != isInMainWindow())
|
|
safeEmitSignal(d->isInMainWindowChanged);
|
|
}
|
|
|
|
safeEmitSignal(d->isMDIChanged);
|
|
}
|
|
|
|
void Group::renameTab(int index, const QString &title)
|
|
{
|
|
m_tabBar->renameTab(index, title);
|
|
}
|
|
|
|
void Group::changeTabIcon(int index, const Icon &icon)
|
|
{
|
|
m_tabBar->changeTabIcon(index, icon);
|
|
}
|
|
|
|
int Group::nonContentsHeight() const
|
|
{
|
|
return dynamic_cast<Core::GroupViewInterface *>(view())->nonContentsHeight();
|
|
}
|
|
|
|
Core::Stack *Group::stack() const
|
|
{
|
|
return m_stack;
|
|
}
|
|
|
|
Core::TabBar *Group::tabBar() const
|
|
{
|
|
return m_stack->tabBar();
|
|
}
|
|
|
|
void Group::updateTitleAndIcon()
|
|
{
|
|
if (DockWidget *dw = currentDockWidget()) {
|
|
m_titleBar->setTitle(dw->title());
|
|
m_titleBar->setIcon(dw->icon());
|
|
|
|
if (auto fw = floatingWindow()) {
|
|
if (fw->hasSingleGroup()) {
|
|
fw->updateTitleAndIcon();
|
|
}
|
|
}
|
|
|
|
setObjectName(dw->uniqueName());
|
|
|
|
} else if (currentTabIndex() != -1) {
|
|
KDDW_ERROR("Invalid dock widget for group. index={}", currentTabIndex());
|
|
}
|
|
}
|
|
|
|
void Group::onDockWidgetTitleChanged(DockWidget *dw)
|
|
{
|
|
updateTitleAndIcon();
|
|
|
|
if (!m_inCtor) { // don't call pure virtual in ctor
|
|
int index = indexOfDockWidget(dw);
|
|
renameTab(index, dw->title());
|
|
changeTabIcon(index, dw->icon(IconPlace::TabBar));
|
|
}
|
|
}
|
|
|
|
void Group::addTab(DockWidget *dockWidget, const InitialOption &addingOption)
|
|
{
|
|
insertWidget(dockWidget, dockWidgetCount(), addingOption); // append
|
|
|
|
// The dock widget might have changed title *while* being inserted
|
|
// For example, if the text depends on whether it's floating or not.
|
|
// In that case tabbar won't notice the title change, as the titleChanged signal
|
|
// is emitted with the old parent still. (#468)
|
|
// Simply refresh title now:
|
|
onDockWidgetTitleChanged(dockWidget);
|
|
}
|
|
|
|
void Group::addTab(Group *group, const InitialOption &addingOption)
|
|
{
|
|
if (group->isEmpty()) {
|
|
KDDW_ERROR("Group::addTab: group is empty. group={}", ( void * )group);
|
|
return;
|
|
}
|
|
|
|
const auto &docks = group->dockWidgets();
|
|
for (DockWidget *dockWidget : docks)
|
|
addTab(dockWidget, addingOption);
|
|
}
|
|
|
|
void Group::addTab(FloatingWindow *floatingWindow, const InitialOption &addingOption)
|
|
{
|
|
assert(floatingWindow);
|
|
const auto groups = floatingWindow->groups();
|
|
for (Group *f : groups)
|
|
addTab(f, addingOption);
|
|
}
|
|
|
|
void Group::insertWidget(DockWidget *dockWidget, int index, const InitialOption &addingOption)
|
|
{
|
|
assert(dockWidget);
|
|
if (containsDockWidget(dockWidget)) {
|
|
if (!dockWidget->isPersistentCentralDockWidget())
|
|
KDDW_ERROR("Group::addTab dockWidget already exists. this={} ; dockWidget={}", ( void * )this, ( void * )dockWidget);
|
|
return;
|
|
}
|
|
if (d->m_layoutItem)
|
|
dockWidget->d->addPlaceholderItem(d->m_layoutItem);
|
|
|
|
const int originalCurrentIndex = currentIndex();
|
|
insertDockWidget(dockWidget, index);
|
|
|
|
if (addingOption.startsHidden()) {
|
|
dockWidget->view()->close(); // Ensure closed.
|
|
} else {
|
|
if (hasSingleDockWidget()) {
|
|
setObjectName(dockWidget->uniqueName());
|
|
|
|
if (!d->m_layoutItem) {
|
|
// When adding the 1st dock widget of a fresh group, let's give the group the size
|
|
// of the dock widget, so that when adding it to the main window, the main window
|
|
// can use that size as the initial suggested size.
|
|
view()->resize(dockWidget->size());
|
|
}
|
|
} else if (addingOption.preservesCurrentTab() && originalCurrentIndex != -1) {
|
|
setCurrentTabIndex(originalCurrentIndex);
|
|
}
|
|
|
|
dockWidget->d->setIsOpen(true);
|
|
}
|
|
|
|
KDBindings::ScopedConnection titleChangedConnection = dockWidget->d->titleChanged.connect(
|
|
[this, dockWidget] { onDockWidgetTitleChanged(dockWidget); });
|
|
|
|
KDBindings::ScopedConnection iconChangedConnection = dockWidget->d->iconChanged.connect(
|
|
[this, dockWidget] { onDockWidgetTitleChanged(dockWidget); });
|
|
|
|
d->titleChangedConnections[dockWidget] = std::move(titleChangedConnection);
|
|
d->iconChangedConnections[dockWidget] = std::move(iconChangedConnection);
|
|
}
|
|
|
|
void Group::removeWidget(DockWidget *dw)
|
|
{
|
|
auto it = d->titleChangedConnections.find(dw);
|
|
if (it != d->titleChangedConnections.end())
|
|
d->titleChangedConnections.erase(it);
|
|
|
|
it = d->iconChangedConnections.find(dw);
|
|
if (it != d->iconChangedConnections.end())
|
|
d->iconChangedConnections.erase(it);
|
|
|
|
if (auto gvi = dynamic_cast<Core::GroupViewInterface *>(view()))
|
|
gvi->removeDockWidget(dw);
|
|
}
|
|
|
|
FloatingWindow *Group::detachTab(DockWidget *dockWidget)
|
|
{
|
|
if (m_inCtor || m_inDtor)
|
|
return nullptr;
|
|
|
|
dockWidget->d->saveTabIndex();
|
|
|
|
Rect r = dockWidget->geometry();
|
|
removeWidget(dockWidget);
|
|
|
|
auto newGroup = new Group();
|
|
const Point globalPoint = mapToGlobal(Point(0, 0));
|
|
newGroup->addTab(dockWidget);
|
|
|
|
// We're potentially already dead at this point, as groups with 0 tabs auto-destruct. Don't
|
|
// access members from this point.
|
|
|
|
auto floatingWindow = new FloatingWindow(newGroup, {});
|
|
r.moveTopLeft(globalPoint);
|
|
floatingWindow->setSuggestedGeometry(r, SuggestedGeometryHint_GeometryIsFromDocked);
|
|
floatingWindow->view()->show();
|
|
|
|
return floatingWindow;
|
|
}
|
|
|
|
int Group::indexOfDockWidget(const DockWidget *dw)
|
|
{
|
|
if (m_inCtor || m_inDtor)
|
|
return -1;
|
|
|
|
return m_tabBar->indexOfDockWidget(dw);
|
|
}
|
|
|
|
int Group::currentIndex() const
|
|
{
|
|
if (m_inCtor || m_inDtor)
|
|
return -1;
|
|
|
|
return m_tabBar->currentIndex();
|
|
}
|
|
|
|
void Group::setCurrentTabIndex(int index)
|
|
{
|
|
if (m_inCtor || m_inDtor)
|
|
return;
|
|
|
|
m_tabBar->setCurrentIndex(index);
|
|
}
|
|
|
|
void Group::setCurrentDockWidget(DockWidget *dw)
|
|
{
|
|
if (m_inCtor || m_inDtor)
|
|
return;
|
|
|
|
m_tabBar->setCurrentDockWidget(dw);
|
|
}
|
|
|
|
void Group::insertDockWidget(DockWidget *dw, int index)
|
|
{
|
|
if (m_inCtor || m_inDtor)
|
|
return;
|
|
|
|
dynamic_cast<Core::GroupViewInterface *>(view())->insertDockWidget(dw, index);
|
|
dw->d->onParentChanged();
|
|
onDockWidgetTitleChanged(dw);
|
|
}
|
|
|
|
Core::DockWidget *Group::dockWidgetAt(int index) const
|
|
{
|
|
if (m_inCtor || m_inDtor)
|
|
return nullptr;
|
|
|
|
return m_tabBar->dockWidgetAt(index);
|
|
}
|
|
|
|
Core::DockWidget *Group::currentDockWidget() const
|
|
{
|
|
if (m_inCtor || m_inDtor)
|
|
return nullptr;
|
|
|
|
return m_tabBar->currentDockWidget();
|
|
}
|
|
|
|
int Group::dockWidgetCount() const
|
|
{
|
|
if (m_inCtor || m_inDtor)
|
|
return 0;
|
|
|
|
return m_stack->numDockWidgets();
|
|
}
|
|
|
|
void Group::onDockWidgetCountChanged()
|
|
{
|
|
if (isEmpty() && !isCentralGroup()) {
|
|
scheduleDeleteLater();
|
|
} else {
|
|
updateTitleBarVisibility();
|
|
|
|
// We don't really keep track of the state, so emit even if the visibility didn't change. No
|
|
// biggie.
|
|
if (!(d->m_options & FrameOption_AlwaysShowsTabs))
|
|
safeEmitSignal(d->hasTabsVisibleChanged);
|
|
|
|
const DockWidget::List docks = dockWidgets();
|
|
for (DockWidget *dock : docks) {
|
|
if (!dock->inDtor())
|
|
dock->d->updateFloatAction();
|
|
}
|
|
|
|
if (auto fw = floatingWindow()) {
|
|
safeEmitSignal(fw->dptr()->numDockWidgetsChanged);
|
|
}
|
|
}
|
|
|
|
safeEmitSignal(d->numDockWidgetsChanged);
|
|
}
|
|
|
|
void Group::isFocusedChangedCallback()
|
|
{
|
|
safeEmitSignal(d->isFocusedChanged);
|
|
}
|
|
|
|
void Group::focusedWidgetChangedCallback()
|
|
{
|
|
safeEmitSignal(d->focusedWidgetChanged);
|
|
}
|
|
|
|
void Group::updateTitleBarVisibility()
|
|
{
|
|
if (m_updatingTitleBar || m_beingDeleted) {
|
|
// To break a cyclic dependency
|
|
return;
|
|
}
|
|
|
|
ScopedValueRollback guard(m_updatingTitleBar, true);
|
|
|
|
bool visible = false;
|
|
if (isCentralGroup()) {
|
|
visible = false;
|
|
} else if ((Config::self().flags() & Config::Flag_HideTitleBarWhenTabsVisible)
|
|
&& hasTabsVisible()) {
|
|
visible = false;
|
|
} else if (FloatingWindow *fw = floatingWindow()) {
|
|
// If there's nested groups then show each Frame's title bar
|
|
visible = !fw->hasSingleGroup();
|
|
} else if (isMDIWrapper()) {
|
|
auto dropArea = this->mdiDropAreaWrapper();
|
|
visible = !dropArea->hasSingleGroup();
|
|
} else {
|
|
visible = true;
|
|
}
|
|
|
|
const bool wasVisible = m_titleBar->isVisible();
|
|
m_titleBar->setVisible(visible);
|
|
|
|
if (wasVisible != visible) {
|
|
safeEmitSignal(d->actualTitleBarChanged);
|
|
const auto docks = dockWidgets();
|
|
for (auto dw : docks)
|
|
safeEmitSignal(dw->d->actualTitleBarChanged);
|
|
}
|
|
|
|
if (auto fw = floatingWindow()) {
|
|
// Update the floating window which might be using Flag_HideTitleBarWhenTabsVisible
|
|
// In that case it might not show title bar depending on the number of tabs that the group
|
|
// has
|
|
fw->updateTitleBarVisibility();
|
|
}
|
|
}
|
|
|
|
void Group::updateFloatingActions()
|
|
{
|
|
const Vector<DockWidget *> widgets = dockWidgets();
|
|
for (DockWidget *dw : widgets)
|
|
dw->d->updateFloatAction();
|
|
}
|
|
|
|
bool Group::containsMouse(Point globalPos) const
|
|
{
|
|
return view()->d->globalGeometry().contains(globalPos);
|
|
}
|
|
|
|
Core::TitleBar *Group::titleBar() const
|
|
{
|
|
return m_titleBar;
|
|
}
|
|
|
|
Core::TitleBar *Group::actualTitleBar() const
|
|
{
|
|
if (FloatingWindow *fw = floatingWindow()) {
|
|
// If there's nested groups then show each Group's title bar
|
|
if (fw->hasSingleGroup())
|
|
return fw->titleBar();
|
|
} else if (auto mdiDropArea = mdiDropAreaWrapper()) {
|
|
if (mdiDropArea->hasSingleGroup()) {
|
|
return mdiFrame()->titleBar();
|
|
}
|
|
}
|
|
|
|
return titleBar();
|
|
}
|
|
|
|
QString Group::title() const
|
|
{
|
|
return m_titleBar->title();
|
|
}
|
|
|
|
Icon Group::icon() const
|
|
{
|
|
return m_titleBar->icon();
|
|
}
|
|
|
|
Core::DockWidget::List Group::dockWidgets() const
|
|
{
|
|
if (m_inCtor || m_inDtor)
|
|
return {};
|
|
|
|
DockWidget::List dockWidgets;
|
|
const int count = dockWidgetCount();
|
|
dockWidgets.reserve(count);
|
|
for (int i = 0; i < count; ++i)
|
|
dockWidgets.push_back(dockWidgetAt(i));
|
|
|
|
return dockWidgets;
|
|
}
|
|
|
|
bool Group::containsDockWidget(DockWidget *dockWidget) const
|
|
{
|
|
const int count = dockWidgetCount();
|
|
for (int i = 0, e = count; i != e; ++i) {
|
|
if (dockWidget == dockWidgetAt(i))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
FloatingWindow *Group::floatingWindow() const
|
|
{
|
|
// Returns the first FloatingWindow* parent in the hierarchy.
|
|
// However, if there's a MainWindow in the hierarchy it stops, which can
|
|
// happen with nested main windows.
|
|
|
|
auto p = view()->parentView();
|
|
while (p) {
|
|
if (p->is(ViewType::MainWindow))
|
|
return nullptr;
|
|
|
|
if (auto fw = p->asFloatingWindowController())
|
|
return fw;
|
|
|
|
if (p->equals(view()->rootView())) {
|
|
// We stop at the window. (top-levels can have parent, but we're not interested)
|
|
return nullptr;
|
|
}
|
|
|
|
p = p->parentView();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void Group::restoreToPreviousPosition()
|
|
{
|
|
if (hasSingleDockWidget()) {
|
|
KDDW_ERROR("Invalid usage, there's no tabs");
|
|
return;
|
|
}
|
|
|
|
if (!d->m_layoutItem) {
|
|
KDDW_DEBUG("Group::restoreToPreviousPosition: There's no previous position known");
|
|
return;
|
|
}
|
|
|
|
if (!d->m_layoutItem->isPlaceholder()) {
|
|
// Maybe in this case just fold the group into the placeholder, which probably has other
|
|
// dockwidgets which were added meanwhile. TODO
|
|
KDDW_DEBUG("Group::restoreToPreviousPosition: Previous position isn't a placeholder");
|
|
return;
|
|
}
|
|
|
|
d->m_layoutItem->restore(d);
|
|
}
|
|
|
|
int Group::currentTabIndex() const
|
|
{
|
|
return currentIndex();
|
|
}
|
|
|
|
bool Group::anyNonClosable() const
|
|
{
|
|
const auto docks = dockWidgets();
|
|
for (auto dw : docks) {
|
|
if ((dw->options() & DockWidgetOption_NotClosable)
|
|
&& !Platform::instance()->isProcessingAppQuitEvent())
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Group::anyNonDockable() const
|
|
{
|
|
const auto docks = dockWidgets();
|
|
for (auto dw : docks) {
|
|
if (dw->options() & DockWidgetOption_NotDockable)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Group::Private::setLayoutItem_impl(Item *item)
|
|
{
|
|
m_layoutItem = item;
|
|
|
|
const auto docks = q->dockWidgets();
|
|
if (item) {
|
|
for (DockWidget *dw : docks)
|
|
dw->d->addPlaceholderItem(item);
|
|
} else {
|
|
for (DockWidget *dw : docks)
|
|
dw->d->lastPosition()->removePlaceholders();
|
|
}
|
|
}
|
|
|
|
LayoutingHost *Group::Private::host() const
|
|
{
|
|
return q->m_layout ? q->m_layout->asLayoutingHost() : nullptr;
|
|
}
|
|
|
|
void Group::Private::setHost(LayoutingHost *host)
|
|
{
|
|
Core::View *parent = nullptr;
|
|
if (auto layout = Layout::fromLayoutingHost(host)) {
|
|
parent = layout->view();
|
|
}
|
|
|
|
q->setParentView(parent);
|
|
}
|
|
|
|
Item *Group::layoutItem() const
|
|
{
|
|
return d->m_layoutItem;
|
|
}
|
|
|
|
int Group::dbg_numFrames()
|
|
{
|
|
return s_dbg_numFrames;
|
|
}
|
|
|
|
bool Group::beingDeletedLater() const
|
|
{
|
|
return m_beingDeleted;
|
|
}
|
|
|
|
bool Group::hasTabsVisible() const
|
|
{
|
|
if (m_beingDeleted)
|
|
return false;
|
|
|
|
return alwaysShowsTabs() || dockWidgetCount() > 1;
|
|
}
|
|
|
|
Vector<QString> Group::affinities() const
|
|
{
|
|
if (isEmpty()) {
|
|
if (auto m = mainWindow())
|
|
return m->affinities();
|
|
return {};
|
|
} else {
|
|
return dockWidgetAt(0)->affinities();
|
|
}
|
|
}
|
|
|
|
void Group::setLayoutItem(Core::Item *item)
|
|
{
|
|
d->setLayoutItem(item);
|
|
}
|
|
|
|
bool Group::isTheOnlyGroup() const
|
|
{
|
|
return m_layout && m_layout->visibleCount() == 1;
|
|
}
|
|
|
|
bool Group::isOverlayed() const
|
|
{
|
|
return d->m_options & FrameOption_IsOverlayed;
|
|
}
|
|
|
|
void Group::unoverlay()
|
|
{
|
|
d->m_options &= ~FrameOption_IsOverlayed;
|
|
}
|
|
|
|
bool Group::isFloating() const
|
|
{
|
|
if (isInMainWindow() || isMDI())
|
|
return false;
|
|
|
|
return isTheOnlyGroup();
|
|
}
|
|
|
|
bool Group::isInFloatingWindow() const
|
|
{
|
|
return floatingWindow() != nullptr;
|
|
}
|
|
|
|
bool Group::isInMainWindow() const
|
|
{
|
|
return mainWindow() != nullptr;
|
|
}
|
|
|
|
Group *Group::deserialize(const LayoutSaver::Group &f)
|
|
{
|
|
if (!f.isValid())
|
|
return nullptr;
|
|
|
|
const FrameOptions options = actualOptions(FrameOptions(f.options));
|
|
Group *group = nullptr;
|
|
const bool isPersistentCentralFrame = options & FrameOption::FrameOption_IsCentralFrame;
|
|
|
|
if (isPersistentCentralFrame) {
|
|
// Don't create a new Group if we're restoring the Persistent Central group (the one created
|
|
// by MainWindowOption_HasCentralFrame). It already exists.
|
|
|
|
if (f.mainWindowUniqueName.isEmpty()) {
|
|
// Can happen with older serialization formats
|
|
KDDW_ERROR("Group is the persistent central group but doesn't have"
|
|
"an associated window name");
|
|
} else {
|
|
if (MainWindow *mw = DockRegistry::self()->mainWindowByName(f.mainWindowUniqueName)) {
|
|
group = mw->dropArea()->centralGroup();
|
|
if (!group) {
|
|
// Doesn't happen...
|
|
KDDW_ERROR("Main window {} doesn't have central group", f.mainWindowUniqueName);
|
|
}
|
|
} else {
|
|
// Doesn't happen...
|
|
KDDW_ERROR("Couldn't find main window {}", f.mainWindowUniqueName);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!group)
|
|
group = new Group(nullptr, options);
|
|
|
|
group->setObjectName(f.objectName);
|
|
|
|
for (const auto &savedDock : std::as_const(f.dockWidgets)) {
|
|
if (DockWidget *dw = DockWidget::deserialize(savedDock)) {
|
|
group->addTab(dw);
|
|
}
|
|
}
|
|
|
|
group->setCurrentTabIndex(f.currentTabIndex);
|
|
group->view()->setGeometry(f.geometry);
|
|
|
|
return group;
|
|
}
|
|
|
|
LayoutSaver::Group Group::serialize() const
|
|
{
|
|
LayoutSaver::Group group;
|
|
group.isNull = false;
|
|
|
|
const DockWidget::List docks = dockWidgets();
|
|
|
|
group.objectName = objectName();
|
|
group.geometry = geometry();
|
|
group.options = options();
|
|
group.currentTabIndex = currentTabIndex();
|
|
group.id = view()->d->id(); // for coorelation purposes
|
|
|
|
if (MainWindow *mw = mainWindow()) {
|
|
group.mainWindowUniqueName = mw->uniqueName();
|
|
}
|
|
|
|
for (DockWidget *dock : docks)
|
|
group.dockWidgets.push_back(dock->d->serialize());
|
|
|
|
if (group.currentTabIndex == -1 && !docks.isEmpty()) {
|
|
KDDW_ERROR("Group::serialize: Current index shouldn't be -1. Setting to 0 instead.");
|
|
group.currentTabIndex = 0;
|
|
}
|
|
|
|
return group;
|
|
}
|
|
|
|
void Group::scheduleDeleteLater()
|
|
{
|
|
KDDW_TRACE("Group::scheduleDeleteLater: {}", ( void * )this);
|
|
m_beingDeleted = true;
|
|
|
|
if (auto item = layoutItem()) {
|
|
if (item->parentContainer())
|
|
item->turnIntoPlaceholder();
|
|
}
|
|
|
|
// Can't use deleteLater() here due to QTBUG-83030 (deleteLater() never delivered if
|
|
// triggered by a sendEvent() before event loop starts)
|
|
destroyLater();
|
|
}
|
|
|
|
void Group::createMDIResizeHandler()
|
|
{
|
|
delete m_resizeHandler;
|
|
m_resizeHandler = new WidgetResizeHandler(WidgetResizeHandler::EventFilterMode::Global,
|
|
WidgetResizeHandler::WindowMode::MDI, view());
|
|
|
|
if (Platform::instance()->isQtQuick()) {
|
|
// Our C++ WidgetResizeHandler is triggered manually by MDIResizeHandlerHelper.qml's MouseArea
|
|
m_resizeHandler->setEventFilterStartsManually();
|
|
|
|
// MouseCursor set by QML as well
|
|
m_resizeHandler->setHandlesMouseCursor(false);
|
|
}
|
|
}
|
|
|
|
Size Group::dockWidgetsMinSize() const
|
|
{
|
|
Size size = Item::hardcodedMinimumSize;
|
|
const auto docks = dockWidgets();
|
|
for (DockWidget *dw : docks) {
|
|
if (!dw->inDtor())
|
|
size = size.expandedTo(dw->view()->minSize());
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
Size Group::biggestDockWidgetMaxSize() const
|
|
{
|
|
Size size = Item::hardcodedMaximumSize;
|
|
const auto docks = dockWidgets();
|
|
for (DockWidget *dw : docks) {
|
|
if (dw->inDtor())
|
|
continue;
|
|
const Size dwMax = dw->view()->maxSizeHint();
|
|
if (size == Item::hardcodedMaximumSize) {
|
|
size = dwMax;
|
|
continue;
|
|
}
|
|
|
|
const bool hasMaxSize = dwMax != Item::hardcodedMaximumSize;
|
|
if (hasMaxSize)
|
|
size = dwMax.expandedTo(size);
|
|
}
|
|
|
|
// Interpret 0 max-size as not having one too.
|
|
if (size.width() == 0)
|
|
size.setWidth(Item::hardcodedMaximumSize.width());
|
|
if (size.height() == 0)
|
|
size.setHeight(Item::hardcodedMaximumSize.height());
|
|
|
|
return size;
|
|
}
|
|
|
|
Rect Group::dragRect() const
|
|
{
|
|
Rect rect;
|
|
if (m_titleBar->isVisible()) {
|
|
rect = m_titleBar->view()->rect();
|
|
rect.moveTopLeft(m_titleBar->view()->mapToGlobal(Point(0, 0)));
|
|
}
|
|
|
|
if (rect.isValid())
|
|
return rect;
|
|
|
|
if (auto gvi = dynamic_cast<Core::GroupViewInterface *>(view()))
|
|
return gvi->dragRect();
|
|
|
|
return {};
|
|
}
|
|
|
|
MainWindow *Group::mainWindow() const
|
|
{
|
|
return m_layout ? m_layout->mainWindow() : nullptr;
|
|
}
|
|
|
|
///@brief Returns whether all dock widgets have the specified option set
|
|
bool Group::allDockWidgetsHave(DockWidgetOption option) const
|
|
{
|
|
const DockWidget::List docks = dockWidgets();
|
|
return std::all_of(docks.cbegin(), docks.cend(),
|
|
[option](DockWidget *dw) { return dw->options() & option; });
|
|
}
|
|
|
|
///@brief Returns whether at least one dock widget has the specified option set
|
|
bool Group::anyDockWidgetsHas(DockWidgetOption option) const
|
|
{
|
|
const DockWidget::List docks = dockWidgets();
|
|
return std::any_of(docks.cbegin(), docks.cend(),
|
|
[option](DockWidget *dw) { return dw->options() & option; });
|
|
}
|
|
|
|
bool Group::allDockWidgetsHave(LayoutSaverOption option) const
|
|
{
|
|
const DockWidget::List docks = dockWidgets();
|
|
return std::all_of(docks.cbegin(), docks.cend(),
|
|
[option](DockWidget *dw) { return dw->layoutSaverOptions() & option; });
|
|
}
|
|
|
|
bool Group::anyDockWidgetsHas(LayoutSaverOption option) const
|
|
{
|
|
const DockWidget::List docks = dockWidgets();
|
|
return std::any_of(docks.cbegin(), docks.cend(),
|
|
[option](DockWidget *dw) { return dw->layoutSaverOptions() & option; });
|
|
}
|
|
|
|
void Group::setAllowedResizeSides(CursorPositions sides)
|
|
{
|
|
if (sides) {
|
|
createMDIResizeHandler();
|
|
m_resizeHandler->setAllowedResizeSides(sides);
|
|
} else {
|
|
delete m_resizeHandler;
|
|
m_resizeHandler = nullptr;
|
|
}
|
|
}
|
|
|
|
bool Group::isMDI() const
|
|
{
|
|
return mdiLayout() != nullptr;
|
|
}
|
|
|
|
bool Group::isMDIWrapper() const
|
|
{
|
|
return mdiDropAreaWrapper() != nullptr;
|
|
}
|
|
|
|
Group *Group::mdiFrame() const
|
|
{
|
|
if (auto dwWrapper = mdiDockWidgetWrapper()) {
|
|
return dwWrapper->d->group();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
Core::DockWidget *Group::mdiDockWidgetWrapper() const
|
|
{
|
|
if (auto dropArea = mdiDropAreaWrapper())
|
|
return dropArea->view()->parentView()->asDockWidgetController();
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
DropArea *Group::mdiDropAreaWrapper() const
|
|
{
|
|
auto p = view()->parentView();
|
|
auto dropArea = p ? p->asDropAreaController() : nullptr;
|
|
if (dropArea && dropArea->isMDIWrapper())
|
|
return dropArea;
|
|
return nullptr;
|
|
}
|
|
|
|
MDILayout *Group::mdiLayout() const
|
|
{
|
|
return m_layout ? m_layout->asMDILayout() : nullptr;
|
|
}
|
|
|
|
bool Group::hasNestedMDIDockWidgets() const
|
|
{
|
|
if (!isMDI() || dockWidgetCount() == 0)
|
|
return false;
|
|
|
|
if (dockWidgetCount() != 1) {
|
|
KDDW_ERROR("Expected a single dock widget wrapper as group child");
|
|
return false;
|
|
}
|
|
|
|
return dockWidgetAt(0)->d->isMDIWrapper();
|
|
}
|
|
|
|
int Group::userType() const
|
|
{
|
|
return d->m_userType;
|
|
}
|
|
|
|
WidgetResizeHandler *Group::resizeHandler() const
|
|
{
|
|
return m_resizeHandler;
|
|
}
|
|
|
|
void Group::setParentView_impl(View *parent)
|
|
{
|
|
Controller::setParentView_impl(parent);
|
|
setLayout(parent ? parent->asLayout() : nullptr);
|
|
}
|
|
|
|
FloatingWindowFlags Group::requestedFloatingWindowFlags() const
|
|
{
|
|
const auto dockwidgets = this->dockWidgets();
|
|
if (!dockwidgets.isEmpty())
|
|
return dockwidgets.first()->floatingWindowFlags();
|
|
|
|
return FloatingWindowFlag::FromGlobalConfig;
|
|
}
|
|
|
|
FrameOptions Core::Group::options() const
|
|
{
|
|
return d->m_options;
|
|
}
|
|
bool Core::Group::alwaysShowsTabs() const
|
|
{
|
|
return d->m_options & FrameOption_AlwaysShowsTabs;
|
|
}
|
|
bool Core::Group::isDockable() const
|
|
{
|
|
return !(d->m_options & FrameOption_NonDockable);
|
|
}
|
|
bool Core::Group::isCentralGroup() const
|
|
{
|
|
return d->m_options & FrameOption_IsCentralFrame;
|
|
}
|
|
|
|
Group::Private *Group::dptr() const
|
|
{
|
|
return d;
|
|
}
|
|
|
|
LayoutingGuest *Group::asLayoutingGuest() const
|
|
{
|
|
return d;
|
|
}
|
|
|
|
bool Group::close() const
|
|
{
|
|
bool allAccepted = true;
|
|
|
|
const DockWidget::List docks = dockWidgets();
|
|
for (DockWidget *dock : docks) {
|
|
allAccepted = allAccepted && dock->close();
|
|
}
|
|
|
|
return allAccepted;
|
|
}
|
|
|
|
Group::Private::Private(Group *qq, int userType, FrameOptions options)
|
|
: q(qq)
|
|
, m_userType(userType)
|
|
, m_options(options)
|
|
{
|
|
m_parentViewChangedConnection = q->Controller::dptr()->parentViewChanged.connect([this] {
|
|
safeEmitSignal(hostChanged, host());
|
|
});
|
|
|
|
q->view()->d->layoutInvalidated.connect([this] {
|
|
if (auto item = q->layoutItem()) {
|
|
|
|
if (item->m_sizingInfo.minSize == minSize() && item->m_sizingInfo.maxSizeHint == maxSizeHint()) {
|
|
// No point in disturbing the layout if constraints didn't change.
|
|
// QTabWidget::resizeEvent for example will issue layout invalidation even if constraints haven't changed
|
|
return;
|
|
}
|
|
|
|
if (m_invalidatingLayout) {
|
|
// Fixes case where we're in the middle of adding a widget to layout and that triggers
|
|
// another unrelated widget to emit layoutInvalidated due to resize. It would trigger a relayout while
|
|
// we were in a middle of adding a dock widget.
|
|
// An example is QTabWidget::resizeEvent(), it calls updateGeometry() unconditionally.
|
|
return;
|
|
}
|
|
|
|
ScopedValueRollback guard(m_invalidatingLayout, true);
|
|
|
|
// Here we tell the KDDW layout that a widget change min/max sizes.
|
|
// KDDW will do some resizing to honour the new min/max constraint
|
|
safeEmitSignal(layoutInvalidated);
|
|
}
|
|
});
|
|
}
|
|
|
|
Group::Private::~Private()
|
|
{
|
|
m_visibleWidgetCountChangedConnection->disconnect();
|
|
|
|
safeEmitSignal(beingDestroyed);
|
|
}
|
|
|
|
Core::Group *Group::fromItem(const Core::Item *item)
|
|
{
|
|
if (!item)
|
|
return nullptr;
|
|
|
|
if (auto guest = item->guest()) {
|
|
if (auto group = dynamic_cast<Core::Group::Private *>(guest))
|
|
return group->q;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|