/* This file is part of KDDockWidgets. SPDX-FileCopyrightText: 2019 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. */ /** * @file * @brief The MainWindow base class that's shared between QtWidgets and QtQuick stack * * @author Sérgio Martins \ */ #include "MainWindow.h" #include "MainWindow_p.h" #include "kddockwidgets/KDDockWidgets.h" #include "DockRegistry.h" #include "Layout_p.h" #include "core/MDILayout.h" #include "core/DropArea.h" #include "core/Utils_p.h" #include "core/Logging_p.h" #include "core/ScopedValueRollback_p.h" #include "core/WidgetResizeHandler_p.h" #include "core/ViewFactory.h" #include "core/LayoutSaver_p.h" #include "core/layouting/Item_p.h" #include "Platform.h" #include "core/DockWidget_p.h" #include "core/Group.h" #include "core/SideBar.h" #include "kddockwidgets/core/views/MainWindowViewInterface.h" #include #include using namespace KDDockWidgets; using namespace KDDockWidgets::Core; static Layout *createLayout(MainWindow *mainWindow, MainWindowOptions options) { if (options & MainWindowOption_MDI) return new MDILayout(mainWindow->view()); return new DropArea(mainWindow->view(), options); } MainWindow::MainWindow(View *view, const QString &uniqueName, MainWindowOptions options) : Controller(ViewType::MainWindow, view) , d(new Private(this, uniqueName, options)) { } void MainWindow::init(const QString &name) { d->init(); d->m_layout = createLayout(this, d->m_options); d->m_persistentCentralDockWidget = d->createPersistentCentralDockWidget(d->name); setUniqueName(name); d->m_visibleWidgetCountConnection = d->m_layout->d_ptr()->visibleWidgetCountChanged.connect([this](int count) { d->groupCountChanged.emit(count); }); view()->d->closeRequested.connect([this](CloseEvent *ev) { d->m_layout->onCloseEvent(ev); }); d->m_resizeConnection = view()->d->resized.connect([this](Size size) { d->onResized(size); }); } MainWindow::~MainWindow() { DockRegistry::self()->unregisterMainWindow(this); delete d; } void MainWindow::addDockWidgetAsTab(Core::DockWidget *widget) { assert(widget); KDDW_DEBUG("dock={}", ( void * )widget); if (!DockRegistry::self()->affinitiesMatch(d->affinities, widget->affinities())) { KDDW_ERROR("Refusing to dock widget with incompatible affinity. {} {}", widget->affinities(), affinities()); return; } if (widget->options() & DockWidgetOption_NotDockable) { KDDW_ERROR("Refusing to dock non-dockable widget {}", ( void * )widget); return; } if (isMDI()) { // Not applicable to MDI return; } if (d->supportsPersistentCentralWidget()) { KDDW_ERROR("Not supported with MainWindowOption_HasCentralWidget." "MainWindowOption_HasCentralWidget can only have 1 widget in the center.", "Use MainWindowOption_HasCentralGroup instead, which is similar but supports " "tabbing"); } else if (d->supportsCentralFrame()) { dropArea()->centralGroup()->addTab(widget); } else { KDDW_ERROR("Not supported without MainWindowOption_HasCentralGroup"); } } void MainWindow::addDockWidget(Core::DockWidget *dw, Location location, Core::DockWidget *relativeTo, const InitialOption &option) { if (dw->options() & DockWidgetOption_NotDockable) { KDDW_ERROR("Refusing to dock non-dockable widget dw={}", ( void * )dw); return; } if (isMDI()) { // Not applicable to MDI return; } dropArea()->addDockWidget(dw, location, relativeTo, option); } void MainWindow::addDockWidgetToSide(KDDockWidgets::Core::DockWidget *dockWidget, KDDockWidgets::Location location, const KDDockWidgets::InitialOption &initialOption) { if (!dockWidget || location == Location_None || isMDI()) return; if (!(d->m_options & MainWindowOption_HasCentralGroup)) { KDDW_ERROR("MainWindow::addDockWidgetToSide: A central group is required. Either MainWindowOption_HasCentralGroup or MainWindowOption_HasCentralWidget"); return; } Group *group = dropArea()->centralGroup(); if (!group || !group->layoutItem()) { // Doesn't happen KDDW_ERROR("MainWindow::addDockWidgetToSide: no group"); return; } auto locToUse = [](Location loc) { switch (loc) { case Location_None: return Location_None; case Location_OnLeft: return Location_OnBottom; case Location_OnTop: return Location_OnRight; case Location_OnRight: return Location_OnBottom; case Location_OnBottom: return Location_OnRight; } return Location_None; }; Core::Item *neighbor = group->layoutItem()->outermostNeighbor(location, /*visibleOnly=*/false); if (neighbor) { if (neighbor->isContainer()) { auto container = object_cast(neighbor); const auto children = container->childItems(); if (children.isEmpty()) { // Doesn't happen KDDW_ERROR("MainWindow::addDockWidgetToSide: no children"); } else { // There's an existing container with opposite orientation, add there but to end dropArea()->_addDockWidget(dockWidget, locToUse(location), children.last(), initialOption); } } else { dropArea()->_addDockWidget(dockWidget, locToUse(location), neighbor, initialOption); } } else { addDockWidget(dockWidget, location, nullptr, initialOption); } } QString MainWindow::uniqueName() const { return d->name; } MainWindowOptions MainWindow::options() const { return d->m_options; } DropArea *MainWindow::dropArea() const { return d->m_layout->asDropArea(); } DropArea *MainWindow::multiSplitter() const { return dropArea(); } Layout *MainWindow::layout() const { return d->m_layout; } MDILayout *MainWindow::mdiLayout() const { return d->m_layout->asMDILayout(); } void MainWindow::setAffinities(const Vector &affinityNames) { Vector affinities = affinityNames; affinities.removeAll(QString()); if (d->affinities == affinities) return; if (!d->affinities.isEmpty()) { KDDW_ERROR("Affinity is already set, refusing to change." "Submit a feature request with a good justification."); return; } d->affinities = affinities; } Vector MainWindow::affinities() const { return d->affinities; } void MainWindow::layoutEqually() { dropArea()->layoutEqually(); } void MainWindow::layoutParentContainerEqually(Core::DockWidget *dockWidget) { dropArea()->layoutParentContainerEqually(dockWidget); } CursorPositions MainWindow::Private::allowedResizeSides(SideBarLocation loc) const { // When a sidebar is on top, you can only resize its bottom. // and so forth... switch (loc) { case SideBarLocation::North: return CursorPosition_Bottom; case SideBarLocation::East: return CursorPosition_Left; case SideBarLocation::West: return CursorPosition_Right; case SideBarLocation::South: return CursorPosition_Top; case SideBarLocation::None: case SideBarLocation::Last: return CursorPosition_Undefined; } return CursorPosition_Undefined; } Rect MainWindow::Private::rectForOverlay(Core::Group *group, SideBarLocation location) const { Core::SideBar *sb = q->sideBar(location); if (!sb) return {}; const Rect centralAreaGeo = q->centralAreaGeometry(); const Margins centerWidgetMargins = q->centerWidgetMargins(); Rect rect; const int margin = m_overlayMargin; switch (location) { case SideBarLocation::North: case SideBarLocation::South: { Core::SideBar *leftSideBar = q->sideBar(SideBarLocation::West); Core::SideBar *rightSideBar = q->sideBar(SideBarLocation::East); const int leftSideBarWidth = (leftSideBar && leftSideBar->isVisible()) ? leftSideBar->width() : 0; const int rightSideBarWidth = (rightSideBar && rightSideBar->isVisible()) ? rightSideBar->width() : 0; rect.setHeight(( std::max )(300, group->view()->minSize().height())); rect.setWidth(centralAreaGeo.width() - (margin * 2) - leftSideBarWidth - rightSideBarWidth); rect.moveLeft(margin + leftSideBarWidth); if (location == SideBarLocation::South) { rect.moveTop(centralAreaGeo.bottom() - centerWidgetMargins.bottom() - rect.height() - sb->height()); } else { rect.moveTop(centralAreaGeo.y() + sb->height() + centerWidgetMargins.top()); } break; } case SideBarLocation::West: case SideBarLocation::East: { Core::SideBar *topSideBar = q->sideBar(SideBarLocation::North); Core::SideBar *bottomSideBar = q->sideBar(SideBarLocation::South); const int topSideBarHeight = (topSideBar && topSideBar->isVisible()) ? topSideBar->height() : 0; const int bottomSideBarHeight = (bottomSideBar && bottomSideBar->isVisible()) ? bottomSideBar->height() : 0; rect.setWidth(( std::max )(300, group->view()->minSize().width())); rect.setHeight(centralAreaGeo.height() - topSideBarHeight - bottomSideBarHeight - centerWidgetMargins.top() - centerWidgetMargins.bottom()); rect.moveTop(sb->view()->mapTo(q->view(), Point(0, 0)).y() + topSideBarHeight - 1); if (location == SideBarLocation::East) { rect.moveLeft(centralAreaGeo.x() + centralAreaGeo.width() - rect.width() - sb->width() - centerWidgetMargins.right() - margin); } else { rect.moveLeft(margin + centralAreaGeo.x() + centerWidgetMargins.left() + sb->width()); } break; } case SideBarLocation::None: case SideBarLocation::Last: break; } return rect; } static SideBarLocation opposedSideBarLocationForBorder(Core::LayoutBorderLocation loc) { switch (loc) { case Core::LayoutBorderLocation_North: return SideBarLocation::South; case Core::LayoutBorderLocation_East: return SideBarLocation::West; case Core::LayoutBorderLocation_West: return SideBarLocation::East; case Core::LayoutBorderLocation_South: return SideBarLocation::North; case Core::LayoutBorderLocation_All: case Core::LayoutBorderLocation_Verticals: case Core::LayoutBorderLocation_Horizontals: case Core::LayoutBorderLocation_None: break; } KDDW_ERROR("Unknown loc={}", loc); return SideBarLocation::None; } static SideBarLocation sideBarLocationForBorder(Core::LayoutBorderLocations loc) { switch (loc) { case Core::LayoutBorderLocation_North: return SideBarLocation::North; case Core::LayoutBorderLocation_East: return SideBarLocation::East; case Core::LayoutBorderLocation_West: return SideBarLocation::West; case Core::LayoutBorderLocation_South: return SideBarLocation::South; case Core::LayoutBorderLocation_All: case Core::LayoutBorderLocation_Verticals: case Core::LayoutBorderLocation_Horizontals: case Core::LayoutBorderLocation_None: break; default: break; } return SideBarLocation::None; } SideBarLocation MainWindow::Private::preferredSideBar(Core::DockWidget *dw) const { Group *group = dw->d->group(); Core::Item *item = q->layout()->itemForGroup(group); if (!item) { KDDW_ERROR("No item for dock widget"); return SideBarLocation::None; } const Core::LayoutBorderLocations borders = item->adjacentLayoutBorders(); const double aspectRatio = group->width() / (( std::max )(1, group->height()) * 1.0); /// 1. It's touching all borders if (borders == Core::LayoutBorderLocation_All) { return aspectRatio > 1.0 ? SideBarLocation::South : SideBarLocation::East; } /// 2. It's touching 3 borders for (auto borderLoc : { Core::LayoutBorderLocation_North, Core::LayoutBorderLocation_East, Core::LayoutBorderLocation_West, Core::LayoutBorderLocation_South }) { if (borders == (Core::LayoutBorderLocation_All & ~borderLoc)) return opposedSideBarLocationForBorder(borderLoc); } /// 3. It's touching left and right borders if ((borders & Core::LayoutBorderLocation_Verticals) == Core::LayoutBorderLocation_Verticals) { const int distanceToTop = group->geometry().y(); const int distanceToBottom = q->layout()->layoutHeight() - group->geometry().bottom(); return distanceToTop > distanceToBottom ? SideBarLocation::South : SideBarLocation::North; } /// 4. It's touching top and bottom borders if ((borders & Core::LayoutBorderLocation_Horizontals) == Core::LayoutBorderLocation_Horizontals) { const int distanceToLeft = group->geometry().x(); const int distanceToRight = q->layout()->layoutWidth() - group->geometry().right(); return distanceToLeft > distanceToRight ? SideBarLocation::East : SideBarLocation::West; } // 5. It's in a corner if (borders == (Core::LayoutBorderLocation_West | Core::LayoutBorderLocation_South)) { return aspectRatio > 1.0 ? SideBarLocation::South : SideBarLocation::West; } else if (borders == (Core::LayoutBorderLocation_East | Core::LayoutBorderLocation_South)) { return aspectRatio > 1.0 ? SideBarLocation::South : SideBarLocation::East; } else if (borders == (Core::LayoutBorderLocation_West | Core::LayoutBorderLocation_North)) { return aspectRatio > 1.0 ? SideBarLocation::North : SideBarLocation::West; } else if (borders == (Core::LayoutBorderLocation_East | Core::LayoutBorderLocation_North)) { return aspectRatio > 1.0 ? SideBarLocation::North : SideBarLocation::East; } { // 6. It's only touching 1 border SideBarLocation loc = sideBarLocationForBorder(borders); if (loc != SideBarLocation::None) return loc; } // It's not touching any border, use aspect ratio. return aspectRatio > 1.0 ? SideBarLocation::South : SideBarLocation::West; } void MainWindow::Private::updateOverlayGeometry(Size suggestedSize) { if (!m_overlayedDockWidget) return; Core::SideBar *sb = q->sideBarForDockWidget(m_overlayedDockWidget); if (!sb) { KDDW_ERROR("Expected a sidebar"); return; } const Rect defaultGeometry = rectForOverlay(m_overlayedDockWidget->d->group(), sb->location()); Rect newGeometry = defaultGeometry; Core::Group *group = m_overlayedDockWidget->d->group(); if (suggestedSize.isValid() && !suggestedSize.isEmpty()) { // Let's try to honour the suggested overlay size switch (sb->location()) { case SideBarLocation::North: { const int maxHeight = q->height() - group->pos().y() - 10; // gap newGeometry.setHeight(( std::min )(suggestedSize.height(), maxHeight)); break; } case SideBarLocation::South: { const int maxHeight = sb->pos().y() - m_layout->view()->pos().y() - 10; // gap const int bottom = newGeometry.bottom(); newGeometry.setHeight(( std::min )(suggestedSize.height(), maxHeight)); newGeometry.moveBottom(bottom); break; } case SideBarLocation::East: { const int maxWidth = sb->pos().x() - m_layout->view()->pos().x() - 10; // gap const int right = newGeometry.right(); newGeometry.setWidth(( std::min )(suggestedSize.width(), maxWidth)); newGeometry.moveRight(right); break; } case SideBarLocation::West: { const int maxWidth = q->width() - group->pos().x() - 10; // gap newGeometry.setWidth(( std::min )(suggestedSize.width(), maxWidth)); break; } case SideBarLocation::None: case SideBarLocation::Last: KDDW_ERROR("Unexpected sidebar value"); break; } } m_overlayedDockWidget->d->group()->view()->setGeometry(newGeometry); } void MainWindow::Private::clearSideBars() { for (auto loc : { SideBarLocation::North, SideBarLocation::South, SideBarLocation::East, SideBarLocation::West }) { if (Core::SideBar *sb = q->sideBar(loc)) sb->clear(); } } Rect MainWindow::Private::windowGeometry() const { /// @brief Returns the window geometry /// This is usually the same as the view's geometry() /// But fixes the following special cases: /// - QWidgets: Our MainWindow is embedded in another widget /// - QtQuick: Our MainWindow is QQuickItem if (Core::Window::Ptr window = q->view()->window()) return window->geometry(); return q->window()->geometry(); } void MainWindow::moveToSideBar(Core::DockWidget *dw) { moveToSideBar(dw, d->preferredSideBar(dw)); } void MainWindow::moveToSideBar(Core::DockWidget *dw, SideBarLocation location) { if (dw->isPersistentCentralDockWidget()) return; if (Core::SideBar *sb = sideBar(location)) { ScopedValueRollback rollback(dw->d->m_isMovingToSideBar, true); CloseReasonSetter reason(CloseReason::MovedToSideBar); dw->forceClose(); sb->addDockWidget(dw); } else { // Shouldn't happen KDDW_ERROR("Minimization supported, probably disabled in Config::self().flags()"); } } void MainWindow::restoreFromSideBar(Core::DockWidget *dw) { if (!dw) return; DockWidget::Private::UpdateActions updateActions(dw); // First un-overlay it, if it's overlayed if (dw == d->m_overlayedDockWidget) clearSideBarOverlay(); Core::SideBar *sb = sideBarForDockWidget(dw); if (!sb) { // Doesn't happen KDDW_ERROR("Dock widget isn't in any sidebar"); return; } sb->removeDockWidget(dw); dw->setFloating(false); // dock it } void MainWindow::overlayOnSideBar(Core::DockWidget *dw) { if (!dw || dw->isPersistentCentralDockWidget()) return; const Core::SideBar *sb = sideBarForDockWidget(dw); if (!sb) { KDDW_ERROR("You need to add the dock widget to the sidebar before you can overlay it"); return; } if (d->m_overlayedDockWidget == dw) { // Already overlayed return; } // We only support one overlay at a time, remove any existing overlay clearSideBarOverlay(); auto group = new Core::Group(nullptr, FrameOption_IsOverlayed); group->setParentView(view()); d->m_overlayedDockWidget = dw; group->addTab(dw); d->updateOverlayGeometry(dw->d->lastPosition()->lastOverlayedGeometry(sb->location()).size()); group->setAllowedResizeSides(d->allowedResizeSides(sb->location())); group->view()->show(); dw->d->isOverlayedChanged.emit(true); } void MainWindow::toggleOverlayOnSideBar(Core::DockWidget *dw) { const bool wasOverlayed = d->m_overlayedDockWidget == dw; clearSideBarOverlay(); // Because only 1 dock widget can be overlayed each time if (!wasOverlayed) { overlayOnSideBar(dw); } } void MainWindow::clearSideBarOverlay(bool deleteGroup) { if (!d->m_overlayedDockWidget) return; auto overlayedDockWidget = d->m_overlayedDockWidget; d->m_overlayedDockWidget = nullptr; Core::Group *group = overlayedDockWidget->d->group(); if (!group) { // prophylactic check return; } const SideBarLocation loc = overlayedDockWidget->sideBarLocation(); overlayedDockWidget->d->lastPosition()->setLastOverlayedGeometry(loc, group->geometry()); CloseReasonSetter reason(CloseReason::OverlayCollapse); group->unoverlay(); if (deleteGroup) { // only update actions at the end DockWidget::Private::UpdateActions updateActions(overlayedDockWidget); overlayedDockWidget->setParent(nullptr); { ScopedValueRollback guard(overlayedDockWidget->d->m_removingFromOverlay, true); overlayedDockWidget->setParentView(nullptr); overlayedDockWidget->dptr()->setIsOpen(false); } overlayedDockWidget->d->isOverlayedChanged.emit(false); overlayedDockWidget = nullptr; delete group; } else { // No cleanup, just unset. When we drag the overlay it becomes a normal floating window // meaning we reuse Frame. Don't delete it. overlayedDockWidget->d->isOverlayedChanged.emit(false); overlayedDockWidget = nullptr; } } Core::SideBar *MainWindow::sideBarForDockWidget(const Core::DockWidget *dw) const { for (auto loc : { SideBarLocation::North, SideBarLocation::South, SideBarLocation::East, SideBarLocation::West }) { if (Core::SideBar *sb = sideBar(loc)) { if (sb->containsDockWidget(const_cast(dw))) return sb; } } return nullptr; } Core::DockWidget *MainWindow::overlayedDockWidget() const { return d->m_overlayedDockWidget; } bool MainWindow::sideBarIsVisible(SideBarLocation loc) const { if (Core::SideBar *sb = sideBar(loc)) { return !sb->isEmpty(); // isVisible() is always true, but its height is 0 when empty. } return false; } bool MainWindow::anySideBarIsVisible() const { for (auto loc : { SideBarLocation::North, SideBarLocation::South, SideBarLocation::East, SideBarLocation::West }) { if (sideBarIsVisible(loc)) return true; } return false; } bool MainWindow::isMDI() const { return d->m_options & MainWindowOption_MDI; } bool MainWindow::closeDockWidgets(bool force) { bool allClosed = true; const auto dockWidgets = d->m_layout->dockWidgets(); for (Core::DockWidget *dw : dockWidgets) { Core::Group *group = dw->d->group(); if (force) { dw->forceClose(); } else { const bool closed = dw->view()->close(); allClosed = allClosed && closed; } if (group->beingDeletedLater()) { // The dock widget was closed and this group is empty, delete immediately instead of // waiting. I'm not a big fan of deleting stuff later, as state becomes inconsistent // Empty groups are historically deleted later since they are triggered by mouse click // on the title bar, and the title bar is inside the group. // When doing it programmatically we can delete immediately. delete group; } } return allClosed; } void MainWindow::setUniqueName(const QString &uniqueName) { if (uniqueName.isEmpty()) return; if (d->name.isEmpty()) { d->name = uniqueName; d->uniqueNameChanged.emit(); DockRegistry::self()->registerMainWindow(this); } else { KDDW_ERROR("Already has a name. {} {}", this->uniqueName(), uniqueName); } } bool MainWindow::deserialize(const LayoutSaver::MainWindow &mw) { if (mw.options != options()) { KDDW_ERROR("Refusing to restore MainWindow with different options ; expected={}, has={}", int(mw.options), int(options())); return false; } if (d->affinities != mw.affinities) { KDDW_ERROR("Affinity name changed from {} to {}", d->affinities, mw.affinities); d->affinities = mw.affinities; } // Restore the SideBars d->clearSideBars(); for (SideBarLocation loc : { SideBarLocation::North, SideBarLocation::East, SideBarLocation::West, SideBarLocation::South }) { Core::SideBar *sb = sideBar(loc); if (!sb) continue; const Vector dockWidgets = mw.dockWidgetsForSideBar(loc); for (const QString &uniqueName : dockWidgets) { Core::DockWidget *dw = DockRegistry::self()->dockByName( uniqueName, DockRegistry::DockByNameFlag::CreateIfNotFound); if (!dw) { KDDW_ERROR("Could not find dock widget {} . Won't restore it to sidebar", uniqueName); continue; } sb->addDockWidget(dw); } } const bool success = layout()->deserialize(mw.multiSplitterLayout); // Commented-out for now, we don't want to restore the popup/overlay. popups are perishable // if (!mw.overlayedDockWidget.isEmpty()) // overlayOnSideBar(DockRegistry::self()->dockByName(mw.overlayedDockWidget)); return success; } LayoutSaver::MainWindow MainWindow::serialize() const { LayoutSaver::MainWindow m; Window::Ptr window = view()->window(); m.options = options(); m.geometry = d->windowGeometry(); m.normalGeometry = view()->normalGeometry(); m.isVisible = isVisible(); m.uniqueName = uniqueName(); m.screenIndex = Platform::instance()->screenNumberForView(view()); m.screenSize = Platform::instance()->screenSizeFor(view()); m.multiSplitterLayout = layout()->serialize(); m.affinities = d->affinities; m.windowState = window ? window->windowState() : WindowState::None; for (SideBarLocation loc : { SideBarLocation::North, SideBarLocation::East, SideBarLocation::West, SideBarLocation::South }) { if (Core::SideBar *sb = sideBar(loc)) { const Vector dockwidgets = sb->serialize(); if (!dockwidgets.isEmpty()) m.dockWidgetsPerSideBar[loc] = dockwidgets; } } return m; } void MainWindow::setPersistentCentralView(std::shared_ptr widget) { if (!d->supportsPersistentCentralWidget()) { KDDW_ERROR("MainWindow::setPersistentCentralWidget() requires " "MainWindowOption_HasCentralWidget"); return; } if (auto dw = d->m_persistentCentralDockWidget) { dw->setGuestView(widget); } else { KDDW_ERROR("Unexpected null central dock widget"); } } std::shared_ptr MainWindow::persistentCentralView() const { if (auto dw = d->m_persistentCentralDockWidget) return dw->guestView(); return {}; } void MainWindow::setContentsMargins(int left, int top, int right, int bottom) { auto v = dynamic_cast(view()); v->setContentsMargins(left, top, right, bottom); } Margins MainWindow::centerWidgetMargins() const { auto v = dynamic_cast(view()); return v->centerWidgetMargins(); } Core::SideBar *MainWindow::sideBar(SideBarLocation loc) const { auto it = d->m_sideBars.find(loc); return it == d->m_sideBars.cend() ? nullptr : it->second; } Rect MainWindow::centralAreaGeometry() const { auto v = dynamic_cast(view()); return v->centralAreaGeometry(); } int MainWindow::overlayMargin() const { return d->m_overlayMargin; } void MainWindow::setOverlayMargin(int margin) { if (margin == d->m_overlayMargin) return; d->m_overlayMargin = margin; d->overlayMarginChanged.emit(margin); } bool MainWindow::isInDockWidget() const { auto v = view(); if (!v) return false; if (auto p = v->parentView()) return p->firstParentOfType(p.get(), ViewType::DockWidget) != nullptr; return false; }