Files
pilotclient/src/blackgui/dockwidget.cpp
Klaus Basan 60b4b08da6 Ref T592, remember size/position where last floating
This seems to work more reliable as the Qt mechanism,
but still suffers incorrect positions from time to time.

The floating window opens on to of the main widget,
which is uncritical but still strange why it happens
2019-04-11 22:12:10 +01:00

670 lines
22 KiB
C++

/* Copyright (C) 2014
* swift project Community / Contributors
*
* This file is part of swift Project. It is subject to the license terms in the LICENSE file found in the top-level
* directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated,
* or distributed except according to the terms contained in the LICENSE file.
*/
#include "blackgui/components/marginsinput.h"
#include "blackgui/menus/fontmenus.h"
#include "blackgui/dockwidget.h"
#include "blackgui/guiapplication.h"
#include "blackgui/guiutility.h"
#include "blackgui/stylesheetutility.h"
#include "blackmisc/icons.h"
#include "blackmisc/logmessage.h"
#include "blackmisc/verify.h"
#include <QCloseEvent>
#include <QFrame>
#include <QLayout>
#include <QLayoutItem>
#include <QMenu>
#include <QScopedPointer>
#include <QScreen>
#include <QSettings>
#include <QSizePolicy>
#include <QStatusBar>
#include <QStyle>
#include <QVBoxLayout>
#include <QVariant>
#include <QWidgetAction>
#include <QWidget>
#include <Qt>
#include <QTimer>
#include <QPointer>
#include <QtGlobal>
#include <QApplication>
#include <QDesktopWidget>
using namespace BlackMisc;
using namespace BlackGui::Components;
using namespace BlackGui::Settings;
using namespace BlackGui::Menus;
namespace BlackGui
{
CDockWidget::CDockWidget(bool allowStatusBar, QWidget *parent) :
COverlayMessagesDockWidget(parent),
CEnableForFramelessWindow(CEnableForFramelessWindow::WindowTool, false, "framelessDockWidget", this),
m_allowStatusBar(allowStatusBar)
{
// init settings
this->onStyleSheetsChanged();
this->initTitleBarWidgets();
// context menu
m_input = new CMarginsInput(this);
m_input->setMaximumWidth(150);
m_marginMenuAction = new QWidgetAction(this);
m_marginMenuAction->setDefaultWidget(m_input);
m_fontMenu = new CFontMenu(this, Qt::WidgetWithChildrenShortcut);
this->setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, &CDockWidget::customContextMenuRequested, this, &CDockWidget::showContextMenu, Qt::QueuedConnection);
connect(m_input, &CMarginsInput::changedMargins, this, &CDockWidget::menuChangeMargins, Qt::QueuedConnection);
// connect
connect(sGui, &CGuiApplication::styleSheetsChanged, this, &CDockWidget::onStyleSheetsChanged, Qt::QueuedConnection);
connect(this, &QDockWidget::topLevelChanged, this, &CDockWidget::onTopLevelChanged, Qt::QueuedConnection);
connect(this, &QDockWidget::visibilityChanged, this, &CDockWidget::onVisibilityChanged, Qt::QueuedConnection);
connect(m_fontMenu, &CFontMenu::fontSizeMinus, this, &CDockWidget::fontSizeMinus, Qt::QueuedConnection);
connect(m_fontMenu, &CFontMenu::fontSizePlus, this, &CDockWidget::fontSizePlus, Qt::QueuedConnection);
}
void CDockWidget::setMargins()
{
if (this->isFloating())
{
this->setContentsMargins(this->isFrameless() ? this->getMarginsWhenFramelessFloating() : this->getMarginsWhenFloating());
}
else
{
this->setContentsMargins(this->getMarginsWhenDocked());
}
}
CDockWidget::CDockWidget(QWidget *parent): CDockWidget(true, parent)
{ }
void CDockWidget::setOriginalTitleBar()
{
if (!m_titleBarWidgetOriginal) { this->initTitleBarWidgets(); }
if (this->titleBarWidget() == m_titleBarWidgetOriginal) { return; } // on purpose, as I do not know what happens when I call setTitleBar
this->setTitleBarWidget(m_titleBarWidgetOriginal);
}
void CDockWidget::setEmptyTitleBar()
{
if (!m_titleBarWidgetOriginal) { this->initTitleBarWidgets(); }
if (this->titleBarWidget() == m_titleBarWidgetEmpty) { return; } // on purpose, as I do not know what happens when I call setTitleBar
this->setTitleBarWidget(m_titleBarWidgetEmpty);
}
void CDockWidget::setNullTitleBarWidget()
{
this->setTitleBarWidget(nullptr);
}
void CDockWidget::setMarginsWhenFloating(const QMargins &margins)
{
CDockWidgetSettings s = this->getSettings();
s.setMarginsWhenFloating(margins);
this->saveSettings(s);
}
void CDockWidget::setMarginsWhenFloating(int left, int top, int right, int bottom)
{
this->setMarginsWhenFloating(QMargins(left, top, right, bottom));
}
QMargins CDockWidget::getMarginsWhenFloating() const
{
return this->getSettings().getMarginsWhenFloating();
}
void CDockWidget::setMarginsWhenFramelessFloating(const QMargins &margins)
{
CDockWidgetSettings s = this->getSettings();
s.setMarginsWhenFramelessFloating(margins);
this->saveSettings(s);
}
void CDockWidget::setMarginsWhenFramelessFloating(int left, int top, int right, int bottom)
{
this->setMarginsWhenFramelessFloating(QMargins(left, top, right, bottom));
}
QMargins CDockWidget::getMarginsWhenFramelessFloating() const
{
return this->getSettings().getMarginsWhenFramelessFloating();
}
void CDockWidget::setMarginsWhenDocked(const QMargins &margins)
{
CDockWidgetSettings s = this->getSettings();
s.setMarginsWhenDocked(margins);
this->saveSettings(s);
}
void CDockWidget::setMarginsWhenDocked(int left, int top, int right, int bottom)
{
this->setMarginsWhenDocked(QMargins(left, top, right, bottom));
}
QMargins CDockWidget::getMarginsWhenDocked() const
{
return this->getSettings().getMarginsWhenDocked();
}
bool CDockWidget::isWidgetVisible() const
{
return m_dockWidgetVisible && this->isVisible();
}
void CDockWidget::setWindowTitle(const QString &title)
{
m_windowTitleBackup = title;
QDockWidget::setWindowTitle(title);
}
void CDockWidget::displayStatusMessage(const CStatusMessage &statusMessage)
{
if (!m_allowStatusBar || !this->isFloating()) { return; }
m_statusBar.displayStatusMessage(statusMessage);
}
void CDockWidget::displayStatusMessages(const CStatusMessageList &statusMessages)
{
if (!m_allowStatusBar || !this->isFloating()) { return; }
m_statusBar.displayStatusMessages(statusMessages);
}
void CDockWidget::showTitleWhenDocked(bool show)
{
m_windowTitleWhenDocked = show;
if (show)
{
QDockWidget::setWindowTitle(m_windowTitleBackup);
}
else
{
QDockWidget::setWindowTitle("");
}
}
void CDockWidget::resetWasAlreadyFloating()
{
m_wasAlreadyFloating = false;
}
void CDockWidget::setPreferredSizeWhenFloating(const QSize &size)
{
m_preferredSizeWhenFloating = size;
}
void CDockWidget::setFrameless(bool frameless)
{
CEnableForFramelessWindow::setFrameless(frameless);
// grip
bool hasStatusBar = m_statusBar.getStatusBar();
if (frameless)
{
if (hasStatusBar)
{
this->addFramelessSizeGripToStatusBar(m_statusBar.getStatusBar());
}
}
else
{
if (hasStatusBar)
{
this->hideFramelessSizeGripInStatusBar();
}
}
// resize
if (frameless)
{
QWidget *innerWidget = this->widget(); // the inner widget containing the layout
Q_ASSERT(innerWidget);
this->resize(innerWidget->size());
}
this->setMargins();
this->forceStyleSheetUpdate(); // force style sheet reload
}
const QString &CDockWidget::propertyOuterWidget()
{
static const QString s("outerwidget");
return s;
}
const QString &CDockWidget::propertyInnerWidget()
{
static const QString s("innerwidget");
return s;
}
void CDockWidget::toggleFloating()
{
const bool floating = !this->isFloating();
if (!floating) { this->setFrameless(false); } // remove frameless if not floating
const Qt::KeyboardModifiers km = QGuiApplication::queryKeyboardModifiers();
const bool shift = km.testFlag(Qt::ShiftModifier);
this->setFloating(floating);
this->setAlwaysOnTop(m_alwaysOnTop && floating);
if (shift && floating) { this->resetPosition(); }
}
void CDockWidget::toggleVisibility()
{
this->setVisible(!this->isVisible());
}
void CDockWidget::toggleFrameless()
{
const bool frameless = this->isFrameless();
this->setFrameless(!frameless);
}
void CDockWidget::windowAlwaysOnTop()
{
if (this->isFloating())
{
this->setAlwaysOnTopFlag(true);
m_alwaysOnTop = true;
}
}
void CDockWidget::windowNotAlwaysOnTop()
{
if (this->isFloating())
{
this->setAlwaysOnTopFlag(false);
m_alwaysOnTop = false;
}
}
void CDockWidget::setAlwaysOnTopFlag(bool onTop)
{
if (onTop)
{
this->setWindowFlags(this->windowFlags() | Qt::WindowStaysOnTopHint);
}
else
{
this->setWindowFlags(this->windowFlags() & ~Qt::WindowStaysOnTopHint);
}
}
bool CDockWidget::restoreFromSettings()
{
const CDockWidgetSettings s = this->getSettings();
if (s.isFloating() != this->isFloating())
{
this->toggleFloating();
}
if (s.isFramless() != this->isFrameless())
{
this->toggleFrameless();
}
const QByteArray geo(s.getGeometry());
if (!geo.isEmpty())
{
const bool ok = this->restoreGeometry(geo);
if (ok) { this->rememberFloatingSizeAndPosition(); }
}
this->setMargins();
return true;
}
void CDockWidget::closeEvent(QCloseEvent *event)
{
if (this->isFloating())
{
this->toggleFloating();
event->setAccepted(false); // refuse -> do not close
}
else
{
QDockWidget::closeEvent(event);
}
}
void CDockWidget::paintEvent(QPaintEvent *event)
{
CStyleSheetUtility::useStyleSheetInDerivedWidget(this, QStyle::PE_FrameDockWidget);
QDockWidget::paintEvent(event);
this->rememberFloatingSizeAndPosition();
}
void CDockWidget::mouseMoveEvent(QMouseEvent *event)
{
if (!handleMouseMoveEvent(event)) { QDockWidget::mouseMoveEvent(event); }
}
void CDockWidget::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Escape && this->isFloating()) { this->toggleFloating(); }
QDockWidget::keyPressEvent(event);
}
void CDockWidget::mousePressEvent(QMouseEvent *event)
{
if (!handleMousePressEvent(event)) { QDockWidget::mousePressEvent(event); }
}
void CDockWidget::addToContextMenu(QMenu *contextMenu) const
{
if (this->isFloating())
{
const bool frameless = this->isFrameless();
contextMenu->addAction(CIcons::dockTop16(), "Dock", this, &CDockWidget::toggleFloating, Qt::QueuedConnection);
contextMenu->addAction(CIcons::tableSheet16(), frameless ? "Normal window" : "Frameless", this, &CDockWidget::toggleFrameless, Qt::QueuedConnection);
contextMenu->addAction(CIcons::dockTop16(), "Always on top", this, &CDockWidget::windowAlwaysOnTop, Qt::QueuedConnection);
contextMenu->addAction(CIcons::dockTop16(), "Not on top", this, &CDockWidget::windowNotAlwaysOnTop, Qt::QueuedConnection);
contextMenu->addAction(CIcons::refresh16(), "Redraw", this, qOverload<>(&CDockWidget::update), Qt::QueuedConnection);
}
else
{
contextMenu->addAction(CIcons::floatOne16(), "Float", this, &CDockWidget::toggleFloating, Qt::QueuedConnection);
}
// Font actions
Q_ASSERT_X(m_fontMenu, Q_FUNC_INFO, "Missing menu object");
if (m_fontMenu)
{
contextMenu->addActions(m_fontMenu->getActions());
}
// State actions (windows state)
contextMenu->addAction(CIcons::load16(), "Restore from settings", this, &CDockWidget::restoreFromSettings);
contextMenu->addAction(CIcons::save16(), "Save state", this, &CDockWidget::saveCurrentStateToSettings);
contextMenu->addAction(CIcons::refresh16(), "Reset to defaults", this, &CDockWidget::resetSettings);
contextMenu->addAction(CIcons::refresh16(), "Reset position", this, &CDockWidget::resetPosition);
m_input->setMargins(this->contentsMargins());
contextMenu->addAction(CIcons::tableSheet16(), "Margins", this, &CDockWidget::dummy);
contextMenu->addAction(m_marginMenuAction);
}
void CDockWidget::initialFloating()
{
// init status bar, as we have now all structures set and name is known
this->initStatusBarAndProperties();
// for the first time resize
BLACK_VERIFY(!m_preferredSizeWhenFloating.isNull());
if (!m_preferredSizeWhenFloating.isNull())
{
m_initialDockedMinimumSize = this->minimumSize();
this->resize(m_preferredSizeWhenFloating);
}
// and move
this->resetPosition();
}
QString CDockWidget::windowTitleOrBackup() const
{
const QString t(windowTitle());
if (t.isEmpty()) { return this->windowTitleBackup(); }
return t;
}
void CDockWidget::onTopLevelChanged(bool topLevel)
{
# ifdef Q_OS_LINUX
// Give XCB platforms enough time to handle window events before adjusting it.
QThread::msleep(100);
# endif
this->setMargins(); // from settings or default
if (topLevel)
{
if (m_windowTitleBackup != QDockWidget::windowTitle())
{
QDockWidget::setWindowTitle(m_windowTitleBackup);
}
this->setNullTitleBarWidget();
if (!m_wasAlreadyFloating)
{
this->initialFloating();
this->rememberFloatingSizeAndPosition();
}
else
{
if (m_wasFrameless) { this->setFrameless(true); }
this->restoreFloatingSizeAndPositionDeferred(); // after everything was applied move and resize
}
m_statusBar.show();
m_wasAlreadyFloating = true;
}
else
{
// frameless
this->setFrameless(false);
if (!m_windowTitleWhenDocked) { QDockWidget::setWindowTitle(""); }
m_statusBar.hide();
this->setEmptyTitleBar();
// sometimes floating sets a new minimum size, here we reset it
if (this->minimumHeight() > m_initialDockedMinimumSize.height())
{
this->setMinimumSize(m_initialDockedMinimumSize);
}
}
// relay
emit this->widgetTopLevelChanged(this, topLevel);
}
void CDockWidget::initTitleBarWidgets()
{
m_titleBarWidgetOriginal = this->titleBarWidget();
m_titleBarWidgetEmpty = new QWidget(this);
this->setTitleBarWidget(m_titleBarWidgetEmpty);
}
void CDockWidget::initStatusBarAndProperties()
{
if (m_statusBar.getStatusBar()) { return; }
// Typical reasons for asserts here
// 1) Check the structure, we expect the following hierarchy:
// QDockWidget (CDockWidget/CDockWidgetInfoArea) -> QWidget (outer widget) -> QFrame (inner widget)
// Structure used for frameless floating windows
// 2) Check if the "floating" flag is accidentally set for the dock widget in the GUI builder
// 3) Is the dock widget promoted BlackGui::CDockWidgetInfoArea?
QWidget *outerWidget = this->widget(); // the outer widget containing the layout
Q_ASSERT_X(outerWidget, "CDockWidget::initStatusBar", "No outer widget");
if (!outerWidget) { return; }
outerWidget->setProperty("dockwidget", propertyOuterWidget());
Q_ASSERT_X(outerWidget->layout(), "CDockWidget::initStatusBar", "No outer widget layout");
if (!outerWidget->layout()) { return; }
Q_ASSERT_X(outerWidget->layout()->itemAt(0) && outerWidget->layout()->itemAt(0)->widget(), "CDockWidget::initStatusBar", "No outer widget layout item");
if (!outerWidget->layout()->itemAt(0) || !outerWidget->layout()->itemAt(0)->widget()) { m_allowStatusBar = false; return; }
// Inner widget is supposed to be a QFrame / promoted QFrame
QFrame *innerWidget = qobject_cast<QFrame *>(outerWidget->layout()->itemAt(0)->widget()); // the inner widget containing the layout
Q_ASSERT_X(innerWidget, "CDockWidget::initStatusBar", "No inner widget");
if (!innerWidget) { m_allowStatusBar = false; return; }
innerWidget->setProperty("dockwidget", propertyInnerWidget());
// status bar
if (!m_allowStatusBar) { return; }
m_statusBar.initStatusBar();
// layout
QVBoxLayout *vLayout = qobject_cast<QVBoxLayout *>(innerWidget->layout());
Q_ASSERT_X(vLayout, "CDockWidget::initStatusBar", "No outer widget layout");
if (!vLayout) { m_allowStatusBar = false; return; }
vLayout->addWidget(m_statusBar.getStatusBar(), 0, Qt::AlignBottom); // 0->vertical stretch minimum
// adjust stretching of the original widget. It was the only widget so far
// and should occupy maximum space
QWidget *compWidget = innerWidget->findChild<QWidget *>(QString(), Qt::FindDirectChildrenOnly);
Q_ASSERT(compWidget);
if (!compWidget) { return; }
QSizePolicy sizePolicy = compWidget->sizePolicy();
sizePolicy.setVerticalStretch(1); // make the original widget occupying maximum space
compWidget->setSizePolicy(sizePolicy);
// hide status bar if not floating T592 no longer init floating
if (!this->isFloating()) { m_statusBar.hide(); }
}
void CDockWidget::showContextMenu(const QPoint &pos)
{
QPoint globalPos = this->mapToGlobal(pos);
QScopedPointer<QMenu> contextMenu(new QMenu(this));
this->addToContextMenu(contextMenu.data());
QAction *selectedItem = contextMenu.data()->exec(globalPos);
Q_UNUSED(selectedItem);
}
void CDockWidget::onVisibilityChanged(bool visible)
{
m_dockWidgetVisible = visible;
}
void CDockWidget::menuChangeMargins(const QMargins &margins)
{
const bool frameless = this->isFrameless();
const bool floating = this->isFloating();
if (floating)
{
if (frameless)
{
this->setMarginsWhenFramelessFloating(margins);
}
else
{
this->setMarginsWhenFloating(margins);
}
}
else
{
this->setMarginsWhenDocked(margins);
}
this->setContentsMargins(margins);
this->repaint();
}
void CDockWidget::settingsChanged()
{
// void, normally not used
}
void CDockWidget::dummy()
{
// void
}
void CDockWidget::onStyleSheetsChanged()
{
// style sheet changes go here
}
void CDockWidget::forceStyleSheetUpdate()
{
const QString qss = this->styleSheet();
this->setStyleSheet(qss.isEmpty() ? QStringLiteral(" ") : QString());
this->setStyleSheet(qss);
}
void CDockWidget::rememberFloatingSizeAndPosition()
{
if (!this->isFloating()) { return; }
m_lastFloatingSize = this->size();
m_lastFloatingPosition = this->pos();
}
void CDockWidget::restoreFloatingSizeAndPosition()
{
if (!m_lastFloatingSize.isValid() || m_lastFloatingPosition.isNull()) { return; }
this->resize(m_lastFloatingSize);
this->move(m_lastFloatingPosition);
}
void CDockWidget::restoreFloatingSizeAndPositionDeferred()
{
if (!m_lastFloatingSize.isValid() || m_lastFloatingPosition.isNull()) { return; }
QPointer<CDockWidget> myself(this);
QTimer::singleShot(1000, this, [ = ]
{
if (myself) { myself->restoreFloatingSizeAndPosition(); }
});
}
CDockWidgetSettings CDockWidget::getSettings() const
{
if (this->objectName().isEmpty()) { return CDockWidgetSettings(); }
// we need object name for settings %OwnerName%"
const CDockWidgetSettings s = m_settings.get();
return s;
}
void CDockWidget::saveSettings(const CDockWidgetSettings &settings)
{
BLACK_VERIFY_X(!this->objectName().isEmpty(), Q_FUNC_INFO, "Need object name for settings %OwnerName%");
if (this->objectName().isEmpty())
{
CStatusMessage(this).error(u"Settings cannot be saved!");
return;
}
const CStatusMessage m = m_settings.setAndSave(settings);
if (m.isFailure())
{
CLogMessage::preformatted(m);
}
}
void CDockWidget::saveCurrentStateToSettings()
{
CDockWidgetSettings s = this->getSettings();
s.setFloating(this->isFloating());
s.setFrameless(this->isFrameless());
s.setGeometry(this->saveGeometry());
this->saveSettings(s);
}
void CDockWidget::resetSettings()
{
CDockWidgetSettings s = this->getSettings();
s.reset();
this->saveSettings(s);
this->restoreFromSettings();
}
void CDockWidget::resetPosition()
{
// center on screen when floating
if (!this->isFloating()) { return; }
if (!sGui) { return; }
// pos can be null during init
QPoint pos = CGuiUtility::mainWidgetGlobalPosition();
if (pos.isNull())
{
pos = sGui->currentScreen()->geometry().center() - this->rect().center();
}
const int x = pos.x() + m_offsetWhenFloating.x();
const int y = pos.y() + m_offsetWhenFloating.y();
this->move(x, y);
}
} // namespace