2026-04-06 00:20:51 -05:00

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;
}