968 lines
25 KiB
C++
968 lines
25 KiB
C++
/*
|
|
This file is part of KDDockWidgets.
|
|
|
|
SPDX-FileCopyrightText: 2020-2022 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 "Frame.h"
|
|
|
|
#include "kddockwidgets/Config.h"
|
|
#include "kddockwidgets/FrameworkWidgetFactory.h"
|
|
|
|
#include "Controller.h"
|
|
#include "View.h"
|
|
#include "views/Frame.h"
|
|
#include "controllers/TitleBar.h"
|
|
#include "controllers/Stack.h"
|
|
#include "controllers/FloatingWindow.h"
|
|
#include "private/Logging_p.h"
|
|
#include "private/Utils_p.h"
|
|
#include "private/DockRegistry_p.h"
|
|
#include "DockWidget_p.h"
|
|
#include "private/LayoutSaver_p.h"
|
|
#include "private/LayoutWidget_p.h"
|
|
#include "private/Position_p.h"
|
|
#include "private/WidgetResizeHandler_p.h"
|
|
#include "private/MDILayoutWidget_p.h"
|
|
#include "private/DropAreaWithCentralFrame_p.h"
|
|
#include "private/multisplitter/Item_p.h"
|
|
|
|
#include <QCloseEvent>
|
|
#include <QTimer>
|
|
|
|
#define MARGIN_THRESHOLD 100
|
|
|
|
static int s_dbg_numFrames = 0;
|
|
|
|
using namespace KDDockWidgets;
|
|
using namespace KDDockWidgets::Controllers;
|
|
|
|
namespace KDDockWidgets {
|
|
|
|
static FrameOptions actualOptions(FrameOptions options)
|
|
{
|
|
if (Config::self().flags() & Config::Flag_AlwaysShowTabs)
|
|
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 frame either
|
|
return StackOption_DocumentMode;
|
|
}
|
|
|
|
return StackOption_None;
|
|
}
|
|
|
|
}
|
|
|
|
Frame::Frame(View *parent, FrameOptions options, int userType)
|
|
: Controller(Type::Frame, Config::self().frameworkWidgetFactory()->createFrame(this, parent))
|
|
, FocusScope(view())
|
|
, m_tabWidget(new Controllers::Stack(this, tabWidgetOptions(options)))
|
|
, m_titleBar(new Controllers::TitleBar(this))
|
|
, m_options(actualOptions(options))
|
|
, m_userType(userType)
|
|
{
|
|
s_dbg_numFrames++;
|
|
DockRegistry::self()->registerFrame(this);
|
|
|
|
connect(this, &Frame::currentDockWidgetChanged, this, &Frame::updateTitleAndIcon);
|
|
connect(m_tabWidget, &Controllers::Stack::currentTabChanged,
|
|
this, &Frame::onCurrentTabChanged);
|
|
|
|
setLayoutWidget(qobject_cast<LayoutWidget *>(parent ? parent->asQObject() : nullptr)); // TODO
|
|
|
|
view()->init();
|
|
|
|
m_inCtor = false;
|
|
}
|
|
|
|
Frame::~Frame()
|
|
{
|
|
m_inDtor = true;
|
|
s_dbg_numFrames--;
|
|
if (m_layoutItem)
|
|
m_layoutItem->unref();
|
|
|
|
delete m_resizeHandler;
|
|
m_resizeHandler = nullptr;
|
|
|
|
DockRegistry::self()->unregisterFrame(this);
|
|
|
|
// Run some disconnects() too, so we don't receive signals during destruction:
|
|
setLayoutWidget(nullptr);
|
|
delete m_titleBar;
|
|
delete m_tabWidget;
|
|
}
|
|
|
|
void Frame::onCloseEvent(QCloseEvent *e)
|
|
{
|
|
qCDebug(closing) << "Frame::closeEvent";
|
|
e->accept(); // Accepted by default (will close unless ignored)
|
|
const DockWidget::List docks = dockWidgets();
|
|
for (DockWidget *dock : docks) {
|
|
qApp->sendEvent(dock->view()->asQWidget(), e);
|
|
if (!e->isAccepted())
|
|
break; // Stop when the first dockwidget prevents closing
|
|
}
|
|
}
|
|
|
|
void Frame::setLayoutWidget(LayoutWidget *dt)
|
|
{
|
|
if (dt == m_layoutWidget)
|
|
return;
|
|
|
|
const bool wasInMainWindow = dt && isInMainWindow();
|
|
const bool wasMDI = isMDI();
|
|
if (m_layoutWidget)
|
|
disconnect(m_visibleWidgetCountChangedConnection);
|
|
|
|
m_layoutWidget = dt;
|
|
delete m_resizeHandler;
|
|
m_resizeHandler = nullptr;
|
|
|
|
if (m_layoutWidget) {
|
|
if (isMDI())
|
|
m_resizeHandler = new WidgetResizeHandler(/*topLevel=*/false, view());
|
|
|
|
// We keep the connect result so we don't dereference m_layoutWidget at shutdown
|
|
m_visibleWidgetCountChangedConnection =
|
|
connect(m_layoutWidget, &LayoutWidget::visibleWidgetCountChanged, this,
|
|
&Frame::updateTitleBarVisibility);
|
|
updateTitleBarVisibility();
|
|
if (wasInMainWindow != isInMainWindow())
|
|
Q_EMIT isInMainWindowChanged();
|
|
}
|
|
|
|
if (wasMDI != isMDI())
|
|
Q_EMIT isMDIChanged();
|
|
}
|
|
|
|
void Frame::renameTab(int index, const QString &title)
|
|
{
|
|
dynamic_cast<Views::Frame *>(view())->renameTab(index, title);
|
|
}
|
|
|
|
void Frame::changeTabIcon(int index, const QIcon &icon)
|
|
{
|
|
dynamic_cast<Views::Frame *>(view())->changeTabIcon(index, icon);
|
|
}
|
|
|
|
void Frame::removeWidget_impl(DockWidget *dw)
|
|
{
|
|
dynamic_cast<Views::Frame *>(view())->removeWidget_impl(dw);
|
|
}
|
|
|
|
int Frame::indexOfDockWidget_impl(const DockWidget *dw)
|
|
{
|
|
return dynamic_cast<Views::Frame *>(view())->indexOfDockWidget_impl(dw);
|
|
}
|
|
|
|
int Frame::currentIndex_impl() const
|
|
{
|
|
return dynamic_cast<Views::Frame *>(view())->currentIndex_impl();
|
|
}
|
|
|
|
void Frame::setCurrentTabIndex_impl(int index)
|
|
{
|
|
dynamic_cast<Views::Frame *>(view())->setCurrentTabIndex_impl(index);
|
|
}
|
|
|
|
void Frame::setCurrentDockWidget_impl(DockWidget *dw)
|
|
{
|
|
dynamic_cast<Views::Frame *>(view())->setCurrentDockWidget_impl(dw);
|
|
}
|
|
|
|
void Frame::insertDockWidget_impl(DockWidget *dw, int index)
|
|
{
|
|
dynamic_cast<Views::Frame *>(view())->insertDockWidget_impl(dw, index);
|
|
}
|
|
|
|
DockWidgetBase *Frame::dockWidgetAt_impl(int index) const
|
|
{
|
|
return dynamic_cast<Views::Frame *>(view())->dockWidgetAt_impl(index);
|
|
}
|
|
|
|
DockWidgetBase *Frame::currentDockWidget_impl() const
|
|
{
|
|
return dynamic_cast<Views::Frame *>(view())->currentDockWidget_impl();
|
|
}
|
|
|
|
int Frame::nonContentsHeight() const
|
|
{
|
|
return dynamic_cast<Views::Frame *>(view())->nonContentsHeight();
|
|
}
|
|
|
|
Controllers::Stack *Frame::tabWidget() const
|
|
{
|
|
return m_tabWidget;
|
|
}
|
|
|
|
Controllers::TabBar *Frame::tabBar() const
|
|
{
|
|
return m_tabWidget->tabBar();
|
|
}
|
|
|
|
void Frame::updateTitleAndIcon()
|
|
{
|
|
if (DockWidget *dw = currentDockWidget()) {
|
|
m_titleBar->setTitle(dw->title());
|
|
m_titleBar->setIcon(dw->icon());
|
|
|
|
if (auto fw = floatingWindow()) {
|
|
if (fw->hasSingleFrame()) {
|
|
fw->updateTitleAndIcon();
|
|
}
|
|
}
|
|
|
|
setObjectName(dw->uniqueName());
|
|
|
|
} else if (currentTabIndex() != -1) {
|
|
qWarning() << Q_FUNC_INFO << "Invalid dock widget for frame." << currentTabIndex();
|
|
}
|
|
}
|
|
|
|
void Frame::onDockWidgetTitleChanged()
|
|
{
|
|
updateTitleAndIcon();
|
|
|
|
if (!m_inCtor) { // don't call pure virtual in ctor
|
|
if (auto dw = qobject_cast<DockWidget *>(sender())) {
|
|
int index = indexOfDockWidget(dw);
|
|
renameTab(index, dw->title());
|
|
changeTabIcon(index, dw->icon(DockWidget::IconPlace::TabBar));
|
|
}
|
|
}
|
|
}
|
|
|
|
void Frame::addWidget(DockWidget *dockWidget, InitialOption addingOption)
|
|
{
|
|
insertWidget(dockWidget, dockWidgetCount(), addingOption); // append
|
|
}
|
|
|
|
void Frame::addWidget(Frame *frame, InitialOption addingOption)
|
|
{
|
|
if (frame->isEmpty()) {
|
|
qWarning() << "Frame::addWidget: frame is empty." << frame;
|
|
return;
|
|
}
|
|
|
|
const auto &docks = frame->dockWidgets();
|
|
for (DockWidget *dockWidget : docks)
|
|
addWidget(dockWidget, addingOption);
|
|
}
|
|
|
|
void Frame::addWidget(FloatingWindow *floatingWindow, InitialOption addingOption)
|
|
{
|
|
Q_ASSERT(floatingWindow);
|
|
for (Frame *f : floatingWindow->frames())
|
|
addWidget(f, addingOption);
|
|
}
|
|
|
|
void Frame::insertWidget(DockWidget *dockWidget, int index, InitialOption addingOption)
|
|
{
|
|
Q_ASSERT(dockWidget);
|
|
if (containsDockWidget(dockWidget)) {
|
|
if (!dockWidget->isPersistentCentralDockWidget())
|
|
qWarning() << "Frame::addWidget dockWidget already exists. this=" << this << "; dockWidget=" << dockWidget;
|
|
return;
|
|
}
|
|
if (m_layoutItem)
|
|
dockWidget->d->addPlaceholderItem(m_layoutItem);
|
|
|
|
insertDockWidget(dockWidget, index);
|
|
|
|
if (addingOption.startsHidden()) {
|
|
dockWidget->view()->close(); // Ensure closed
|
|
} else {
|
|
if (hasSingleDockWidget()) {
|
|
Q_EMIT currentDockWidgetChanged(dockWidget);
|
|
setObjectName(dockWidget->uniqueName());
|
|
|
|
if (!m_layoutItem) {
|
|
// When adding the 1st dock widget of a fresh frame, let's give the frame 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());
|
|
}
|
|
}
|
|
}
|
|
|
|
connect(dockWidget, &DockWidget::titleChanged, this, &Frame::onDockWidgetTitleChanged);
|
|
connect(dockWidget, &DockWidget::iconChanged, this, &Frame::onDockWidgetTitleChanged);
|
|
}
|
|
|
|
void Frame::removeWidget(DockWidget *dw)
|
|
{
|
|
disconnect(dw, &DockWidget::titleChanged, this, &Frame::onDockWidgetTitleChanged);
|
|
disconnect(dw, &DockWidget::iconChanged, this, &Frame::onDockWidgetTitleChanged);
|
|
removeWidget_impl(dw);
|
|
}
|
|
|
|
FloatingWindow *Frame::detachTab(DockWidget *dockWidget)
|
|
{
|
|
if (m_inCtor || m_inDtor)
|
|
return nullptr;
|
|
|
|
dockWidget->d->saveTabIndex();
|
|
|
|
QRect r = dockWidget->geometry();
|
|
removeWidget(dockWidget);
|
|
|
|
auto newFrame = new Frame();
|
|
const QPoint globalPoint = mapToGlobal(QPoint(0, 0));
|
|
newFrame->addWidget(dockWidget);
|
|
|
|
// We're potentially already dead at this point, as frames with 0 tabs auto-destruct. Don't access members from this point.
|
|
|
|
auto floatingWindow = new FloatingWindow(newFrame, {});
|
|
r.moveTopLeft(globalPoint);
|
|
floatingWindow->setSuggestedGeometry(r, SuggestedGeometryHint_GeometryIsFromDocked);
|
|
floatingWindow->view()->show();
|
|
|
|
return floatingWindow;
|
|
}
|
|
|
|
int Frame::indexOfDockWidget(const DockWidget *dw)
|
|
{
|
|
if (m_inCtor || m_inDtor)
|
|
return -1;
|
|
|
|
return indexOfDockWidget_impl(dw);
|
|
}
|
|
|
|
int Frame::currentIndex() const
|
|
{
|
|
if (m_inCtor || m_inDtor)
|
|
return -1;
|
|
|
|
return currentIndex_impl();
|
|
}
|
|
|
|
void Frame::setCurrentTabIndex(int index)
|
|
{
|
|
if (m_inCtor || m_inDtor)
|
|
return;
|
|
|
|
setCurrentTabIndex_impl(index);
|
|
}
|
|
|
|
void Frame::setCurrentDockWidget(DockWidget *dw)
|
|
{
|
|
if (m_inCtor || m_inDtor)
|
|
return;
|
|
|
|
setCurrentDockWidget_impl(dw);
|
|
}
|
|
|
|
void Frame::insertDockWidget(DockWidget *dw, int index)
|
|
{
|
|
if (m_inCtor || m_inDtor)
|
|
return;
|
|
|
|
insertDockWidget_impl(dw, index);
|
|
}
|
|
|
|
DockWidgetBase *Frame::dockWidgetAt(int index) const
|
|
{
|
|
if (m_inCtor || m_inDtor)
|
|
return nullptr;
|
|
|
|
return dockWidgetAt_impl(index);
|
|
}
|
|
|
|
DockWidgetBase *Frame::currentDockWidget() const
|
|
{
|
|
if (m_inCtor || m_inDtor)
|
|
return nullptr;
|
|
|
|
return currentDockWidget_impl();
|
|
}
|
|
|
|
int Frame::dockWidgetCount() const
|
|
{
|
|
if (m_inCtor || m_inDtor)
|
|
return 0;
|
|
|
|
return m_tabWidget->numDockWidgets();
|
|
}
|
|
|
|
void Frame::onDockWidgetCountChanged()
|
|
{
|
|
qCDebug(docking) << "Frame::onDockWidgetCountChanged:" << this << "; widgetCount=" << dockWidgetCount();
|
|
if (isEmpty() && !isCentralFrame()) {
|
|
scheduleDeleteLater();
|
|
} else {
|
|
updateTitleBarVisibility();
|
|
|
|
// We don't really keep track of the state, so emit even if the visibility didn't change. No biggie.
|
|
if (!(m_options & FrameOption_AlwaysShowsTabs))
|
|
Q_EMIT hasTabsVisibleChanged();
|
|
|
|
const DockWidget::List docks = dockWidgets();
|
|
for (DockWidget *dock : docks)
|
|
dock->d->updateFloatAction();
|
|
}
|
|
|
|
Q_EMIT numDockWidgetsChanged();
|
|
}
|
|
|
|
void Frame::onCurrentTabChanged(int index)
|
|
{
|
|
if (index != -1) {
|
|
if (auto dock = dockWidgetAt(index)) {
|
|
Q_EMIT currentDockWidgetChanged(dock);
|
|
} else {
|
|
qWarning() << "dockWidgetAt" << index << "returned nullptr" << this;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Frame::isFocusedChangedCallback()
|
|
{
|
|
Q_EMIT isFocusedChanged();
|
|
}
|
|
|
|
void Frame::focusedWidgetChangedCallback()
|
|
{
|
|
Q_EMIT focusedWidgetChanged();
|
|
}
|
|
|
|
void Frame::updateTitleBarVisibility()
|
|
{
|
|
if (m_updatingTitleBar || m_beingDeleted) {
|
|
// To break a cyclic dependency
|
|
return;
|
|
}
|
|
|
|
QScopedValueRollback<bool> guard(m_updatingTitleBar, true);
|
|
|
|
bool visible = false;
|
|
if (isCentralFrame()) {
|
|
visible = false;
|
|
} else if ((Config::self().flags() & Config::Flag_HideTitleBarWhenTabsVisible) && hasTabsVisible()) {
|
|
visible = false;
|
|
} else if (FloatingWindow *fw = floatingWindow()) {
|
|
// If there's nested frames then show each Frame's title bar
|
|
visible = !fw->hasSingleFrame();
|
|
} else if (isMDIWrapper()) {
|
|
auto dropArea = this->mdiDropAreaWrapper();
|
|
visible = !dropArea->hasSingleFrame();
|
|
} else {
|
|
visible = true;
|
|
}
|
|
|
|
const bool wasVisible = m_titleBar->view()->isVisible();
|
|
m_titleBar->view()->setVisible(visible);
|
|
|
|
if (wasVisible != visible) {
|
|
Q_EMIT actualTitleBarChanged();
|
|
for (auto dw : dockWidgets())
|
|
Q_EMIT dw->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 frame has
|
|
fw->updateTitleBarVisibility();
|
|
}
|
|
}
|
|
|
|
void Frame::updateFloatingActions()
|
|
{
|
|
const QVector<DockWidget *> widgets = dockWidgets();
|
|
for (DockWidget *dw : widgets)
|
|
dw->d->updateFloatAction();
|
|
}
|
|
|
|
bool Frame::containsMouse(QPoint globalPos) const
|
|
{
|
|
return rect().contains(view()->mapFromGlobal(globalPos));
|
|
}
|
|
|
|
Controllers::TitleBar *Frame::titleBar() const
|
|
{
|
|
return m_titleBar;
|
|
}
|
|
|
|
Controllers::TitleBar *Frame::actualTitleBar() const
|
|
{
|
|
if (FloatingWindow *fw = floatingWindow()) {
|
|
// If there's nested frames then show each Frame's title bar
|
|
if (fw->hasSingleFrame())
|
|
return fw->titleBar();
|
|
} else if (auto mdiDropArea = mdiDropAreaWrapper()) {
|
|
if (mdiDropArea->hasSingleFrame()) {
|
|
return mdiFrame()->titleBar();
|
|
}
|
|
}
|
|
|
|
return titleBar();
|
|
}
|
|
|
|
QString Frame::title() const
|
|
{
|
|
return m_titleBar->title();
|
|
}
|
|
|
|
QIcon Frame::icon() const
|
|
{
|
|
return m_titleBar->icon();
|
|
}
|
|
|
|
const DockWidgetBase::List Frame::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 << dockWidgetAt(i);
|
|
|
|
return dockWidgets;
|
|
}
|
|
|
|
bool Frame::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 *Frame::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(Type::MainWindow))
|
|
return nullptr;
|
|
|
|
if (auto fw = p->asFloatingWindowController())
|
|
return fw;
|
|
|
|
if (p->equals(view()->window())) {
|
|
// We stop at the window. (top-levels can have parent, but we're not interested)
|
|
return nullptr;
|
|
}
|
|
|
|
p = p->parentView();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void Frame::restoreToPreviousPosition()
|
|
{
|
|
if (hasSingleDockWidget()) {
|
|
qWarning() << Q_FUNC_INFO << "Invalid usage, there's no tabs";
|
|
return;
|
|
}
|
|
|
|
if (!m_layoutItem) {
|
|
qCDebug(placeholder) << Q_FUNC_INFO << "There's no previous position known";
|
|
return;
|
|
}
|
|
|
|
if (!m_layoutItem->isPlaceholder()) {
|
|
// Maybe in this case just fold the frame into the placeholder, which probably has other dockwidgets which were added meanwhile. TODO
|
|
qCDebug(placeholder) << Q_FUNC_INFO << "Previous position isn't a placeholder";
|
|
return;
|
|
}
|
|
|
|
m_layoutItem->restore(view());
|
|
}
|
|
|
|
int Frame::currentTabIndex() const
|
|
{
|
|
return currentIndex();
|
|
}
|
|
|
|
bool Frame::anyNonClosable() const
|
|
{
|
|
for (auto dw : dockWidgets()) {
|
|
if ((dw->options() & DockWidget::Option_NotClosable) && !DockRegistry::self()->isProcessingAppQuitEvent())
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Frame::anyNonDockable() const
|
|
{
|
|
for (auto dw : dockWidgets()) {
|
|
if (dw->options() & DockWidget::Option_NotDockable)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Frame::onDockWidgetShown(DockWidget *w)
|
|
{
|
|
if (hasSingleDockWidget() && containsDockWidget(w)) { // We have to call contains because it might be being in process of being reparented
|
|
if (!isVisible()) {
|
|
qCDebug(hiding) << "Widget" << w << " was shown, we're="
|
|
<< "; visible="
|
|
<< isVisible();
|
|
setVisible(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Frame::onDockWidgetHidden(DockWidget *w)
|
|
{
|
|
if (!isCentralFrame() && hasSingleDockWidget() && containsDockWidget(w)) { // We have to call contains because it might be being in process of being reparented
|
|
if (isVisible()) {
|
|
qCDebug(hiding) << "Widget" << w << " was hidden, we're="
|
|
<< "; visible=" << isVisible()
|
|
<< "; dockWidgets=" << dockWidgets();
|
|
setVisible(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Frame::setLayoutItem(Layouting::Item *item)
|
|
{
|
|
if (item == m_layoutItem)
|
|
return;
|
|
|
|
if (m_layoutItem)
|
|
m_layoutItem->unref();
|
|
|
|
if (item)
|
|
item->ref();
|
|
|
|
m_layoutItem = item;
|
|
if (item) {
|
|
for (DockWidget *dw : dockWidgets())
|
|
dw->d->addPlaceholderItem(item);
|
|
} else {
|
|
for (DockWidget *dw : dockWidgets())
|
|
dw->d->lastPosition()->removePlaceholders();
|
|
}
|
|
}
|
|
|
|
Layouting::Item *Frame::layoutItem() const
|
|
{
|
|
return m_layoutItem;
|
|
}
|
|
|
|
int Frame::dbg_numFrames()
|
|
{
|
|
return s_dbg_numFrames;
|
|
}
|
|
|
|
bool Frame::beingDeletedLater() const
|
|
{
|
|
return m_beingDeleted;
|
|
}
|
|
|
|
bool Frame::hasTabsVisible() const
|
|
{
|
|
if (m_beingDeleted)
|
|
return false;
|
|
|
|
return alwaysShowsTabs() || dockWidgetCount() > 1;
|
|
}
|
|
|
|
QStringList Frame::affinities() const
|
|
{
|
|
if (isEmpty()) {
|
|
return {};
|
|
} else {
|
|
return dockWidgetAt(0)->affinities();
|
|
}
|
|
}
|
|
|
|
bool Frame::isTheOnlyFrame() const
|
|
{
|
|
return m_layoutWidget && m_layoutWidget->visibleCount() == 1;
|
|
}
|
|
|
|
bool Frame::isOverlayed() const
|
|
{
|
|
return m_options & FrameOption_IsOverlayed;
|
|
}
|
|
|
|
void Frame::unoverlay()
|
|
{
|
|
m_options &= ~FrameOption_IsOverlayed;
|
|
}
|
|
|
|
bool Frame::isFloating() const
|
|
{
|
|
if (isInMainWindow() || isMDI())
|
|
return false;
|
|
|
|
return isTheOnlyFrame();
|
|
}
|
|
|
|
bool Frame::isInFloatingWindow() const
|
|
{
|
|
return floatingWindow() != nullptr;
|
|
}
|
|
|
|
bool Frame::isInMainWindow() const
|
|
{
|
|
return mainWindow() != nullptr;
|
|
}
|
|
|
|
Frame *Frame::deserialize(const LayoutSaver::Frame &f)
|
|
{
|
|
if (!f.isValid())
|
|
return nullptr;
|
|
|
|
const FrameOptions options = FrameOptions(f.options);
|
|
Frame *frame = nullptr;
|
|
const bool isPersistentCentralFrame = options & FrameOption::FrameOption_IsCentralFrame;
|
|
|
|
if (isPersistentCentralFrame) {
|
|
// Don't create a new Frame if we're restoring the Persistent Central frame (the one created
|
|
// by MainWindowOption_HasCentralFrame). It already exists.
|
|
|
|
if (f.mainWindowUniqueName.isEmpty()) {
|
|
// Can happen with older serialization formats
|
|
qWarning() << Q_FUNC_INFO << "Frame is the persistent central frame but doesn't have"
|
|
<< "an associated window name";
|
|
} else {
|
|
if (MainWindow *mw = DockRegistry::self()->mainWindowByName(f.mainWindowUniqueName)) {
|
|
frame = mw->dropArea()->m_centralFrame;
|
|
if (!frame) {
|
|
// Doesn't happen...
|
|
qWarning() << "Main window" << f.mainWindowUniqueName << "doesn't have central frame";
|
|
}
|
|
} else {
|
|
// Doesn't happen...
|
|
qWarning() << Q_FUNC_INFO << "Couldn't find main window"
|
|
<< f.mainWindowUniqueName;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!frame)
|
|
frame = new Frame(nullptr, options);
|
|
|
|
frame->setObjectName(f.objectName);
|
|
|
|
for (const auto &savedDock : qAsConst(f.dockWidgets)) {
|
|
if (DockWidget *dw = DockWidget::deserialize(savedDock)) {
|
|
frame->addWidget(dw);
|
|
}
|
|
}
|
|
|
|
frame->setCurrentTabIndex(f.currentTabIndex);
|
|
frame->view()->setGeometry(f.geometry);
|
|
|
|
return frame;
|
|
}
|
|
|
|
LayoutSaver::Frame Frame::serialize() const
|
|
{
|
|
LayoutSaver::Frame frame;
|
|
frame.isNull = false;
|
|
|
|
const DockWidget::List docks = dockWidgets();
|
|
|
|
frame.objectName = objectName();
|
|
frame.geometry = geometry();
|
|
frame.options = options();
|
|
frame.currentTabIndex = currentTabIndex();
|
|
frame.id = view()->id(); // for coorelation purposes
|
|
|
|
if (MainWindow *mw = mainWindow())
|
|
frame.mainWindowUniqueName = mw->uniqueName();
|
|
|
|
for (DockWidget *dock : docks)
|
|
frame.dockWidgets.push_back(dock->d->serialize());
|
|
|
|
return frame;
|
|
}
|
|
|
|
void Frame::scheduleDeleteLater()
|
|
{
|
|
qCDebug(creation) << Q_FUNC_INFO << this;
|
|
m_beingDeleted = true;
|
|
QTimer::singleShot(0, this, [this] {
|
|
// Can't use deleteLater() here due to QTBUG-83030 (deleteLater() never delivered if triggered by a sendEvent() before event loop starts)
|
|
delete this;
|
|
});
|
|
}
|
|
|
|
QSize Frame::dockWidgetsMinSize() const
|
|
{
|
|
QSize size = Layouting::Item::hardcodedMinimumSize;
|
|
for (DockWidget *dw : dockWidgets())
|
|
size = size.expandedTo(dw->view()->minSize());
|
|
|
|
return size;
|
|
}
|
|
|
|
QSize Frame::biggestDockWidgetMaxSize() const
|
|
{
|
|
QSize size = Layouting::Item::hardcodedMaximumSize;
|
|
for (DockWidget *dw : dockWidgets()) {
|
|
const QSize dwMax = dw->view()->maxSizeHint();
|
|
if (size == Layouting::Item::hardcodedMaximumSize) {
|
|
size = dwMax;
|
|
continue;
|
|
}
|
|
|
|
const bool hasMaxSize = dwMax != Layouting::Item::hardcodedMaximumSize;
|
|
if (hasMaxSize)
|
|
size = dw->view()->maximumSize().expandedTo(size);
|
|
}
|
|
|
|
// Interpret 0 max-size as not having one too.
|
|
if (size.width() == 0)
|
|
size.setWidth(Layouting::Item::hardcodedMaximumSize.width());
|
|
if (size.height() == 0)
|
|
size.setHeight(Layouting::Item::hardcodedMaximumSize.height());
|
|
|
|
return size;
|
|
}
|
|
|
|
QRect Frame::dragRect() const
|
|
{
|
|
QRect rect;
|
|
if (m_titleBar->view()->isVisible()) {
|
|
rect = m_titleBar->view()->rect();
|
|
rect.moveTopLeft(m_titleBar->view()->mapToGlobal(QPoint(0, 0)));
|
|
}
|
|
|
|
if (rect.isValid())
|
|
return rect;
|
|
|
|
return dynamic_cast<Views::Frame *>(view())->dragRect();
|
|
}
|
|
|
|
MainWindow *Frame::mainWindow() const
|
|
{
|
|
return m_layoutWidget ? m_layoutWidget->mainWindow() : nullptr;
|
|
}
|
|
|
|
///@brief Returns whether all dock widgets have the specified option set
|
|
bool Frame::allDockWidgetsHave(DockWidget::Option 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 Frame::anyDockWidgetsHas(DockWidget::Option option) const
|
|
{
|
|
const DockWidget::List docks = dockWidgets();
|
|
return std::any_of(docks.cbegin(), docks.cend(), [option](DockWidget *dw) {
|
|
return dw->options() & option;
|
|
});
|
|
}
|
|
|
|
bool Frame::allDockWidgetsHave(DockWidget::LayoutSaverOption option) const
|
|
{
|
|
const DockWidget::List docks = dockWidgets();
|
|
return std::all_of(docks.cbegin(), docks.cend(), [option](DockWidget *dw) {
|
|
return dw->layoutSaverOptions() & option;
|
|
});
|
|
}
|
|
|
|
bool Frame::anyDockWidgetsHas(DockWidget::LayoutSaverOption option) const
|
|
{
|
|
const DockWidget::List docks = dockWidgets();
|
|
return std::any_of(docks.cbegin(), docks.cend(), [option](DockWidget *dw) {
|
|
return dw->layoutSaverOptions() & option;
|
|
});
|
|
}
|
|
|
|
void Frame::setAllowedResizeSides(CursorPositions sides)
|
|
{
|
|
if (sides) {
|
|
delete m_resizeHandler;
|
|
m_resizeHandler = new WidgetResizeHandler(/*topLevel=*/false, view());
|
|
m_resizeHandler->setAllowedResizeSides(sides);
|
|
} else {
|
|
delete m_resizeHandler;
|
|
m_resizeHandler = nullptr;
|
|
}
|
|
}
|
|
|
|
bool Frame::isMDI() const
|
|
{
|
|
return mdiLayoutWidget() != nullptr;
|
|
}
|
|
|
|
bool Frame::isMDIWrapper() const
|
|
{
|
|
return mdiDropAreaWrapper() != nullptr;
|
|
}
|
|
|
|
Frame *Frame::mdiFrame() const
|
|
{
|
|
if (auto dwWrapper = mdiDockWidgetWrapper()) {
|
|
return dwWrapper->d->frame();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
DockWidgetBase *Frame::mdiDockWidgetWrapper() const
|
|
{
|
|
if (auto dropArea = mdiDropAreaWrapper())
|
|
return dropArea->parentView()->asDockWidgetController();
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
DropArea *Frame::mdiDropAreaWrapper() const
|
|
{
|
|
auto dropArea = qobject_cast<DropArea *>(view()->asQWidget()->parentWidget());
|
|
if (dropArea && dropArea->isMDIWrapper())
|
|
return dropArea;
|
|
return nullptr;
|
|
}
|
|
|
|
MDILayoutWidget *Frame::mdiLayoutWidget() const
|
|
{
|
|
return qobject_cast<MDILayoutWidget *>(m_layoutWidget);
|
|
}
|
|
|
|
bool Frame::hasNestedMDIDockWidgets() const
|
|
{
|
|
if (!isMDI() || dockWidgetCount() == 0)
|
|
return false;
|
|
|
|
if (dockWidgetCount() != 1) {
|
|
qWarning() << Q_FUNC_INFO << "Expected a single dock widget wrapper as frame child";
|
|
return false;
|
|
}
|
|
|
|
return dockWidgetAt(0)->d->isMDIWrapper();
|
|
}
|
|
|
|
int Frame::userType() const
|
|
{
|
|
return m_userType;
|
|
}
|
|
|
|
WidgetResizeHandler *Frame::resizeHandler() const
|
|
{
|
|
return m_resizeHandler;
|
|
}
|