Files
KDDockWidgets/src/DockWidgetBase.cpp
Sergio Martins aded290573 Don't save last position if the window was hidden already
Bug was happening when calling close() an closed dock widget, it
would then save the position of the invisible window. Next time it
was open it would appear in a bogus location. 0,0 in my case.
2020-02-06 15:01:58 +00:00

601 lines
16 KiB
C++

/*
This file is part of KDDockWidgets.
Copyright (C) 2018-2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Author: Sérgio Martins <sergio.martins@kdab.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "DockWidgetBase.h"
#include "DragController_p.h"
#include "Frame_p.h"
#include "FloatingWindow_p.h"
#include "Logging_p.h"
#include "TabWidget_p.h"
#include "Utils_p.h"
#include "DockRegistry_p.h"
#include "WidgetResizeHandler_p.h"
#include "DropArea_p.h"
#include "LastPosition_p.h"
#include "multisplitter/Item_p.h"
#include "Config.h"
#include "FrameworkWidgetFactory.h"
#include <QAction>
#include <QEvent>
#include <QCloseEvent>
#include <QTimer>
#include <QScopedValueRollback>
/**
* @file
* @brief The DockWidget base-class that's shared between QtWidgets and QtQuick stack.
*
* @author Sérgio Martins \<sergio.martins@kdab.com\>
*/
using namespace KDDockWidgets;
class DockWidgetBase::Private
{
public:
Private(const QString &dockName, DockWidgetBase::Options options_, DockWidgetBase *qq)
: name(dockName)
, title(dockName)
, q(qq)
, options(options_)
, toggleAction(new QAction(q))
{
q->connect(q, &DockWidgetBase::shown, q, [this] { onDockWidgetShown(); } );
q->connect(q, &DockWidgetBase::hidden, q, [this] { onDockWidgetHidden(); } );
q->connect(toggleAction, &QAction::toggled, q, [this] (bool enabled) {
if (!m_updatingToggleAction) { // guard against recursiveness
toggleAction->blockSignals(true); // and don't emit spurious toggle. Like when a dock widget is inserted into a tab widget it might get hide events, ignore those. The Dock Widget is open.
toggle(enabled);
toggleAction->blockSignals(false);
}
});
toggleAction->setCheckable(true);
}
void init()
{
updateTitle();
}
QPoint defaultCenterPosForFloating();
void updateTitle();
void updateIcon();
void toggle(bool enabled);
void updateToggleAction();
void onDockWidgetShown();
void onDockWidgetHidden();
TabWidget *parentTabWidget() const;
void show();
void close();
void restoreToPreviousPosition();
void maybeRestoreToPreviousPosition();
int currentTabIndex() const;
/**
* Before floating a dock widget we save its position. So it can be restored when calling
* DockWidget::setFloating(false)
*/
void saveTabIndex();
const QString name;
QString title;
QIcon icon;
QWidget *widget = nullptr;
DockWidgetBase *const q;
const DockWidgetBase::Options options;
QAction *const toggleAction;
LastPosition m_lastPosition;
bool m_updatingToggleAction = false;
bool m_isForceClosing = false;
};
DockWidgetBase::DockWidgetBase(const QString &name, Options options)
: QWidgetOrQuick(nullptr, Qt::Tool)
, d(new Private(name, options, this))
{
d->init();
DragController::instance();
DockRegistry::self()->registerDockWidget(this);
qCDebug(creation) << "DockWidget" << this;
if (name.isEmpty())
qWarning() << Q_FUNC_INFO << "Name can't be null";
}
DockWidgetBase::~DockWidgetBase()
{
DockRegistry::self()->unregisterDockWidget(this);
qCDebug(creation) << "~DockWidget" << this;
delete d;
}
void DockWidgetBase::addDockWidgetAsTab(DockWidgetBase *other)
{
qCDebug(addwidget) << Q_FUNC_INFO << other;
if (other == this) {
qWarning() << Q_FUNC_INFO << "Refusing to add dock widget into itself" << other;
return;
}
if (!other) {
qWarning() << Q_FUNC_INFO << "dock widget is null";
return;
}
Frame *frame = this->frame();
if (frame) {
if (frame->contains(other)) {
qWarning() << Q_FUNC_INFO << "Already contains" << other;
return;
}
} else {
if (isWindow()) {
// Doesn't have a frame yet
morphIntoFloatingWindow();
frame = this->frame();
}
}
Q_ASSERT(frame);
other->setParent(nullptr);
frame->addWidget(other);
}
void DockWidgetBase::addDockWidgetToContainingWindow(DockWidgetBase *other, Location location, DockWidgetBase *relativeTo)
{
qCDebug(addwidget) << Q_FUNC_INFO << other << location << relativeTo;
if (qobject_cast<MainWindowBase*>(window())) {
qWarning() << Q_FUNC_INFO << "Just use MainWindow::addWidget() directly. This function is for floating nested windows only.";
return;
}
if (isWindow())
morphIntoFloatingWindow();
if (auto fw = qobject_cast<FloatingWindow *>(window())) {
fw->dropArea()->addDockWidget(other, location, relativeTo);
} else {
qWarning() << Q_FUNC_INFO << "Couldn't find floating nested window";
}
}
void DockWidgetBase::setWidget(QWidget *w)
{
Q_ASSERT(w && !d->widget);
qCDebug(addwidget) << Q_FUNC_INFO << w;
d->widget = w;
Q_EMIT widgetChanged(w);
setWindowTitle(uniqueName());
}
QWidget *DockWidgetBase::widget() const
{
return d->widget;
}
bool DockWidgetBase::isFloating() const
{
if (isWindow())
return true;
auto fw = qobject_cast<FloatingWindow *>(window());
return fw && fw->hasSingleDockWidget();
}
void DockWidgetBase::setFloating(bool floats)
{
const bool alreadyFloating = isFloating();
qCDebug(docking) << Q_FUNC_INFO << "yes=" << floats
<< "; already floating=" << alreadyFloating;
if ((floats && alreadyFloating) || (!floats && !alreadyFloating))
return; // Nothing to do
if (floats) {
d->saveTabIndex();
if (isTabbed()) {
TabWidget *tabWidget= d->parentTabWidget();
if (!tabWidget) {
qWarning() << "DockWidget::setFloating: Tabbed but no tabbar exists"
<< this;
Q_ASSERT(false);
}
tabWidget->detachTab(this);
} else {
frame()->titleBar()->makeWindow();
}
} else {
if (d->m_lastPosition.isValid()) {
d->restoreToPreviousPosition();
} else {
qCDebug(placeholder) << Q_FUNC_INFO << "Don't have a place to restore";
// TODO: Restore to preferred place ?
}
}
}
QAction *DockWidgetBase::toggleAction() const
{
return d->toggleAction;
}
QString DockWidgetBase::uniqueName() const
{
return d->name;
}
QString DockWidgetBase::title() const
{
return d->title;
}
void DockWidgetBase::setTitle(const QString &title)
{
if (title != d->title) {
d->title = title;
d->updateTitle();
Q_EMIT titleChanged();
}
}
DockWidgetBase::Options DockWidgetBase::options() const
{
return d->options;
}
bool DockWidgetBase::isTabbed() const
{
if (TabWidget* tabWidget = d->parentTabWidget()) {
return frame()->alwaysShowsTabs() || tabWidget->numDockWidgets() > 1;
} else {
if (!isFloating())
qWarning() << "DockWidget::isTabbed() Couldn't find any tab widget.";
return false;
}
}
bool DockWidgetBase::isCurrentTab() const
{
if (TabWidget* tabWidget = d->parentTabWidget()) {
return tabWidget->currentIndex() == tabWidget->indexOfDockWidget(const_cast<DockWidgetBase*>(this));
} else {
return true;
}
}
void DockWidgetBase::setAsCurrentTab()
{
if (TabWidget* tabWidget = d->parentTabWidget())
tabWidget->setCurrentDockWidget(this);
}
void DockWidgetBase::setIcon(const QIcon &icon)
{
d->icon = icon;
d->updateIcon();
Q_EMIT iconChanged();
}
QIcon DockWidgetBase::icon() const
{
return d->icon;
}
void DockWidgetBase::forceClose()
{
QScopedValueRollback<bool> rollback(d->m_isForceClosing, true);
d->close();
}
TitleBar *DockWidgetBase::titleBar() const
{
if (Frame *f = frame())
return f->actualTitleBar();
return nullptr;
}
bool DockWidgetBase::isOpen() const
{
return d->toggleAction->isChecked();
}
FloatingWindow *DockWidgetBase::morphIntoFloatingWindow()
{
qCDebug(creation) << "DockWidget::morphIntoFloatingWindow() this=" << this
<< "; visible=" << isVisible();
if (auto fw = qobject_cast<FloatingWindow*>(window()))
return fw; // Nothing to do
if (isWindow()) {
QRect geo = lastPosition()->lastFloatingGeometry();
if (geo.isNull()) {
geo = geometry();
const QPoint center = d->defaultCenterPosForFloating();
if (!center.isNull())
geo.moveCenter(center);
}
auto frame = Config::self().frameworkWidgetFactory()->createFrame();
frame->addWidget(this);
auto floatingWindow = Config::self().frameworkWidgetFactory()->createFloatingWindow(frame);
floatingWindow->setGeometry(geo);
floatingWindow->show();
return floatingWindow;
} else {
return nullptr;
}
}
void DockWidgetBase::maybeMorphIntoFloatingWindow()
{
if (isWindow() && isVisible())
morphIntoFloatingWindow();
}
Frame *DockWidgetBase::frame() const
{
QWidgetOrQuick *p = parentWidget();
while (p) {
if (auto frame = qobject_cast<Frame *>(p))
return frame;
p = p->parentWidget();
}
return nullptr;
}
FloatingWindow *DockWidgetBase::floatingWindow() const
{
return qobject_cast<FloatingWindow*>(window());
}
void DockWidgetBase::addPlaceholderItem(Item *item)
{
qCDebug(placeholder) << Q_FUNC_INFO << this << item;
Q_ASSERT(item);
d->m_lastPosition.addPlaceholderItem(item);
}
LastPosition *DockWidgetBase::lastPosition() const
{
return &d->m_lastPosition;
}
QPoint DockWidgetBase::Private::defaultCenterPosForFloating()
{
MainWindowBase::List mainWindows = DockRegistry::self()->mainwindows();
// We don't care about multiple mainwindows yet. Or, let's just say that the first one is more main than the others
MainWindowBase *mw = mainWindows.isEmpty() ? nullptr : mainWindows.constFirst();
if (!mw || !q->isFloating())
return {};
return mw->geometry().center();
}
void DockWidgetBase::Private::updateTitle()
{
if (q->isFloating())
q->window()->setWindowTitle(title);
toggleAction->setText(title);
}
void DockWidgetBase::Private::updateIcon()
{
}
void DockWidgetBase::Private::toggle(bool enabled)
{
if (enabled) {
show();
} else {
q->close();
}
}
void DockWidgetBase::Private::updateToggleAction()
{
QScopedValueRollback<bool> recursionGuard(m_updatingToggleAction, true); // Guard against recursiveness
m_updatingToggleAction = true;
if ((q->isVisible() || parentTabWidget()) && !toggleAction->isChecked()) {
toggleAction->setChecked(true);
} else if ((!q->isVisible() && !parentTabWidget()) && toggleAction->isChecked()) {
toggleAction->setChecked(false);
}
}
void DockWidgetBase::Private::onDockWidgetShown()
{
updateToggleAction();
qCDebug(hiding) << Q_FUNC_INFO << "parent=" << q->parentWidget();
}
void DockWidgetBase::Private::onDockWidgetHidden()
{
updateToggleAction();
qCDebug(hiding) << Q_FUNC_INFO << "parent=" << q->parentWidget();
}
TabWidget *DockWidgetBase::Private::parentTabWidget() const
{
if (auto f = q->frame())
return f->tabWidget();
return nullptr;
}
void DockWidgetBase::Private::close()
{
if (!m_isForceClosing && q->isFloating() && q->isVisible()) { // only user-closing is interesting to save the geometry
// We check for isVisible so we don't save geometry if you call close() on an already closed dock widget
m_lastPosition.setLastFloatingGeometry(q->window()->geometry());
}
qCDebug(hiding) << "DockWidget::close" << this;
saveTabIndex();
// Do some cleaning. Widget is hidden, but we must hide the tab containing it.
if (auto tabWidget = parentTabWidget()) {
tabWidget->removeDockWidget(q);
q->setParent(nullptr);
}
}
void DockWidgetBase::Private::restoreToPreviousPosition()
{
if (!m_lastPosition.isValid()) {
qWarning() << Q_FUNC_INFO << "Only restoring to MainWindow supported for now";
return;
}
m_lastPosition.layoutItem()->restorePlaceholder(q, m_lastPosition.m_tabIndex);
}
void DockWidgetBase::Private::maybeRestoreToPreviousPosition()
{
// This is called when we get a QEvent::Show. Let's see if we have to restore it to a previous position.
Item *layoutItem = m_lastPosition.layoutItem();
qCDebug(placeholder) << Q_FUNC_INFO << layoutItem << m_lastPosition.m_wasFloating;
if (!layoutItem)
return; // nothing to do, no last position
if (m_lastPosition.m_wasFloating)
return; // Nothing to do, it was floating before, now it'll just get visible
Frame *frame = q->frame();
if (frame && frame->parentWidget() == layoutItem->parentWidget()) {
// There's a frame already. Means the DockWidget was hidden instead of closed.
// Nothing to do, the dock widget will simply be shown
qCDebug(placeholder) << Q_FUNC_INFO << "Already had frame.";
return;
}
// Now we deal with the case where the DockWidget was close()ed. In this case it doesn't have a parent.
if (q->parentWidget()) {
// The QEvent::Show is due to it being made floating. Nothing to restore.
qCDebug(placeholder) << Q_FUNC_INFO << "Already had parentWidget";
return;
}
// Finally, restore it
restoreToPreviousPosition();
}
int DockWidgetBase::Private::currentTabIndex() const
{
TabWidget *tabWidget = parentTabWidget();
return tabWidget ? tabWidget->indexOfDockWidget(q) : 0;
}
void DockWidgetBase::Private::saveTabIndex()
{
m_lastPosition.m_tabIndex = currentTabIndex();
m_lastPosition.m_wasFloating = q->isFloating();
}
void DockWidgetBase::Private::show()
{
// Only show for now
q->show();
}
void DockWidgetBase::onParentChanged()
{
Q_EMIT parentChanged();
d->updateToggleAction();
}
void DockWidgetBase::onShown(bool spontaneous)
{
Q_EMIT shown();
if (Frame *f = frame()) {
if (!spontaneous) {
f->onDockWidgetShown(this);
}
}
d->maybeRestoreToPreviousPosition();
// Transform into a FloatingWindow if this will be a regular floating dock widget.
QTimer::singleShot(0, this, &DockWidgetBase::maybeMorphIntoFloatingWindow);
}
void DockWidgetBase::onHidden(bool spontaneous)
{
Q_EMIT hidden();
if (Frame *f = frame()) {
if (!spontaneous) {
f->onDockWidgetHidden(this);
}
}
}
void DockWidgetBase::onClosed(QCloseEvent *e)
{
e->accept(); // By default we accept, means DockWidget closes
if (d->widget)
qApp->sendEvent(d->widget, e); // Give a change for the widget to ignore
if (e->isAccepted())
d->close();
}
DockWidgetBase *DockWidgetBase::deserialize(const LayoutSaver::DockWidget::Ptr &saved)
{
DockWidgetBase *dw = DockRegistry::self()->dockByName(saved->uniqueName);
if (!dw) {
if (auto factoryFunc = Config::self().dockWidgetFactoryFunc()) {
// DockWidget doesn't exist, ask to create it
dw = factoryFunc(saved->uniqueName);
}
}
if (dw) {
if (QWidget *w = dw->widget())
w->setVisible(true);
dw->setProperty("kddockwidget_was_restored", true);
} else {
qWarning() << Q_FUNC_INFO << "Couldn't find dock widget" << saved->uniqueName;
}
return dw;
}
LayoutSaver::DockWidget::Ptr DockWidgetBase::serialize() const
{
return LayoutSaver::DockWidget::dockWidgetForName(uniqueName());
}