You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3707 lines
149 KiB
C++
3707 lines
149 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project.
|
|
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
|
|
#include <cmath>
|
|
|
|
#include <AzCore/Casting/numeric_cast.h>
|
|
#include <AzCore/Debug/Trace.h>
|
|
|
|
#include <AzQtComponents/Components/DockTabBar.h>
|
|
#include <AzQtComponents/Components/FancyDocking.h>
|
|
#include <AzQtComponents/Components/FancyDockingGhostWidget.h>
|
|
#include <AzQtComponents/Components/FancyDockingDropZoneWidget.h>
|
|
#include <AzQtComponents/Components/RepolishMinimizer.h>
|
|
#include <AzQtComponents/Components/Style.h>
|
|
#include <AzQtComponents/Components/Titlebar.h>
|
|
#include <AzQtComponents/Components/WindowDecorationWrapper.h>
|
|
#include <AzQtComponents/Utilities/QtWindowUtilities.h>
|
|
#include <AzQtComponents/Utilities/RandomNumberGenerator.h>
|
|
#include <AzQtComponents/Utilities/ScreenUtilities.h>
|
|
|
|
#include <QAbstractButton>
|
|
#include <QApplication>
|
|
#include <QCursor>
|
|
#include <QDebug>
|
|
#include <QDesktopWidget>
|
|
#include <QDockWidget>
|
|
#include <QEvent>
|
|
#include <QTimer>
|
|
#include <QLabel>
|
|
#include <QMouseEvent>
|
|
#include <QPainter>
|
|
#include <QRandomGenerator>
|
|
#include <QScopedValueRollback>
|
|
#include <QScreen>
|
|
#include <QStyle>
|
|
#include <QStackedWidget>
|
|
#include <QStyleOptionToolButton>
|
|
#include <QVBoxLayout>
|
|
#include <QWindow>
|
|
#include <QtGui/private/qhighdpiscaling_p.h>
|
|
|
|
|
|
static void OptimizedSetParent(QWidget* widget, QWidget* parent)
|
|
{
|
|
AzQtComponents::RepolishMinimizer minimizer; // Blocks useless polish requests caused by setParent()
|
|
widget->setParent(parent);
|
|
}
|
|
|
|
namespace AzQtComponents
|
|
{
|
|
static FancyDockingDropZoneConstants g_FancyDockingConstants;
|
|
|
|
// Constant for the threshold in pixels for snapping to edges while dragging for docking
|
|
static const int g_snapThresholdInPixels = 15;
|
|
|
|
static QString g_minimizeButtonObjectName = "minimizeButton";
|
|
static QString g_maximizeButtonObjectName = "maximizeButton";
|
|
static QString g_closeButtonObjectName = "closeButton";
|
|
|
|
static Qt::Orientation orientation(Qt::DockWidgetArea area)
|
|
{
|
|
switch (area)
|
|
{
|
|
default:
|
|
case Qt::BottomDockWidgetArea:
|
|
case Qt::TopDockWidgetArea:
|
|
return Qt::Vertical;
|
|
case Qt::LeftDockWidgetArea:
|
|
case Qt::RightDockWidgetArea:
|
|
return Qt::Horizontal;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stream operator for writing out the TabContainerType to a data stream
|
|
*/
|
|
static QDataStream& operator<<(QDataStream& out, const FancyDocking::TabContainerType& myObj)
|
|
{
|
|
out << myObj.floatingDockName << myObj.tabNames << myObj.currentIndex;
|
|
return out;
|
|
}
|
|
|
|
/**
|
|
* Stream operator for reading in a TabContainerType from a data stream
|
|
*/
|
|
static QDataStream& operator>>(QDataStream& in, FancyDocking::TabContainerType& myObj)
|
|
{
|
|
in >> myObj.floatingDockName;
|
|
in >> myObj.tabNames;
|
|
in >> myObj.currentIndex;
|
|
return in;
|
|
}
|
|
|
|
namespace
|
|
{
|
|
static const char* g_AutoSavePropertyName = "AutoSaveLayout";
|
|
|
|
static bool shouldSkipTitleBarOverdraw(QDockWidget* dockWidget)
|
|
{
|
|
StyledDockWidget* styledDockChild = qobject_cast<StyledDockWidget*>(dockWidget);
|
|
if (styledDockChild != nullptr)
|
|
{
|
|
return styledDockChild->skipTitleBarOverdraw();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create our fancy docking widget
|
|
*/
|
|
FancyDocking::FancyDocking(DockMainWindow* mainWindow, const char* identifierPrefix)
|
|
: QWidget(mainWindow, Qt::WindowFlags(Qt::ToolTip | Qt::BypassWindowManagerHint | Qt::FramelessWindowHint))
|
|
, m_mainWindow(mainWindow)
|
|
, m_emptyWidget(new QWidget(this))
|
|
, m_dropZoneHoverFadeInTimer(new QTimer(this))
|
|
, m_ghostWidget(new FancyDockingGhostWidget(mainWindow))
|
|
, m_dropZoneWidgets()
|
|
, m_floatingWindowIdentifierPrefix(QString("_%1_").arg(identifierPrefix))
|
|
, m_tabContainerIdentifierPrefix(QString("_%1tabcontainer_").arg(identifierPrefix))
|
|
{
|
|
m_dropZoneState.setDropZoneColorOnHover(Style::dropZoneColorOnHover());
|
|
|
|
// Register our TabContainerType stream operators so that they will be used
|
|
// when reading/writing from/to data streams
|
|
qRegisterMetaTypeStreamOperators<FancyDocking::TabContainerType>("FancyDocking::TabContainerType");
|
|
mainWindow->installEventFilter(this);
|
|
mainWindow->SetFancyDockingOwner(this);
|
|
setAutoFillBackground(false);
|
|
setAttribute(Qt::WA_TransparentForMouseEvents);
|
|
setAttribute(Qt::WA_TranslucentBackground);
|
|
setAttribute(Qt::WA_NoSystemBackground);
|
|
|
|
// Make sure our placeholder empty widget is hidden by default
|
|
m_emptyWidget->hide();
|
|
|
|
// Update our docking overlay geometry, and listen for any changes to the
|
|
// desktop screens being resized or added/removed so we can recalculate
|
|
// our docking overlay
|
|
updateDockingGeometry();
|
|
|
|
for (auto screen : QApplication::screens())
|
|
{
|
|
QObject::connect(screen, &QScreen::geometryChanged, this, &FancyDocking::updateDockingGeometry);
|
|
}
|
|
|
|
QObject::connect(qApp, &QApplication::screenAdded, this, &FancyDocking::handleScreenAdded);
|
|
QObject::connect(qApp, &QApplication::screenRemoved, this, &FancyDocking::handleScreenRemoved);
|
|
|
|
// Timer for updating our hovered drop zone opacity
|
|
QObject::connect(m_dropZoneHoverFadeInTimer, &QTimer::timeout, this, &FancyDocking::onDropZoneHoverFadeInUpdate);
|
|
m_dropZoneHoverFadeInTimer->setInterval(g_FancyDockingConstants.dropZoneHoverFadeUpdateIntervalMS);
|
|
QIcon dragIcon = QIcon(QStringLiteral(":/Cursors/Grabbing.svg"));
|
|
m_dragCursor = QCursor(dragIcon.pixmap(16), 5, 2);
|
|
}
|
|
|
|
FancyDocking::~FancyDocking()
|
|
{
|
|
for (auto i = m_dropZoneWidgets.begin(); i != m_dropZoneWidgets.end(); i++)
|
|
{
|
|
delete i.value();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a new QDockWidget whose main widget will be a DockMainWindow. It will be created floating
|
|
* with the given geometry. The QDockWidget will be named with the given name
|
|
*/
|
|
QMainWindow* FancyDocking::createFloatingMainWindow(const QString& name, const QRect& geometry, bool skipTitleBarOverdraw)
|
|
{
|
|
auto dockWidget = new StyledDockWidget(QString(), skipTitleBarOverdraw, m_mainWindow);
|
|
dockWidget->setObjectName(name);
|
|
if (!restoreDockWidget(dockWidget))
|
|
{
|
|
m_mainWindow->addDockWidget(Qt::LeftDockWidgetArea, dockWidget);
|
|
}
|
|
dockWidget->setFloating(true);
|
|
|
|
// Make sure the floating dock container is deleted when closed so that
|
|
// its children can be restored properly when re-opened (otherwise they
|
|
// will try to show up on a floating dock widget that is invisible)
|
|
dockWidget->setAttribute(Qt::WA_DeleteOnClose);
|
|
|
|
// Stack this floating dock widget name on the top of our z-ordered list
|
|
// since it was just created
|
|
m_orderedFloatingDockWidgetNames.prepend(name);
|
|
|
|
// Hide the title bar when the group is docked
|
|
//commented out because our styled dockwidget takes care of this
|
|
//connect(dockWidget, &QDockWidget::topLevelChanged, [dockWidget](bool fl)
|
|
// { if (!fl) dockWidget->setTitleBarWidget(new QWidget()); });
|
|
DockMainWindow* mainWindow = new DockMainWindow(dockWidget);
|
|
mainWindow->SetFancyDockingOwner(this);
|
|
mainWindow->setWindowFlags(Qt::Widget);
|
|
mainWindow->installEventFilter(this);
|
|
|
|
dockWidget->setWidget(mainWindow);
|
|
dockWidget->show();
|
|
QRect adjustedGeometry = geometry;
|
|
AzQtComponents::EnsureGeometryWithinScreenTop(adjustedGeometry);
|
|
if (!adjustedGeometry.isNull())
|
|
{
|
|
dockWidget->setGeometry(adjustedGeometry);
|
|
}
|
|
|
|
return mainWindow;
|
|
}
|
|
|
|
/**
|
|
* Create a new tab widget and a dock widget container to hold it
|
|
*/
|
|
DockTabWidget* FancyDocking::createTabWidget(QMainWindow* mainWindow, QDockWidget* widgetToReplace, QString name)
|
|
{
|
|
// If a name wasn't provided, then generate a random one
|
|
if (name.isEmpty())
|
|
{
|
|
name = getUniqueDockWidgetName(m_tabContainerIdentifierPrefix);
|
|
}
|
|
|
|
// Create a container dock widget for our tab widget
|
|
StyledDockWidget* tabWidgetContainer = new StyledDockWidget(mainWindow);
|
|
tabWidgetContainer->setObjectName(name);
|
|
tabWidgetContainer->setFloating(false);
|
|
|
|
// Set an empty QWidget as the custom title bar to hide it, since our tab widget will drive it's own custom tab bar
|
|
// that will replace it (the empty QWidget is parented to the dock widget, so it will be cleaned up whenever the dock widget is deleted)
|
|
tabWidgetContainer->setTitleBarWidget(new QWidget());
|
|
|
|
// Create our new tab widget and listen for tab pressed, inserted, count changed, and undock events
|
|
DockTabWidget* tabWidget = new DockTabWidget(m_mainWindow, mainWindow);
|
|
QObject::connect(tabWidget, &DockTabWidget::tabIndexPressed, this, &FancyDocking::onTabIndexPressed);
|
|
QObject::connect(tabWidget, &DockTabWidget::tabWidgetInserted, this, &FancyDocking::onTabWidgetInserted);
|
|
QObject::connect(tabWidget, &DockTabWidget::tabCountChanged, this, &FancyDocking::onTabCountChanged);
|
|
QObject::connect(tabWidget, &DockTabWidget::currentChanged, this, &FancyDocking::onCurrentTabIndexChanged);
|
|
QObject::connect(tabWidget, &DockTabWidget::undockTab, this, &FancyDocking::onUndockTab);
|
|
|
|
// Set our tab widget as the widget for our tab container docking widget
|
|
tabWidgetContainer->setWidget(tabWidget);
|
|
|
|
// There isn't a way to replace a dock widget in a layout, so we have to place our tab container dock widget
|
|
// split next to our replaced widget, and then remove our replaced widget from the layout. The replaced widget
|
|
// will then be moved to our tab widget, so it effectively will remain in the same spot, but now it will be tabbed
|
|
// instead of a standalone dock widget.
|
|
if (widgetToReplace)
|
|
{
|
|
splitDockWidget(mainWindow, widgetToReplace, tabWidgetContainer, Qt::Horizontal);
|
|
mainWindow->removeDockWidget(widgetToReplace);
|
|
tabWidget->addTab(widgetToReplace);
|
|
}
|
|
|
|
return tabWidget;
|
|
}
|
|
|
|
/**
|
|
* Return a unique object name with the specified prefix that doesn't collide with any QDockWidget children of our main window
|
|
*/
|
|
QString FancyDocking::getUniqueDockWidgetName(const QString& prefix)
|
|
{
|
|
QString name;
|
|
do
|
|
{
|
|
name = QString("%1%2").arg(prefix).arg(GetRandomGenerator()->generate(), 16);
|
|
} while (m_mainWindow->findChild<QDockWidget*>(name));
|
|
|
|
return name;
|
|
}
|
|
|
|
/**
|
|
* Update the geometry of our docking overlay to be a union of all the screen
|
|
* rects for each desktop monitor
|
|
*/
|
|
void FancyDocking::updateDockingGeometry()
|
|
{
|
|
QRect totalScreenRect;
|
|
int numScreens = QApplication::screens().count();
|
|
|
|
#ifdef AZ_PLATFORM_WINDOWS
|
|
for (QWidget* w : m_perScreenFullScreenWidgets) {
|
|
delete w;
|
|
}
|
|
m_perScreenFullScreenWidgets.clear();
|
|
#endif
|
|
|
|
for (int i = 0; i < numScreens; ++i)
|
|
{
|
|
#ifdef AZ_PLATFORM_WINDOWS
|
|
QWidget* screenWidget = new QWidget(this);
|
|
screenWidget->setGeometry(QApplication::screens().at(i)->geometry());
|
|
m_perScreenFullScreenWidgets.push_back(screenWidget);
|
|
#else
|
|
totalScreenRect = totalScreenRect.united(QApplication::screens().at(i)->geometry());
|
|
#endif
|
|
}
|
|
|
|
#ifndef AZ_PLATFORM_WINDOWS
|
|
setGeometry(totalScreenRect);
|
|
#endif
|
|
|
|
// Update our list of screens whenever screens are added/removed so that we
|
|
// don't have to query them every time
|
|
m_desktopScreens = qApp->screens();
|
|
}
|
|
|
|
/**
|
|
* Handle a screen being added to the current layout
|
|
*/
|
|
void FancyDocking::handleScreenAdded(QScreen* screen)
|
|
{
|
|
QObject::connect(screen, &QScreen::geometryChanged, this, &FancyDocking::updateDockingGeometry);
|
|
|
|
updateDockingGeometry();
|
|
}
|
|
|
|
/**
|
|
* Handle a screen being removed from the current layout
|
|
*/
|
|
void FancyDocking::handleScreenRemoved(QScreen* screen)
|
|
{
|
|
QObject::disconnect(screen, &QScreen::geometryChanged, this, &FancyDocking::updateDockingGeometry);
|
|
|
|
updateDockingGeometry();
|
|
}
|
|
|
|
/**
|
|
* Called on a timer interval to update the hovered drop zone opacity to make it
|
|
* fade in with a set delay
|
|
*/
|
|
void FancyDocking::onDropZoneHoverFadeInUpdate()
|
|
{
|
|
const qreal dropZoneHoverOpacity = g_FancyDockingConstants.dropZoneHoverFadeIncrement + m_dropZoneState.dropZoneHoverOpacity();
|
|
|
|
// Once we've reached the full drop zone opacity, cut it off in case we
|
|
// went over and stop the timer
|
|
if (dropZoneHoverOpacity >= g_FancyDockingConstants.dropZoneOpacity)
|
|
{
|
|
m_dropZoneState.setDropZoneHoverOpacity(g_FancyDockingConstants.dropZoneOpacity);
|
|
m_dropZoneHoverFadeInTimer->stop();
|
|
}
|
|
else
|
|
{
|
|
m_dropZoneState.setDropZoneHoverOpacity(dropZoneHoverOpacity);
|
|
}
|
|
|
|
// Trigger a re-paint so the opacity will update
|
|
RepaintFloatingIndicators();
|
|
}
|
|
|
|
/**
|
|
* Return the number of visible dock widget children for the specified main window
|
|
*/
|
|
int FancyDocking::NumVisibleDockWidgets(QMainWindow* mainWindow)
|
|
{
|
|
if (!mainWindow)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
// Count the number of visible dock widgets for our main window
|
|
const QList<QDockWidget*> list = mainWindow->findChildren<QDockWidget*>(QString(), Qt::FindDirectChildrenOnly);
|
|
ptrdiff_t count = std::count_if(list.cbegin(), list.cend(), [](QDockWidget* dockWidget) {
|
|
return dockWidget->isVisible();
|
|
});
|
|
|
|
return aznumeric_cast<int>(count);
|
|
}
|
|
|
|
/**
|
|
* Update the window title for the specified floating dock widget based on
|
|
* one of its child dock widgets. This window title appears in the taskbar.
|
|
* The update occurs on a SingleShot timer after any actions have completed.
|
|
* Otherwise the dock widget may not be visible yet or the dock widget could
|
|
* be a tab widget container that has not set its tab widget yet. Also, the
|
|
* dock positioning may not yet be final
|
|
*/
|
|
void FancyDocking::QueueUpdateFloatingWindowTitle(QMainWindow* mainWindow)
|
|
{
|
|
if (!mainWindow || mainWindow == m_mainWindow)
|
|
{
|
|
return;
|
|
}
|
|
|
|
StyledDockWidget* floatingDockWidget = qobject_cast<StyledDockWidget*>(mainWindow->parentWidget());
|
|
if (!floatingDockWidget)
|
|
{
|
|
return;
|
|
}
|
|
|
|
QString floatingDockWidgetName = floatingDockWidget->objectName();
|
|
QTimer::singleShot(0, m_mainWindow, [this, floatingDockWidgetName] {
|
|
|
|
StyledDockWidget* floatingDockWidget = m_mainWindow->findChild<StyledDockWidget*>(floatingDockWidgetName, Qt::FindDirectChildrenOnly);
|
|
if (!floatingDockWidget)
|
|
{
|
|
return;
|
|
}
|
|
|
|
QMainWindow* mainWindow = qobject_cast<QMainWindow*>(floatingDockWidget->widget());
|
|
if (!mainWindow || mainWindow == m_mainWindow)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const QDockWidget* topLeftWidget = nullptr;
|
|
int topLeftWidgetManhattanDist = 0;
|
|
for (QDockWidget* childDockWidget : mainWindow->findChildren<QDockWidget*>(QString(), Qt::FindDirectChildrenOnly))
|
|
{
|
|
if (!childDockWidget->isVisible())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
int childDockManhattanDist = childDockWidget->pos().manhattanLength();
|
|
if (!topLeftWidget || childDockManhattanDist < topLeftWidgetManhattanDist)
|
|
{
|
|
topLeftWidget = childDockWidget;
|
|
topLeftWidgetManhattanDist = childDockManhattanDist;
|
|
}
|
|
}
|
|
|
|
QString title;
|
|
if (topLeftWidget)
|
|
{
|
|
// Check if the child dock widget is a tab widget container
|
|
DockTabWidget* tabWidget = qobject_cast<DockTabWidget*>(topLeftWidget->widget());
|
|
if (tabWidget)
|
|
{
|
|
int currentTabIndex = tabWidget->currentIndex();
|
|
if (currentTabIndex >= 0)
|
|
{
|
|
title = tabWidget->tabText(currentTabIndex);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
title = topLeftWidget->windowTitle();
|
|
}
|
|
}
|
|
floatingDockWidget->setWindowTitle(title);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Adds close, maximize and minimize buttons from the DockTabWidget
|
|
*/
|
|
void FancyDocking::AddTitleBarButtons(AzQtComponents::DockTabWidget* tabWidget, AzQtComponents::TitleBar* titleBar)
|
|
{
|
|
if (tabWidget->actions().count() > 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Show Action Toolbar
|
|
tabWidget->setActionToolBarVisible(true);
|
|
|
|
// Minimize Icon
|
|
QAction* minimizeAction = new QAction(tr("Minimize"));
|
|
minimizeAction->setObjectName(g_minimizeButtonObjectName);
|
|
|
|
connect(minimizeAction, &QAction::triggered, this, [titleBar]() {
|
|
titleBar->handleMinimize();
|
|
});
|
|
|
|
tabWidget->addAction(minimizeAction);
|
|
|
|
// Maximize Icon
|
|
QAction* maximizeAction = new QAction(tr("Maximize"));
|
|
maximizeAction->setObjectName(g_maximizeButtonObjectName);
|
|
|
|
connect(maximizeAction, &QAction::triggered, this, [titleBar]() {
|
|
titleBar->handleMaximize();
|
|
});
|
|
|
|
tabWidget->addAction(maximizeAction);
|
|
|
|
// Close Icon
|
|
QAction* closeAction = new QAction(tr("Close"));
|
|
closeAction->setObjectName(g_closeButtonObjectName);
|
|
|
|
connect(closeAction, &QAction::triggered, this, [titleBar]() {
|
|
titleBar->handleClose();
|
|
});
|
|
|
|
tabWidget->addAction(closeAction);
|
|
|
|
QObject::connect(tabWidget, &DockTabWidget::tabBarDoubleClicked, this, &FancyDocking::onTabBarDoubleClicked);
|
|
|
|
// Toggle the TabBar and TitleBar to show the Window context menus
|
|
auto dockTabBar = qobject_cast<AzQtComponents::DockTabBar*>(tabWidget->tabBar());
|
|
if (dockTabBar)
|
|
{
|
|
dockTabBar->setIsShowingWindowControls(true);
|
|
}
|
|
|
|
titleBar->setIsShowingWindowControls(true);
|
|
}
|
|
|
|
/**
|
|
* Remove close, maximize and minimize buttons from the DockTabWidget
|
|
*/
|
|
void FancyDocking::RemoveTitleBarButtons(AzQtComponents::DockTabWidget* tabWidget, AzQtComponents::TitleBar* titleBar)
|
|
{
|
|
if (tabWidget->actions().count() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Hide Action Toolbar
|
|
tabWidget->setActionToolBarVisible(false);
|
|
|
|
for (QAction* action : tabWidget->actions())
|
|
{
|
|
tabWidget->removeAction(action);
|
|
}
|
|
|
|
QObject::disconnect(tabWidget, &DockTabWidget::tabBarDoubleClicked, this, &FancyDocking::onTabBarDoubleClicked);
|
|
qobject_cast<DockTabBar*>(tabWidget->tabBar())->setIsShowingWindowControls(false);
|
|
|
|
if (titleBar)
|
|
{
|
|
titleBar->setIsShowingWindowControls(false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update TitleBars for docked and floating widgets
|
|
*/
|
|
void FancyDocking::UpdateTitleBars(QMainWindow* mainWindow)
|
|
{
|
|
if (!mainWindow)
|
|
{
|
|
return;
|
|
}
|
|
|
|
QList<QDockWidget*> childrenDockWidgets = mainWindow->findChildren<QDockWidget*>(QString(), Qt::FindDirectChildrenOnly);
|
|
|
|
if (mainWindow == m_mainWindow)
|
|
{
|
|
// Main Window: remove all titlebar buttons from all widgets
|
|
|
|
for (QDockWidget* dockWidget : childrenDockWidgets)
|
|
{
|
|
// Skip floating windows (as they also are direct children of the mainwindow)
|
|
if (IsFloatingDockWidget(dockWidget))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// TabWidgets
|
|
for (auto tabWidget : dockWidget->findChildren<AzQtComponents::DockTabWidget*>(QString(), Qt::FindDirectChildrenOnly))
|
|
{
|
|
RemoveTitleBarButtons(tabWidget);
|
|
}
|
|
|
|
// Regular DockWidgets
|
|
if (auto innerTitleBar = qobject_cast<AzQtComponents::TitleBar*>(dockWidget->titleBarWidget()))
|
|
{
|
|
innerTitleBar->setButtons({ });
|
|
innerTitleBar->setIsShowingWindowControls(false);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Floating Window
|
|
|
|
AzQtComponents::TitleBar* floatingWindowTitleBar = qobject_cast<StyledDockWidget*>(mainWindow->parentWidget())->customTitleBar();
|
|
|
|
if (childrenDockWidgets.count() == 1)
|
|
{
|
|
// If there's just one child, TabWidgets and DockWidgets get the buttons and we hide the TitleBar
|
|
|
|
// Hide TitleBar
|
|
floatingWindowTitleBar->setDrawMode(AzQtComponents::TitleBar::TitleBarDrawMode::Hidden);
|
|
|
|
// Show Buttons on TabWidgets
|
|
auto tabWidgets = childrenDockWidgets[0]->findChildren<AzQtComponents::DockTabWidget*>(QString(), Qt::FindDirectChildrenOnly);
|
|
for (auto tabWidget : tabWidgets)
|
|
{
|
|
AddTitleBarButtons(tabWidget, floatingWindowTitleBar);
|
|
}
|
|
|
|
// Show Buttons on DockWidgets
|
|
if (auto innerTitleBar = qobject_cast<AzQtComponents::TitleBar*>(childrenDockWidgets[0]->titleBarWidget()))
|
|
{
|
|
innerTitleBar->setButtons({ DockBarButton::MinimizeButton, DockBarButton::MaximizeButton, DockBarButton::CloseButton });
|
|
innerTitleBar->setIsShowingWindowControls(true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If multiple children are found, show the TitleBar and remove buttons from TabWidgets and DockWidgets
|
|
|
|
// Show TitleBar with buttons
|
|
floatingWindowTitleBar->setDrawMode(AzQtComponents::TitleBar::TitleBarDrawMode::Simple);
|
|
floatingWindowTitleBar->setButtons({ DockBarButton::MinimizeButton, DockBarButton::MaximizeButton, DockBarButton::CloseButton });
|
|
|
|
// Remove buttons from widgets
|
|
for (QDockWidget* dockWidget : childrenDockWidgets)
|
|
{
|
|
// TabWidgets
|
|
for (auto tabWidget : dockWidget->findChildren<AzQtComponents::DockTabWidget*>(QString(), Qt::FindDirectChildrenOnly))
|
|
{
|
|
RemoveTitleBarButtons(tabWidget, floatingWindowTitleBar);
|
|
}
|
|
|
|
// DockWidget
|
|
if (auto innerTitleBar = qobject_cast<AzQtComponents::TitleBar*>(dockWidget->titleBarWidget()))
|
|
{
|
|
innerTitleBar->setButtons({ });
|
|
innerTitleBar->setIsShowingWindowControls(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns true if the DockWidget is a FancyDocking floating window
|
|
*/
|
|
bool FancyDocking::IsFloatingDockWidget(QDockWidget* dockWidget)
|
|
{
|
|
QString dockName = dockWidget->objectName();
|
|
return dockName.startsWith(m_floatingWindowIdentifierPrefix);
|
|
}
|
|
|
|
/**
|
|
* Adjust mapFromGlobal to account for DPI scaling on multiple screens
|
|
*/
|
|
QPoint FancyDocking::multiscreenMapFromGlobal(const QPoint& point) const
|
|
{
|
|
#if 0 //def AZ_PLATFORM_WINDOWS
|
|
int index = 0;
|
|
for (auto screen : QApplication::screens()) {
|
|
if (screen->geometry().contains(point)) {
|
|
qreal scaleFactor = QHighDpiScaling::factor(screen);
|
|
return (
|
|
(m_perScreenFullScreenWidgets[index]->mapFromGlobal(point) * scaleFactor) +
|
|
(m_perScreenFullScreenWidgets[index]->mapToGlobal({0, 0})) / scaleFactor);
|
|
}
|
|
++index;
|
|
}
|
|
|
|
// If the point isn't contained in any screen, return the regular mapFromGlobal() result for now
|
|
// TODO - may need to do some shenanigan like the above based to the closest screen?
|
|
return mapFromGlobal(point);
|
|
#else
|
|
return mapFromGlobal(point);
|
|
#endif
|
|
}
|
|
|
|
bool FancyDocking::WidgetContainsPoint(QWidget* widget, const QPoint& pos) const
|
|
{
|
|
if (!widget)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
QPoint globalPos = multiscreenMapFromGlobal(pos);
|
|
|
|
// Find out the true global position of the widgets top left coordinate
|
|
QPoint widgetTopLeft = multiscreenMapFromGlobal(widget->mapToGlobal(QPoint(0, 0)));
|
|
|
|
// Construct the global rect given our widgets top left coordinate plus size
|
|
QRect widgetGlobalRect(widgetTopLeft, QSize(widget->width(), widget->height()));
|
|
|
|
return widgetGlobalRect.contains(globalPos);
|
|
}
|
|
|
|
/**
|
|
* Destroy a floating main window if it no longer contains any QDockWidgets
|
|
*/
|
|
void FancyDocking::destroyIfUseless(QMainWindow* mainWindow)
|
|
{
|
|
// Ignore if this was triggered on our main window, or if this is triggered
|
|
// during a tabify action, during which the dock widgets may be hidden
|
|
// so it ends up deleting the floating main window
|
|
if (!mainWindow || mainWindow == m_mainWindow || m_state.updateInProgress)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Remove the container main window if there are no more visible QDockWidgets
|
|
int count = NumVisibleDockWidgets(mainWindow);
|
|
if (count > 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Avoid a recursion
|
|
mainWindow->removeEventFilter(this);
|
|
|
|
// Verify the parent is a styled dock widget
|
|
auto floatingDockWidget = qobject_cast<AzQtComponents::StyledDockWidget*>(mainWindow->parentWidget());
|
|
|
|
if (!floatingDockWidget)
|
|
{
|
|
AZ_Warning("FancyDocking", false, "Error - Floating dock widget parent isn't a StyledDockWidget.");
|
|
return;
|
|
}
|
|
|
|
// Save the state of this floating dock widget that's about to be destroyed
|
|
// so that we can re-create it if necessary when restoring any panes whose
|
|
// last location was in this floating dock widget
|
|
QString floatingDockWidgetName = floatingDockWidget->objectName();
|
|
if (!floatingDockWidgetName.isEmpty())
|
|
{
|
|
m_restoreFloatings[floatingDockWidgetName] = qMakePair(mainWindow->saveState(), floatingDockWidget->geometry());
|
|
}
|
|
|
|
// Any dock widgets left in our floating main window were hidden, so
|
|
// reparent them to the editor main window and make sure they remain
|
|
// hidden. This is so they will be restored properly the next time
|
|
// someone tries to open them, because otherwise, it would try to
|
|
// open them on the floating main window that no longer exists.
|
|
for (QDockWidget* dockWidget : mainWindow->findChildren<QDockWidget*>(QString(), Qt::FindDirectChildrenOnly))
|
|
{
|
|
OptimizedSetParent(dockWidget, m_mainWindow);
|
|
dockWidget->setVisible(false);
|
|
}
|
|
|
|
// Remove this floating dock widget from our z-ordered list of dock widget names
|
|
m_orderedFloatingDockWidgetNames.removeAll(floatingDockWidgetName);
|
|
|
|
// If a top level window has the dock widget we're about to destroy as transient parent,
|
|
// update its transient parent to that of the dock widget, so that z-ordering is maintained.
|
|
if (const QWindow* dockWidgetWindow = floatingDockWidget->windowHandle())
|
|
{
|
|
for (QWidget* widget : QApplication::topLevelWidgets())
|
|
{
|
|
if (QWindow* window = widget->windowHandle())
|
|
{
|
|
if (window->transientParent() == dockWidgetWindow)
|
|
{
|
|
window->setTransientParent(dockWidgetWindow->transientParent());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Lastly, delete our empty floating dock widget container, which will
|
|
// also delete the floating main window since it is a child.
|
|
floatingDockWidget->deleteLater();
|
|
}
|
|
|
|
/**
|
|
* Return an absolute drop zone (if applicable) for the given drop target
|
|
*/
|
|
QRect FancyDocking::getAbsoluteDropZone(QWidget* dock, Qt::DockWidgetArea& area, const QPoint& globalPos)
|
|
{
|
|
QRect absoluteDropZoneRect;
|
|
if (!dock || ForceTabbedDocksEnabled())
|
|
{
|
|
return absoluteDropZoneRect;
|
|
}
|
|
|
|
// Check if we are trying to drop onto a main window, and if not, get the
|
|
// main window from the drop target parent
|
|
QMainWindow* mainWindow = qobject_cast<QMainWindow*>(dock);
|
|
bool dropTargetIsMainWindow = true;
|
|
if (!mainWindow)
|
|
{
|
|
dropTargetIsMainWindow = false;
|
|
mainWindow = qobject_cast<QMainWindow*>(dock->parentWidget());
|
|
}
|
|
|
|
// If we still couldn't find a valid main window, then bail out
|
|
if (!mainWindow)
|
|
{
|
|
return absoluteDropZoneRect;
|
|
}
|
|
|
|
// Don't allow the dragged dock widget to be docked as absolute
|
|
// if it's already in the target main window and there is only
|
|
// one other widget alongside it
|
|
if (mainWindow != m_mainWindow)
|
|
{
|
|
const auto childDockWidgets = mainWindow->findChildren<QDockWidget*>(QString(), Qt::FindDirectChildrenOnly);
|
|
if (childDockWidgets.size() <= 2 && childDockWidgets.contains(m_state.dock))
|
|
{
|
|
return absoluteDropZoneRect;
|
|
}
|
|
}
|
|
|
|
// Setup the possible absolute drop zones for the given main window
|
|
QRect mainWindowRect(mainWindow->rect());
|
|
QPoint mainWindowTopLeft = multiscreenMapFromGlobal(mainWindow->mapToGlobal(mainWindowRect.topLeft()));
|
|
QPoint mainWindowTopRight = multiscreenMapFromGlobal(mainWindow->mapToGlobal(mainWindowRect.topRight()));
|
|
QPoint mainWindowBottomLeft = multiscreenMapFromGlobal(mainWindow->mapToGlobal(mainWindowRect.bottomLeft()));
|
|
QSize absoluteLeftRightSize(g_FancyDockingConstants.absoluteDropZoneSizeInPixels, mainWindowRect.height());
|
|
QRect absoluteLeftDropZone(mainWindowTopLeft, absoluteLeftRightSize);
|
|
QRect absoluteRightDropZone(mainWindowTopRight - QPoint(g_FancyDockingConstants.absoluteDropZoneSizeInPixels, 0), absoluteLeftRightSize);
|
|
QSize absoluteTopBottomSize(mainWindowRect.width(), g_FancyDockingConstants.absoluteDropZoneSizeInPixels);
|
|
QRect absoluteTopDropZone(mainWindowTopLeft, absoluteTopBottomSize);
|
|
QRect absoluteBottomDropZone(mainWindowBottomLeft - QPoint(0, g_FancyDockingConstants.absoluteDropZoneSizeInPixels), absoluteTopBottomSize);
|
|
|
|
// If the drop target is a main window, then we will only show the absolute
|
|
// drop zone if the cursor is in that zone already
|
|
if (dropTargetIsMainWindow)
|
|
{
|
|
QPoint localPos = multiscreenMapFromGlobal(globalPos);
|
|
|
|
if (absoluteLeftDropZone.contains(localPos))
|
|
{
|
|
absoluteDropZoneRect = absoluteLeftDropZone;
|
|
area = Qt::LeftDockWidgetArea;
|
|
}
|
|
else if (absoluteRightDropZone.contains(localPos))
|
|
{
|
|
absoluteDropZoneRect = absoluteRightDropZone;
|
|
area = Qt::RightDockWidgetArea;
|
|
}
|
|
else if (absoluteTopDropZone.contains(localPos))
|
|
{
|
|
absoluteDropZoneRect = absoluteTopDropZone;
|
|
area = Qt::TopDockWidgetArea;
|
|
}
|
|
else if (absoluteBottomDropZone.contains(localPos))
|
|
{
|
|
absoluteDropZoneRect = absoluteBottomDropZone;
|
|
area = Qt::BottomDockWidgetArea;
|
|
}
|
|
}
|
|
// Otherwise if the drop target is just a normal dock widget, then we will
|
|
// show the absolute drop zone once a normal drop zone sharing that edge
|
|
// is activated
|
|
else
|
|
{
|
|
const QRect& dockRect = dock->rect();
|
|
QPoint dockTopLeft = multiscreenMapFromGlobal(dock->mapToGlobal(dockRect.topLeft()));
|
|
QPoint dockBottomRight = multiscreenMapFromGlobal(dock->mapToGlobal(dockRect.bottomRight()));
|
|
area = m_dropZoneState.dropArea();
|
|
|
|
// If the hovered over drop zone shares a side with an absolute edge, then we need to setup
|
|
// an absolute drop zone for that area (if absolute drop zones are allowed for this target)
|
|
switch (m_dropZoneState.dropArea())
|
|
{
|
|
case Qt::LeftDockWidgetArea:
|
|
if (dockTopLeft.x() == mainWindowTopLeft.x())
|
|
{
|
|
absoluteDropZoneRect = absoluteLeftDropZone;
|
|
}
|
|
break;
|
|
case Qt::RightDockWidgetArea:
|
|
if (dockBottomRight.x() == mainWindowTopRight.x())
|
|
{
|
|
absoluteDropZoneRect = absoluteRightDropZone;
|
|
}
|
|
break;
|
|
case Qt::TopDockWidgetArea:
|
|
if (dockTopLeft.y() == mainWindowTopLeft.y())
|
|
{
|
|
absoluteDropZoneRect = absoluteTopDropZone;
|
|
}
|
|
break;
|
|
case Qt::BottomDockWidgetArea:
|
|
if (dockBottomRight.y() == mainWindowBottomLeft.y())
|
|
{
|
|
absoluteDropZoneRect = absoluteBottomDropZone;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return absoluteDropZoneRect;
|
|
}
|
|
|
|
/**
|
|
* Set m_dropZoneState.dropOnto and the m_dropZoneState.dropZones as to drop within the specified dock.
|
|
*/
|
|
void FancyDocking::setupDropZones(QWidget* dock, const QPoint& globalPos)
|
|
{
|
|
// If there is no dock widget, then reset our drop zones and return
|
|
if (!dock)
|
|
{
|
|
m_dropZoneState.setDropOnto(dock);
|
|
m_dropZoneState.setDropZones({});
|
|
m_dropZoneState.setDockDropZoneRect(QRect());
|
|
m_dropZoneState.setInnerDropZoneRect(QRect());
|
|
m_dropZoneState.setAbsoluteDropZoneArea(Qt::NoDockWidgetArea);
|
|
m_dropZoneState.setAbsoluteDropZoneRect(QRect());
|
|
return;
|
|
}
|
|
|
|
// If the drop widget is a QMainWindow, then we won't show the normal drop zones
|
|
QMainWindow* mainWindow = qobject_cast<QMainWindow*>(dock);
|
|
bool normalDropZonesAllowed = !mainWindow;
|
|
|
|
// Figure out if we need to recalculate the drop zones
|
|
QRect dockRect = dock->rect();
|
|
if (m_dropZoneState.dropOnto() == dock)
|
|
{
|
|
if (mainWindow)
|
|
{
|
|
// If the drop target is a main window, this means the mouse is
|
|
// hovered over a dead zone margin, the central widget (viewport),
|
|
// or the widget that is being dragged, so we will need to setup
|
|
// an absolute drop zone based on the mouse position
|
|
if (m_dropZoneState.onAbsoluteDropZone())
|
|
{
|
|
// If we're already hovered on the applicable absolute
|
|
// drop zone, then we don't need to re-calculate
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
Qt::DockWidgetArea area = Qt::NoDockWidgetArea;
|
|
m_dropZoneState.setAbsoluteDropZoneRect(getAbsoluteDropZone(dock, area, globalPos));
|
|
m_dropZoneState.setAbsoluteDropZoneArea(area);
|
|
}
|
|
}
|
|
else if (m_dropZoneState.dropArea() == Qt::NoDockWidgetArea || m_dropZoneState.dropArea() == Qt::AllDockWidgetAreas)
|
|
{
|
|
// If we're hovered over the dead zone or the center tab, then reset the absolute drop
|
|
// zone if there is one so we can recalculate the drop zones
|
|
if (m_dropZoneState.absoluteDropZoneRect().isValid())
|
|
{
|
|
m_dropZoneState.setAbsoluteDropZoneArea(Qt::NoDockWidgetArea);
|
|
m_dropZoneState.setAbsoluteDropZoneRect(QRect());
|
|
}
|
|
// Otherwise the drop zones don't need to be updated, so return
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If we're still hovered over the same area, no need to re-calculate the absolute drop zones
|
|
if (m_dropZoneState.absoluteDropZoneArea() == m_dropZoneState.dropArea())
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
// Try to setup an absolute drop zone based on the dock widget
|
|
Qt::DockWidgetArea area = Qt::NoDockWidgetArea;
|
|
QRect absoluteDropZoneRect = getAbsoluteDropZone(dock, area);
|
|
|
|
// If we setup an absolute drop zone, then cache it
|
|
if (absoluteDropZoneRect.isValid())
|
|
{
|
|
m_dropZoneState.setAbsoluteDropZoneRect(absoluteDropZoneRect);
|
|
m_dropZoneState.setAbsoluteDropZoneArea(area);
|
|
}
|
|
// If the current area doesn't need an absolute drop zone, and we didn't have an absolute drop zone previously,
|
|
// then we don't need to make any changes so return
|
|
else if (!m_dropZoneState.absoluteDropZoneRect().isValid())
|
|
{
|
|
return;
|
|
}
|
|
// Otherwise clear out our cached absolute drop zone so we can reset everything
|
|
else
|
|
{
|
|
m_dropZoneState.setAbsoluteDropZoneArea(Qt::NoDockWidgetArea);
|
|
m_dropZoneState.setAbsoluteDropZoneRect(QRect());
|
|
}
|
|
}
|
|
}
|
|
// We switched drop widgets; clear out the absolute drop zone data
|
|
else
|
|
{
|
|
m_dropZoneState.setAbsoluteDropZoneArea(Qt::NoDockWidgetArea);
|
|
m_dropZoneState.setAbsoluteDropZoneRect(QRect());
|
|
}
|
|
|
|
// We need to recalculate the drop zones, so clear them and proceed
|
|
m_dropZoneState.setDropOnto(dock);
|
|
m_dropZoneState.setDropZones({});
|
|
m_dropZoneState.setInnerDropZoneRect(QRect());
|
|
StartDropZone(m_dropZoneState.dropOnto(), globalPos);
|
|
|
|
// Don't setup the normal drop zones if our drop target is a QMainWindow
|
|
if (!normalDropZonesAllowed)
|
|
{
|
|
raiseDockWidgets();
|
|
return;
|
|
}
|
|
|
|
// If there is a valid absolute drop zone, adjust our outer dock widget rectangle accordingly to make room for it
|
|
switch (m_dropZoneState.absoluteDropZoneArea())
|
|
{
|
|
case Qt::LeftDockWidgetArea:
|
|
dockRect.setX(dockRect.x() + g_FancyDockingConstants.absoluteDropZoneSizeInPixels);
|
|
break;
|
|
case Qt::RightDockWidgetArea:
|
|
dockRect.setWidth(dockRect.width() - g_FancyDockingConstants.absoluteDropZoneSizeInPixels);
|
|
break;
|
|
case Qt::TopDockWidgetArea:
|
|
dockRect.setY(dockRect.y() + g_FancyDockingConstants.absoluteDropZoneSizeInPixels);
|
|
break;
|
|
case Qt::BottomDockWidgetArea:
|
|
dockRect.setHeight(dockRect.height() - g_FancyDockingConstants.absoluteDropZoneSizeInPixels);
|
|
break;
|
|
}
|
|
|
|
// Store our potentially adjusted outer dock widget rectangle and retrieve its corner points for later calculations
|
|
m_dropZoneState.setDockDropZoneRect(dockRect);
|
|
const QPoint topLeft = multiscreenMapFromGlobal(dock->mapToGlobal(dockRect.topLeft()));
|
|
const QPoint topRight = multiscreenMapFromGlobal(dock->mapToGlobal(dockRect.topRight()));
|
|
const QPoint bottomLeft = multiscreenMapFromGlobal(dock->mapToGlobal(dockRect.bottomLeft()));
|
|
const QPoint bottomRight = multiscreenMapFromGlobal(dock->mapToGlobal(dockRect.bottomRight()));
|
|
|
|
/*
|
|
The normal drop zones for left/right/top/bottom of a dock widget are trapezoids with the longer
|
|
side on the edges of the widget, and the shorter side towards the middle of the widget. Here
|
|
is a rough depiction:
|
|
_______________________
|
|
|\ /|
|
|
| \ / |
|
|
| \_________________/ |
|
|
| | | |
|
|
| | | |
|
|
| | | |
|
|
| |_______________| |
|
|
| / \ |
|
|
| / \ |
|
|
|/_____________________\|
|
|
The drop zones are constructed using polygons with the appropriate points from the dock widget
|
|
and the calculated inner points.
|
|
*/
|
|
int dockWidth = dockRect.width();
|
|
int dockHeight = dockRect.height();
|
|
int topLeftX = topLeft.x();
|
|
int topLeftY = topLeft.y();
|
|
int topRightX = topRight.x();
|
|
int bottomLeftY = bottomLeft.y();
|
|
|
|
// Set the drop zone width/height to the default, but if the dock widget
|
|
// width and/or height is below the threshold, then switch to scaling them
|
|
// down accordingly
|
|
int dropZoneWidth = g_FancyDockingConstants.dropZoneSizeInPixels;
|
|
if (dockWidth < g_FancyDockingConstants.minDockSizeBeforeDropZoneScalingInPixels)
|
|
{
|
|
dropZoneWidth = aznumeric_cast<int>(dockWidth * g_FancyDockingConstants.dropZoneScaleFactor);
|
|
}
|
|
int dropZoneHeight = g_FancyDockingConstants.dropZoneSizeInPixels;
|
|
if (dockHeight < g_FancyDockingConstants.minDockSizeBeforeDropZoneScalingInPixels)
|
|
{
|
|
dropZoneHeight = aznumeric_cast<int>(dockHeight * g_FancyDockingConstants.dropZoneScaleFactor);
|
|
}
|
|
|
|
// Calculate the inner corners to be used when constructing the drop zone polygons
|
|
QPoint innerTopLeft(topLeftX + dropZoneWidth, topLeftY + dropZoneHeight);
|
|
QPoint innerTopRight(topRightX - dropZoneWidth, topLeftY + dropZoneHeight);
|
|
QPoint innerBottomLeft(topLeftX + dropZoneWidth, bottomLeftY - dropZoneHeight);
|
|
QPoint innerBottomRight(topRightX - dropZoneWidth, bottomLeftY - dropZoneHeight);
|
|
m_dropZoneState.setInnerDropZoneRect(QRect(innerTopLeft, innerBottomRight));
|
|
|
|
auto dropZones = m_dropZoneState.dropZones();
|
|
|
|
// Only setup the left/right/top/bottom drop zones if our main window doesn't
|
|
// have the force tabbed docks only flag set.
|
|
if (!ForceTabbedDocksEnabled())
|
|
{
|
|
// Setup the left/right/top/bottom drop zones using our calculated points
|
|
QPolygon leftDropZone, rightDropZone, topDropZone, bottomDropZone;
|
|
leftDropZone << topLeft << innerTopLeft << innerBottomLeft << bottomLeft;
|
|
rightDropZone << topRight << bottomRight << innerBottomRight << innerTopRight;
|
|
topDropZone << topLeft << topRight << innerTopRight << innerTopLeft;
|
|
bottomDropZone << bottomLeft << innerBottomLeft << innerBottomRight << bottomRight;
|
|
|
|
dropZones[Qt::LeftDockWidgetArea] = leftDropZone;
|
|
dropZones[Qt::RightDockWidgetArea] = rightDropZone;
|
|
dropZones[Qt::TopDockWidgetArea] = topDropZone;
|
|
dropZones[Qt::BottomDockWidgetArea] = bottomDropZone;
|
|
}
|
|
|
|
// Add the center drop zone for docking as a tab. The drop zone will be
|
|
// stored as a polygon, although it will actually be drawn/evaluated
|
|
// as a circle. The center drop zone size will be whichever is smaller
|
|
// between the inner drop zone width vs height, and scaled accordingly
|
|
int innerDropZoneWidth = m_dropZoneState.innerDropZoneRect().width();
|
|
int innerDropZoneHeight = m_dropZoneState.innerDropZoneRect().height();
|
|
int centerDropZoneDiameter = (innerDropZoneWidth < innerDropZoneHeight) ? innerDropZoneWidth : innerDropZoneHeight;
|
|
centerDropZoneDiameter = aznumeric_cast<int>(centerDropZoneDiameter * g_FancyDockingConstants.centerTabDropZoneScale);
|
|
|
|
// Setup our center tab drop zone
|
|
const QSize centerDropZoneSize(centerDropZoneDiameter, centerDropZoneDiameter);
|
|
const QRect centerDropZoneRect(m_dropZoneState.innerDropZoneRect().center() - QPoint(centerDropZoneDiameter, centerDropZoneDiameter) / 2, centerDropZoneSize);
|
|
dropZones[Qt::AllDockWidgetAreas] = QPolygon(centerDropZoneRect, true); // AllDockWidgetAreas means we want tab
|
|
|
|
m_dropZoneState.setDropZones(dropZones);
|
|
|
|
// Make sure the drop zones don't overlap with floating dock windows in the foreground
|
|
raiseDockWidgets();
|
|
}
|
|
|
|
/**
|
|
* Raise the appropriate dock widgets given the current widget to be dropped on
|
|
* so that the drop zones don't overlap with floating dock windows in the foreground
|
|
*/
|
|
void FancyDocking::raiseDockWidgets()
|
|
{
|
|
QWidget* dropOnto = m_dropZoneState.dropOnto();
|
|
if (!dropOnto)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// If our drop target isn't a main window, then retrieve the main window
|
|
// from the dock widget parent
|
|
QMainWindow* mainWindow = qobject_cast<QMainWindow*>(dropOnto);
|
|
if (!mainWindow)
|
|
{
|
|
mainWindow = qobject_cast<QMainWindow*>(dropOnto->parentWidget());
|
|
}
|
|
|
|
if (mainWindow && mainWindow != m_mainWindow)
|
|
{
|
|
// If our dock widget is part of a floating main window, then we need
|
|
// to retrieve its container dock widget to raise that to the
|
|
// foreground and then raise our docking overlay on top
|
|
QDockWidget* containerDockWidget = qobject_cast<QDockWidget*>(mainWindow->parentWidget());
|
|
if (containerDockWidget)
|
|
{
|
|
containerDockWidget->raise();
|
|
}
|
|
}
|
|
|
|
if (m_activeDropZoneWidgets.size())
|
|
{
|
|
// the floating dropzone indicators clip against everything above them
|
|
// so they should always be on top of everything else
|
|
for (FancyDockingDropZoneWidget* dropZoneWidget : m_activeDropZoneWidgets)
|
|
{
|
|
dropZoneWidget->raise();
|
|
}
|
|
}
|
|
|
|
// floating pixmap is always on top; it'll clip what it's supposed to
|
|
m_ghostWidget->raise();
|
|
}
|
|
|
|
/*!
|
|
Return on which dockArea should we drop something depending on the global position of the cursor
|
|
*/
|
|
Qt::DockWidgetArea FancyDocking::dockAreaForPos(const QPoint& globalPos)
|
|
{
|
|
m_dropZoneState.setOnAbsoluteDropZone(false);
|
|
if (!m_dropZoneState.dropOnto())
|
|
{
|
|
return Qt::NoDockWidgetArea;
|
|
}
|
|
const QPoint& pos = multiscreenMapFromGlobal(globalPos);
|
|
|
|
// First, check if we are hovered over an absolute drop zone
|
|
if (m_dropZoneState.absoluteDropZoneRect().isValid() && m_dropZoneState.absoluteDropZoneRect().contains(pos))
|
|
{
|
|
m_dropZoneState.setOnAbsoluteDropZone(true);
|
|
return m_dropZoneState.absoluteDropZoneArea();
|
|
}
|
|
|
|
// Then, check all of the default drop zones
|
|
auto dropZones = m_dropZoneState.dropZones();
|
|
for (auto it = dropZones.cbegin(); it != dropZones.cend(); ++it)
|
|
{
|
|
const Qt::DockWidgetArea area = it.key();
|
|
const QPolygon& dropZoneShape = it.value();
|
|
|
|
// For the center tab drop zone, we need to translate the shape into a circle before we
|
|
// check if the mouse position is inside the shape.
|
|
if (area == Qt::AllDockWidgetAreas)
|
|
{
|
|
QRegion circleRegion(dropZoneShape.boundingRect(), QRegion::Ellipse);
|
|
if (circleRegion.contains(pos))
|
|
{
|
|
return area;
|
|
}
|
|
}
|
|
// For the left/right/top/bottom drop zones we can use the default polygon check if the mouse
|
|
// position is inside the shape
|
|
else
|
|
{
|
|
if (dropZoneShape.containsPoint(pos, Qt::OddEvenFill))
|
|
{
|
|
return area;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Qt::NoDockWidgetArea;
|
|
}
|
|
|
|
/**
|
|
* For a given widget, determine if it is a valid drop target and return the
|
|
* valid drop target if applicable. If the drop target is excluded (e.g. we
|
|
* are dragging this widget), then its parent main window will be returned.
|
|
*/
|
|
QWidget* FancyDocking::dropTargetForWidget(QWidget* widget, const QPoint& globalPos, QWidget* exclude) const
|
|
{
|
|
auto isExcluded = [&](const QWidget* x)
|
|
{
|
|
for (auto i = x; i; i = i->parentWidget())
|
|
{
|
|
if (i == exclude)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
if (!widget || widget->isHidden())
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
if (isExcluded(widget))
|
|
{
|
|
// If the mouse is over our excluded widget, then return its parent
|
|
// instead so we can still evaluate for absolute drop zones
|
|
if (WidgetContainsPoint(widget, globalPos))
|
|
{
|
|
return qobject_cast<QMainWindow*>(widget->parentWidget());
|
|
}
|
|
else
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
if (WidgetContainsPoint(widget, globalPos))
|
|
{
|
|
return widget;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* Given a position in global coordinates, returns a QDockWidget, or a QMainWindow onto which
|
|
* one can drop a widget. This exclude the 'exclude' widget and all it's children.
|
|
*/
|
|
QWidget* FancyDocking::dropWidgetUnderMouse(const QPoint& globalPos, QWidget* exclude) const
|
|
{
|
|
// After this logic block, this will hold a valid QMainWindow reference if
|
|
// our current drop target is on a floating main window
|
|
QMainWindow* dropOntoFloatingMainWindow = nullptr;
|
|
QWidget* dropOnto = m_dropZoneState.dropOnto();
|
|
if (qobject_cast<QDockWidget*>(dropOnto))
|
|
{
|
|
// If our drop target is a dock widget, then check if its parent is
|
|
// a floating main window
|
|
QMainWindow* mainWindow = qobject_cast<QMainWindow*>(dropOnto->parentWidget());
|
|
if (mainWindow != m_mainWindow)
|
|
{
|
|
// If we're still hovered over the same dock widget, this shortcuts
|
|
// all the logic below
|
|
if (WidgetContainsPoint(dropOnto, globalPos))
|
|
{
|
|
return dropOnto;
|
|
}
|
|
// Otherwise, our mouse still be hovered over the same floating
|
|
// window, so we need to give it precedence over other floating
|
|
// main windows and the main editor window
|
|
else
|
|
{
|
|
dropOntoFloatingMainWindow = mainWindow;
|
|
}
|
|
}
|
|
}
|
|
else if (dropOnto && dropOnto != m_mainWindow)
|
|
{
|
|
// If we have a valid drop target and it wasn't a dock widget, then
|
|
// it's a QMainWindow so we need to flag it if it's floating
|
|
dropOntoFloatingMainWindow = qobject_cast<QMainWindow*>(dropOnto);
|
|
}
|
|
|
|
// Create a list of our floating drop targets separate from the dock widgets
|
|
// on our main editor window so we can give precedence to the floating targets
|
|
// We iterate through our floating drop targets by our z-ordered list of
|
|
// floating dock widgets that we maintain ourselves since we can't retrieve
|
|
// a z-ordered list from Qt, and we need to guarantee that dock widgets
|
|
// in the front have precedence over widgets that are lower
|
|
QList<QWidget*> floatingDropTargets;
|
|
for (QString name : m_orderedFloatingDockWidgetNames)
|
|
{
|
|
QDockWidget* dockWidget = m_mainWindow->findChild<QDockWidget*>(name, Qt::FindDirectChildrenOnly);
|
|
if (!dockWidget)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Make sure this is a floating dock widget container
|
|
// We need to add its dock widget children and floating main window as
|
|
// drop targets
|
|
if (!dockWidget->isFloating())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Ignore this floating container if it is hidden, which means it
|
|
// is a single pane floating window that is the one being dragged
|
|
// so it is currently hidden
|
|
if (dockWidget->isHidden())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Ignore this floating container it the window is minimized
|
|
if (dockWidget->isMinimized())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
QMainWindow* mainWindow = qobject_cast<QMainWindow*>(dockWidget->widget());
|
|
if (!mainWindow)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// If our current drop target lives in this floating main window,
|
|
// then we need to add it to the front of the list so that it will
|
|
// get precedence over other floating windows, but we need to do this
|
|
// first so that the dock widgets of this main window will be prepended
|
|
// in front of it
|
|
if (mainWindow == dropOntoFloatingMainWindow)
|
|
{
|
|
floatingDropTargets.prepend(mainWindow);
|
|
}
|
|
|
|
// Add all of the child dock widgets in this floating main window
|
|
// to our list of floating drop targets
|
|
bool shouldAddFloatingMainWindow = true;
|
|
for (QDockWidget* floatingDockWidget : mainWindow->findChildren<QDockWidget*>(QString(), Qt::FindDirectChildrenOnly))
|
|
{
|
|
// Don't allow dock widgets that have no allowed areas to be
|
|
// drop targets, and also prevent this floating main window
|
|
// from being added as a drop target as well if it contains
|
|
// a dock widget that has docking disabled
|
|
if (floatingDockWidget->allowedAreas() == Qt::NoDockWidgetArea)
|
|
{
|
|
shouldAddFloatingMainWindow = false;
|
|
continue;
|
|
}
|
|
|
|
// If our current drop target lives in this floating main window,
|
|
// then put these dock widgets on the front of our list so they
|
|
// get precedence over other floating drop targets
|
|
if (mainWindow == dropOntoFloatingMainWindow)
|
|
{
|
|
floatingDropTargets.prepend(floatingDockWidget);
|
|
}
|
|
// Otherwise just add them to the list of other floating drop targets
|
|
else
|
|
{
|
|
floatingDropTargets.append(floatingDockWidget);
|
|
}
|
|
}
|
|
|
|
// If our current drop target does not live in this floating main
|
|
// window, then store this floating main window in our list of
|
|
// floating drop targets after its dock widgets so that they will
|
|
// be found first
|
|
if (shouldAddFloatingMainWindow && mainWindow != dropOntoFloatingMainWindow)
|
|
{
|
|
floatingDropTargets.append(mainWindow);
|
|
}
|
|
}
|
|
|
|
// Then, find the normal dock widgets on the main editor window and add
|
|
// them to the end of list so the floating widgets have priority
|
|
QList<QDockWidget*> mainWindowDockWidgets;
|
|
if (!m_mainWindow->window()->isMinimized())
|
|
{
|
|
for (QDockWidget* dockWidget : m_mainWindow->findChildren<QDockWidget*>(QString(), Qt::FindDirectChildrenOnly))
|
|
{
|
|
if (!dockWidget->isFloating())
|
|
{
|
|
mainWindowDockWidgets.append(dockWidget);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Next, check all of the floating drop targets. This includes the floating
|
|
// dock widgets, and the floating main windows themselves so we catch the
|
|
// absolute drop zones when hovered over the dead zone margins or the excluded
|
|
// target (widget being dragged).
|
|
for (QWidget* widget : floatingDropTargets)
|
|
{
|
|
QWidget* dropTarget = dropTargetForWidget(widget, globalPos, exclude);
|
|
if (dropTarget)
|
|
{
|
|
return dropTarget;
|
|
}
|
|
}
|
|
|
|
// Then, check all the dock widgets on the main window
|
|
for (QDockWidget* dockWidget : mainWindowDockWidgets)
|
|
{
|
|
QWidget* dropTarget = dropTargetForWidget(dockWidget, globalPos, exclude);
|
|
if (dropTarget)
|
|
{
|
|
return dropTarget;
|
|
}
|
|
}
|
|
|
|
// Fallback to check if the mouse is inside our main window, which will cover
|
|
// both the central widget (viewport) and the dead zone margins between
|
|
// dock widgets on the main window
|
|
if (m_mainWindow->rect().contains(m_mainWindow->mapFromGlobal(globalPos)) && !m_mainWindow->window()->isMinimized())
|
|
{
|
|
return m_mainWindow;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* Handle a mouse move event.
|
|
*/
|
|
bool FancyDocking::dockMouseMoveEvent(QDockWidget* dock, QMouseEvent* event)
|
|
{
|
|
if (!m_state.dock)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// If we are dragging a floating dock widget, then we need to use the
|
|
// actual dock widget child as our reference
|
|
if (m_state.floatingDockContainer && m_state.floatingDockContainer == dock)
|
|
{
|
|
dock = m_state.dock;
|
|
}
|
|
|
|
if (m_state.dock != dock)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// use QCursor::pos(); in scenarios with multiple screens and different scale factors,
|
|
// it's much more reliable about actually reporting a global position than
|
|
// using event->globalPos();
|
|
QPoint globalPos = QCursor::pos();
|
|
|
|
if (!m_dropZoneState.dragging())
|
|
{
|
|
// Check if we should start dragging if the user has pressed and dragged
|
|
// the mouse beyond the drag distance threshold, taking into account the
|
|
// title bar height if we are dragging by the floating title bar
|
|
QPoint dragDifference = globalPos - dock->mapToGlobal(m_state.pressPos);
|
|
if (m_state.floatingDockContainer)
|
|
{
|
|
dragDifference.ry() += dock->titleBarWidget()->height();
|
|
}
|
|
bool shouldStartDrag = dragDifference.manhattanLength() > QApplication::startDragDistance();
|
|
|
|
// Only initiate the tab re-ordering logic for tab widgets that have
|
|
// multiple tabs
|
|
if (m_state.tabWidget && m_state.tabWidget->count() > 1)
|
|
{
|
|
// If we are dragging a tab, we shouldn't rip the tab out until the
|
|
// mouse leaves the tab header area
|
|
QTabBar* tabBar = m_state.tabWidget->tabBar();
|
|
shouldStartDrag = !tabBar->rect().contains(tabBar->mapFromGlobal(globalPos));
|
|
|
|
// If the tab has been ripped out, we need to reset the tab widget's
|
|
// internal drag state
|
|
if (shouldStartDrag)
|
|
{
|
|
m_state.tabWidget->finishDrag();
|
|
}
|
|
// Otherwise, the mouse is still being dragged inside the tab header
|
|
// area, so pass the mouse event along to the tab widget so it can
|
|
// use it for internally dragging the tabs to re-order them, and
|
|
// bail out since the tab widget will handle this mouse event
|
|
else
|
|
{
|
|
// Construct a new QMouseEvent with a local mouse position that is correct for
|
|
// the tab widget. We can't just pass the event being filtered because the mouse
|
|
// positions are relative to the widget being watched.
|
|
const auto tabPos = m_state.tabWidget->mapFromGlobal(event->globalPos());
|
|
QMouseEvent tabEvent(event->type(), tabPos, event->button(), event->buttons(), event->modifiers());
|
|
m_state.tabWidget->mouseMoveEvent(&tabEvent);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// If we shouldn't start the drag, then bail out, otherwise we will
|
|
// rip out the dock widget and start the dragging process
|
|
if (!shouldStartDrag)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
m_ghostWidget->show();
|
|
|
|
// We need to explicitly grab the mouse/keyboard on our main window when
|
|
// we start dragging a dock widget so that only our custom docking logic
|
|
// will be executed, instead of qt's default docking. This also allows
|
|
// us to hide the dock widget if it's floating and still receive the events
|
|
// since otherwise they would be lost if the widget was hidden.
|
|
m_mainWindow->grabMouse();
|
|
m_mainWindow->grabKeyboard();
|
|
|
|
// If we're dragging a dock widget that is the only widget in a floating
|
|
// window, let's hide the floating window so it doesn't get in the way.
|
|
// If the dock widget is a tab container, then we will only hide it if
|
|
// it only has one tab.
|
|
QDockWidget* singleFloatingDockWidget = nullptr;
|
|
QMainWindow* mainWindow = qobject_cast<QMainWindow*>(dock->parentWidget());
|
|
|
|
if (mainWindow && mainWindow != m_mainWindow)
|
|
{
|
|
QDockWidget* containerDockWidget = qobject_cast<QDockWidget*>(mainWindow->parentWidget());
|
|
if (containerDockWidget && containerDockWidget->isFloating())
|
|
{
|
|
int numVisibleDockWidgets = 0;
|
|
for (QDockWidget* dockWidget : mainWindow->findChildren<QDockWidget*>(QString(), Qt::FindDirectChildrenOnly))
|
|
{
|
|
if (dockWidget->isVisible())
|
|
{
|
|
// If we're only dragging one tab out of a tabWidget, count all tabs separately
|
|
// floatingDockContainer == dock means we're dragging the whole tabWidget, so we're leaving nothing behind.
|
|
if (dockWidget == dock && m_state.tabWidget && m_state.floatingDockContainer != dock)
|
|
{
|
|
numVisibleDockWidgets += m_state.tabWidget->count();
|
|
}
|
|
// Otherwise just count the single dock widget
|
|
else
|
|
{
|
|
++numVisibleDockWidgets;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (numVisibleDockWidgets == 1)
|
|
{
|
|
singleFloatingDockWidget = containerDockWidget;
|
|
}
|
|
}
|
|
}
|
|
if (singleFloatingDockWidget)
|
|
{
|
|
// Restore window if maximized when dragging
|
|
if (singleFloatingDockWidget->isMaximized())
|
|
{
|
|
singleFloatingDockWidget->showNormal();
|
|
}
|
|
|
|
singleFloatingDockWidget->hide();
|
|
}
|
|
// Otherwise, we need to hide the original widget while we are dragging
|
|
// around the placeholder. Actual hiding it would minimize the dock
|
|
// window, so instead we need to replace it with an empty QWidget,
|
|
// and save the original content widget so we can restore it later
|
|
else if (m_state.draggedDockWidget)
|
|
{
|
|
m_state.draggedWidget = m_state.draggedDockWidget->widget();
|
|
m_state.draggedDockWidget->setWidget(m_emptyWidget);
|
|
m_emptyWidget->show();
|
|
}
|
|
|
|
m_dropZoneState.setDragging(true);
|
|
}
|
|
|
|
if (m_dropZoneState.dragging())
|
|
{
|
|
// Don't show dropzones if the window is not dockable
|
|
if (dock->allowedAreas() != Qt::NoDockWidgetArea)
|
|
{
|
|
// Setup the drop zones if there is a valid drop target under the mouse
|
|
QWidget* underMouse = dropWidgetUnderMouse(globalPos, dock);
|
|
setupDropZones(underMouse, globalPos);
|
|
|
|
// Store the previous flag for whether or not the cursor is currently
|
|
// over an absolute drop zone so we can compare it later
|
|
bool previousOnAbsoluteDropZone = m_dropZoneState.onAbsoluteDropZone();
|
|
|
|
// Check if the mouse is hovered over one of our drop zones
|
|
Qt::DockWidgetArea area = dockAreaForPos(globalPos);
|
|
|
|
// If we've hovered over a new drop zone, start our timer to fade in
|
|
// the opacity of the drop zone, which also makes it inactive until
|
|
// the max opacity has been reached
|
|
if (area != Qt::NoDockWidgetArea && (area != m_dropZoneState.dropArea() || previousOnAbsoluteDropZone != m_dropZoneState.onAbsoluteDropZone()))
|
|
{
|
|
m_dropZoneState.setDropZoneHoverOpacity(0);
|
|
m_dropZoneHoverFadeInTimer->start();
|
|
}
|
|
|
|
SetFloatingPixmapClipping(m_dropZoneState.dropOnto(), area);
|
|
|
|
// Save the drop zone area in our drag state
|
|
m_dropZoneState.setDropArea(area);
|
|
}
|
|
else
|
|
{
|
|
m_dropZoneState.setDropArea(Qt::NoDockWidgetArea);
|
|
}
|
|
|
|
// Calculate the placeholder rectangle based on the drag position
|
|
QRect dockGeometry = dock->geometry();
|
|
QRect placeholder(dockGeometry
|
|
.translated(globalPos - dock->mapToGlobal(m_state.pressPos))
|
|
.translated(dock->isWindow() ? QPoint() : dock->parentWidget()->mapToGlobal(QPoint())));
|
|
|
|
// If we restored the last floating screen grab for this dock widget,
|
|
// then we need to change the placeholder size and update the X coordinate
|
|
// to account for the extrapolated mouse press position
|
|
if (m_state.draggedDockWidget && m_lastFloatingScreenGrab.contains(m_state.draggedDockWidget->objectName()))
|
|
{
|
|
QSize lastFloatingSize = m_state.dockWidgetScreenGrab.size;
|
|
int pressPosX = m_state.pressPos.x();
|
|
int relativeX = (int)(((float)pressPosX / (float)dockGeometry.width()) * ((float)lastFloatingSize.width()));
|
|
|
|
placeholder.setSize(lastFloatingSize);
|
|
placeholder.translate(pressPosX - relativeX, 0);
|
|
}
|
|
|
|
// Handle snapping to the screen edges/other floating windows while dragging
|
|
QScreen* screen = QApplication::screenAt(globalPos);
|
|
|
|
if (screen)
|
|
{
|
|
AdjustForSnapping(placeholder, screen);
|
|
m_state.setPlaceholder(placeholder, screen);
|
|
|
|
m_ghostWidget->Enable();
|
|
RepaintFloatingIndicators();
|
|
}
|
|
}
|
|
|
|
return m_dropZoneState.dragging();
|
|
}
|
|
|
|
void FancyDocking::AdjustForSnapping(QRect& rect, QScreen* cursorScreen)
|
|
{
|
|
m_state.snappedSide = 0;
|
|
|
|
// Prevent snapping if any drop zones are present, or if the modifier key
|
|
// is pressed, since that also disables docking
|
|
bool modifiedKeyPressed = FancyDockingDropZoneWidget::CheckModifierKey();
|
|
if (m_dropZoneState.hasDropZones() || m_dropZoneState.absoluteDropZoneRect().isValid() || modifiedKeyPressed)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// First, check if we can snap to any of the floating panes
|
|
for (QDockWidget* dockWidget : m_mainWindow->findChildren<QDockWidget*>(QString(), Qt::FindDirectChildrenOnly))
|
|
{
|
|
// Only look at floating windows that aren't hidden
|
|
QWindow* dockWidgetWindow = dockWidget->windowHandle();
|
|
if (dockWidget->isHidden() || !dockWidget->isFloating() || !dockWidgetWindow)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
QRect floatingRect = isWin10() ? dockWidgetWindow->geometry() : dockWidgetWindow->frameGeometry();
|
|
AdjustForSnappingToFloatingWindow(rect, floatingRect);
|
|
}
|
|
|
|
// Don't continue on if we snapped to a floating pane
|
|
if (m_state.snappedSide != 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Next, check if we can snap to the screen edges that the cursor is currently on
|
|
if (AdjustForSnappingToScreenEdges(rect, cursorScreen))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Then, check the rest of the screens
|
|
for (QScreen* screen : QApplication::screens())
|
|
{
|
|
// We already checked this one explicitly first, so move on
|
|
if (screen == cursorScreen)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (AdjustForSnappingToScreenEdges(rect, screen))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Lastly, check if we can snap to the main editor window
|
|
QWindow* mainWindow = m_mainWindow->window()->windowHandle();
|
|
if (mainWindow)
|
|
{
|
|
QRect mainWindowRect = isWin10() ? mainWindow->geometry() : mainWindow->frameGeometry();
|
|
AdjustForSnappingToFloatingWindow(rect, mainWindowRect);
|
|
}
|
|
}
|
|
|
|
bool FancyDocking::AdjustForSnappingToScreenEdges(QRect& rect, QScreen* cursorScreen)
|
|
{
|
|
QRect screenRect = cursorScreen->geometry();
|
|
if (screenRect.isNull())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Qt returns right/bottom with a -1 offset because of historical reasons,
|
|
// so we need to use x + width instead (but not on Win10)
|
|
int screenRectRight = screenRect.x() + screenRect.width();
|
|
int screenRectBottom = screenRect.y() + screenRect.height();
|
|
if (isWin10())
|
|
{
|
|
screenRectRight -= 1;
|
|
screenRectBottom -= 1;
|
|
}
|
|
|
|
// First, check snapping to left/right edges of the screen
|
|
if (qAbs(rect.left() - screenRect.left()) <= g_snapThresholdInPixels)
|
|
{
|
|
rect.moveLeft(screenRect.left());
|
|
m_state.snappedSide |= SnapLeft;
|
|
}
|
|
else if (qAbs(rect.right() - screenRectRight) <= g_snapThresholdInPixels)
|
|
{
|
|
rect.moveRight(screenRectRight);
|
|
m_state.snappedSide |= SnapRight;
|
|
}
|
|
|
|
// Then, check shapping to the top/bottom edges of the screen
|
|
if (qAbs(rect.top() - screenRect.top()) <= g_snapThresholdInPixels)
|
|
{
|
|
rect.moveTop(screenRect.top());
|
|
m_state.snappedSide |= SnapTop;
|
|
}
|
|
else if (qAbs(rect.bottom() - screenRectBottom) <= g_snapThresholdInPixels)
|
|
{
|
|
rect.moveBottom(screenRectBottom);
|
|
m_state.snappedSide |= SnapBottom;
|
|
}
|
|
|
|
// Return true if we snapped to a screen edge
|
|
return m_state.snappedSide != 0;
|
|
}
|
|
|
|
bool FancyDocking::AdjustForSnappingToFloatingWindow(QRect& rect, const QRect& floatingRect)
|
|
{
|
|
QRect currentPlaceholderRect = m_state.placeholder();
|
|
int rectLeft = rect.left();
|
|
int rectRight = rect.x() + rect.width();
|
|
int rectTop = rect.top();
|
|
int rectBottom = rect.y() + rect.height();
|
|
int floatingRectLeft = floatingRect.left();
|
|
int floatingRectRight = floatingRect.x() + floatingRect.width();
|
|
int floatingRectTop = floatingRect.top();
|
|
int floatingRectBottom = floatingRect.y() + floatingRect.height();
|
|
|
|
// Qt returns right/bottom with a -1 offset because of historical reasons,
|
|
// so we need to use x + width instead (but not on Win10)
|
|
if (isWin10())
|
|
{
|
|
rectRight -= 1;
|
|
rectBottom -= 1;
|
|
floatingRectRight -= 1;
|
|
floatingRectBottom -= 1;
|
|
}
|
|
|
|
// First, check snapping to the left/right edges of the floating window
|
|
// Ensure that we only snap if the placeholder has been dragged within the top/bottom
|
|
// range of the floating window, or if we've already snapped to the top/bottom in the
|
|
// case of snapping left -> left or right -> right
|
|
QRect topBottomRect(floatingRectLeft, rectTop, floatingRect.width(), rect.height());
|
|
bool topOrBottomIntersected = topBottomRect.intersects(floatingRect);
|
|
bool snappedToTopOrBottom = currentPlaceholderRect.top() == floatingRectBottom || currentPlaceholderRect.bottom() == floatingRectTop;
|
|
if ((qAbs(rectLeft - floatingRectLeft) <= g_snapThresholdInPixels) && snappedToTopOrBottom)
|
|
{
|
|
rect.moveLeft(floatingRectLeft);
|
|
m_state.snappedSide |= SnapLeft;
|
|
}
|
|
else if ((qAbs(rectLeft - floatingRectRight) <= g_snapThresholdInPixels) && topOrBottomIntersected)
|
|
{
|
|
rect.moveLeft(floatingRectRight);
|
|
m_state.snappedSide |= SnapLeft;
|
|
}
|
|
else if ((qAbs(rectRight - floatingRectLeft) <= g_snapThresholdInPixels) && topOrBottomIntersected)
|
|
{
|
|
rect.moveRight(floatingRectLeft);
|
|
m_state.snappedSide |= SnapRight;
|
|
}
|
|
else if ((qAbs(rectRight - floatingRectRight) <= g_snapThresholdInPixels) && snappedToTopOrBottom)
|
|
{
|
|
rect.moveRight(floatingRectRight);
|
|
m_state.snappedSide |= SnapRight;
|
|
}
|
|
|
|
// Then, check snapping to the top/bottom edges of the floating window
|
|
// Ensure that we only snap if the placeholder has been dragged within the left/right
|
|
// range of the floating window, or if we've already snapped to the left/right in the
|
|
// case of snapping top -> top or bottom -> bottom
|
|
QRect leftRightRect(rectLeft, floatingRectTop, rect.width(), floatingRect.height());
|
|
bool leftOrRightIntersected = leftRightRect.intersects(floatingRect);
|
|
bool snappedToLeftOrRight = currentPlaceholderRect.left() == floatingRectRight || currentPlaceholderRect.right() == floatingRectLeft;
|
|
if ((qAbs(rectTop - floatingRectTop) <= g_snapThresholdInPixels) && snappedToLeftOrRight)
|
|
{
|
|
rect.moveTop(floatingRectTop);
|
|
m_state.snappedSide |= SnapTop;
|
|
}
|
|
else if ((qAbs(rectTop - floatingRectBottom) <= g_snapThresholdInPixels) && leftOrRightIntersected)
|
|
{
|
|
rect.moveTop(floatingRectBottom);
|
|
m_state.snappedSide |= SnapTop;
|
|
}
|
|
else if ((qAbs(rectBottom - floatingRectTop) <= g_snapThresholdInPixels) && leftOrRightIntersected)
|
|
{
|
|
rect.moveBottom(floatingRectTop);
|
|
m_state.snappedSide |= SnapBottom;
|
|
}
|
|
else if ((qAbs(rectBottom - floatingRectBottom) <= g_snapThresholdInPixels) && snappedToLeftOrRight)
|
|
{
|
|
rect.moveBottom(floatingRectBottom);
|
|
m_state.snappedSide |= SnapBottom;
|
|
}
|
|
|
|
// Return true if we snapped to a floating window
|
|
return m_state.snappedSide != 0;
|
|
}
|
|
|
|
void FancyDocking::RepaintFloatingIndicators()
|
|
{
|
|
updateFloatingPixmap();
|
|
}
|
|
|
|
void FancyDocking::SetFloatingPixmapClipping(QWidget* dropOnto, Qt::DockWidgetArea area)
|
|
{
|
|
// If our drop target isn't a main window, then retrieve the main window
|
|
// from the dock widget parent
|
|
QMainWindow* mainWindow = qobject_cast<QMainWindow*>(m_dropZoneState.dropOnto());
|
|
if (!mainWindow && m_dropZoneState.dropOnto())
|
|
{
|
|
mainWindow = qobject_cast<QMainWindow*>(m_dropZoneState.dropOnto()->parentWidget());
|
|
}
|
|
|
|
if ((mainWindow == m_mainWindow) && (area != Qt::NoDockWidgetArea) && dropOnto)
|
|
{
|
|
m_ghostWidget->EnableClippingToDockWidgets();
|
|
}
|
|
else
|
|
{
|
|
m_ghostWidget->DisableClippingToDockWidgets();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle a mouse press event.
|
|
*/
|
|
bool FancyDocking::dockMousePressEvent(QDockWidget* dock, QMouseEvent* event)
|
|
{
|
|
QPoint pressPos = event->pos();
|
|
if (event->button() != Qt::LeftButton || !canDragDockWidget(dock, pressPos))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (m_state.dock)
|
|
{
|
|
qWarning() << "Press event without the previous being a release?" << dock << m_state.dock;
|
|
return true;
|
|
}
|
|
|
|
startDraggingWidget(dock, pressPos);
|
|
|
|
// Show the floating pixmap, but don't start it rendering
|
|
// It will early out in it's paint event, but then there
|
|
// won't be any delay when the user has dragged far enough
|
|
// to trigger dragging
|
|
m_ghostWidget->show();
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Initialize the dragging state for the specified dock widget. The tabIndex will be -1
|
|
* if you are dragging a regular panel by the title bar, and will be set to a valid index
|
|
* if you are dragging a tab of a DockTabWidget
|
|
*/
|
|
void FancyDocking::startDraggingWidget(QDockWidget* dock, const QPoint& pressPos, int tabIndex)
|
|
{
|
|
if (!dock)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!QApplication::overrideCursor())
|
|
{
|
|
QApplication::setOverrideCursor(m_dragCursor);
|
|
}
|
|
|
|
QPoint relativePressPos = pressPos;
|
|
|
|
// If we are dragging a floating window, we need to grab a reference to its
|
|
// actual single visible child dock widget to use as our target
|
|
if (dock->isFloating())
|
|
{
|
|
QDockWidget* childDockWidget = nullptr;
|
|
QMainWindow* mainWindow = qobject_cast<QMainWindow*>(dock->widget());
|
|
if (mainWindow)
|
|
{
|
|
for (QDockWidget* dockWidget : mainWindow->findChildren<QDockWidget*>(QString(), Qt::FindDirectChildrenOnly))
|
|
{
|
|
if (dockWidget->isVisible())
|
|
{
|
|
childDockWidget = dockWidget;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!childDockWidget)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Adjust pressPos so that the child widget to be dragged does not change its position.
|
|
relativePressPos = QPoint(pressPos.x(), -(titleBarOffset(dock) - pressPos.y()));
|
|
|
|
// Use the visible child as our drag target going forward, and keep a
|
|
// reference to the floating container for decision making later
|
|
m_state.floatingDockContainer = dock;
|
|
dock = childDockWidget;
|
|
}
|
|
|
|
if (tabIndex == -1 && m_state.tabWidget)
|
|
{
|
|
// If we're dragging a tab widget by the non tab area, set this so that it gets unpacked properly later.
|
|
m_state.floatingDockContainer = dock;
|
|
}
|
|
|
|
QDockWidget* draggedDockWidget = dock;
|
|
m_state.dock = dock;
|
|
|
|
// If we are dragging a tab widget, then get a reference to the appropriate widget
|
|
// so we can get the screen grab of just that tab
|
|
if (tabIndex != -1 && m_state.tabWidget)
|
|
{
|
|
QDockWidget* widget = qobject_cast<QDockWidget*>(m_state.tabWidget->widget(tabIndex));
|
|
if (widget)
|
|
{
|
|
draggedDockWidget = widget;
|
|
}
|
|
}
|
|
|
|
m_state.draggedDockWidget = draggedDockWidget;
|
|
|
|
// If we have cached the last floating screen grab for this dock widget,
|
|
// then retrieve it here, otherwise retrieve a screen grab from the dock
|
|
// widget itself
|
|
QString paneName = draggedDockWidget->objectName();
|
|
if (m_lastFloatingScreenGrab.contains(paneName))
|
|
{
|
|
m_state.dockWidgetScreenGrab = m_lastFloatingScreenGrab[paneName];
|
|
}
|
|
else
|
|
{
|
|
m_state.dockWidgetScreenGrab = { draggedDockWidget->grab(), draggedDockWidget->size() };
|
|
}
|
|
|
|
m_state.pressPos = relativePressPos;
|
|
m_dropZoneState.setDragging(false);
|
|
setupDropZones(nullptr);
|
|
}
|
|
|
|
bool FancyDocking::dockMouseReleaseEvent(QDockWidget* dock, QMouseEvent* event)
|
|
{
|
|
if (!m_state.dock || event->button() != Qt::LeftButton)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// If we are dragging a floating dock widget, then we need to use the
|
|
// actual dock widget child as our reference
|
|
if (m_state.floatingDockContainer && m_state.floatingDockContainer == dock)
|
|
{
|
|
dock = m_state.dock;
|
|
}
|
|
|
|
if (m_dropZoneState.dragging())
|
|
{
|
|
Qt::DockWidgetArea area = m_dropZoneState.dropArea();
|
|
|
|
// If the modifier key is pressed, or the hovered drop zone opacity
|
|
// hasn't faded in all the way yet, then ignore the drop zone area
|
|
// which will make the widget floating
|
|
bool modifiedKeyPressed = FancyDockingDropZoneWidget::CheckModifierKey();
|
|
if (modifiedKeyPressed || m_dropZoneState.dropZoneHoverOpacity() != g_FancyDockingConstants.dropZoneOpacity)
|
|
{
|
|
area = Qt::NoDockWidgetArea;
|
|
}
|
|
|
|
dropDockWidget(dock, m_dropZoneState.dropOnto(), area);
|
|
}
|
|
else
|
|
{
|
|
// Pass the mouse release event to the tab widget (if applicable) since
|
|
// we grab the mouse/keyboard from it
|
|
if (m_state.tabWidget)
|
|
{
|
|
m_state.tabWidget->mouseReleaseEvent(event);
|
|
}
|
|
|
|
clearDraggingState();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Handle tab index presses from our DockTabWidgets
|
|
*/
|
|
void FancyDocking::onTabIndexPressed(int index)
|
|
{
|
|
DockTabWidget* tabWidget = qobject_cast<DockTabWidget*>(sender());
|
|
if (!tabWidget)
|
|
{
|
|
return;
|
|
}
|
|
|
|
QDockWidget* dockWidget = qobject_cast<QDockWidget*>(tabWidget->parent());
|
|
if (!dockWidget)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Initialize our drag state with the dock widget that contains our tab widget
|
|
QPoint pressPos = dockWidget->mapFromGlobal(QCursor::pos());
|
|
m_state.tabWidget = tabWidget;
|
|
startDraggingWidget(dockWidget, pressPos, index);
|
|
|
|
// We need to grab the mouse and keyboard immediately because the QTabBar that is part of our
|
|
// DockTabWidget overrides the mouse/key press/move/release events
|
|
m_mainWindow->grabMouse();
|
|
m_mainWindow->grabKeyboard();
|
|
}
|
|
|
|
/**
|
|
* Handle tab index presses from our DockTabWidgets so we can delete the tab coutainer if all the tabs are removed
|
|
*/
|
|
void FancyDocking::onTabCountChanged(int count)
|
|
{
|
|
// We only care if there are no tabs left
|
|
if (count != 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Retrieve the dock widget container for our tab widget
|
|
QDockWidget* dockWidget = getTabWidgetContainer(sender());
|
|
if (!dockWidget)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Retrieve the main window that our dock widget container lives in
|
|
QMainWindow* mainWindow = qobject_cast<QMainWindow*>(dockWidget->parent());
|
|
if (!mainWindow)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Remove the dock widget tab container from the main window and then delete it since
|
|
// it is no longer needed (this will also delete the dock tab widget since it is a child)
|
|
mainWindow->removeDockWidget(dockWidget);
|
|
OptimizedSetParent(dockWidget, nullptr);
|
|
dockWidget->deleteLater();
|
|
|
|
// If this tab widget was on a floating window, run the check if this main
|
|
// window needs to be destroyed (if this tab widget was the only thing
|
|
// left in this floating window)
|
|
if (mainWindow != m_mainWindow)
|
|
{
|
|
destroyIfUseless(mainWindow);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle tab index changes from our DockTabWidgets so we can update
|
|
* the floating widget's window title based on the current tab name
|
|
*/
|
|
void FancyDocking::onCurrentTabIndexChanged(int /*index*/)
|
|
{
|
|
QDockWidget* tabWidgetContainer = getTabWidgetContainer(sender());
|
|
if (!tabWidgetContainer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
QMainWindow* mainWindow = qobject_cast<QMainWindow*>(tabWidgetContainer->parentWidget());
|
|
if (mainWindow && mainWindow != m_mainWindow)
|
|
{
|
|
QueueUpdateFloatingWindowTitle(mainWindow);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Whenever widgets are inserted as tabs, cache the tab container they were
|
|
* added to so that if they are closed, we can restore them to the last tab
|
|
* container they were in
|
|
*/
|
|
void FancyDocking::onTabWidgetInserted(QWidget* widget)
|
|
{
|
|
if (!widget)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Retrieve the dock widget container for our tab widget
|
|
QDockWidget* dockWidget = getTabWidgetContainer(sender());
|
|
if (!dockWidget)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_lastTabContainerForDockWidget[widget->objectName()] = dockWidget->objectName();
|
|
}
|
|
|
|
/**
|
|
* Handle request to undock a tab from a tab group, or undock the entire tab
|
|
* group from its main window
|
|
*/
|
|
void FancyDocking::onUndockTab(int index)
|
|
{
|
|
QDockWidget* tabWidgetContainer = getTabWidgetContainer(sender());
|
|
if (!tabWidgetContainer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// If the index given is -1, then we are going to undock the entire tab
|
|
// group, so grab the tab widget container as our target dock widget
|
|
QDockWidget* dockWidget = nullptr;
|
|
if (index == -1)
|
|
{
|
|
dockWidget = tabWidgetContainer;
|
|
}
|
|
// Otherwise, grab the specific dock widget from the tab widget using
|
|
// the specified tab index
|
|
else
|
|
{
|
|
DockTabWidget* tabWidget = qobject_cast<DockTabWidget*>(sender());
|
|
if (!tabWidget)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Set the necessary drag state parameters so that we can undock the
|
|
// given dock widget from the tab widget
|
|
m_state.tabWidget = tabWidget;
|
|
dockWidget = qobject_cast<QDockWidget*>(tabWidget->widget(index));
|
|
}
|
|
|
|
undockDockWidget(dockWidget, tabWidgetContainer);
|
|
}
|
|
|
|
/**
|
|
* Handle double clicking a tab bar to maximize/restore when the tab bar
|
|
* is shown as a title bar
|
|
*/
|
|
void FancyDocking::onTabBarDoubleClicked()
|
|
{
|
|
QDockWidget* tabWidgetContainer = getTabWidgetContainer(sender());
|
|
if (!tabWidgetContainer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
QMainWindow* mainWindow = qobject_cast<QMainWindow*>(tabWidgetContainer->parentWidget());
|
|
if (mainWindow && mainWindow != m_mainWindow)
|
|
{
|
|
StyledDockWidget* floatingDockWidget = qobject_cast<StyledDockWidget*>(mainWindow->parentWidget());
|
|
if (floatingDockWidget)
|
|
{
|
|
TitleBar* titleBar = floatingDockWidget->customTitleBar();
|
|
if (titleBar)
|
|
{
|
|
titleBar->handleMaximize();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle request from a dock widget to be undocked from its main window
|
|
*/
|
|
void FancyDocking::onUndockDockWidget()
|
|
{
|
|
QDockWidget* dockWidget = qobject_cast<QDockWidget*>(sender());
|
|
undockDockWidget(dockWidget);
|
|
}
|
|
|
|
/**
|
|
* Undock the specified dock widget
|
|
*/
|
|
void FancyDocking::undockDockWidget(QDockWidget* dockWidget, QDockWidget* placeholder)
|
|
{
|
|
if (!dockWidget)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Offset the geometry that the undocked dock widget will be given from the
|
|
// placeholder geometry with the height of our title dock bar so that it isn't
|
|
// undocked directly above its current position
|
|
int offset = titleBarOffset(dockWidget);
|
|
|
|
// The placeholder is an optional parameter to provide a different reference
|
|
// geometry with which to undock the dock widget, so if it isn't provided,
|
|
// then just use our dock widget for reference
|
|
// In practice, if the reference geometry is not provided, that means it's not
|
|
// untabbifying, which means that the title bar will get re-added and/or the size
|
|
// doesn't take it into account, so we need to below otherwise the widget gets smaller
|
|
QSize newSize;
|
|
QPoint newPosition;
|
|
if (!placeholder)
|
|
{
|
|
newSize = dockWidget->size();
|
|
newSize.setHeight(newSize.height() + offset);
|
|
newPosition = dockWidget->mapToGlobal(QPoint(offset, offset));
|
|
}
|
|
else
|
|
{
|
|
newSize = placeholder->size();
|
|
newPosition = placeholder->mapToGlobal(QPoint(offset, offset));
|
|
}
|
|
|
|
// Setup the new placeholder using the screen of its new position
|
|
QScreen* screen = Utilities::ScreenAtPoint(newPosition);
|
|
m_state.setPlaceholder(QRect(newPosition, newSize), screen);
|
|
updateFloatingPixmap();
|
|
|
|
// Set the widget as being dragged
|
|
m_state.draggedDockWidget = dockWidget;
|
|
|
|
// Undock the dock widget
|
|
dropDockWidget(dockWidget, nullptr, Qt::NoDockWidgetArea);
|
|
}
|
|
|
|
/**
|
|
* If the specified object is our custom dock tab widget, then return its QDockWidget
|
|
* parent container, otherwise return nullptr
|
|
*/
|
|
QDockWidget* FancyDocking::getTabWidgetContainer(QObject* obj)
|
|
{
|
|
// Check if the object is our custom dock tab widget
|
|
DockTabWidget* tabWidget = qobject_cast<DockTabWidget*>(obj);
|
|
if (!tabWidget)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
// Retrieve the dock widget container for our tab widget
|
|
return qobject_cast<QDockWidget*>(tabWidget->parent());
|
|
}
|
|
|
|
/*
|
|
* Determine whether or not you can drag the specified dock widget based on if the mouse
|
|
* position is inside the title bar
|
|
*/
|
|
bool FancyDocking::canDragDockWidget(QDockWidget* dock, QPoint mousePos)
|
|
{
|
|
if (!dock)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
QWidget* title = dock->titleBarWidget();
|
|
if (title)
|
|
{
|
|
return title->geometry().contains(mousePos);
|
|
}
|
|
|
|
// Some dock widgets don't have a title bar (DockTabWidget and the viewport)
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Make a dock widget floating by creating a new floating main window containt
|
|
* for it and adding it as the only dock widget
|
|
*/
|
|
void FancyDocking::makeDockWidgetFloating(QDockWidget* dock, const QRect& geometry)
|
|
{
|
|
if (!dock)
|
|
{
|
|
return;
|
|
}
|
|
|
|
auto styledDockWidget = qobject_cast<StyledDockWidget*>(dock);
|
|
if (styledDockWidget && styledDockWidget->isSingleFloatingChild())
|
|
{
|
|
// Reuse the existing container
|
|
QRect adjustedGeometry = geometry;
|
|
AzQtComponents::EnsureGeometryWithinScreenTop(adjustedGeometry);
|
|
styledDockWidget->window()->setGeometry(adjustedGeometry);
|
|
styledDockWidget->activateWindow();
|
|
}
|
|
else
|
|
{
|
|
// Create a floating window container for this dock widget
|
|
QScopedValueRollback<bool> guard(m_state.updateInProgress, true); // Don't let mainWindow get deleted while we do this
|
|
QMainWindow* mainWindow = createFloatingMainWindow(getUniqueDockWidgetName(m_floatingWindowIdentifierPrefix), geometry, shouldSkipTitleBarOverdraw(dock));
|
|
OptimizedSetParent(dock, mainWindow);
|
|
mainWindow->addDockWidget(Qt::LeftDockWidgetArea, dock);
|
|
dock->show();
|
|
|
|
// Make sure we listen for events on the dock widget being put into a floating dock window
|
|
// because this might be called programmatically, so the dock widget might have never been
|
|
// parented to our m_mainWindow initially, so it won't already have an event filter,
|
|
// which will prevent the docking functionality from working.
|
|
dock->installEventFilter(this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Safe version of the QMainWindow splitDockWidget method to workaround an odd Qt bug
|
|
*/
|
|
void FancyDocking::splitDockWidget(QMainWindow* mainWindow, QDockWidget* target, QDockWidget* dropped, Qt::Orientation orientation)
|
|
{
|
|
if (!mainWindow || !target || !dropped)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Calculate the split width (or height) so that our target and dropped
|
|
// widgets can be resized to share the space
|
|
int splitSize = 0;
|
|
if (orientation == Qt::Horizontal)
|
|
{
|
|
splitSize = target->width() / 2;
|
|
}
|
|
else
|
|
{
|
|
splitSize = target->height() / 2;
|
|
}
|
|
|
|
// As detailed in LY-42497, there is an odd Qt bug where if dock widget A is
|
|
// already split with dock widget B, and you try to split B with A in the
|
|
// opposite orientation after restoring the QMainWindow state, you will end
|
|
// up with what looks like an empty dock widget in the old location of A,
|
|
// but it's actually a ghost copy in the main window layout of A, which
|
|
// you can tell because it will flicker sometimes and you can see the contents
|
|
// of A. So to fix, we need to remove the widget being dropped from the main
|
|
// window layout before we split it with the target, and show it afterwards
|
|
// since removing it will also hide it. This eliminates the ghost copy of
|
|
// the dropped widget that gets left in the main window layout.
|
|
mainWindow->removeDockWidget(dropped);
|
|
mainWindow->splitDockWidget(target, dropped, orientation);
|
|
dropped->show();
|
|
|
|
// Resize the target and dropped widgets so they evenly split the space
|
|
// in the orientation that they were split
|
|
mainWindow->resizeDocks({ target, dropped }, { splitSize, splitSize }, orientation);
|
|
}
|
|
|
|
/**
|
|
* Use this to prevent the fancy docking system from auto-saving this widget
|
|
* with the rest of the layout state.
|
|
*/
|
|
void FancyDocking::disableAutoSaveLayout(QDockWidget* dock)
|
|
{
|
|
dock->setProperty(g_AutoSavePropertyName, false);
|
|
}
|
|
|
|
/**
|
|
* Use this to enable the fancy docking system to auto-save this widget
|
|
* with the rest of the layout state.
|
|
*
|
|
* Note that this method does not need to be called in most cases.
|
|
* Unless disableAutoSaveLayout has previously been called, all fancy dock
|
|
* widgets will have their layout state saved.
|
|
*/
|
|
void FancyDocking::enableAutoSaveLayout(QDockWidget* dock)
|
|
{
|
|
dock->setProperty(g_AutoSavePropertyName, false);
|
|
}
|
|
|
|
bool FancyDocking::IsDockWidgetBeingDragged(QDockWidget* dock)
|
|
{
|
|
return m_state.draggedDockWidget == dock;
|
|
}
|
|
|
|
/**
|
|
* Dock a QDockWidget onto a QDockWidget or a QMainWindow
|
|
* NOTE: This method is responsible for calling clearDraggingState() when it has
|
|
* completed its actions
|
|
*/
|
|
void FancyDocking::dropDockWidget(QDockWidget* dock, QWidget* onto, Qt::DockWidgetArea area)
|
|
{
|
|
// If the dock widget we are dropping is currently a tab, we need to retrieve it from
|
|
// the tab widget, and remove it as a tab. We also need to remove its item from our
|
|
// cache of widget <-> tab container since we are moving it somewhere else.
|
|
if (m_state.tabWidget)
|
|
{
|
|
if (m_state.draggedDockWidget)
|
|
{
|
|
m_lastTabContainerForDockWidget.remove(m_state.draggedDockWidget->objectName());
|
|
m_state.tabWidget->removeTab(m_state.draggedDockWidget);
|
|
dock = m_state.draggedDockWidget;
|
|
}
|
|
else
|
|
{
|
|
m_lastTabContainerForDockWidget.remove(dock->objectName());
|
|
m_state.tabWidget->removeTab(dock);
|
|
}
|
|
}
|
|
|
|
if (area == Qt::NoDockWidgetArea)
|
|
{
|
|
// Make this dock widget floating, since it has been dropped on no dock area
|
|
// We need to adjust the geometry if it has been snapped to an edge to account
|
|
// for the frame margins, and handle the title bar height offset if it hasn't
|
|
// been snapped to the top edge
|
|
QRect placeholderRect = m_state.placeholder();
|
|
QMargins margins;
|
|
QWindow* mainWindowHandle = m_mainWindow->window()->windowHandle();
|
|
if (mainWindowHandle && !isWin10())
|
|
{
|
|
margins = mainWindowHandle->frameMargins();
|
|
}
|
|
|
|
// We also need to account for the window decoration wrapper margins
|
|
// that get added on to floating dock widgets. There is no extra
|
|
// top margin because of the title bar.
|
|
WindowDecorationWrapper* windowWrapper = qobject_cast<WindowDecorationWrapper*>(m_mainWindow->parentWidget()->parentWidget());
|
|
if (windowWrapper)
|
|
{
|
|
QMargins wrapperMargins = windowWrapper->margins();
|
|
margins.setLeft(margins.left() + wrapperMargins.left());
|
|
margins.setRight(margins.right() + wrapperMargins.right());
|
|
margins.setBottom(margins.bottom() + wrapperMargins.bottom());
|
|
}
|
|
|
|
// Qt returns right/bottom with a -1 offset because of historical reasons,
|
|
// but even though we ignore this on Win10 when snapping the placeholder,
|
|
// we have to actually put the offset back in before we place it because
|
|
// it ends up being adjusted afterwards
|
|
if (isWin10())
|
|
{
|
|
margins.setRight(margins.right() + 1);
|
|
margins.setBottom(margins.bottom() + 1);
|
|
}
|
|
|
|
if (m_state.snappedSide & SnapLeft)
|
|
{
|
|
placeholderRect.translate(margins.left(), 0);
|
|
}
|
|
if (m_state.snappedSide & SnapRight)
|
|
{
|
|
placeholderRect.translate(-margins.right(), 0);
|
|
}
|
|
if (m_state.snappedSide & SnapTop)
|
|
{
|
|
placeholderRect.translate(0, margins.top());
|
|
}
|
|
else
|
|
{
|
|
placeholderRect.adjust(0, -titleBarOffset(dock), 0, 0);
|
|
}
|
|
if (m_state.snappedSide & SnapBottom)
|
|
{
|
|
placeholderRect.translate(0, -margins.bottom());
|
|
}
|
|
|
|
// Also adjust the placeholderRect by the relative dpi change from the original screen, since setGeometry uses the screen's
|
|
// virtualGeometry!
|
|
QScreen* fromScreen = dock->screen();
|
|
QScreen* toScreen = Utilities::ScreenAtPoint(placeholderRect.topLeft());
|
|
|
|
if (fromScreen != toScreen)
|
|
{
|
|
qreal factorRatio = QHighDpiScaling::factor(fromScreen) / QHighDpiScaling::factor(toScreen);
|
|
placeholderRect.setWidth(aznumeric_cast<int>(aznumeric_cast<qreal>(placeholderRect.width()) * factorRatio));
|
|
placeholderRect.setHeight(aznumeric_cast<int>(aznumeric_cast<qreal>(placeholderRect.height()) * factorRatio));
|
|
}
|
|
|
|
// Place the floating dock widget
|
|
makeDockWidgetFloating(dock, placeholderRect);
|
|
clearDraggingState();
|
|
|
|
// We can remove any cached floating screen grab for this dock widget
|
|
// now that it's been undocked as floating, since it will be cached
|
|
// whenever it is docked into a main window in the future
|
|
m_lastFloatingScreenGrab.remove(dock->objectName());
|
|
}
|
|
else
|
|
{
|
|
// If we are docking a dock widget that is currently the only dock widget
|
|
// in a floating main window, then cache its screen grab so that we can
|
|
// restore its last floating size when undocking it later in the future
|
|
if (qobject_cast<StyledDockWidget*>(dock)->isSingleFloatingChild())
|
|
{
|
|
m_lastFloatingScreenGrab[dock->objectName()] = m_state.dockWidgetScreenGrab;
|
|
}
|
|
|
|
// do the rest after the show has been fully processed, just to be sure
|
|
QTimer::singleShot(0, [=]()
|
|
{
|
|
// Ensure that the dock window is shown, because we may have hidden it when the drag started
|
|
dock->show();
|
|
|
|
// Handle an absolute drop zone
|
|
QMainWindow* mainWindow = qobject_cast<QMainWindow*>(onto);
|
|
if (m_dropZoneState.onAbsoluteDropZone())
|
|
{
|
|
// Find the main window for the drop target (if it's not a main window),
|
|
// since we will use it instead of the drop target itself for docking on the
|
|
// absolute edge
|
|
if (!mainWindow)
|
|
{
|
|
mainWindow = qobject_cast<QMainWindow*>(onto->parentWidget());
|
|
}
|
|
|
|
// Fallback to the editor main window if we couldn't find one
|
|
if (!mainWindow)
|
|
{
|
|
mainWindow = m_mainWindow;
|
|
}
|
|
|
|
// Set the absolute drop zone corners properly for this
|
|
// main window
|
|
setAbsoluteCornersForDockArea(mainWindow, area);
|
|
}
|
|
|
|
if (mainWindow)
|
|
{
|
|
OptimizedSetParent(dock, onto);
|
|
if (area == Qt::AllDockWidgetAreas)
|
|
{
|
|
// should not happen
|
|
}
|
|
else
|
|
{
|
|
// LY-43595 (similar to LY-42497), there is a bug in Qt where
|
|
// re-docking a dock widget to different areas in the main
|
|
// window layout if it was already split in a different
|
|
// part in the layout results in the dock widget being
|
|
// duplicated in the layout
|
|
// We have to show the dock widget after adding it because
|
|
// the call to removeDockWidget hides the dock widget
|
|
mainWindow->removeDockWidget(dock);
|
|
mainWindow->addDockWidget(area, dock, orientation(area));
|
|
dock->show();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QDockWidget* dockWidget = qobject_cast<QDockWidget*>(onto);
|
|
if (dockWidget)
|
|
{
|
|
mainWindow = static_cast<QMainWindow*>(dockWidget->parentWidget());
|
|
OptimizedSetParent(dock, mainWindow);
|
|
if (area == Qt::AllDockWidgetAreas)
|
|
{
|
|
tabifyDockWidget(dockWidget, dock, mainWindow, &m_state.dockWidgetScreenGrab);
|
|
}
|
|
else
|
|
{
|
|
splitDockWidget(mainWindow, dockWidget, dock, orientation(area));
|
|
if (area == Qt::LeftDockWidgetArea || area == Qt::TopDockWidgetArea)
|
|
{
|
|
// Is was actually the other way around that we needed to do.
|
|
// But we needed the first call so the dock is in the right area.
|
|
splitDockWidget(mainWindow, dock, dockWidget, orientation(area));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
clearDraggingState();
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dock the dropped dock widget into our custom tab system on the drop target,
|
|
* and return a reference to the tab widget
|
|
*/
|
|
DockTabWidget* FancyDocking::tabifyDockWidget(QDockWidget* dropTarget, QDockWidget* dropped, QMainWindow* mainWindow, FancyDocking::WidgetGrab* droppedGrab)
|
|
{
|
|
if (!dropTarget || !dropped || !mainWindow)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
// Check if the target dock is already in a tabbed dock
|
|
// If yes, then forward the request to this dock instead.
|
|
{
|
|
if (DockTabWidget::IsTabbed(dropTarget))
|
|
{
|
|
DockTabWidget* tabWidget = DockTabWidget::ParentTabWidget(dropTarget);
|
|
if (QDockWidget* dock = qobject_cast<QDockWidget*>(tabWidget->parentWidget()))
|
|
{
|
|
return tabifyDockWidget(dock, dropped, mainWindow, droppedGrab);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Flag that we have a tabify action in progress so that we can ignore our
|
|
// destroyIfUseless cleanup method that gets inadvertantly triggered
|
|
// while we are tabifying
|
|
QScopedValueRollback<bool> rollback(m_state.updateInProgress, true);
|
|
|
|
// Check if the drop target is already one of our custom tab widgets
|
|
DockTabWidget* tabWidget = qobject_cast<DockTabWidget*>(dropTarget->widget());
|
|
|
|
QString saveGrabName;
|
|
if (!tabWidget)
|
|
{
|
|
saveGrabName = dropTarget->objectName();
|
|
}
|
|
else if (tabWidget->count() == 1)
|
|
{
|
|
saveGrabName = tabWidget->tabText(0);
|
|
}
|
|
|
|
// The dropped dock is already part of this tab widget
|
|
if (tabWidget && tabWidget->indexOf(dropped) != -1)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
// Special case this one: if we're dropping onto an untabbed widget, save it's state so that it resizes properly
|
|
// when torn off
|
|
// Should be cleared again when the widget goes back to being a single tab
|
|
if (!m_lastFloatingScreenGrab.contains(saveGrabName))
|
|
{
|
|
m_lastFloatingScreenGrab[saveGrabName] = { dropTarget->grab(), dropTarget->size() };
|
|
}
|
|
|
|
if (!tabWidget)
|
|
{
|
|
// The drop target wasn't already a custom tab widget, so create one and replace the drop target
|
|
// with the tab widget (with the drop target as the initial tab)
|
|
tabWidget = createTabWidget(mainWindow, dropTarget);
|
|
}
|
|
|
|
// Special case this one: if a widget gets tabbified, when it's untabbified, it won't render properly
|
|
// for the floating pixmap. So we force it to store the state here, if it isn't already
|
|
// It's only if it isn't already, because if it was dragged from a tabgroup and into another tabgroup
|
|
// then we shouldn't be saving it (because it's already been saved)
|
|
QString droppedName = dropped->objectName();
|
|
if (!m_lastFloatingScreenGrab.contains(droppedName) && (droppedGrab != nullptr))
|
|
{
|
|
m_lastFloatingScreenGrab[droppedName] = *droppedGrab;
|
|
}
|
|
|
|
// If our dropped widget is also a tab widget (e.g. we dragged a floating tab container),
|
|
// then we need to move the tabs into our drop target tab widget
|
|
int newActiveIndex = 0;
|
|
if (m_state.floatingDockContainer && droppedName.startsWith(m_tabContainerIdentifierPrefix))
|
|
{
|
|
DockTabWidget* oldTabWidget = qobject_cast<DockTabWidget*>(dropped->widget());
|
|
if (!oldTabWidget)
|
|
{
|
|
if (m_state.tabWidget)
|
|
{
|
|
oldTabWidget = m_state.tabWidget;
|
|
}
|
|
else
|
|
{
|
|
return tabWidget;
|
|
}
|
|
}
|
|
|
|
// Calculate the new active tab index based on adding the tabs to our
|
|
// drop target
|
|
int numOldTabs = oldTabWidget->count();
|
|
newActiveIndex = tabWidget->count() + oldTabWidget->currentIndex();
|
|
|
|
// Remove our dropped tabs from their existing tab widget and add them to
|
|
// the drop target tab widget
|
|
for (int i = 0; i < numOldTabs; ++i)
|
|
{
|
|
QDockWidget* dockWidget = qobject_cast<QDockWidget*>(oldTabWidget->widget(0));
|
|
m_lastTabContainerForDockWidget.remove(dockWidget->objectName());
|
|
oldTabWidget->removeTab(0);
|
|
tabWidget->addTab(dockWidget);
|
|
}
|
|
}
|
|
// Otherwise, the dropped widget is a normal dock widget so just add it as
|
|
// a new tab
|
|
else
|
|
{
|
|
newActiveIndex = tabWidget->addTab(dropped);
|
|
}
|
|
|
|
// Set the dropped widget as the active tab (or the active tab of the dropped
|
|
// tab widget)
|
|
tabWidget->setCurrentIndex(newActiveIndex);
|
|
|
|
return tabWidget;
|
|
}
|
|
|
|
/**
|
|
* Reserve the absolute corners for the specified drop zone area for this
|
|
* main window so that any widget docked to that area will take the absolute edge
|
|
*/
|
|
void FancyDocking::setAbsoluteCornersForDockArea(QMainWindow* mainWindow, Qt::DockWidgetArea area)
|
|
{
|
|
if (!mainWindow)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Since a widget is being docked on an absolute drop zone,
|
|
// we need to reserve the corners for the absolute drop
|
|
// area so that it will take precedence over other widgets
|
|
// that may already be docked in absolute positions
|
|
switch (area)
|
|
{
|
|
case Qt::LeftDockWidgetArea:
|
|
mainWindow->setCorner(Qt::TopLeftCorner, area);
|
|
mainWindow->setCorner(Qt::BottomLeftCorner, area);
|
|
break;
|
|
case Qt::RightDockWidgetArea:
|
|
mainWindow->setCorner(Qt::TopRightCorner, area);
|
|
mainWindow->setCorner(Qt::BottomRightCorner, area);
|
|
break;
|
|
case Qt::TopDockWidgetArea:
|
|
mainWindow->setCorner(Qt::TopLeftCorner, area);
|
|
mainWindow->setCorner(Qt::TopRightCorner, area);
|
|
break;
|
|
case Qt::BottomDockWidgetArea:
|
|
mainWindow->setCorner(Qt::BottomLeftCorner, area);
|
|
mainWindow->setCorner(Qt::BottomRightCorner, area);
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool FancyDocking::eventFilter(QObject* watched, QEvent* event)
|
|
{
|
|
if (watched == m_mainWindow)
|
|
{
|
|
StyledDockWidget* dockWidget = nullptr;
|
|
switch (event->type())
|
|
{
|
|
case QEvent::ChildPolished:
|
|
dockWidget = qobject_cast<StyledDockWidget*>(static_cast<QChildEvent*>(event)->child());
|
|
if (dockWidget)
|
|
{
|
|
dockWidget->installEventFilter(this);
|
|
// Remove the movable feature because we will handle that ourselves
|
|
dockWidget->setFeatures(dockWidget->features() & ~(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable));
|
|
|
|
// Connect to undock requests from this dock widget
|
|
// MUST BE A UNIQUE CONNECTION! Otherwise, every time through this method will connect to the signal again
|
|
QObject::connect(dockWidget, &StyledDockWidget::undock, this, &FancyDocking::onUndockDockWidget, Qt::UniqueConnection);
|
|
}
|
|
break;
|
|
case QEvent::MouseMove:
|
|
if (m_state.dock && dockMouseMoveEvent(m_state.dock, static_cast<QMouseEvent*>(event)))
|
|
{
|
|
return true;
|
|
}
|
|
break;
|
|
case QEvent::MouseButtonRelease:
|
|
if (m_state.dock && dockMouseReleaseEvent(m_state.dock, static_cast<QMouseEvent*>(event)))
|
|
{
|
|
return true;
|
|
}
|
|
break;
|
|
case QEvent::KeyPress:
|
|
case QEvent::ShortcutOverride:
|
|
if (m_dropZoneState.dragging())
|
|
{
|
|
// Cancel the dragging state when the Escape key is pressed
|
|
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
|
if (keyEvent->key() == Qt::Key_Escape)
|
|
{
|
|
clearDraggingState();
|
|
}
|
|
else
|
|
{
|
|
// modifier keys can affect things, so do a redraw
|
|
RepaintFloatingIndicators();
|
|
}
|
|
}
|
|
break;
|
|
case QEvent::KeyRelease:
|
|
if (m_dropZoneState.dragging())
|
|
{
|
|
// modifier keys can affect things, so do a redraw
|
|
RepaintFloatingIndicators();
|
|
}
|
|
break;
|
|
case QEvent::WindowDeactivate:
|
|
// If our main window is deactivated while we are in the middle of
|
|
// a docking drag operation (e.g. popup dialog for new level), we
|
|
// should cancel our drag operation because the mouse release event
|
|
// will be lost since we lost focus
|
|
if (m_dropZoneState.dragging())
|
|
{
|
|
clearDraggingState();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QDockWidget* dockWidget = qobject_cast<QDockWidget*>(watched);
|
|
if (dockWidget)
|
|
{
|
|
QString dockWidgetName = dockWidget->objectName();
|
|
switch (event->type())
|
|
{
|
|
case QEvent::MouseButtonPress:
|
|
if (dockMousePressEvent(dockWidget, static_cast<QMouseEvent*>(event)))
|
|
{
|
|
return true;
|
|
}
|
|
break;
|
|
case QEvent::MouseMove:
|
|
if (dockMouseMoveEvent(dockWidget, static_cast<QMouseEvent*>(event)))
|
|
{
|
|
return true;
|
|
}
|
|
break;
|
|
case QEvent::MouseButtonRelease:
|
|
if (dockMouseReleaseEvent(dockWidget, static_cast<QMouseEvent*>(event)))
|
|
{
|
|
return true;
|
|
}
|
|
break;
|
|
case QEvent::ShowToParent:
|
|
{
|
|
QMainWindow* mainWindow = qobject_cast<QMainWindow*>(dockWidget->parent());
|
|
if (mainWindow)
|
|
{
|
|
UpdateTitleBars(mainWindow);
|
|
|
|
if (mainWindow != m_mainWindow)
|
|
{
|
|
QueueUpdateFloatingWindowTitle(mainWindow);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case QEvent::HideToParent:
|
|
{
|
|
QMainWindow* mainWindow = qobject_cast<QMainWindow*>(dockWidget->parent());
|
|
if (mainWindow)
|
|
{
|
|
UpdateTitleBars(mainWindow);
|
|
|
|
// Update the floating widget's window title
|
|
if (mainWindow != m_mainWindow)
|
|
{
|
|
QueueUpdateFloatingWindowTitle(mainWindow);
|
|
}
|
|
|
|
// The dockwidget was hidden, we the parent floating mainwindow might need to be
|
|
// destroyed. But delay the call to destroyIfUseless to the next iteration of the
|
|
// event loop, as the it might only be temporarily hidden (e.g. reparenting).
|
|
if (!m_state.updateInProgress)
|
|
{
|
|
QTimer::singleShot(0, mainWindow, [this, mainWindow] {
|
|
destroyIfUseless(mainWindow);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case QEvent::Close:
|
|
// If the user tries to close an entire floating window using
|
|
// the top title bar, we need to handle the close ourselves
|
|
if (dockWidgetName.startsWith(m_floatingWindowIdentifierPrefix))
|
|
{
|
|
QMainWindow* mainWindow = qobject_cast<QMainWindow*>(dockWidget->widget());
|
|
if (mainWindow)
|
|
{
|
|
// Close the child dock widgets in our floating main window individually so
|
|
// that they will eventually trigger our destroyIfUseless method, which will
|
|
// properly save the floating window state in our m_restoreFloatings before
|
|
// deleting the floating main window, so the next time any of these child
|
|
// panes are opened, we can re-create the floating main window and restore
|
|
// them properly
|
|
for (QDockWidget* childDockWidget : mainWindow->findChildren<QDockWidget*>(QString(), Qt::FindDirectChildrenOnly))
|
|
{
|
|
if (childDockWidget->isVisible())
|
|
{
|
|
if (childDockWidget->close())
|
|
{
|
|
// Destroy any empty container immediately
|
|
destroyIfUseless(mainWindow);
|
|
}
|
|
else
|
|
{
|
|
// If the child dock widget rejected the close,
|
|
// then no need to continue trying to close the
|
|
// other children, we can just stop now and ignore
|
|
// the close event
|
|
event->ignore();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
break;
|
|
case QEvent::WindowActivate:
|
|
case QEvent::ZOrderChange:
|
|
// Whenever a floating dock widget is raised to the front, we need
|
|
// to move it to the front of our z-order list of floating dock widget
|
|
// names, since Qt doesn't have a way of retrieving the z-order of our
|
|
// floating dock widgets. The raise can either occur when the user clicks
|
|
// inside a floating dock widget (WindowActivate), or if the raise() method
|
|
// is called manually when dragging a dock widget on top of the floating
|
|
// dock widget (ZOrderChange)
|
|
if (dockWidgetName.startsWith(m_floatingWindowIdentifierPrefix))
|
|
{
|
|
m_orderedFloatingDockWidgetNames.removeAll(dockWidgetName);
|
|
m_orderedFloatingDockWidgetNames.prepend(dockWidgetName);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QPointer<QMainWindow> mainWindow = qobject_cast<QMainWindow*>(watched);
|
|
if (mainWindow)
|
|
{
|
|
QDockWidget* eventChildDockWidget = nullptr;
|
|
switch (event->type())
|
|
{
|
|
case QEvent::ChildAdded:
|
|
{
|
|
eventChildDockWidget = qobject_cast<QDockWidget*>(static_cast<QChildEvent*>(event)->child());
|
|
if (eventChildDockWidget)
|
|
{
|
|
QueueUpdateFloatingWindowTitle(mainWindow);
|
|
UpdateTitleBars(mainWindow);
|
|
}
|
|
}
|
|
break;
|
|
case QEvent::ChildRemoved:
|
|
{
|
|
QueueUpdateFloatingWindowTitle(mainWindow);
|
|
UpdateTitleBars(mainWindow);
|
|
|
|
SetDragOrDockOnFloatingMainWindow(mainWindow);
|
|
destroyIfUseless(mainWindow);
|
|
eventChildDockWidget = qobject_cast<QDockWidget*>(static_cast<QChildEvent*>(event)->child());
|
|
if (eventChildDockWidget)
|
|
{
|
|
// If the dock was deleted, the qobject_cast would fail. So this mean the widget will
|
|
// be added somewhere else (unless it's not dockable, in which case we'll need to know
|
|
// that it was last seen in a floating window when it's restored)
|
|
if (!eventChildDockWidget->objectName().isEmpty() && eventChildDockWidget->allowedAreas() != Qt::NoDockWidgetArea)
|
|
{
|
|
m_placeholders.remove(eventChildDockWidget->objectName());
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case QEvent::ChildPolished:
|
|
// Queue this call since the dock widget won't be visible yet
|
|
QTimer::singleShot(0, this, [this, mainWindow] {
|
|
if (mainWindow)
|
|
{
|
|
SetDragOrDockOnFloatingMainWindow(mainWindow);
|
|
}
|
|
});
|
|
|
|
eventChildDockWidget = qobject_cast<QDockWidget*>(static_cast<QChildEvent*>(event)->child());
|
|
if (eventChildDockWidget)
|
|
{
|
|
if (!eventChildDockWidget->objectName().isEmpty())
|
|
{
|
|
m_placeholders[eventChildDockWidget->objectName()] = watched->parent()->objectName();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* If a floating main window has multiple dock widgets, its top title bar should
|
|
* be used for just dragging around to re-position, but if there's only a single
|
|
* dock widget (or single tab widget), then the top title bar should allow
|
|
* the single dock widget to be docked
|
|
*/
|
|
void FancyDocking::SetDragOrDockOnFloatingMainWindow(QMainWindow* mainWindow)
|
|
{
|
|
if (!mainWindow)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int count = NumVisibleDockWidgets(mainWindow);
|
|
StyledDockWidget* floatingDockWidget = qobject_cast<StyledDockWidget*>(mainWindow->parentWidget());
|
|
if (floatingDockWidget)
|
|
{
|
|
TitleBar* titleBar = floatingDockWidget->customTitleBar();
|
|
if (titleBar)
|
|
{
|
|
bool dragEnabled = (count > 1);
|
|
|
|
// If there is only a single dock widget in this floating main window
|
|
// and it has no allowed dockable areas, then set the top title bar
|
|
// be used for dragging to reposition instead of docking
|
|
if (count == 1)
|
|
{
|
|
QDockWidget* singleDockWidget = mainWindow->findChild<QDockWidget*>(QString(), Qt::FindDirectChildrenOnly);
|
|
if (singleDockWidget && singleDockWidget->allowedAreas() == Qt::NoDockWidgetArea)
|
|
{
|
|
dragEnabled = true;
|
|
}
|
|
}
|
|
|
|
titleBar->setDragEnabled(dragEnabled);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FancyDocking::updateFloatingPixmap()
|
|
{
|
|
if (m_dropZoneState.dragging() && m_state.placeholder().isValid())
|
|
{
|
|
bool modifiedKeyPressed = FancyDockingDropZoneWidget::CheckModifierKey();
|
|
|
|
m_ghostWidget->setWindowOpacity(modifiedKeyPressed ? 1.0f : g_FancyDockingConstants.draggingDockWidgetOpacity);
|
|
m_ghostWidget->setPixmap(m_state.dockWidgetScreenGrab.screenGrab, m_state.placeholder(), m_state.placeholderScreen());
|
|
}
|
|
}
|
|
|
|
void FancyDocking::StartDropZone(QWidget* dropZoneContainer, const QPoint& globalPos)
|
|
{
|
|
// Find any screens that the drop zone container is on
|
|
QList<QScreen*> dropZoneScreens;
|
|
if (dropZoneContainer)
|
|
{
|
|
QRect dropTargetRect = dropZoneContainer->geometry();
|
|
QWidget* dropTargetParent = dropZoneContainer->parentWidget();
|
|
if (dropTargetParent)
|
|
{
|
|
dropTargetRect.moveTopLeft(dropTargetParent->mapToGlobal(dropTargetRect.topLeft()));
|
|
}
|
|
for (QScreen* screen : m_desktopScreens)
|
|
{
|
|
if (dropTargetRect.intersects(screen->geometry()))
|
|
{
|
|
dropZoneScreens.append(screen);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If there's no drop zone target or couldn't find the screen the drop zone
|
|
// target is on, then pick the screen the mouse is currently on so we can
|
|
// have that drop zone widget warmed up
|
|
if (dropZoneScreens.isEmpty())
|
|
{
|
|
for (QScreen* screen : m_desktopScreens)
|
|
{
|
|
if (screen->geometry().contains(globalPos))
|
|
{
|
|
dropZoneScreens.append(screen);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Raise any current active drop zone widgets that should still be active
|
|
// and stop any that should no longer be active
|
|
int numActiveDropZoneWidgets = m_activeDropZoneWidgets.size();
|
|
for (int i = 0; i < numActiveDropZoneWidgets; ++i)
|
|
{
|
|
FancyDockingDropZoneWidget* dropZoneWidget = m_activeDropZoneWidgets.takeFirst();
|
|
QScreen* dropZoneScreen = dropZoneWidget->GetScreen();
|
|
if (dropZoneScreens.contains(dropZoneScreen))
|
|
{
|
|
// This screen is already active, so remove it from our list of
|
|
// drop zone screens that need to be activated and raise it
|
|
dropZoneScreens.removeAll(dropZoneScreen);
|
|
dropZoneWidget->raise();
|
|
|
|
// Put this drop zone widget back on the end of our active list
|
|
// since we've already processed it
|
|
m_activeDropZoneWidgets.append(dropZoneWidget);
|
|
}
|
|
else
|
|
{
|
|
// Stop this active drop zone widget since it's no longer needed
|
|
dropZoneWidget->Stop();
|
|
}
|
|
}
|
|
|
|
// Any screens left aren't active already, so they need to be created if
|
|
// they haven't been already and started
|
|
for (QScreen* screen : dropZoneScreens)
|
|
{
|
|
// Create this drop zone widget if it doesn't already exist, and add
|
|
// it to our list of active drop zone widgets
|
|
FancyDockingDropZoneWidget* dropZoneWidget = m_dropZoneWidgets[screen];
|
|
if (!dropZoneWidget)
|
|
{
|
|
m_dropZoneWidgets[screen] = dropZoneWidget = new FancyDockingDropZoneWidget(m_mainWindow, this, screen, &m_dropZoneState);
|
|
}
|
|
m_activeDropZoneWidgets.append(dropZoneWidget);
|
|
|
|
// Start and raise this drop zone widget
|
|
dropZoneWidget->Start();
|
|
dropZoneWidget->raise();
|
|
}
|
|
|
|
// floating pixmap is always on top; it'll clip what it's supposed to
|
|
m_ghostWidget->raise();
|
|
}
|
|
|
|
void FancyDocking::StopDropZone()
|
|
{
|
|
if (m_activeDropZoneWidgets.size())
|
|
{
|
|
// we have to ensure that we force a repaint, so that there isn't
|
|
// one frame of junk the next time we show the floating drop zones
|
|
for (FancyDockingDropZoneWidget* dropZoneWidget : m_activeDropZoneWidgets)
|
|
{
|
|
dropZoneWidget->repaint();
|
|
dropZoneWidget->Stop();
|
|
}
|
|
m_activeDropZoneWidgets.clear();
|
|
}
|
|
}
|
|
|
|
bool FancyDocking::ForceTabbedDocksEnabled() const
|
|
{
|
|
return m_mainWindow->dockOptions() & QMainWindow::ForceTabbedDocks;
|
|
}
|
|
|
|
template <typename T>
|
|
T GetProperty(QObject* object, const char* propertyName, const T& returnIfPropertyNotFound)
|
|
{
|
|
QVariant propertyValue = object->property(propertyName);
|
|
if (!propertyValue.isNull() && propertyValue.canConvert<T>())
|
|
{
|
|
return propertyValue.value<T>();
|
|
}
|
|
|
|
return returnIfPropertyNotFound;
|
|
}
|
|
|
|
/**
|
|
* Analog to QMainWindow::saveState(). The state can be restored with FancyDocking::restoreState()
|
|
*/
|
|
QByteArray FancyDocking::saveState()
|
|
{
|
|
SerializedMapType map;
|
|
for (QDockWidget* dockWidget : m_mainWindow->findChildren<QDockWidget*>(
|
|
QRegularExpression(QString("%1.*").arg(m_floatingWindowIdentifierPrefix)), Qt::FindChildrenRecursively))
|
|
{
|
|
QMainWindow* mainWindow = qobject_cast<QMainWindow*>(dockWidget->widget());
|
|
if (!mainWindow)
|
|
{
|
|
continue;
|
|
}
|
|
const auto subs = mainWindow->findChildren<QDockWidget*>(QString(), Qt::FindDirectChildrenOnly);
|
|
|
|
// Don't persist any floating windows that have no dock widgets or only have dock widgets that aren't going to be saved (multiple instance panes)
|
|
bool hasValidTab = false;
|
|
for (auto& sub : subs)
|
|
{
|
|
QDockWidget* subDockWidget = qobject_cast<QDockWidget*>(sub);
|
|
if (subDockWidget)
|
|
{
|
|
bool autoSave = GetProperty(subDockWidget, g_AutoSavePropertyName, true); // we auto-save by default
|
|
hasValidTab |= autoSave;
|
|
}
|
|
}
|
|
if (!hasValidTab)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
QStringList names;
|
|
std::transform(subs.begin(), subs.end(), std::back_inserter(names),
|
|
[](QDockWidget* o) { return o->objectName(); });
|
|
map[dockWidget->objectName()] = qMakePair(names, mainWindow->saveState());
|
|
|
|
// Store geometry for this floating dock widget. This could also be stored
|
|
// in the map since we don't need the main window's save state again, but
|
|
// that would involve a new save/restore version
|
|
if (mainWindow != m_mainWindow)
|
|
{
|
|
QString floatingDockWidgetName = dockWidget->objectName();
|
|
if (!floatingDockWidgetName.isEmpty())
|
|
{
|
|
m_restoreFloatings[floatingDockWidgetName] = qMakePair(mainWindow->saveState(), dockWidget->geometry());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find all of our tab container dock widgets that hold our dock tab widgets
|
|
SerializedTabType tabContainers;
|
|
for (QDockWidget* dockWidget : m_mainWindow->findChildren<QDockWidget*>(
|
|
QRegularExpression(QString("%1.*").arg(m_tabContainerIdentifierPrefix)), Qt::FindChildrenRecursively))
|
|
{
|
|
DockTabWidget* tabWidget = qobject_cast<DockTabWidget*>(dockWidget->widget());
|
|
if (!tabWidget)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Retrieve the names of dock widgets tabbed inside the tab widget
|
|
// which will be what is matched against when restoring the state
|
|
QStringList tabNames;
|
|
int numTabs = tabWidget->count();
|
|
for (int i = 0; i < numTabs; ++i)
|
|
{
|
|
QWidget* tabbedWidget = tabWidget->widget(i);
|
|
if (tabbedWidget)
|
|
{
|
|
tabNames.append(tabbedWidget->objectName());
|
|
}
|
|
}
|
|
|
|
// Retrieve the main window for the tab widget so that we can see if it
|
|
// is docked in our main window, or in a floating window
|
|
QMainWindow* mainWindow = qobject_cast<QMainWindow*>(dockWidget->parentWidget());
|
|
if (!mainWindow)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// If the tab container is docked in our main window, we will store the
|
|
// floatingDockName as empty. Otherwise, we need to retrieve the name
|
|
// of the floating dock widget so we can restore the tab container
|
|
// to the appropriate main window.
|
|
QString floatingDockName;
|
|
if (mainWindow != m_mainWindow)
|
|
{
|
|
QDockWidget* floatingDockWidget = qobject_cast<QDockWidget*>(mainWindow->parentWidget());
|
|
if (floatingDockWidget)
|
|
{
|
|
floatingDockName = floatingDockWidget->objectName();
|
|
}
|
|
}
|
|
|
|
// Store this tab container state
|
|
TabContainerType state;
|
|
state.floatingDockName = floatingDockName;
|
|
state.tabNames = tabNames;
|
|
state.currentIndex = tabWidget->currentIndex();
|
|
tabContainers[dockWidget->objectName()] = state;
|
|
}
|
|
|
|
QByteArray stateData;
|
|
QDataStream stream(&stateData, QIODevice::WriteOnly);
|
|
stream << quint32(VersionMarker) << m_mainWindow->saveState() << map
|
|
<< m_placeholders << m_restoreFloatings << tabContainers;
|
|
return stateData;
|
|
}
|
|
|
|
/**
|
|
* Analog to QMainWindow::restoreState(). The state must be created with FancyDocking::saveState()
|
|
*/
|
|
bool FancyDocking::restoreState(const QByteArray& state)
|
|
{
|
|
if (state.isEmpty())
|
|
{
|
|
return false;
|
|
}
|
|
QByteArray stateData = state;
|
|
QDataStream stream(&stateData, QIODevice::ReadOnly);
|
|
|
|
quint32 version;
|
|
SerializedMapType map;
|
|
SerializedTabType tabContainers;
|
|
QByteArray mainState;
|
|
stream >> version;
|
|
if (stream.status() != QDataStream::Ok || version != VersionMarker)
|
|
{
|
|
return false;
|
|
}
|
|
stream >> mainState >> map;
|
|
if (stream.status() != QDataStream::Ok)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
stream >> m_placeholders >> m_restoreFloatings >> tabContainers;
|
|
|
|
// First, delete all the current floating window
|
|
for (QDockWidget* dockWidget : m_mainWindow->findChildren<QDockWidget*>(
|
|
QRegularExpression(QString("%1.*").arg(m_floatingWindowIdentifierPrefix)), Qt::FindChildrenRecursively))
|
|
{
|
|
QMainWindow* mainWindow = qobject_cast<QMainWindow*>(dockWidget->widget());
|
|
if (!mainWindow)
|
|
{
|
|
continue;
|
|
}
|
|
for (QDockWidget* subDockWidget : mainWindow->findChildren<QDockWidget*>(QString(), Qt::FindDirectChildrenOnly))
|
|
{
|
|
OptimizedSetParent(subDockWidget, m_mainWindow);
|
|
if (!m_mainWindow->restoreDockWidget(subDockWidget))
|
|
{
|
|
m_mainWindow->addDockWidget(Qt::LeftDockWidgetArea, subDockWidget);
|
|
}
|
|
}
|
|
delete dockWidget;
|
|
}
|
|
|
|
// Untab tabbed dock widgets before restoring, as the restore only works on dock widgets parented directly to the main window
|
|
for (QDockWidget* dockWidget : m_mainWindow->findChildren<QDockWidget*>(
|
|
QRegularExpression(QString("%1.*").arg(m_tabContainerIdentifierPrefix)), Qt::FindChildrenRecursively))
|
|
{
|
|
DockTabWidget* tabWidget = qobject_cast<DockTabWidget*>(dockWidget->widget());
|
|
if (!tabWidget)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Remove the tabs from the tab widget (we don't actually want to close them, which could delete them at this point)
|
|
int numTabs = tabWidget->count();
|
|
for (int i = 0; i < numTabs; ++i)
|
|
{
|
|
tabWidget->removeTab(0);
|
|
}
|
|
}
|
|
|
|
// Restore the floating windows
|
|
QHash<QMainWindow*, QByteArray> floatingMainWindows;
|
|
for (auto it = map.begin(); it != map.end(); ++it)
|
|
{
|
|
QString floatingDockName = it.key();
|
|
QStringList childDockNames = it->first;
|
|
QByteArray floatingState = it->second;
|
|
|
|
// Don't restore any floating windows that have no cached dock widgets
|
|
if (childDockNames.size() == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Since the names of panes could change, we need to make sure at least
|
|
// one of the panes in the floating window still exist, otherwise we would
|
|
// be left with an empty floating window
|
|
if (!AnyDockWidgetsExist(childDockNames))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Iterate over the child dock widgets to determine their dock widget options; must do this first before creating the floating main window
|
|
QVector<QDockWidget*> childDockWidgets;
|
|
bool skipTitleBarOverdraw = false;
|
|
for (const QString &childName : childDockNames)
|
|
{
|
|
QDockWidget* child = m_mainWindow->findChild<QDockWidget*>(childName, Qt::FindDirectChildrenOnly);
|
|
if (!child)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// save the children so that we don't have to do a find on the main window again in the next loop
|
|
childDockWidgets.push_back(child);
|
|
|
|
skipTitleBarOverdraw = skipTitleBarOverdraw || shouldSkipTitleBarOverdraw(child);
|
|
}
|
|
|
|
// Restore geometry for this floating dock widget
|
|
QRect restoredRect;
|
|
auto restoreFloating = m_restoreFloatings.find(floatingDockName);
|
|
if (restoreFloating != m_restoreFloatings.end())
|
|
{
|
|
restoredRect = restoreFloating->second;
|
|
m_restoreFloatings.erase(restoreFloating);
|
|
}
|
|
|
|
// reparent and dock the child widgets to the new container now
|
|
QMainWindow* mainWindow = createFloatingMainWindow(floatingDockName, restoredRect, skipTitleBarOverdraw);
|
|
for (auto t = childDockWidgets.begin(); t != childDockWidgets.end(); t++)
|
|
{
|
|
QDockWidget* child = *t;
|
|
OptimizedSetParent(child, mainWindow);
|
|
mainWindow->addDockWidget(Qt::LeftDockWidgetArea, child);
|
|
}
|
|
|
|
// Store the floating main window with its state so we can restore them
|
|
// after the tab containers have been restored
|
|
floatingMainWindows[mainWindow] = floatingState;
|
|
}
|
|
|
|
// Restore our tab containers (need to set our updateInProgress flag here
|
|
// as well or floating windows that contain tab containers will get
|
|
// deleted inadvertently)
|
|
QScopedValueRollback<bool> rollback(m_state.updateInProgress, true);
|
|
for (auto it = tabContainers.begin(); it != tabContainers.end(); ++it)
|
|
{
|
|
QString tabContainerName = it.key();
|
|
TabContainerType tabState = it.value();
|
|
QString floatingDockName = tabState.floatingDockName;
|
|
QStringList tabNames = tabState.tabNames;
|
|
int currentIndex = tabState.currentIndex;
|
|
|
|
// Since the names of panes could change, we need to make sure at least
|
|
// one of the panes in the tab container still exist, otherwise we would
|
|
// be left with an empty tab container
|
|
if (!AnyDockWidgetsExist(tabNames))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// If the floatingDockName is empty, then this tab container is meant
|
|
// for our main window
|
|
QMainWindow* mainWindow = nullptr;
|
|
if (floatingDockName.isEmpty())
|
|
{
|
|
mainWindow = m_mainWindow;
|
|
}
|
|
// Otherwise, we need to find the floating dock widget that was
|
|
// restored previously so we can get a reference to its main window
|
|
else
|
|
{
|
|
QDockWidget* floatingDockWidget = m_mainWindow->findChild<QDockWidget*>(floatingDockName, Qt::FindDirectChildrenOnly);
|
|
if (!floatingDockWidget)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
mainWindow = qobject_cast<QMainWindow*>(floatingDockWidget->widget());
|
|
if (!mainWindow)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Create a new tab container and tab widget with the same name as the cached tab container
|
|
// so it will be restored in the same spot in the appropriate main window layout
|
|
DockTabWidget* tabWidget = createTabWidget(mainWindow, nullptr, tabContainerName);
|
|
|
|
// Move the dock widgets for the specified tabs into our tab widget
|
|
for (QString name : tabNames)
|
|
{
|
|
// The dock widgets will be restored with the same name in the main window, they just won't
|
|
// be in the proper layout since we have our own custom tab system
|
|
QDockWidget* dockWidget = m_mainWindow->findChild<QDockWidget*>(name);
|
|
if (!dockWidget || dockWidget->allowedAreas() == Qt::NoDockWidgetArea)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Move the dock widget into our tab widget
|
|
tabWidget->addTab(dockWidget);
|
|
}
|
|
|
|
// Restore the cached active tab index
|
|
tabWidget->setCurrentIndex(currentIndex);
|
|
}
|
|
|
|
// Restore the state of our floating main windows after the tab containers have
|
|
// been restored, so that their place in the floating main window layouts will
|
|
// be restored properly. Also keep track if any of our main window restore
|
|
// calls fail so we can report back our status.
|
|
bool ok = true;
|
|
for (auto it = floatingMainWindows.begin(); it != floatingMainWindows.end(); ++it)
|
|
{
|
|
QMainWindow* mainWindow = it.key();
|
|
QByteArray floatingState = it.value();
|
|
if (!mainWindow->restoreState(floatingState))
|
|
{
|
|
ok = false;
|
|
}
|
|
}
|
|
|
|
// Restore the main layout
|
|
if (!m_mainWindow->restoreState(mainState))
|
|
{
|
|
ok = false;
|
|
}
|
|
|
|
// If any dock widgets are currently docked to the main window that have
|
|
// docking disabled, this means they were previously docked to the main
|
|
// window (or tabbed) and the isDockable flag was later changed to false,
|
|
// so we need to change them to floating dock widgets
|
|
for (QDockWidget* subDockWidget : m_mainWindow->findChildren<QDockWidget*>(QString(), Qt::FindDirectChildrenOnly))
|
|
{
|
|
if (subDockWidget->allowedAreas() == Qt::NoDockWidgetArea)
|
|
{
|
|
makeDockWidgetFloating(subDockWidget, subDockWidget->geometry());
|
|
}
|
|
}
|
|
|
|
// Update the TitleBars for the Main Editor Window and all floating windows
|
|
UpdateTitleBars(m_mainWindow);
|
|
for (auto it = floatingMainWindows.begin(); it != floatingMainWindows.end(); ++it)
|
|
{
|
|
UpdateTitleBars(it.key());
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
/**
|
|
* Same as QMainWindow::restoreDockWidget, but extended to checking if it was
|
|
* last in one of our custom tab widgets or floating windows
|
|
*/
|
|
bool FancyDocking::restoreDockWidget(QDockWidget* dock)
|
|
{
|
|
if (!dock)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// First, check if this dock widget was last in a tab container
|
|
QString dockObjectName = dock->objectName();
|
|
if (m_lastTabContainerForDockWidget.contains(dockObjectName))
|
|
{
|
|
QString tabDockWidgetName = m_lastTabContainerForDockWidget[dockObjectName];
|
|
QDockWidget* dockWidget = m_mainWindow->findChild<QDockWidget*>(tabDockWidgetName);
|
|
if (dockWidget)
|
|
{
|
|
DockTabWidget* tabWidget = qobject_cast<DockTabWidget*>(dockWidget->widget());
|
|
if (tabWidget)
|
|
{
|
|
tabWidget->addTab(dock);
|
|
UpdateTitleBars(qobject_cast<QMainWindow*>(dockWidget->widget()));
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Then, check if it was last in a floating window
|
|
auto it = m_placeholders.find(dockObjectName);
|
|
if (it != m_placeholders.end())
|
|
{
|
|
// The QDockWidget we try to restore was last seen in a floating QMainWindow.
|
|
QString floatingDockWidgetName = *it;
|
|
QDockWidget* dockWidget = m_mainWindow->findChild<QDockWidget*>(floatingDockWidgetName);
|
|
if (dockWidget)
|
|
{
|
|
// That floating QMainWindow still exist.
|
|
QMainWindow* mainWindow = qobject_cast<QMainWindow*>(dockWidget->widget());
|
|
if (mainWindow)
|
|
{
|
|
OptimizedSetParent(dock, mainWindow);
|
|
|
|
if (mainWindow->restoreDockWidget(dock))
|
|
{
|
|
UpdateTitleBars(mainWindow);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// It no longer exists, so we need to re-create the floating main
|
|
// window before restoring the dock widget
|
|
auto it2 = m_restoreFloatings.find(floatingDockWidgetName);
|
|
if (it2 != m_restoreFloatings.end())
|
|
{
|
|
QMainWindow* mainWindow = createFloatingMainWindow(floatingDockWidgetName, it2->second, true);
|
|
mainWindow->restoreState(it2->first);
|
|
OptimizedSetParent(dock, mainWindow);
|
|
m_restoreFloatings.erase(it2);
|
|
|
|
if (mainWindow->restoreDockWidget(dock))
|
|
{
|
|
UpdateTitleBars(mainWindow);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
m_placeholders.erase(it);
|
|
}
|
|
|
|
// Fallback to letting our main window try to restore it
|
|
return m_mainWindow->restoreDockWidget(dock);
|
|
}
|
|
|
|
/**
|
|
* Clear our dragging state and remove the any drop zones that have been setup
|
|
*/
|
|
void FancyDocking::clearDraggingState()
|
|
{
|
|
if (QApplication::overrideCursor())
|
|
{
|
|
QApplication::restoreOverrideCursor();
|
|
}
|
|
|
|
m_ghostWidget->hide();
|
|
|
|
// Release the mouse and keyboard from our main window since we grab them when we start dragging
|
|
m_mainWindow->releaseMouse();
|
|
m_mainWindow->releaseKeyboard();
|
|
|
|
// Restore the dragged widget to its dock widget, and reparent our empty
|
|
// placeholder widget to ourselves so that it will get cleaned up properly
|
|
// We do this outside of the check for m_state.dock since there is a case
|
|
// where the m_state.dock could no longer exist if you had ripped out a
|
|
// single tab which would result in the tab container being destroyed
|
|
if (m_state.draggedDockWidget)
|
|
{
|
|
// If the drag was cancelled before the mouse had actually moved far enough to
|
|
// initiate the drag (manhattan length), then we don't need to restore the actual
|
|
// dock widget contents since they will have remained unchanged
|
|
if (m_state.draggedWidget)
|
|
{
|
|
m_state.draggedDockWidget->setWidget(m_state.draggedWidget);
|
|
}
|
|
|
|
m_state.draggedDockWidget = nullptr;
|
|
m_state.draggedWidget = nullptr;
|
|
m_emptyWidget->hide();
|
|
m_emptyWidget->setParent(this);
|
|
}
|
|
|
|
// If we hid the floating container of the dragged widget because it was
|
|
// the only visible one, then we need to show it again
|
|
if (m_state.dock)
|
|
{
|
|
QMainWindow* mainWindow = qobject_cast<QMainWindow*>(m_state.dock->parentWidget());
|
|
if (mainWindow && mainWindow != m_mainWindow)
|
|
{
|
|
QDockWidget* containerDockWidget = qobject_cast<QDockWidget*>(mainWindow->parentWidget());
|
|
if (containerDockWidget && containerDockWidget->isFloating() && !containerDockWidget->isVisible())
|
|
{
|
|
containerDockWidget->show();
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we were dragging from a tab widget, make sure to reset its drag state
|
|
if (m_state.tabWidget)
|
|
{
|
|
m_state.tabWidget->finishDrag();
|
|
}
|
|
|
|
m_state.dock = nullptr;
|
|
m_dropZoneState.setDragging(false);
|
|
m_state.tabWidget = nullptr;
|
|
m_state.setPlaceholder(QRect(), nullptr);
|
|
m_state.floatingDockContainer = nullptr;
|
|
m_state.snappedSide = 0;
|
|
|
|
StopDropZone();
|
|
setupDropZones(nullptr);
|
|
|
|
m_ghostWidget->Disable();
|
|
}
|
|
|
|
/**
|
|
* Check if at least one of the the specified dock widgets exist on our
|
|
* main window
|
|
*/
|
|
bool FancyDocking::AnyDockWidgetsExist(QStringList names)
|
|
{
|
|
for (QString name : names)
|
|
{
|
|
// Consider a tab container dock widget as a success case because
|
|
// these will be created by us when restoring state
|
|
if (name.startsWith(m_tabContainerIdentifierPrefix))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
QDockWidget* dockWidget = m_mainWindow->findChild<QDockWidget*>(name, Qt::FindDirectChildrenOnly);
|
|
if (dockWidget)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns the offset which should be used to account for the height of the TitleBar.
|
|
*
|
|
* We want the height of the TitleBar of the QMainWindow that this QDockWidget gets placed into
|
|
* when it is floated, not the TitleBar of the QDockWidget itself.
|
|
*/
|
|
int FancyDocking::titleBarOffset(const QDockWidget* dockWidget) const
|
|
{
|
|
if (auto outerDockWidget = qobject_cast<StyledDockWidget*>(dockWidget->parentWidget()->parentWidget()))
|
|
{
|
|
return style()->pixelMetric(QStyle::PM_TitleBarHeight, nullptr, outerDockWidget->customTitleBar());
|
|
}
|
|
else
|
|
{
|
|
// This does not return the perfect value every time because the first time a widget is
|
|
// undocked this function is called before the new QMainWindow and TitleBar have been
|
|
// created. In this case we use the dockWidget titleBarWidget to guess a reasonable
|
|
// value.
|
|
return style()->pixelMetric(QStyle::PM_TitleBarHeight, nullptr, dockWidget->titleBarWidget());
|
|
}
|
|
}
|
|
|
|
} // namespace AzQtComponents
|
|
|
|
#include "Components/moc_FancyDocking.cpp"
|