854 lines
27 KiB
C++
854 lines
27 KiB
C++
/*
|
|
This file is part of KDDockWidgets.
|
|
|
|
SPDX-FileCopyrightText: 2020 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 "FloatingWindow.h"
|
|
#include "FloatingWindow_p.h"
|
|
#include "MainWindow.h"
|
|
#include "core/Logging_p.h"
|
|
#include "TitleBar.h"
|
|
#include "Group.h"
|
|
#include "Platform.h"
|
|
#include "KDDockWidgets.h"
|
|
#include "core/WindowBeingDragged_p.h"
|
|
#include "core/Utils_p.h"
|
|
#include "core/Controller_p.h"
|
|
#include "core/WidgetResizeHandler_p.h"
|
|
#include "DockRegistry.h"
|
|
#include "Config.h"
|
|
#include "Layout_p.h"
|
|
#include "core/ViewFactory.h"
|
|
#include "core/DelayedCall_p.h"
|
|
#include "core/DragController_p.h"
|
|
#include "core/LayoutSaver_p.h"
|
|
#include "DockWidget_p.h"
|
|
#include "DropArea.h"
|
|
#include "core/ScopedValueRollback_p.h"
|
|
#include "core/layouting/Item_p.h"
|
|
#include "View.h"
|
|
#include "core/View_p.h"
|
|
|
|
#include "kdbindings/signal.h"
|
|
|
|
#ifdef KDDW_FRONTEND_QT
|
|
#include <QTimer>
|
|
#ifdef Q_OS_WIN
|
|
#include <QGuiApplication>
|
|
#include <Windows.h>
|
|
#endif
|
|
#endif
|
|
|
|
#include <limits>
|
|
|
|
using namespace KDDockWidgets;
|
|
using namespace KDDockWidgets::Core;
|
|
|
|
static FloatingWindowFlags floatingWindowFlagsForGroup(Group *group)
|
|
{
|
|
if (!group)
|
|
return FloatingWindowFlag::FromGlobalConfig;
|
|
|
|
const auto dockwidgets = group->dockWidgets();
|
|
if (!dockwidgets.isEmpty())
|
|
return dockwidgets.first()->floatingWindowFlags();
|
|
|
|
return FloatingWindowFlag::FromGlobalConfig;
|
|
}
|
|
|
|
/** static */
|
|
Qt::WindowFlags FloatingWindow::s_windowFlagsOverride = {};
|
|
|
|
static Qt::WindowFlags windowFlagsToUse(FloatingWindowFlags requestedFlags)
|
|
{
|
|
if (requestedFlags & FloatingWindowFlag::UseQtTool) {
|
|
// User has explicitly chosen Qt::Tool for this FloatingWindow
|
|
return Qt::Tool;
|
|
}
|
|
|
|
if (requestedFlags & FloatingWindowFlag::UseQtWindow) {
|
|
// User has explicitly chosen Qt::Window for this FloatingWindow
|
|
return Qt::Window;
|
|
}
|
|
|
|
if (FloatingWindow::s_windowFlagsOverride) {
|
|
// User overridden the default for all FloatingWindows
|
|
return FloatingWindow::s_windowFlagsOverride;
|
|
}
|
|
|
|
if (KDDockWidgets::usesNativeDraggingAndResizing())
|
|
return Qt::Window;
|
|
|
|
if (Config::self().internalFlags()
|
|
& Config::InternalFlag_DontUseQtToolWindowsForFloatingWindows)
|
|
return Qt::Window;
|
|
|
|
return Qt::Tool;
|
|
}
|
|
|
|
static MainWindow *hackFindParentHarder(Core::Group *group, MainWindow *candidateParent)
|
|
{
|
|
const FloatingWindowFlags requestedFlags =
|
|
group ? group->requestedFloatingWindowFlags() : FloatingWindowFlag::FromGlobalConfig;
|
|
if (requestedFlags & FloatingWindowFlag::DontUseParentForFloatingWindows) {
|
|
// User explicitly requested no parent for this floating window
|
|
return nullptr;
|
|
}
|
|
|
|
if (Config::self().internalFlags() & Config::InternalFlag_DontUseParentForFloatingWindows) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Using a parent helps the floating windows stay in front of the main window always.
|
|
// We're not receiving the parent via ctor argument as the app can have multiple-main windows,
|
|
// so use a hack here.
|
|
// Not quite clear what to do if the app supports multiple main windows though.
|
|
|
|
if (candidateParent)
|
|
return candidateParent;
|
|
|
|
const MainWindow::List windows = DockRegistry::self()->mainwindows();
|
|
|
|
if (windows.isEmpty())
|
|
return nullptr;
|
|
|
|
if (windows.size() == 1)
|
|
return windows.first();
|
|
|
|
const Vector<QString> affinities = group ? group->affinities() : Vector<QString>();
|
|
const MainWindow::List mainWindows =
|
|
DockRegistry::self()->mainWindowsWithAffinity(affinities);
|
|
|
|
if (mainWindows.isEmpty()) {
|
|
KDDW_ERROR("No window with affinity={} found", affinities, "found");
|
|
return nullptr;
|
|
}
|
|
|
|
return mainWindows.first();
|
|
}
|
|
|
|
static MainWindow *actualParent(MainWindow *candidate)
|
|
{
|
|
return (Config::self().internalFlags() & Config::InternalFlag_DontUseParentForFloatingWindows)
|
|
? nullptr
|
|
: candidate;
|
|
}
|
|
|
|
FloatingWindow::FloatingWindow(Rect suggestedGeometry, MainWindow *parent,
|
|
FloatingWindowFlags requestedFlags)
|
|
: Controller(ViewType::FloatingWindow,
|
|
Config::self().viewFactory()->createFloatingWindow(
|
|
this, actualParent(parent), windowFlagsToUse(requestedFlags)))
|
|
, Draggable(view(),
|
|
KDDockWidgets::usesNativeDraggingAndResizing()) // FloatingWindow is only draggable
|
|
// when using a native title bar.
|
|
// Otherwise the
|
|
// KDDockWidgets::TitleBar is the
|
|
// draggable
|
|
, d(new Private(requestedFlags, this))
|
|
, m_titleBar(new Core::TitleBar(this))
|
|
{
|
|
view()->init();
|
|
if (!suggestedGeometry.isNull())
|
|
view()->setGeometry(suggestedGeometry);
|
|
|
|
#if defined(Q_OS_WIN) && defined(KDDW_FRONTEND_QTWIDGETS)
|
|
// For QtQuick we do it a bit later, once we have the QQuickWindow
|
|
if (Platform::instance()->isQtWidgets()) {
|
|
view()->createPlatformWindow(); // QWidget::create
|
|
|
|
// Handle WM_NCHITTEST
|
|
m_nchittestFilter = new NCHITTESTEventFilter(view());
|
|
qGuiApp->installNativeEventFilter(m_nchittestFilter);
|
|
|
|
// Enables native drop-shadow
|
|
WidgetResizeHandler::setupWindow(view()->window());
|
|
|
|
// WM_NCCALCSIZE is handled in the views's nativeEvent(), not here.
|
|
}
|
|
#endif
|
|
|
|
DockRegistry::self()->registerFloatingWindow(this);
|
|
|
|
if (d->m_flags & FloatingWindowFlag::KeepAboveIfNotUtilityWindow)
|
|
view()->setFlag(Qt::WindowStaysOnTopHint, true);
|
|
|
|
if (Platform::instance()->isQtWidgets()) {
|
|
// QtQuick will do it a bit later, once it has a QWindow
|
|
maybeCreateResizeHandler();
|
|
}
|
|
|
|
updateTitleBarVisibility();
|
|
|
|
d->m_visibleWidgetCountConnection =
|
|
d->m_dropArea->d_ptr()->visibleWidgetCountChanged.connect([this](int count) {
|
|
onFrameCountChanged(count);
|
|
d->numGroupsChanged.emit();
|
|
onVisibleFrameCountChanged(count);
|
|
});
|
|
|
|
view()->d->closeRequested.connect([this](CloseEvent *ev) { onCloseEvent(ev); });
|
|
|
|
view()->d->layoutInvalidated.connect([this] { updateSizeConstraints(); });
|
|
|
|
d->m_layoutDestroyedConnection = d->m_dropArea->Controller::dptr()->aboutToBeDeleted.connect(&FloatingWindow::scheduleDeleteLater, this);
|
|
|
|
d->numGroupsChanged.connect([this] {
|
|
d->numDockWidgetsChanged.emit();
|
|
});
|
|
}
|
|
|
|
FloatingWindow::FloatingWindow(Core::Group *group, Rect suggestedGeometry,
|
|
MainWindow *parent)
|
|
: FloatingWindow(suggestedGeometry, hackFindParentHarder(group, parent), floatingWindowFlagsForGroup(group))
|
|
{
|
|
ScopedValueRollback guard(m_disableSetVisible, true);
|
|
|
|
if (group->hasNestedMDIDockWidgets()) {
|
|
// When using DockWidget::MDINestable, the docked MDI widget is wrapped by a drop area so we
|
|
// can drop things into it. When floating it, we can delete that helper drop area, as
|
|
// FloatingWindow already has one
|
|
|
|
if (group->dockWidgetCount() == 0) {
|
|
// doesn't happen
|
|
KDDW_ERROR("Unexpected empty group");
|
|
return;
|
|
}
|
|
|
|
DockWidget *dwMDIWrapper = group->dockWidgetAt(0);
|
|
DropArea *dropAreaMDIWrapper = dwMDIWrapper->d->mdiDropAreaWrapper();
|
|
|
|
if (dropAreaMDIWrapper->hasSingleGroup()) {
|
|
Core::Group *innerFrame = dropAreaMDIWrapper->groups().constFirst();
|
|
if (innerFrame->hasSingleDockWidget()) {
|
|
// When pressing the unfloat button, the dock widgets gets docked to the previous
|
|
// position it was at. Core::DockWidget::Private::m_lastPosition stores that
|
|
// location, however, when having nested MDI, we have an extra Dock Widget, the
|
|
// wrapper, and it contains the last position. So, when floating, we need to
|
|
// transfer that and not lose it.
|
|
DockWidget *dw = innerFrame->dockWidgetAt(0);
|
|
dw->d->lastPosition() = dwMDIWrapper->d->lastPosition();
|
|
}
|
|
}
|
|
|
|
d->m_dropArea->addMultiSplitter(dropAreaMDIWrapper, Location_OnTop);
|
|
dwMDIWrapper->setVisible(false);
|
|
if (!DragController::instance()->isIdle()) {
|
|
// We're dragging a MDI window and we reached the border, detaching it, and making it
|
|
// float. We can't delete the wrapper group just yet, as that would delete the title bar
|
|
// which is currently being dragged. Delete it once the drag finishes
|
|
d->m_currentStateChangedConnection = DragController::instance()->currentStateChanged.connect([this, dwMDIWrapper] {
|
|
if (DragController::instance()->isIdle()) {
|
|
d->m_currentStateChangedConnection = KDBindings::ScopedConnection();
|
|
delete dwMDIWrapper;
|
|
}
|
|
});
|
|
} else {
|
|
dwMDIWrapper->destroyLater();
|
|
}
|
|
|
|
} else {
|
|
// Adding a widget will trigger onFrameCountChanged, which triggers a setVisible(true).
|
|
// The problem with setVisible(true) will forget about or requested geometry and place the
|
|
// window at 0,0 So disable the setVisible(true) call while in the ctor.
|
|
d->m_dropArea->addWidget(group->view(), KDDockWidgets::Location_OnTop, {});
|
|
}
|
|
|
|
if (!suggestedGeometry.isNull())
|
|
view()->setGeometry(suggestedGeometry);
|
|
}
|
|
|
|
FloatingWindow::~FloatingWindow()
|
|
{
|
|
m_inDtor = true;
|
|
view()->d->setAboutToBeDestroyed();
|
|
|
|
if (auto da = dropArea()) {
|
|
// Avoid a bunch of QML warnings and constraints being violated at destruction.
|
|
// Also simply avoiding unneeded work, as QML is destroying stuff 1 by 1
|
|
da->view()->d->setAboutToBeDestroyed();
|
|
}
|
|
|
|
d->m_layoutDestroyedConnection = KDBindings::ScopedConnection();
|
|
|
|
#ifdef KDDW_FRONTEND_QT_WINDOWS
|
|
delete m_nchittestFilter;
|
|
#endif
|
|
|
|
DockRegistry::self()->unregisterFloatingWindow(this);
|
|
delete m_titleBar;
|
|
delete d;
|
|
}
|
|
|
|
void FloatingWindow::maybeCreateResizeHandler()
|
|
{
|
|
if (!KDDockWidgets::usesNativeDraggingAndResizing()) {
|
|
view()->setFlag(Qt::FramelessWindowHint, true);
|
|
// EGLFS can't have different mouse cursors per window, needs global filter hack to unset
|
|
// when cursor leaves
|
|
const auto filterMode = isEGLFS() ? WidgetResizeHandler::EventFilterMode::Global
|
|
: WidgetResizeHandler::EventFilterMode::Local;
|
|
setWidgetResizeHandler(
|
|
new WidgetResizeHandler(filterMode, WidgetResizeHandler::WindowMode::TopLevel, view()));
|
|
}
|
|
}
|
|
|
|
DropArea *FloatingWindow::dropArea() const
|
|
{
|
|
return d->m_dropArea;
|
|
}
|
|
|
|
std::unique_ptr<WindowBeingDragged> FloatingWindow::makeWindow()
|
|
{
|
|
return std::make_unique<WindowBeingDragged>(this, this);
|
|
}
|
|
|
|
Core::DockWidget *FloatingWindow::singleDockWidget() const
|
|
{
|
|
const Core::Group::List groups = this->groups();
|
|
if (groups.size() == 1) {
|
|
Core::Group *group = groups.first();
|
|
if (group->hasSingleDockWidget())
|
|
return group->dockWidgetAt(0);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
Core::DockWidget::List FloatingWindow::dockWidgets() const
|
|
{
|
|
return d->m_dropArea->dockWidgets();
|
|
}
|
|
|
|
Core::Group::List FloatingWindow::groups() const
|
|
{
|
|
assert(d->m_dropArea);
|
|
return d->m_dropArea->groups();
|
|
}
|
|
|
|
Size FloatingWindow::maxSizeHint() const
|
|
{
|
|
Size result = Core::Item::hardcodedMaximumSize;
|
|
|
|
if (!d->m_dropArea) {
|
|
// Still early, no layout set
|
|
return result;
|
|
}
|
|
|
|
const Core::Group::List groups = this->groups();
|
|
if (groups.size() == 1) {
|
|
// Let's honour max-size when we have a single-group.
|
|
// multi-group cases are more complicated and we're not sure if we want the window to
|
|
// bounce around. single-group is the most common case, like floating a dock widget, so
|
|
// let's do that first, it's also easy.
|
|
Core::Group *group = groups[0];
|
|
if (group->dockWidgetCount() == 1) { // We don't support if there's tabbing
|
|
const Size waste =
|
|
(view()->minSize() - group->view()->minSize()).expandedTo(Size(0, 0));
|
|
result = group->view()->maxSizeHint() + waste;
|
|
}
|
|
}
|
|
|
|
// Semantically the result is fine, but bound it so we don't get:
|
|
// QWidget::setMaximumSize: (/KDDockWidgets::FloatingWindowWidget) The largest allowed size is
|
|
// (16777215,16777215)
|
|
return result.boundedTo(Core::Item::hardcodedMaximumSize);
|
|
}
|
|
|
|
void FloatingWindow::setSuggestedGeometry(Rect suggestedRect, SuggestedGeometryHints hint)
|
|
{
|
|
const Size maxSize = maxSizeHint();
|
|
const bool hasMaxSize = maxSize != Core::Item::hardcodedMaximumSize;
|
|
if (hasMaxSize) {
|
|
// Resize to new size but preserve center
|
|
const Point originalCenter = suggestedRect.center();
|
|
suggestedRect.setSize(maxSize.boundedTo(suggestedRect.size()));
|
|
|
|
if ((hint & SuggestedGeometryHint_GeometryIsFromDocked)
|
|
&& (d->m_flags & FloatingWindowFlag::NativeTitleBar)) {
|
|
const auto margins = contentMargins();
|
|
suggestedRect.setHeight(suggestedRect.height() - m_titleBar->view()->height()
|
|
+ margins.top() + margins.bottom());
|
|
}
|
|
|
|
if (hint & SuggestedGeometryHint_PreserveCenter)
|
|
suggestedRect.moveCenter(originalCenter);
|
|
}
|
|
|
|
ensureRectIsOnScreen(suggestedRect);
|
|
|
|
view()->setGeometry(suggestedRect);
|
|
}
|
|
|
|
void FloatingWindow::scheduleDeleteLater()
|
|
{
|
|
m_deleteScheduled = true;
|
|
view()->d->setAboutToBeDestroyed();
|
|
DockRegistry::self()->unregisterFloatingWindow(this);
|
|
destroyLater();
|
|
}
|
|
|
|
Core::DropArea *FloatingWindow::multiSplitter() const
|
|
{
|
|
return d->m_dropArea;
|
|
}
|
|
|
|
Layout *FloatingWindow::layout() const
|
|
{
|
|
return d->m_dropArea;
|
|
}
|
|
|
|
bool FloatingWindow::isInDragArea(Point globalPoint) const
|
|
{
|
|
#ifdef KDDW_FRONTEND_QT_WINDOWS
|
|
// A click near the border will still send a Qt::NonClientMousePressEvent. We shouldn't
|
|
// interpret that as a drag, as it's for a native resize.
|
|
// Keep track of how we handled the WM_NCHITTEST
|
|
if (usesAeroSnapWithCustomDecos())
|
|
return m_lastHitTest == HTCAPTION;
|
|
#endif
|
|
|
|
return dragRect().contains(globalPoint);
|
|
}
|
|
|
|
bool FloatingWindow::anyNonClosable() const
|
|
{
|
|
const auto groups = this->groups();
|
|
for (Core::Group *group : groups) {
|
|
if (group->anyNonClosable())
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FloatingWindow::anyNonDockable() const
|
|
{
|
|
const auto groups = this->groups();
|
|
for (Core::Group *group : groups) {
|
|
if (group->anyNonDockable())
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FloatingWindow::hasSingleGroup() const
|
|
{
|
|
return d->m_dropArea->hasSingleGroup();
|
|
}
|
|
|
|
bool FloatingWindow::hasSingleDockWidget() const
|
|
{
|
|
const Core::Group::List groups = this->groups();
|
|
if (groups.size() != 1)
|
|
return false;
|
|
|
|
Core::Group *group = groups.first();
|
|
return group->dockWidgetCount() == 1;
|
|
}
|
|
|
|
Core::Group *FloatingWindow::singleFrame() const
|
|
{
|
|
const Core::Group::List groups = this->groups();
|
|
return groups.isEmpty() ? nullptr : groups.first();
|
|
}
|
|
|
|
bool FloatingWindow::beingDeleted() const
|
|
{
|
|
if (m_deleteScheduled || m_inDtor)
|
|
return true;
|
|
|
|
const auto groups = this->groups();
|
|
for (Core::Group *f : groups) {
|
|
if (f->beingDeletedLater())
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FloatingWindow::onFrameCountChanged(int count)
|
|
{
|
|
if (count == 0) {
|
|
scheduleDeleteLater();
|
|
} else {
|
|
updateTitleBarVisibility();
|
|
if (count == 1) // if something was removed, then our single dock widget is floating, we
|
|
// need to check the Action
|
|
dropArea()->updateFloatingActions();
|
|
}
|
|
}
|
|
|
|
void FloatingWindow::onVisibleFrameCountChanged(int count)
|
|
{
|
|
if (m_disableSetVisible)
|
|
return;
|
|
|
|
updateSizeConstraints();
|
|
setVisible(count > 0);
|
|
}
|
|
|
|
WindowState FloatingWindow::windowStateOverride() const
|
|
{
|
|
WindowState state = WindowState::None;
|
|
|
|
if (view()->isMaximized())
|
|
state = WindowState::Maximized;
|
|
else if (view()->isMinimized())
|
|
state = WindowState::Minimized;
|
|
|
|
return state;
|
|
}
|
|
|
|
void FloatingWindow::updateTitleBarVisibility()
|
|
{
|
|
if (m_updatingTitleBarVisibility)
|
|
return; // Break recursion
|
|
|
|
ScopedValueRollback guard(m_updatingTitleBarVisibility, true);
|
|
updateTitleAndIcon();
|
|
|
|
bool visible = true;
|
|
|
|
const auto groups = this->groups();
|
|
for (Core::Group *group : groups)
|
|
group->updateTitleBarVisibility();
|
|
|
|
if (KDDockWidgets::usesClientTitleBar()) {
|
|
if ((d->m_flags & FloatingWindowFlag::HideTitleBarWhenTabsVisible)
|
|
&& !(d->m_flags & FloatingWindowFlag::AlwaysTitleBarWhenFloating)) {
|
|
if (hasSingleGroup()) {
|
|
visible = !groups.first()->hasTabsVisible();
|
|
}
|
|
}
|
|
|
|
m_titleBar->updateButtons();
|
|
} else {
|
|
visible = false;
|
|
}
|
|
|
|
m_titleBar->setVisible(visible);
|
|
}
|
|
|
|
Vector<QString> FloatingWindow::affinities() const
|
|
{
|
|
auto groups = this->groups();
|
|
return groups.isEmpty() ? Vector<QString>() : groups.constFirst()->affinities();
|
|
}
|
|
|
|
void FloatingWindow::updateTitleAndIcon()
|
|
{
|
|
QString title;
|
|
Icon icon;
|
|
if (hasSingleGroup()) {
|
|
const Core::Group *group = groups().constFirst();
|
|
title = group->title();
|
|
icon = group->icon();
|
|
} else {
|
|
title = Platform::instance()->applicationName();
|
|
}
|
|
m_titleBar->setTitle(title);
|
|
m_titleBar->setIcon(icon);
|
|
|
|
// Even without a native title bar it's nice to set the window title/icon, so it shows
|
|
// in the taskbar (when minimization is supported), or Alt-Tab (in supporting Window Managers)
|
|
view()->setWindowTitle(title);
|
|
view()->setWindowIcon(icon);
|
|
}
|
|
|
|
void FloatingWindow::onCloseEvent(CloseEvent *e)
|
|
{
|
|
if (e->spontaneous() && anyNonClosable()) {
|
|
// Event from the window system won't close us
|
|
e->ignore();
|
|
return;
|
|
}
|
|
|
|
d->m_dropArea->onCloseEvent(e);
|
|
}
|
|
|
|
bool FloatingWindow::deserialize(const LayoutSaver::FloatingWindow &fw)
|
|
{
|
|
if (dropArea()->deserialize(fw.multiSplitterLayout)) {
|
|
updateTitleBarVisibility();
|
|
|
|
if (int(fw.windowState) & int(WindowState::Maximized)) {
|
|
view()->showMaximized();
|
|
} else if (int(fw.windowState) & int(WindowState::Minimized)) {
|
|
#ifdef KDDW_FRONTEND_QT_WINDOWS
|
|
if (Platform::instance()->isQtQuick()) {
|
|
// We'll minimized it after the 1st frameSwap(), so it appears in alt-tab and taskbar thumbnails.
|
|
// Also fixes non-client area size, due to Qt not honouring WM_NCCALCSIZE correctly when showing minimized without a show normal before
|
|
d->m_minimizationPending = true;
|
|
} else {
|
|
// Workaround not implemented for QtWidgets, needs to be tested there.
|
|
view()->showMinimized();
|
|
}
|
|
#else
|
|
view()->showMinimized();
|
|
#endif
|
|
} else {
|
|
view()->showNormal();
|
|
}
|
|
|
|
d->numDockWidgetsChanged.emit();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
LayoutSaver::FloatingWindow FloatingWindow::serialize() const
|
|
{
|
|
LayoutSaver::FloatingWindow fw;
|
|
|
|
fw.geometry = geometry();
|
|
fw.normalGeometry = view()->normalGeometry();
|
|
fw.isVisible = isVisible();
|
|
fw.multiSplitterLayout = dropArea()->serialize();
|
|
fw.screenIndex = Platform::instance()->screenNumberForView(view());
|
|
fw.screenSize = Platform::instance()->screenSizeFor(view());
|
|
fw.affinities = affinities();
|
|
fw.windowState = windowStateOverride();
|
|
fw.flags = d->m_flags;
|
|
|
|
Window::Ptr transientParentWindow = view()->d->transientWindow();
|
|
auto transientMainWindow = DockRegistry::self()->mainWindowForHandle(transientParentWindow);
|
|
fw.parentIndex =
|
|
transientMainWindow ? DockRegistry::self()->mainwindows().indexOf(transientMainWindow) : -1;
|
|
|
|
return fw;
|
|
}
|
|
|
|
Rect FloatingWindow::dragRect() const
|
|
{
|
|
Rect rect;
|
|
if (m_titleBar->isVisible()) {
|
|
rect = m_titleBar->rect();
|
|
rect.moveTopLeft(m_titleBar->view()->mapToGlobal(Point(0, 0)));
|
|
} else if (hasSingleGroup()) {
|
|
rect = groups().constFirst()->dragRect();
|
|
} else {
|
|
KDDW_ERROR("Expected a title bar");
|
|
}
|
|
|
|
return rect;
|
|
}
|
|
|
|
bool FloatingWindow::allDockWidgetsHave(DockWidgetOption option) const
|
|
{
|
|
const Core::Group::List groups = this->groups();
|
|
return std::all_of(groups.begin(), groups.end(), [option](Core::Group *group) {
|
|
return group->allDockWidgetsHave(option);
|
|
});
|
|
}
|
|
|
|
bool FloatingWindow::anyDockWidgetsHas(DockWidgetOption option) const
|
|
{
|
|
const Core::Group::List groups = this->groups();
|
|
return std::any_of(groups.begin(), groups.end(), [option](Core::Group *group) {
|
|
return group->anyDockWidgetsHas(option);
|
|
});
|
|
}
|
|
|
|
bool FloatingWindow::allDockWidgetsHave(LayoutSaverOption option) const
|
|
{
|
|
const Core::Group::List groups = this->groups();
|
|
return std::all_of(groups.begin(), groups.end(), [option](Core::Group *group) {
|
|
return group->allDockWidgetsHave(option);
|
|
});
|
|
}
|
|
|
|
bool FloatingWindow::anyDockWidgetsHas(LayoutSaverOption option) const
|
|
{
|
|
const Core::Group::List groups = this->groups();
|
|
return std::any_of(groups.begin(), groups.end(), [option](Core::Group *group) {
|
|
return group->anyDockWidgetsHas(option);
|
|
});
|
|
}
|
|
|
|
void FloatingWindow::addDockWidget(Core::DockWidget *dw, Location location,
|
|
Core::DockWidget *relativeTo, const InitialOption &option)
|
|
{
|
|
d->m_dropArea->addDockWidget(dw, location, relativeTo, option);
|
|
}
|
|
|
|
bool FloatingWindow::isMDI() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool FloatingWindow::isWindow() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
MainWindow *FloatingWindow::mainWindow() const
|
|
{
|
|
return view()->parentView()->asMainWindowController();
|
|
}
|
|
|
|
Margins FloatingWindow::contentMargins() const
|
|
{
|
|
return { 4, 4, 4, 4 };
|
|
}
|
|
|
|
WindowState FloatingWindow::lastWindowManagerState() const
|
|
{
|
|
return m_lastWindowManagerState;
|
|
}
|
|
|
|
int FloatingWindow::userType() const
|
|
{
|
|
if (Core::Group *f = singleFrame())
|
|
return f->userType();
|
|
return 0;
|
|
}
|
|
|
|
void FloatingWindow::updateSizeConstraints()
|
|
{
|
|
#ifdef KDDW_FRONTEND_QT
|
|
// Doing a delayed call to make sure the layout has completed any ongoing operation.
|
|
QTimer::singleShot(0, this, [this] {
|
|
// Not simply using layout's max-size support because
|
|
// 1) that's not portable to QtQuick
|
|
// 2) QStackedLayout (from tab-widget) doesn't propagate size constraints up
|
|
// Doing it manually instead.
|
|
view()->setMaximumSize(maxSizeHint());
|
|
});
|
|
#endif
|
|
}
|
|
|
|
void FloatingWindow::ensureRectIsOnScreen(Rect &geometry)
|
|
{
|
|
const auto screens = Platform::instance()->screens();
|
|
if (screens.empty())
|
|
return;
|
|
|
|
int nearestDistSq = std::numeric_limits<int>::max();
|
|
int nearestIndex = -1;
|
|
|
|
const int screenCount = screens.count();
|
|
for (int i = 0; i < screenCount; i++) {
|
|
const Rect scrGeom = screens[i]->geometry();
|
|
|
|
// If the rectangle is visible at all, we need do nothing
|
|
if (scrGeom.intersects(geometry))
|
|
return;
|
|
|
|
// Find the nearest screen, so we can move the geometry onto it
|
|
const Point dist2D = geometry.center() - scrGeom.center();
|
|
const int distSq = (dist2D.x() * dist2D.x()) + (dist2D.y() * dist2D.y());
|
|
if (distSq < nearestDistSq) {
|
|
nearestDistSq = distSq;
|
|
nearestIndex = i;
|
|
}
|
|
}
|
|
|
|
// Move the rectangle to the nearest vertical and/or horizontal screen edge
|
|
auto scrGeom = screens[nearestIndex]->geometry();
|
|
scrGeom.moveTopLeft(scrGeom.topLeft() - screens[nearestIndex]->virtualGeometry().topLeft());
|
|
|
|
if (geometry.left() < scrGeom.left()) {
|
|
geometry.moveLeft(scrGeom.left());
|
|
} else if (geometry.left() > scrGeom.right()) {
|
|
geometry.moveRight(scrGeom.right());
|
|
}
|
|
|
|
if (geometry.top() < scrGeom.top()) {
|
|
geometry.moveTop(scrGeom.top());
|
|
} else if (geometry.top() > scrGeom.bottom()) {
|
|
geometry.moveBottom(scrGeom.bottom());
|
|
}
|
|
}
|
|
|
|
void FloatingWindow::setLastWindowManagerState(WindowState state)
|
|
{
|
|
m_lastWindowManagerState = state;
|
|
}
|
|
|
|
bool FloatingWindow::supportsMinimizeButton() const
|
|
{
|
|
return d->m_flags & FloatingWindowFlag::TitleBarHasMinimizeButton;
|
|
}
|
|
|
|
bool FloatingWindow::supportsMaximizeButton() const
|
|
{
|
|
return d->m_flags & FloatingWindowFlag::TitleBarHasMaximizeButton;
|
|
}
|
|
|
|
bool FloatingWindow::isUtilityWindow() const
|
|
{
|
|
const bool dontUse = (d->m_flags & FloatingWindowFlag::DontUseParentForFloatingWindows) && (d->m_flags & FloatingWindowFlag::UseQtWindow);
|
|
return !dontUse;
|
|
}
|
|
|
|
FloatingWindowFlags FloatingWindow::floatingWindowFlags() const
|
|
{
|
|
return d->m_flags;
|
|
}
|
|
|
|
FloatingWindow::Private *FloatingWindow::dptr() const
|
|
{
|
|
return d;
|
|
}
|
|
|
|
void FloatingWindow::focus(Qt::FocusReason reason)
|
|
{
|
|
const auto groups = this->groups();
|
|
if (groups.isEmpty())
|
|
return; // doesn't really happen
|
|
|
|
groups.constFirst()->focus(reason);
|
|
}
|
|
|
|
static FloatingWindowFlags flagsForFloatingWindow(FloatingWindowFlags requestedFlags)
|
|
{
|
|
if (!(requestedFlags & FloatingWindowFlag::FromGlobalConfig)) {
|
|
// User requested specific flags for this floating window
|
|
return requestedFlags;
|
|
}
|
|
|
|
// Use from KDDockWidgets::Config instead. This is app-wide and not per window.
|
|
|
|
FloatingWindowFlags flags = {};
|
|
|
|
if ((Config::self().flags() & Config::Flag_TitleBarHasMinimizeButton)
|
|
== Config::Flag_TitleBarHasMinimizeButton)
|
|
flags |= FloatingWindowFlag::TitleBarHasMinimizeButton;
|
|
|
|
if (Config::self().flags() & Config::Flag_TitleBarHasMaximizeButton)
|
|
flags |= FloatingWindowFlag::TitleBarHasMaximizeButton;
|
|
|
|
if (Config::self().flags() & Config::Flag_KeepAboveIfNotUtilityWindow)
|
|
flags |= FloatingWindowFlag::KeepAboveIfNotUtilityWindow;
|
|
|
|
if (Config::self().flags() & Config::Flag_NativeTitleBar)
|
|
flags |= FloatingWindowFlag::NativeTitleBar;
|
|
|
|
if (Config::self().flags() & Config::Flag_HideTitleBarWhenTabsVisible)
|
|
flags |= FloatingWindowFlag::HideTitleBarWhenTabsVisible;
|
|
|
|
if (Config::self().flags() & Config::Flag_AlwaysTitleBarWhenFloating)
|
|
flags |= FloatingWindowFlag::AlwaysTitleBarWhenFloating;
|
|
|
|
if (Config::self().internalFlags() & Config::InternalFlag_DontUseParentForFloatingWindows)
|
|
flags |= FloatingWindowFlag::DontUseParentForFloatingWindows;
|
|
|
|
if (Config::self().internalFlags() & Config::InternalFlag_DontUseQtToolWindowsForFloatingWindows)
|
|
flags |= FloatingWindowFlag::UseQtWindow;
|
|
|
|
return flags;
|
|
}
|
|
|
|
FloatingWindow::Private::Private(FloatingWindowFlags requestedFlags, FloatingWindow *q)
|
|
: m_flags(flagsForFloatingWindow(requestedFlags))
|
|
, m_dropArea(new DropArea(q->view(), MainWindowOption_None))
|
|
{
|
|
}
|