/* This file is part of KDDockWidgets. SPDX-FileCopyrightText: 2020 Klarälvdalens Datakonsult AB, a KDAB Group company Author: Sérgio Martins SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only Contact KDAB at 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 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(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(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(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 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 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(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(guest)) return group->q; } return nullptr; }