Compare commits

...

48 Commits

Author SHA1 Message Date
Renato Araujo Oliveira Filho
6fce8e6161 Fixed python bindings generation 2020-10-15 09:55:32 -03:00
Sergio Martins
6479bcabfb Make DropArea::drop() receive WindowBeingDragged instead
In prep for wayland support
2020-10-14 20:42:51 +01:00
Sergio Martins
a8c9735652 wayland: Add needed API to WindowBeingDragged
So it can act as a substitute for FloatingWindow, which we don't
always have during a drag
2020-10-14 20:34:55 +01:00
Sergio Martins
93b25b6a31 Make MultiSplitter::rectForDrop() receive WindowBeingDragged
Instead of FloatingWindow, which won't exist on wayland while a
drag is in progress.
2020-10-14 20:25:09 +01:00
Sergio Martins
4eb5a0940e dont' cast pointer to bool 2020-10-14 19:44:27 +01:00
Sergio Martins
c2cc914b5c indicators: Depend less on FloatingWindow
Deal in terms of WindowBeingDragged instead, as wayland won't have
a floating window being dragged
2020-10-14 19:39:51 +01:00
Sergio Martins
1219ba90af Add WindowBeingDragged::affinities()
So we can deal in WindowBeingDragged instead of FloatingWindow.
For wayland we won't have floating window whilst dragging
2020-10-14 19:22:13 +01:00
Sergio Martins
beef3c7fb5 Specialize StateDragging for wayland
Introduces StateDraggingWayland. Wayland is the alien, so don't want
to introduce hacks in the existing clean code.

StateDraggingWayland will deal with the wayland workarounds.

Although the impl only has placeholders for now I'm committing it
because it's already an improvement: broken detachment is no longer
possible. Detached windows were going to a random place on screen.
2020-10-14 18:12:25 +01:00
Sergio Martins
52184ca72b Added StateBase::isActiveState() 2020-10-14 18:01:48 +01:00
Sergio Martins
78cd7c56f7 Minor: Switch order of ifdefs
Will want to add a case for wayland too.
2020-10-14 16:41:17 +01:00
Sergio Martins
7805a1dc25 wayland: Use both client and native titlebar
native will be used for moving while client for DND
2020-10-14 16:32:10 +01:00
Sergio Martins
92f426e4c0 wayland: Allow floating windows to properly move
They move now, but don't drag yet.
2020-10-14 16:25:05 +01:00
Sergio Martins
90f10042fd Merge branch '1.1' into master 2020-10-14 15:22:04 +01:00
Sergio Martins
3369816d31 Merge branch '1.1' into master 2020-10-14 14:59:44 +01:00
Allen Winter
860cbd29bc Merge branch '1.1' 2020-10-13 13:24:03 -04:00
Allen Winter
2fd4f9ce97 Merge branch '1.1' 2020-10-13 12:51:38 -04:00
Sergio Martins
697cc34c23 Merge branch '1.1' into master 2020-10-12 18:10:53 +01:00
Sergio Martins
d50fcb80c5 qtquick: Fixed all the layouting spam/warnings
Don't layout a view with 0x0.
2020-10-12 17:07:30 +01:00
Sergio Martins
9571ffc30e Remove unneeded qDebug 2020-10-12 17:05:39 +01:00
Sergio Martins
f4a88276b8 qtquick|tests: ported two more tests 2020-10-11 13:05:51 +01:00
Sergio Martins
4ea254029a qtquick: Fix tst_negativeAnchorPosition3
MainWindow needed a parent.
Also normalized deletion of the floating window.
2020-10-11 13:00:09 +01:00
Sergio Martins
f5e85c2196 qtquick|tests: Add a few more tests 2020-10-11 12:55:25 +01:00
Sergio Martins
926103325c qtquick: Fix close events not being delivered
Also ported some tests from tst_docks to tst_common, which
were failing due to close events not working. Fixed now
2020-10-11 12:42:20 +01:00
Sergio Martins
20b2988165 Unfold an assert into a qWarning+assert
Just to print some debug data
2020-10-11 11:58:32 +01:00
Sergio Martins
39aefd312b qtquick|tests: Don't abort tests due to binding loop warnings
Don't want to care about it right now
2020-10-11 11:53:55 +01:00
Sergio Martins
b5478bcb0d tests: Enable a bunch of tests for QtQuick
only one is failing
2020-10-11 11:44:47 +01:00
Sergio Martins
d403557b9e tests: Abstract QPushButton somewhat
Anything refering QPushButton will simply compile with QtQuick.
2020-10-10 21:07:18 +01:00
Sergio Martins
db9884ea64 qtquick: Enable tst_setFloatingSimple and tst_restoreSimple 2020-10-10 20:53:27 +01:00
Sergio Martins
74148aabc3 tests: Don't complain there's a leak if the FloatingWindow is being deleted already 2020-10-10 20:52:56 +01:00
Sergio Martins
7bfac091ae Rename DockRegistry::nestedwindows() to DockRegistry::floatingWindow
which is the naming we use everywhere else
2020-10-10 20:41:42 +01:00
Sergio Martins
60a68817b6 qt_quick: Enable tst_restoreCentralFrame too
Removes a warning from FrameQuick. It's benign.
For QtWidgets we also set a -1 tab index to clear
2020-10-10 20:25:46 +01:00
Sergio Martins
a56e6d7fe8 qtquick: Enable tst_simple2 2020-10-10 20:10:43 +01:00
Sergio Martins
1f0b208922 qtquick: Enable tst_resizeWindow too 2020-10-10 12:15:18 +01:00
Sergio Martins
9da147f50e qtquick: Enable tst_floatingWindowTitleBug too 2020-10-10 11:52:38 +01:00
Sergio Martins
c175451284 qtquick: Fix user geometry not being honoured
QtQuick doesn't set Qt::WA_Moved, which we expect. Set this attribute
in the wrappers then.

Fixes the unit-test too.
2020-10-10 11:46:24 +01:00
Sergio Martins
9a88cf3a72 tests: Enable tst_honourUserGeometry for QtQuick
It's failing. Will fix.
2020-10-10 11:36:16 +01:00
Sergio Martins
d0255e5310 tests: Use DockWidgetType instead of DockWidget
Will be easier to enable QtQuick support
2020-10-10 11:24:32 +01:00
Sergio Martins
2a2fb0a4f2 tests: Enable tst_sizeAfterRedock for QtQuick too 2020-10-10 11:18:40 +01:00
Sergio Martins
b0ad939db8 tests: Enable tst_tabbingWithAffinities for QtQuick 2020-10-10 11:18:40 +01:00
Allen Winter
5abf118b8c Merge branch '1.1' 2020-10-10 05:30:38 -04:00
Sergio Martins
30949bd4c3 quick: Fix test, don't leak window 2020-10-09 19:15:55 +01:00
Sergio Martins
63279c187e quick: Fix QtQuick not reacting to parent change events
- We were calling event() directly, which skiped event filters.
- The handler was calling parent(), but the setParent() call is done
later. So first call setParentItem()
2020-10-09 19:09:45 +01:00
Sergio Martins
fad03f8e0b Debug++ 2020-10-09 19:08:51 +01:00
Sergio Martins
6edfcc4f02 Install DragController_p.h
Still private. It's for advanced used only.
2020-10-09 17:26:00 +01:00
Sergio Martins
62cbd823c9 quick: Assert that we're not using this QtWidgets only function 2020-10-08 16:25:29 +01:00
Sergio Martins
bdcc211308 quick: Resize FloatingWindow and it's QWindow immediately
So it matches QtWidgets behaviour, otherwise tests will
break a lot if they have to wait for an event loop for FloatingWindow
to be resized
2020-10-08 16:20:12 +01:00
Sergio Martins
ddeb4611a1 quick: Fix an invalid cast to QWidget
We don't have QtWidgets in QtQuick.
Will try to fix it at a build-system level so we catch these
cases sooner.
2020-10-08 16:15:56 +01:00
Allen Winter
ad639c8001 CMakeLists.txt - increase version number for 1.2 2020-10-08 09:48:16 -04:00
43 changed files with 1352 additions and 992 deletions

View File

@@ -73,9 +73,10 @@ endif()
set(${PROJECT_NAME}_VERSION_MAJOR 1)
set(${PROJECT_NAME}_VERSION_MINOR 1)
set(${PROJECT_NAME}_VERSION_PATCH 0)
set(${PROJECT_NAME}_VERSION_PATCH 95)
set(${PROJECT_NAME}_VERSION ${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}.${${PROJECT_NAME}_VERSION_PATCH})
set(${PROJECT_NAME}_SOVERSION "1.1")
set(${PROJECT_NAME}_SOVERSION "1.2")
include(FeatureSummary)

View File

@@ -11,7 +11,7 @@ from conans import ConanFile, CMake, tools
class KDDockWidgetsConan(ConanFile):
name = "kddockwidgets"
version = "1.1.0"
version = "1.1.95"
default_user = "kdab"
default_channel = "stable"
license = ("https://raw.githubusercontent.com/KDAB/KDDockWidgets/master/LICENSES/GPL-2.0-only.txt",

View File

@@ -11,6 +11,7 @@
<!-- this is used in a public virtual pure function we need to declare it
otherwise shiboken will ignore the function and will fail to create a wrapper -->
<primitive-type name="DropAreaWithCentralFrame"/>
<primitive-type name="SideBar"/>
<!-- Some plublic enum and flags -->
<enum-type name="Location"/>
@@ -19,6 +20,9 @@
<enum-type name="RestoreOption" flags="RestoreOptions"/>
<enum-type name="DefaultSizeMode"/>
<enum-type name="FrameOption" flags="FrameOptions"/>
<enum-type name="DropIndicatorType"/>
<enum-type name="SideBarLocation"/>
<enum-type name="TitleBarButtonType"/>
<!-- our classes
For class we can use two types:
@@ -32,6 +36,7 @@
<!-- this class contains a internal enum, so it should be declared
inside of the object-type -->
<enum-type name="Option" flags="Options" />
<enum-type name="IconPlace" flags="IconPlaces" />
</object-type>
<object-type name="DockWidget" />

View File

@@ -21,6 +21,7 @@
#include "multisplitter/Widget_qwidget.h"
#include "DockRegistry_p.h"
#include "FrameworkWidgetFactory.h"
#include "Utils_p.h"
#include <QApplication>
#include <QDebug>
@@ -83,8 +84,11 @@ Config::Flags Config::flags() const
void Config::setFlags(Flags f)
{
if (!DockRegistry::self()->isEmpty()) {
qWarning() << Q_FUNC_INFO << "Only use this function at startup before creating any DockWidget or MainWindow";
auto dr = DockRegistry::self();
if (!dr->isEmpty(/*excludeBeingDeleted=*/ true)) {
qWarning() << Q_FUNC_INFO << "Only use this function at startup before creating any DockWidget or MainWindow"
<< "; These are already created: " << dr->mainWindowsNames()
<< dr->dockWidgetNames() << dr->floatingWindows();
return;
}
@@ -135,7 +139,7 @@ int Config::separatorThickness() const
void Config::setSeparatorThickness(int value)
{
if (!DockRegistry::self()->isEmpty()) {
if (!DockRegistry::self()->isEmpty(/*excludeBeingDeleted=*/ true)) {
qWarning() << Q_FUNC_INFO << "Only use this function at startup before creating any DockWidget or MainWindow";
return;
}
@@ -188,10 +192,18 @@ void Config::Private::fixFlags()
// Not supported on macOS:
m_flags = m_flags & ~Flag_AeroSnapWithClientDecos;
#else
// Not supported on linux.
// On Linux, dragging the title bar of a window doesn't generate NonClientMouseEvents
m_flags = m_flags & ~Flag_NativeTitleBar;
m_flags = m_flags & ~Flag_AeroSnapWithClientDecos;
if (KDDockWidgets::isWayland()) {
// Native title bar is forced on Wayland. Needed for moving the window.
// The inner KDDW title bar is used for DnD.
m_flags |= Flag_NativeTitleBar;
} else {
// Not supported on linux/X11
// On Linux, dragging the title bar of a window doesn't generate NonClientMouseEvents
// at least with KWin anyway. We can make this more granular and allow it for other
// X11 window managers
m_flags = m_flags & ~Flag_NativeTitleBar;
m_flags = m_flags & ~Flag_AeroSnapWithClientDecos;
}
#endif
#if !defined(Q_OS_WIN) && !defined(Q_OS_MACOS)

View File

@@ -786,7 +786,7 @@ void DockWidgetBase::onHidden(bool spontaneous)
}
}
void DockWidgetBase::onClosed(QCloseEvent *e)
void DockWidgetBase::onCloseEvent(QCloseEvent *e)
{
e->accept(); // By default we accept, means DockWidget closes
if (d->widget)

View File

@@ -411,7 +411,9 @@ protected:
void onParentChanged();
void onShown(bool spontaneous);
void onHidden(bool spontaneous);
void onClosed(QCloseEvent *e);
#ifndef PYTHON_BINDINGS //Pyside bug: https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-1327
void onCloseEvent(QCloseEvent *e) override;
#endif
#if defined(DOCKS_DEVELOPER_MODE)
public Q_SLOTS:

View File

@@ -163,7 +163,7 @@ QByteArray LayoutSaver::serializeLayout() const
layout.mainWindows.push_back(mainWindow->serialize());
}
const QVector<KDDockWidgets::FloatingWindow*> floatingWindows = d->m_dockRegistry->nestedwindows();
const QVector<KDDockWidgets::FloatingWindow*> floatingWindows = d->m_dockRegistry->floatingWindows();
layout.floatingWindows.reserve(floatingWindows.size());
for (KDDockWidgets::FloatingWindow *floatingWindow : floatingWindows) {
if (d->matchesAffinity(floatingWindow->affinities()))

View File

@@ -200,7 +200,7 @@ DebugWindow::DebugWindow(QWidget *parent)
mainWindow->multiSplitter()->checkSanity();
}
const auto floatingWindows = DockRegistry::self()->nestedwindows();
const auto floatingWindows = DockRegistry::self()->floatingWindows();
for (FloatingWindow *floatingWindow : floatingWindows) {
floatingWindow->multiSplitter()->checkSanity();
}
@@ -308,7 +308,7 @@ void DebugWindow::repaintWidgetRecursive(QWidget *w)
void DebugWindow::dumpDockWidgetInfo()
{
const QVector<FloatingWindow*> floatingWindows = DockRegistry::self()->nestedwindows();
const QVector<FloatingWindow*> floatingWindows = DockRegistry::self()->floatingWindows();
const MainWindowBase::List mainWindows = DockRegistry::self()->mainwindows();
const DockWidgetBase::List dockWidgets = DockRegistry::self()->dockwidgets();

View File

@@ -17,6 +17,7 @@
#include "QWidgetAdapter.h"
#include "Config.h"
#include "SideBar_p.h"
#include "WindowBeingDragged_p.h"
#include <QPointer>
#include <QDebug>
@@ -100,9 +101,13 @@ void DockRegistry::onFocusObjectChanged(QObject *obj)
Q_EMIT m_focusedDockWidget->isFocusedChanged(true);
}
bool DockRegistry::isEmpty() const
bool DockRegistry::isEmpty(bool excludeBeingDeleted) const
{
return m_dockWidgets.isEmpty() && m_mainWindows.isEmpty() && m_nestedWindows.isEmpty();
if (!m_dockWidgets.isEmpty() || !m_mainWindows.isEmpty())
return false;
return excludeBeingDeleted ? !hasFloatingWindows()
: m_floatingWindows.isEmpty();
}
void DockRegistry::checkSanityAll(bool dumpLayout)
@@ -160,7 +165,7 @@ bool DockRegistry::isProbablyObscured(QWindow *window, FloatingWindow *exclude)
return false;
const QRect geo = window->geometry();
for (FloatingWindow *fw : m_nestedWindows) {
for (FloatingWindow *fw : m_floatingWindows) {
QWindow *fwWindow = fw->QWidgetAdapter::windowHandle();
if (fw == exclude || fwWindow == window)
continue;
@@ -186,6 +191,14 @@ bool DockRegistry::isProbablyObscured(QWindow *window, FloatingWindow *exclude)
return false;
}
bool DockRegistry::isProbablyObscured(QWindow *target, WindowBeingDragged *exclude) const
{
FloatingWindow *fw = exclude ? exclude->floatingWindow()
: nullptr; // It's null on Wayland. On wayland obscuring never happens anyway, so not a problem.
return isProbablyObscured(target, fw);
}
SideBarLocation DockRegistry::sideBarLocationForDockWidget(const DockWidgetBase *dw) const
{
if (SideBar *sb = sideBarForDockWidget(dw))
@@ -222,7 +235,7 @@ MultiSplitter *DockRegistry::layoutForItem(const Layouting::Item *item) const
if (!item->hostWidget())
return nullptr;
if (auto ms = qobject_cast<MultiSplitter*>(item->hostWidget()->asQWidget()))
if (auto ms = qobject_cast<MultiSplitter*>(item->hostWidget()->asQObject()))
return ms;
return nullptr;
@@ -284,14 +297,14 @@ void DockRegistry::unregisterMainWindow(MainWindowBase *mainWindow)
maybeDelete();
}
void DockRegistry::registerNestedWindow(FloatingWindow *window)
void DockRegistry::registerFloatingWindow(FloatingWindow *window)
{
m_nestedWindows << window;
m_floatingWindows << window;
}
void DockRegistry::unregisterNestedWindow(FloatingWindow *window)
void DockRegistry::unregisterFloatingWindow(FloatingWindow *window)
{
m_nestedWindows.removeOne(window);
m_floatingWindows.removeOne(window);
maybeDelete();
}
@@ -443,12 +456,12 @@ const Frame::List DockRegistry::frames() const
return m_frames;
}
const QVector<FloatingWindow *> DockRegistry::nestedwindows() const
const QVector<FloatingWindow *> DockRegistry::floatingWindows() const
{
// Returns all the FloatingWindow which aren't being deleted
QVector<FloatingWindow *> result;
result.reserve(m_nestedWindows.size());
for (FloatingWindow *fw : m_nestedWindows) {
result.reserve(m_floatingWindows.size());
for (FloatingWindow *fw : m_floatingWindows) {
if (!fw->beingDeleted())
result.push_back(fw);
}
@@ -456,11 +469,11 @@ const QVector<FloatingWindow *> DockRegistry::nestedwindows() const
return result;
}
const QVector<QWindow *> DockRegistry::floatingWindows() const
const QVector<QWindow *> DockRegistry::floatingQWindows() const
{
QVector<QWindow *> windows;
windows.reserve(m_nestedWindows.size());
for (FloatingWindow *fw : m_nestedWindows) {
windows.reserve(m_floatingWindows.size());
for (FloatingWindow *fw : m_floatingWindows) {
if (!fw->beingDeleted()) {
if (QWindow *window = fw->windowHandle()) {
window->setProperty("kddockwidgets_qwidget", QVariant::fromValue<QWidgetOrQuick*>(fw)); // Since QWidgetWindow is private API
@@ -474,9 +487,16 @@ const QVector<QWindow *> DockRegistry::floatingWindows() const
return windows;
}
bool DockRegistry::hasFloatingWindows() const
{
return std::any_of(m_floatingWindows.begin(), m_floatingWindows.end(), [] (FloatingWindow *fw) {
return !fw->beingDeleted();
});
}
FloatingWindow *DockRegistry::floatingWindowForHandle(QWindow *windowHandle) const
{
for (FloatingWindow *fw : m_nestedWindows) {
for (FloatingWindow *fw : m_floatingWindows) {
if (fw->windowHandle() == windowHandle)
return fw;
}
@@ -487,10 +507,10 @@ FloatingWindow *DockRegistry::floatingWindowForHandle(QWindow *windowHandle) con
QVector<QWindow *> DockRegistry::topLevels(bool excludeFloatingDocks) const
{
QVector<QWindow *> windows;
windows.reserve(m_nestedWindows.size() + m_mainWindows.size());
windows.reserve(m_floatingWindows.size() + m_mainWindows.size());
if (!excludeFloatingDocks) {
for (FloatingWindow *fw : m_nestedWindows) {
for (FloatingWindow *fw : m_floatingWindows) {
if (fw->isVisible()) {
if (QWindow *window = fw->windowHandle()) {
window->setProperty("kddockwidgets_qwidget", QVariant::fromValue<QWidgetOrQuick*>(fw)); // Since QWidgetWindow is private API
@@ -559,8 +579,8 @@ bool DockRegistry::eventFilter(QObject *watched, QEvent *event)
if (auto windowHandle = qobject_cast<QWindow*>(watched)) {
if (FloatingWindow *fw = floatingWindowForHandle(windowHandle)) {
// This floating window was exposed
m_nestedWindows.removeOne(fw);
m_nestedWindows.append(fw);
m_floatingWindows.removeOne(fw);
m_floatingWindows.append(fw);
}
}
} else if (event->type() == QEvent::MouseButtonPress) {

View File

@@ -42,8 +42,8 @@ public:
void registerMainWindow(MainWindowBase *);
void unregisterMainWindow(MainWindowBase *);
void registerNestedWindow(FloatingWindow *);
void unregisterNestedWindow(FloatingWindow *);
void registerFloatingWindow(FloatingWindow *);
void unregisterFloatingWindow(FloatingWindow *);
void registerLayout(MultiSplitter *);
void unregisterLayout(MultiSplitter *);
@@ -84,10 +84,13 @@ public:
///@brief returns all FloatingWindow instances. Not necessarily all floating dock widgets,
/// As there might be DockWidgets which weren't morphed yet.
const QVector<FloatingWindow*> nestedwindows() const;
const QVector<FloatingWindow*> floatingWindows() const;
///@brief overload that returns list of QWindow. This is more friendly for supporting both QtWidgets and QtQuick
const QVector<QWindow*> floatingWindows() const;
const QVector<QWindow*> floatingQWindows() const;
///@brief returns whether if there's at least one floating window
bool hasFloatingWindows() const;
///@brief returns the FloatingWindow with handle @p windowHandle
FloatingWindow *floatingWindowForHandle(QWindow *windowHandle) const;
@@ -127,8 +130,10 @@ public:
/**
* @brief returns true if there's 0 dockwidgets, 0 main windows
*
* @param excludeBeingDeleted if true, any window currently being deleted won't count
*/
bool isEmpty() const;
bool isEmpty(bool excludeBeingDeleted = false) const;
/**
* @brief Calls MultiSplitter::checkSanity() on all layouts.
@@ -174,6 +179,9 @@ public:
/// @param exclude This window should not be counted as an obscurer. (It's being dragged).
bool isProbablyObscured(QWindow *target, FloatingWindow *exclude) const;
/// @overload
bool isProbablyObscured(QWindow *target, WindowBeingDragged *exclude) const;
///@brief Returns whether the specified dock widget is in a side bar, and which.
/// SideBarLocation::None is returned if it's not in a sidebar.
/// This is only relevant when using the auto-hide and side-bar feature.
@@ -193,7 +201,7 @@ private:
DockWidgetBase::List m_dockWidgets;
MainWindowBase::List m_mainWindows;
Frame::List m_frames;
QVector<FloatingWindow*> m_nestedWindows;
QVector<FloatingWindow*> m_floatingWindows;
QVector<MultiSplitter*> m_layouts;
QPointer<DockWidgetBase> m_focusedDockWidget;
};

View File

@@ -89,6 +89,11 @@ StateBase::StateBase(DragController *parent)
{
}
bool StateBase::isActiveState() const
{
return q->activeState() == this;
}
StateBase::~StateBase() = default;
StateNone::StateNone(DragController *parent)
@@ -175,8 +180,8 @@ void StateDragging::onEntry(QEvent *)
q->m_windowBeingDragged = q->m_draggable->makeWindow();
if (q->m_windowBeingDragged) {
#ifdef Q_OS_WIN
# if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
# ifdef Q_OS_WIN
if (!q->m_nonClientDrag && KDDockWidgets::usesNativeDraggingAndResizing()) {
// Started as a client move, as the dock widget was docked,
// but now that we're dragging it as a floating window, switch to native drag
@@ -233,7 +238,7 @@ bool StateDragging::handleMouseButtonRelease(QPoint globalPos)
}
if (q->m_currentDropArea) {
if (q->m_currentDropArea->drop(floatingWindow, globalPos)) {
if (q->m_currentDropArea->drop(q->m_windowBeingDragged.get(), globalPos)) {
Q_EMIT q->dropped();
} else {
qCDebug(state) << "StateDragging: Bailling out, drop not accepted";
@@ -281,7 +286,7 @@ bool StateDragging::handleMouseMove(QPoint globalPos)
}
}
dropArea->hover(fw, globalPos);
dropArea->hover(q->m_windowBeingDragged.get(), globalPos);
}
q->m_currentDropArea = dropArea;
@@ -289,13 +294,39 @@ bool StateDragging::handleMouseMove(QPoint globalPos)
return true;
}
StateDraggingWayland::StateDraggingWayland(DragController *parent)
: StateDragging(parent)
{
}
StateDraggingWayland::~StateDraggingWayland()
{
}
void StateDraggingWayland::onEntry(QEvent *)
{
// Create a QDrag here
}
bool StateDraggingWayland::handleMouseButtonRelease(QPoint /*globalPos*/)
{
Q_EMIT q->dragCanceled();
return true;
}
bool StateDraggingWayland::handleMouseMove(QPoint /*globalPos*/)
{
return true;
}
DragController::DragController(QObject *)
{
qCDebug(creation) << "DragController()";
auto stateNone = new StateNone(this);
auto statepreDrag = new StatePreDrag(this);
auto stateDragging = new StateDragging(this);
auto stateDragging = isWayland() ? new StateDraggingWayland(this)
: new StateDragging(this);
stateNone->addTransition(this, &DragController::mousePressed, statepreDrag);
statepreDrag->addTransition(this, &DragController::dragCanceled, stateNone);
@@ -358,7 +389,7 @@ void DragController::releaseMouse(QWidgetOrQuick *target)
}
}
FloatingWindow *DragController::windowBeingDragged() const
FloatingWindow *DragController::floatingWindowBeingDragged() const
{
return m_windowBeingDragged ? m_windowBeingDragged->floatingWindow()
: nullptr;
@@ -370,6 +401,11 @@ void DragController::enableFallbackMouseGrabber()
m_fallbackMouseGrabber = new FallbackMouseGrabber(this);
}
WindowBeingDragged *DragController::windowBeingDragged() const
{
return m_windowBeingDragged.get();
}
static QMouseEvent *mouseEvent(QEvent *e)
{
switch (e->type()) {
@@ -534,7 +570,7 @@ WidgetType *DragController::qtTopLevelUnderCursor() const
// The floating window list is sorted by z-order, as we catch QEvent::Expose and move it to last of the list
FloatingWindow *tlwBeingDragged = m_windowBeingDragged->floatingWindow();
if (auto tl = qtTopLevelUnderCursor_impl(globalPos, DockRegistry::self()->floatingWindows(), tlwBeingDragged))
if (auto tl = qtTopLevelUnderCursor_impl(globalPos, DockRegistry::self()->floatingQWindows(), tlwBeingDragged))
return tl;
return qtTopLevelUnderCursor_impl<WidgetType*>(globalPos,

View File

@@ -52,7 +52,10 @@ public:
void grabMouseFor(QWidgetOrQuick *);
void releaseMouse(QWidgetOrQuick *);
FloatingWindow *windowBeingDragged() const;
FloatingWindow *floatingWindowBeingDragged() const;
///@brief Returns the window being dragged
WindowBeingDragged* windowBeingDragged() const;
/// Experimental, internal, not for general use.
void enableFallbackMouseGrabber();
@@ -101,6 +104,9 @@ public:
virtual bool handleMouseMove(QPoint /*globalPos*/) { return false; }
virtual bool handleMouseButtonRelease(QPoint /*globalPos*/) { return false; }
// Returns whether this is the current state
bool isActiveState() const;
DragController *const q;
};
@@ -125,6 +131,7 @@ public:
bool handleMouseButtonRelease(QPoint) override;
};
// Used on all platforms except Wayland. @see StateDraggingWayland
class StateDragging : public StateBase
{
Q_OBJECT
@@ -136,6 +143,18 @@ public:
bool handleMouseMove(QPoint globalPos) override;
};
// Used on wayland only to use QDrag instead of setting geometry on mouse-move.
class StateDraggingWayland : public StateDragging
{
Q_OBJECT
public:
explicit StateDraggingWayland(DragController *parent);
~StateDraggingWayland() override;
void onEntry(QEvent *) override;
bool handleMouseButtonRelease(QPoint globalPos) override;
bool handleMouseMove(QPoint globalPos) override;
};
}
#endif

View File

@@ -174,7 +174,7 @@ void DropArea::layoutParentContainerEqually(DockWidgetBase *dw)
layoutEqually(item->parentContainer());
}
void DropArea::hover(FloatingWindow *floatingWindow, QPoint globalPos)
void DropArea::hover(WindowBeingDragged *floatingWindow, QPoint globalPos)
{
if (!validateAffinity(floatingWindow))
return;
@@ -185,7 +185,7 @@ void DropArea::hover(FloatingWindow *floatingWindow, QPoint globalPos)
}
Frame *frame = frameContainingPos(globalPos); // Frame is nullptr if MainWindowOption_HasCentralFrame isn't set
m_dropIndicatorOverlay->setWindowBeingDragged(floatingWindow);
m_dropIndicatorOverlay->setWindowBeingDragged(floatingWindow != nullptr);
m_dropIndicatorOverlay->setHoveredFrame(frame);
m_dropIndicatorOverlay->hover(globalPos);
}
@@ -203,9 +203,12 @@ static bool isOutterLocation(DropIndicatorOverlayInterface::DropLocation locatio
}
}
bool DropArea::drop(FloatingWindow *droppedWindow, QPoint globalPos)
bool DropArea::drop(WindowBeingDragged *droppedWindow, QPoint globalPos)
{
if (droppedWindow == window()) {
FloatingWindow *floatingWindow = droppedWindow ? droppedWindow->floatingWindow()
: nullptr;
if (floatingWindow == window()) {
qWarning() << "Refusing to drop onto itself"; // Doesn't happen
return false;
}
@@ -225,7 +228,7 @@ bool DropArea::drop(FloatingWindow *droppedWindow, QPoint globalPos)
return false;
}
return drop(droppedWindow, acceptingFrame, droploc);
return drop(floatingWindow, acceptingFrame, droploc);
}
bool DropArea::drop(FloatingWindow *droppedWindow, Frame *acceptingFrame,
@@ -314,7 +317,7 @@ bool DropArea::drop(QWidgetOrQuick *droppedWindow, KDDockWidgets::Location locat
void DropArea::removeHover()
{
m_dropIndicatorOverlay->setWindowBeingDragged(nullptr);
m_dropIndicatorOverlay->setWindowBeingDragged(false);
m_dropIndicatorOverlay->setCurrentDropLocation(DropIndicatorOverlayInterface::DropLocation_None);
}

View File

@@ -25,6 +25,8 @@
#include "MultiSplitter_p.h"
#include "DropIndicatorOverlayInterface_p.h"
class TestCommon;
namespace KDDockWidgets {
class Frame;
@@ -42,9 +44,9 @@ public:
~DropArea();
void removeHover();
void hover(FloatingWindow *floatingWindow, QPoint globalPos);
void hover(WindowBeingDragged *floatingWindow, QPoint globalPos);
///@brief Called when a user drops a widget via DND
bool drop(FloatingWindow *droppedWindow, QPoint globalPos);
bool drop(WindowBeingDragged *droppedWindow, QPoint globalPos);
int numFrames() const;
Frame::List frames() const;
@@ -64,6 +66,7 @@ private:
Q_DISABLE_COPY(DropArea)
friend class Frame;
friend class TestDocks;
friend class ::TestCommon;
friend class DropIndicatorOverlayInterface;
friend class AnimatedIndicators;
friend class FloatingWindow;

View File

@@ -13,7 +13,6 @@
#include "Frame_p.h"
#include "DropArea_p.h"
#include "FloatingWindow_p.h"
using namespace KDDockWidgets;
@@ -25,20 +24,20 @@ DropIndicatorOverlayInterface::DropIndicatorOverlayInterface(DropArea *dropArea)
setObjectName(QStringLiteral("DropIndicatorOverlayInterface"));
}
void DropIndicatorOverlayInterface::setWindowBeingDragged(const FloatingWindow *window)
void DropIndicatorOverlayInterface::setWindowBeingDragged(bool is)
{
if (window == m_windowBeingDragged)
if (is == m_draggedWindowIsHovering)
return;
m_windowBeingDragged = window;
if (m_windowBeingDragged) {
m_draggedWindowIsHovering = is;
if (is) {
setGeometry(m_dropArea->QWidgetAdapter::rect());
raise();
} else {
setHoveredFrame(nullptr);
}
setVisible(m_windowBeingDragged != nullptr);
setVisible(is);
updateVisibility();
}
@@ -70,7 +69,7 @@ void DropIndicatorOverlayInterface::setHoveredFrame(Frame *frame)
bool DropIndicatorOverlayInterface::isHovered() const
{
return m_windowBeingDragged != nullptr;
return m_draggedWindowIsHovering;
}
DropIndicatorOverlayInterface::DropLocation DropIndicatorOverlayInterface::currentDropLocation() const

View File

@@ -19,7 +19,6 @@
namespace KDDockWidgets {
class FloatingWindow;
class DropArea;
class DOCKS_EXPORT DropIndicatorOverlayInterface : public QWidgetAdapter
@@ -47,7 +46,7 @@ public:
explicit DropIndicatorOverlayInterface(DropArea *dropArea);
void setHoveredFrame(Frame *);
void setWindowBeingDragged(const FloatingWindow *);
void setWindowBeingDragged(bool);
QRect hoveredFrameRect() const;
bool isHovered() const;
DropLocation currentDropLocation() const;
@@ -77,8 +76,8 @@ protected:
virtual void updateVisibility() {};
Frame *m_hoveredFrame = nullptr;
QPointer<const FloatingWindow> m_windowBeingDragged;
DropArea *const m_dropArea;
bool m_draggedWindowIsHovering = false;
};
}

View File

@@ -155,7 +155,7 @@ FloatingWindow::FloatingWindow(MainWindowBase *parent)
}
#endif
DockRegistry::self()->registerNestedWindow(this);
DockRegistry::self()->registerFloatingWindow(this);
qCDebug(creation) << "FloatingWindow()" << this;
maybeCreateResizeHandler();
@@ -184,7 +184,7 @@ FloatingWindow::~FloatingWindow()
disconnect(m_layoutDestroyedConnection);
delete m_nchittestFilter;
DockRegistry::self()->unregisterNestedWindow(this);
DockRegistry::self()->unregisterFloatingWindow(this);
qCDebug(creation) << "~FloatingWindow";
}
@@ -259,7 +259,7 @@ void FloatingWindow::setSuggestedGeometry(QRect suggestedRect, bool preserveCent
void FloatingWindow::scheduleDeleteLater()
{
m_deleteScheduled = true;
DockRegistry::self()->unregisterNestedWindow(this);
DockRegistry::self()->unregisterFloatingWindow(this);
deleteLater();
}
@@ -350,9 +350,7 @@ void FloatingWindow::updateTitleBarVisibility()
bool visible = true;
if (KDDockWidgets::usesNativeTitleBar()) {
visible = false;
} else {
if (KDDockWidgets::usesClientTitleBar()) {
const auto flags = Config::self().flags();
if ((flags & Config::Flag_HideTitleBarWhenTabsVisible) && !(flags & Config::Flag_AlwaysTitleBarWhenFloating)) {
if (hasSingleFrame()) {
@@ -362,6 +360,8 @@ void FloatingWindow::updateTitleBarVisibility()
for (Frame *frame : frames())
frame->updateTitleBarVisibility();
} else {
visible = false;
}
m_titleBar->setVisible(visible);

View File

@@ -31,6 +31,7 @@
#include "FrameworkWidgetFactory.h"
#include "multisplitter/Widget_qwidget.h"
#include "DropArea_p.h"
#include "WindowBeingDragged_p.h"
#include <QScopedValueRollback>
@@ -436,17 +437,16 @@ Layouting::ItemContainer *MultiSplitter::rootItem() const
return m_rootItem;
}
QRect MultiSplitter::rectForDrop(const FloatingWindow *fw, Location location,
QRect MultiSplitter::rectForDrop(const WindowBeingDragged *wbd, Location location,
const Layouting::Item *relativeTo) const
{
Layouting::Item item(nullptr);
if (!fw)
if (!wbd)
return {};
Layouting::ItemContainer *root = fw->dropArea()->rootItem();
item.setSize(fw->size().boundedTo(root->maxSizeHint()));
item.setMinSize(root->minSize());
item.setMaxSizeHint(root->maxSizeHint());
item.setSize(wbd->size().boundedTo(wbd->maxSize()));
item.setMinSize(wbd->minSize());
item.setMaxSizeHint(wbd->maxSize());
Layouting::ItemContainer *container = relativeTo ? relativeTo->parentContainer()
: m_rootItem;

View File

@@ -40,7 +40,7 @@ namespace KDDockWidgets {
class MainWindowBase;
class FloatingWindow;
class Frame;
class WindowBeingDragged;
/**
* MultiSplitter is simply a wrapper around Layouting::Item in which the hosted widgets are
@@ -131,7 +131,7 @@ public:
* Excludes the Separator thickness, result is actually smaller than what needed. In other words,
* the result will be exactly the same as the geometry the widget will get.
*/
QRect rectForDrop(const FloatingWindow *, KDDockWidgets::Location location,
QRect rectForDrop(const WindowBeingDragged *wbd, KDDockWidgets::Location location,
const Layouting::Item *relativeTo) const;
bool deserialize(const LayoutSaver::MultiSplitter &);

View File

@@ -125,7 +125,7 @@ void Position::deserialize(const LayoutSaver::Position &lp)
if (index == -1) {
continue; // Skip
} else {
const auto floatingWindows = DockRegistry::self()->nestedwindows();
const auto floatingWindows = DockRegistry::self()->floatingWindows();
if (index >= 0 && index < floatingWindows.size()) {
FloatingWindow *fw = floatingWindows.at(index);
layout = fw->multiSplitter();
@@ -170,7 +170,7 @@ LayoutSaver::Position Position::serialize() const
p.isFloatingWindow = fw;
if (p.isFloatingWindow) {
p.indexOfFloatingWindow = fw->beingDeleted() ? -1 : DockRegistry::self()->nestedwindows().indexOf(fw); // TODO: Remove once we stop using deleteLater with FloatingWindow. delete would be better
p.indexOfFloatingWindow = fw->beingDeleted() ? -1 : DockRegistry::self()->floatingWindows().indexOf(fw); // TODO: Remove once we stop using deleteLater with FloatingWindow. delete would be better
} else {
p.mainWindowUniqueName = mainWindow->uniqueName();
Q_ASSERT(!p.mainWindowUniqueName.isEmpty());

View File

@@ -27,6 +27,8 @@ class QHBoxLayout;
class QLabel;
QT_END_NAMESPACE
class TestCommon;
namespace KDDockWidgets {
class DockWidgetBase;
@@ -127,6 +129,7 @@ protected:
private:
friend class TestDocks;
friend class ::TestCommon;
void init();

View File

@@ -30,6 +30,11 @@
namespace KDDockWidgets {
inline bool isWayland()
{
return qApp->platformName() == QLatin1String("wayland");
}
inline bool isLeftButtonPressed()
{
return qApp->mouseButtons() & Qt::LeftButton;
@@ -40,6 +45,17 @@ inline bool usesNativeTitleBar()
return Config::self().flags() & Config::Flag_NativeTitleBar;
}
inline bool usesClientTitleBar()
{
if (isWayland()) {
// Wayland has both client and native title bars, due to limitations.
return true;
}
// All other platforms have either the OS native title bar or a Qt title bar (aka client title bar).
return !usesNativeTitleBar();
}
inline bool usesAeroSnapWithCustomDecos()
{
return Config::self().flags() & Config::Flag_AeroSnapWithClientDecos;

View File

@@ -13,11 +13,15 @@
#include "DragController_p.h"
#include "Logging_p.h"
#include "Utils_p.h"
#include "DropArea_p.h"
using namespace KDDockWidgets;
static Draggable* bestDraggable(Draggable *draggable)
{
if (!draggable)
return nullptr;
// When de detach a title bar it will get hidden and we only the title bar of the FloatingWindow is visible
/// Apparently that causes problems with grabbing the mouse, so instead use a visible draggable.
// grabbing mouse on an hidden window works usually, it's some edge case on Windows with MFC.
@@ -45,6 +49,7 @@ static Draggable* bestDraggable(Draggable *draggable)
WindowBeingDragged::WindowBeingDragged(FloatingWindow *fw, Draggable *draggable)
: m_floatingWindow(fw)
, m_draggable(bestDraggable(draggable)->asWidget())
, m_affinities(fw->affinities())
{
init();
@@ -53,6 +58,17 @@ WindowBeingDragged::WindowBeingDragged(FloatingWindow *fw, Draggable *draggable)
if (!qIsNaN(opacity) && !qFuzzyCompare(1.0, opacity))
fw->setWindowOpacity(opacity);
}
#if DOCKS_DEVELOPER_MODE
// Just used by tests
WindowBeingDragged::WindowBeingDragged(FloatingWindow *fw)
: m_floatingWindow(fw)
, m_draggable(nullptr)
, m_affinities(fw->affinities())
{
}
#endif
WindowBeingDragged::~WindowBeingDragged()
{
@@ -82,3 +98,36 @@ void WindowBeingDragged::grabMouse(bool grab)
else
DragController::instance()->releaseMouse(m_draggable);
}
QStringList WindowBeingDragged::affinities() const
{
return m_affinities;
}
QSize WindowBeingDragged::size() const
{
if (m_floatingWindow)
return m_floatingWindow->size();
return QSize();
}
QSize WindowBeingDragged::minSize() const
{
if (m_floatingWindow) {
Layouting::ItemContainer *root = m_floatingWindow->dropArea()->rootItem();
return root->minSize();
}
return {};
}
QSize WindowBeingDragged::maxSize() const
{
if (m_floatingWindow) {
Layouting::ItemContainer *root = m_floatingWindow->dropArea()->rootItem();
return root->maxSizeHint();
}
return {};
}

View File

@@ -26,6 +26,11 @@ struct DOCKS_EXPORT_FOR_UNIT_TESTS WindowBeingDragged
{
public:
explicit WindowBeingDragged(FloatingWindow *fw, Draggable *draggable);
#if DOCKS_DEVELOPER_MODE
// For tests.
explicit WindowBeingDragged(FloatingWindow *fw);
#endif
~WindowBeingDragged();
void init();
@@ -34,10 +39,23 @@ public:
///@brief grabs or releases the mouse
void grabMouse(bool grab);
///@brief returns the affinities of the window being dragged
QStringList affinities() const;
///@brief size of the window being dragged contents
QSize size() const;
/// @brief returns the min-size of the window being dragged contents
QSize minSize() const;
/// @brief returns the max-size of the window being dragged contents
QSize maxSize() const;
private:
Q_DISABLE_COPY(WindowBeingDragged)
QPointer<FloatingWindow> m_floatingWindow;
QPointer<QWidgetOrQuick> m_draggable;
const QStringList m_affinities;
};
}

View File

@@ -92,15 +92,17 @@ void ClassicIndicators::updateIndicatorsVisibility(bool visible)
m_innerIndicatorsVisible = visible && m_hoveredFrame;
WindowBeingDragged *windowBeingDragged = DragController::instance()->windowBeingDragged();
// If there's only 1 frame in the layout, the outter indicators are redundant, as they do the same thing as the internal ones.
// But there might be another window obscuring our target, so it's useful to show the outter indicators in this case
m_outterIndicatorsVisible = visible && (!isTheOnlyFrame ||
DockRegistry::self()->isProbablyObscured(m_hoveredFrame->window()->windowHandle(), DragController::instance()->windowBeingDragged()));
DockRegistry::self()->isProbablyObscured(m_hoveredFrame->window()->windowHandle(), windowBeingDragged));
// Only allow to dock to center if the affinities match
m_tabIndicatorVisible = m_innerIndicatorsVisible && m_windowBeingDragged &&
DockRegistry::self()->affinitiesMatch(m_hoveredFrame->affinities(), m_windowBeingDragged->affinities());
m_tabIndicatorVisible = m_innerIndicatorsVisible && windowBeingDragged &&
DockRegistry::self()->affinitiesMatch(m_hoveredFrame->affinities(), windowBeingDragged->affinities());
Q_EMIT innerIndicatorsVisibleChanged();
Q_EMIT outterIndicatorsVisibleChanged();
@@ -161,7 +163,7 @@ void ClassicIndicators::setDropLocation(ClassicIndicators::DropLocation location
case DropLocation_Bottom:
if (!m_hoveredFrame) {
qWarning() << "ClassicIndicators::setCurrentDropLocation: frame is null. location=" << location
<< "; windowBeingDragged=" << m_windowBeingDragged
<< "; isHovered=" << isHovered()
<< "; dropArea->widgets=" << m_dropArea->items();
Q_ASSERT(false);
return;
@@ -177,7 +179,9 @@ void ClassicIndicators::setDropLocation(ClassicIndicators::DropLocation location
break;
}
QRect rect = m_dropArea->rectForDrop(m_windowBeingDragged, multisplitterLocation,
auto windowBeingDragged = DragController::instance()->windowBeingDragged();
QRect rect = m_dropArea->rectForDrop(windowBeingDragged, multisplitterLocation,
m_dropArea->itemForFrame(relativeToFrame));
m_rubberBand->setGeometry(rect);

View File

@@ -279,7 +279,12 @@ QObject *Item::host() const
void Item::restore(Widget *guest)
{
Q_ASSERT(!isVisible() && !guestAsQObject());
if (isVisible() || guestAsQObject()) {
qWarning() << Q_FUNC_INFO << "Hitting assert. visible="
<< isVisible() << "; guest=" << guestAsQObject();
Q_ASSERT(false);
}
if (isContainer()) {
qWarning() << Q_FUNC_INFO << "Containers can't be restored";
} else {
@@ -634,9 +639,14 @@ bool Item::checkSanity()
if (m_guest) {
if (m_guest->parent() != hostWidget()->asQObject()) {
qWarning() << Q_FUNC_INFO << "Unexpected parent for our guest"
<< m_guest->parent() << "; host=" << hostWidget()
<< m_guest->asQObject() << this;
if (root())
root()->dumpLayout();
qWarning() << Q_FUNC_INFO << "Unexpected parent for our guest. guest.parent="
<< m_guest->parent() << "; host=" << hostWidget()->asQObject()
<< "; guest.asObj=" << m_guest->asQObject()
<< "; this=" << this
<< "; item.parentContainer=" << parentContainer()
<< "; item.root.parent=" << (root() ? root()->parent() : nullptr);
return false;
}

View File

@@ -51,7 +51,10 @@ public:
virtual void setLayoutItem(Item *) = 0;
// Not strickly necessary, but it's nice conveniance for kddw which is widget based.
virtual QWidget *asQWidget() const { return nullptr; }
virtual QWidget *asQWidget() const {
Q_ASSERT(false); // Only wanted for QtWidgets. All other should not call this.
return nullptr;
}
virtual QSize sizeHint() const { return {}; }
virtual QSize minSize() const = 0;

View File

@@ -45,8 +45,8 @@ void Widget_quick::setParent(Widget *parent)
}
if (auto qquickitem = qobject_cast<QQuickItem*>(parent->asQObject())) {
m_thisWidget->setParentItem(qquickitem);
m_thisWidget->setParent(qquickitem);
m_thisWidget->setParentItem(qquickitem);
} else {
qWarning() << Q_FUNC_INFO << "parent is not a widget, you have a bug" << parent->asQObject();
Q_ASSERT(false);

View File

@@ -82,7 +82,7 @@ bool DockWidgetQuick::event(QEvent *e)
} else if (e->type() == QEvent::Hide) {
onHidden(e->spontaneous());
} else if (e->type() == QEvent::Close) {
onClosed(static_cast<QCloseEvent*>(e));
onCloseEvent(static_cast<QCloseEvent*>(e));
}
return DockWidgetBase::event(e);

View File

@@ -64,7 +64,7 @@ FloatingWindowQuick::~FloatingWindowQuick()
void FloatingWindowQuick::setGeometry(QRect geo)
{
FloatingWindow::setGeometry(geo);
parentItem()->setSize(geo.size());
m_quickWindow->setGeometry(geo);
}
@@ -85,13 +85,17 @@ void FloatingWindowQuick::init()
}
});
m_quickWindow->setResizeMode(QQuickView::SizeRootObjectToView);
const QSize minSize(100, 100);
m_quickWindow->resize(minSize);
m_quickWindow->contentItem()->setSize(minSize);
m_quickWindow->setTransientParent(candidateParentWindow());
QWidgetAdapter::setParent(m_quickWindow->contentItem());
QWidgetAdapter::makeItemFillParent(this);
m_quickWindow->setResizeMode(QQuickView::SizeViewToRootObject);
QQuickItem *visualItem = createItem(Config::self().qmlEngine(), QStringLiteral("qrc:/kddockwidgets/private/quick/qml/FloatingWindow.qml"));
Q_ASSERT(visualItem);
visualItem->setParent(this);
@@ -99,5 +103,4 @@ void FloatingWindowQuick::init()
m_quickWindow->setFlags(windowFlags());
m_quickWindow->show();
m_quickWindow->setGeometry(200, 200, 800, 800); // TODO: remove
}

View File

@@ -170,7 +170,7 @@ QVariant DockWidgetModel::data(const QModelIndex &index, int role) const
DockWidgetBase *DockWidgetModel::dockWidgetAt(int index) const
{
if (index < 0 || index >= m_dockWidgets.size()) {
qWarning() << Q_FUNC_INFO << "Shouldn't happen" << index << m_dockWidgets.size();
// Can happen. Benign.
return nullptr;
}

View File

@@ -26,12 +26,12 @@ MainWindowQuick::MainWindowQuick(const QString &uniqueName, MainWindowOptions op
SideBar *MainWindowQuick::sideBar(SideBarLocation) const
{
qWarning() << Q_FUNC_INFO << "SideBar hasn't been implemented yet";
qDebug() << Q_FUNC_INFO << "SideBar hasn't been implemented yet";
return nullptr;
}
QMargins MainWindowQuick::centerWidgetMargins() const
{
qWarning() << Q_FUNC_INFO << "SideBar hasn't been implemented yet";
qDebug() << Q_FUNC_INFO << "SideBar hasn't been implemented yet";
return {};
}

View File

@@ -79,7 +79,7 @@ void QWidgetAdapter::itemChange(QQuickItem::ItemChange change, const QQuickItem:
switch (change) {
case QQuickItem::ItemParentHasChanged: {
QEvent ev(QEvent::ParentChange);
event(&ev);
qApp->sendEvent(this, &ev); // Not calling event() directly, otherwise it would skip event filters
Q_EMIT parentChanged();
break;
}
@@ -93,6 +93,24 @@ void QWidgetAdapter::itemChange(QQuickItem::ItemChange change, const QQuickItem:
}
}
void QWidgetAdapter::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
{
// Send a few events manually, since QQuickItem doesn't do it for us.
QQuickItem::geometryChanged(newGeometry, oldGeometry);
// Not calling event() directly, otherwise it would skip event filters
if (newGeometry.size() != oldGeometry.size()) {
QEvent ev(QEvent::Resize);
qApp->sendEvent(this, &ev);
}
if (newGeometry.topLeft() != oldGeometry.topLeft()) {
QEvent ev(QEvent::Move);
qApp->sendEvent(this, &ev);
}
}
void QWidgetAdapter::raise()
{
if (QWindow *w = windowHandle())
@@ -141,8 +159,7 @@ void QWidgetAdapter::setGeometry(QRect rect)
{
setWidth(rect.width());
setHeight(rect.height());
setX(rect.x());
setY(rect.y());
move(rect.topLeft());
}
void QWidgetAdapter::grabMouse()
@@ -254,6 +271,19 @@ QPoint QWidgetAdapter::mapTo(const QQuickItem *parent, const QPoint &pos) const
return parent->mapFromGlobal(QQuickItem::mapToGlobal(pos)).toPoint();
}
bool QWidgetAdapter::testAttribute(Qt::WidgetAttribute attr) const
{
return m_widgetAttributes & attr;
}
void QWidgetAdapter::setAttribute(Qt::WidgetAttribute attr, bool enable)
{
if (enable)
m_widgetAttributes |= attr;
else
m_widgetAttributes &= ~attr;
}
void QWidgetAdapter::setWindowTitle(const QString &title)
{
if (QWindow *window = windowHandle())
@@ -281,10 +311,16 @@ QQuickItem *QWidgetAdapter::childAt(QPoint p) const
return QQuickItem::childAt(p.x(), p.y());
}
void QWidgetAdapter::move(QPoint pt)
{
move(pt.x(), pt.y());
}
void QWidgetAdapter::move(int x, int y)
{
setX(x);
setY(y);
setAttribute(Qt::WA_Moved);
}
void QWidgetAdapter::setParent(QQuickItem *p)

View File

@@ -124,13 +124,15 @@ public:
QPoint mapToGlobal(QPoint pt) const;
QPoint mapFromGlobal(QPoint) const;
QPoint mapTo(const QQuickItem *parent, const QPoint &pos) const;
bool testAttribute(Qt::WidgetAttribute) { return false; }
bool testAttribute(Qt::WidgetAttribute) const;
void setAttribute(Qt::WidgetAttribute, bool enabled = true);
void setWindowTitle(const QString &);
void setWindowIcon(const QIcon &);
void close();
QQuickItem *childAt(QPoint) const;
void move(int x, int y);
void move(QPoint);
void setParent(QQuickItem*);
void activateWindow();
@@ -149,6 +151,7 @@ public:
Q_SIGNALS:
void parentChanged();
protected:
void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override;
void raiseAndActivate();
virtual bool onResize(QSize newSize);
virtual void onLayoutRequest();
@@ -163,6 +166,7 @@ private:
QSize m_minimumSize = {KDDOCKWIDGETS_MIN_WIDTH, KDDOCKWIDGETS_MIN_HEIGHT};
QSize m_maximumSize = {KDDOCKWIDGETS_MAX_WIDTH, KDDOCKWIDGETS_MAX_HEIGHT};
Qt::WindowFlags m_windowFlags;
int m_widgetAttributes = 0; // Qt::WidgetAttribute
};
}

View File

@@ -21,7 +21,6 @@
void KDDockWidgets::registerQmlTypes()
{
qDebug() << "Registering types";
qmlRegisterType<DropAreaWithCentralFrame>("com.kdab.dockwidgets", 1, 0, "DropAreaWithCentralFrame");
qmlRegisterType<MainWindowWrapper>("com.kdab.dockwidgets", 1, 0, "MainWindow");

View File

@@ -68,5 +68,5 @@ bool DockWidget::event(QEvent *e)
void DockWidget::closeEvent(QCloseEvent *e)
{
onClosed(e);
onCloseEvent(e);
}

View File

@@ -64,8 +64,8 @@ static bool shouldBlacklistWarning(const QString &msg, const QString &category)
msg.contains(QLatin1String("Testing::")) ||
msg.contains(QLatin1String("outside any known screen, using primary screen"))
#ifdef KDDOCKWIDGETS_QTQUICK
// TODO: Debug why this happens
|| msg.contains(QLatin1String("Layouting::ItemContainer::setSize_recursive"))
// TODO: Fix later, not important right now
|| msg.contains(QLatin1String("Binding loop detected for property"))
#endif
;
}

View File

@@ -137,6 +137,22 @@ namespace Testing {
QSize m_minSz;
};
}
struct SetExpectedWarning
{
explicit SetExpectedWarning(const QString &s)
{
if (!s.isEmpty())
Testing::setExpectedWarning(s);
}
~SetExpectedWarning()
{
Testing::setExpectedWarning({});
}
Q_DISABLE_COPY(SetExpectedWarning)
};
}
#endif

View File

@@ -107,7 +107,7 @@ void Fuzzer::runTest(const Test &test)
for (MainWindowBase *mw : DockRegistry::self()->mainwindows())
delete mw;
for (FloatingWindow *fw : DockRegistry::self()->nestedwindows())
for (FloatingWindow *fw : DockRegistry::self()->floatingWindows())
delete fw;
for (DockWidgetBase *dw : DockRegistry::self()->dockwidgets())

View File

@@ -19,14 +19,21 @@
#include "private/MultiSplitter_p.h"
#include "TitleBar_p.h"
#include "Position_p.h"
#include "DropAreaWithCentralFrame_p.h"
#include "WindowBeingDragged_p.h"
#include <QtTest/QtTest>
#include <QObject>
#include <QApplication>
#ifdef KDDOCKWIDGETS_QTQUICK
# include "quick/DockWidgetQuick.h"
# include <QQmlEngine>
# include <QQuickStyle>
# else
# include "DockWidget.h"
# include <QPushButton>
#endif
using namespace KDDockWidgets;
@@ -61,12 +68,38 @@ public Q_SLOTS:
private Q_SLOTS:
void tst_simple1();
void tst_simple2();
void tst_doesntHaveNativeTitleBar();
void tst_resizeWindow2();
void tst_hasLastDockedLocation();
void tst_ghostSeparator();
void tst_detachFromMainWindow();
void tst_detachPos();
void tst_floatingWindowSize();
void tst_sizeAfterRedock();
void tst_tabbingWithAffinities();
void tst_honourUserGeometry();
void tst_floatingWindowTitleBug();
void tst_setFloatingSimple();
void tst_resizeWindow_data();
void tst_resizeWindow();
void tst_restoreEmpty();
void tst_restoreCentralFrame();
void tst_shutdown();
void tst_doubleClose();
void tst_dockInternal();
void tst_maximizeAndRestore();
void tst_propagateResize2();
void tst_28NestedWidgets();
void tst_28NestedWidgets_data();
void tst_negativeAnchorPosition2();
void tst_negativeAnchorPosition3();
void tst_negativeAnchorPosition4();
void tst_negativeAnchorPosition5();
void tst_startHidden();
void tst_closeReparentsToNull();
void tst_invalidAnchorGroup();
};
void TestCommon::tst_simple1()
@@ -77,6 +110,19 @@ void TestCommon::tst_simple1()
m->multiSplitter()->checkSanity();
}
void TestCommon::tst_simple2()
{
// Simply create a MainWindow, and dock something on top
EnsureTopLevelsDeleted e;
auto m = createMainWindow();
auto dw = createDockWidget("dw", new MyWidget("dw", Qt::blue));
auto fw = dw->floatingWindow();
m->addDockWidget(dw, KDDockWidgets::Location_OnTop);
m->multiSplitter()->checkSanity();
delete fw;
}
void TestCommon::tst_doesntHaveNativeTitleBar()
{
// Tests that a floating window doesn't have a native title bar
@@ -130,6 +176,10 @@ void TestCommon::tst_hasLastDockedLocation()
EnsureTopLevelsDeleted e;
auto m = createMainWindow(QSize(501, 500), MainWindowOption_None);
auto dock1 = createDockWidget("1");
m->multiSplitter()->checkSanity();
m->multiSplitter()->setObjectName("mainWindow-dropArea");
dock1->floatingWindow()->multiSplitter()->setObjectName("first-dropArea1");
dock1->floatingWindow()->multiSplitter()->checkSanity();
auto window1 = dock1->window();
QVERIFY(dock1->isFloating());
QVERIFY(!dock1->hasPreviousDockedLocation());
@@ -139,12 +189,20 @@ void TestCommon::tst_hasLastDockedLocation()
QVERIFY(!dock1->hasPreviousDockedLocation());
m->addDockWidget(dock1, Location_OnBottom);
m->multiSplitter()->checkSanity();
QVERIFY(!dock1->isFloating());
QVERIFY(dock1->setFloating(true));
auto ms1 = dock1->floatingWindow()->multiSplitter();
ms1->setObjectName("dropArea1");
ms1->checkSanity();
QVERIFY(dock1->hasPreviousDockedLocation());
auto window11 = dock1->window();
QVERIFY(dock1->setFloating(false));
delete window1;
delete window11;
}
void TestCommon::tst_ghostSeparator()
@@ -221,6 +279,252 @@ void TestCommon::tst_detachPos()
delete dock1->window();
}
void TestCommon::tst_floatingWindowSize()
{
EnsureTopLevelsDeleted e;
auto m = createMainWindow(QSize(501, 500), MainWindowOption_None);
auto dock1 = createDockWidget("1");
auto fw1 = dock1->window();
QTest::qWait(2000);
QVERIFY(!fw1->geometry().isNull());
QCOMPARE(fw1->size(), fw1->windowHandle()->size());
delete fw1;
}
void TestCommon::tst_tabbingWithAffinities()
{
EnsureTopLevelsDeleted e;
// Tests that dock widgets with different affinities should not tab together
auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None);
m1->setAffinities({ "af1", "af2" });
auto dw1 = new DockWidgetType("1");
dw1->setAffinities({ "af1" });
dw1->show();
auto dw2 = new DockWidgetType("2");
dw2->setAffinities({ "af2" });
dw2->show();
FloatingWindow *fw1 = dw1->floatingWindow();
FloatingWindow *fw2 = dw2->floatingWindow();
{
SetExpectedWarning ignoreWarning("Refusing to dock widget with incompatible affinity");
dw1->addDockWidgetAsTab(dw2);
QVERIFY(dw1->window() != dw2->window());
}
m1->addDockWidget(dw1, Location_OnBottom);
QVERIFY(!dw1->isFloating());
{
SetExpectedWarning ignoreWarning("Refusing to dock widget with incompatible affinity");
auto dropArea = m1->dropArea();
QVERIFY(!dropArea->drop(fw2, dw1->frame(), DropIndicatorOverlayInterface::DropLocation_Center));
QVERIFY(dw1->window() != dw2->window());
}
delete fw1;
delete fw2;
}
void TestCommon::tst_sizeAfterRedock()
{
EnsureTopLevelsDeleted e;
auto dw1 = new DockWidgetType(QStringLiteral("1"));
auto dw2 = new DockWidgetType(QStringLiteral("2"));
dw2->setWidget(new MyWidget("2", Qt::red));
dw1->addDockWidgetToContainingWindow(dw2, Location_OnBottom);
const int height2 = dw2->frame()->height();
dw2->setFloating(true);
QCOMPARE(height2, dw2->window()->height());
auto oldFw2 = dw2->floatingWindow();
// Redock
FloatingWindow *fw1 = dw1->floatingWindow();
DropArea *dropArea = fw1->dropArea();
MultiSplitter *ms1 = fw1->multiSplitter();
{
WindowBeingDragged wbd2(oldFw2);
const QRect suggestedDropRect = ms1->rectForDrop(&wbd2, Location_OnBottom, nullptr);
QCOMPARE(suggestedDropRect.height(), height2);
}
dropArea->drop(dw2->floatingWindow(), Location_OnBottom, nullptr);
QCOMPARE(dw2->frame()->height(), height2);
delete dw1->window();
delete oldFw2;
}
void TestCommon::tst_honourUserGeometry()
{
EnsureTopLevelsDeleted e;
auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None);
auto dw1 = new DockWidgetType(QStringLiteral("1"));
const QPoint pt(10, 10);
dw1->move(pt);
dw1->show();
FloatingWindow *fw1 = dw1->floatingWindow();
QCOMPARE(fw1->windowHandle()->geometry().topLeft(), pt);
delete dw1->window();
}
void TestCommon::tst_floatingWindowTitleBug()
{
// Test for #74
EnsureTopLevelsDeleted e;
auto dw1 = new DockWidgetType(QStringLiteral("1"));
auto dw2 = new DockWidgetType(QStringLiteral("2"));
auto dw3 = new DockWidgetType(QStringLiteral("3"));
dw1->setObjectName(QStringLiteral("1"));
dw2->setObjectName(QStringLiteral("2"));
dw3->setObjectName(QStringLiteral("3"));
dw1->show();
dw1->addDockWidgetAsTab(dw2);
dw1->addDockWidgetToContainingWindow(dw3, Location_OnBottom);
dw1->titleBar()->onFloatClicked();
QCOMPARE(dw3->titleBar()->title(), QLatin1String("3"));
delete dw1->window();
delete dw3->window();
}
void TestCommon::tst_resizeWindow_data()
{
QTest::addColumn<bool>("doASaveRestore");
QTest::newRow("false") << false;
QTest::newRow("true") << true;
}
void TestCommon::tst_resizeWindow()
{
QFETCH(bool, doASaveRestore);
EnsureTopLevelsDeleted e;
auto m = createMainWindow(QSize(501, 500), MainWindowOption_None);
auto dock1 = createDockWidget("1", new MyWidget("1", Qt::red));
auto dock2 = createDockWidget("2", new MyWidget("2", Qt::blue));
QPointer<FloatingWindow> fw1 = dock1->floatingWindow();
QPointer<FloatingWindow> fw2 = dock2->floatingWindow();
m->addDockWidget(dock1, Location_OnLeft);
m->addDockWidget(dock2, Location_OnRight);
auto layout = m->multiSplitter();
layout->checkSanity();
const int oldWidth1 = dock1->width();
const int oldWidth2 = dock2->width();
QVERIFY(oldWidth2 - oldWidth1 <= 1); // They're not equal if separator thickness if even
if (doASaveRestore) {
LayoutSaver saver;
saver.restoreLayout(saver.serializeLayout());
}
m->showMaximized();
Testing::waitForResize(m.get());
const int maximizedWidth1 = dock1->width();
const int maximizedWidth2 = dock2->width();
const double relativeDifference = qAbs((maximizedWidth1 - maximizedWidth2) / (1.0 * layout->width()));
qDebug() << oldWidth1 << oldWidth2 << maximizedWidth1 << maximizedWidth2 << relativeDifference;
QVERIFY(relativeDifference <= 0.01);
m->showNormal();
Testing::waitForResize(m.get());
const int newWidth1 = dock1->width();
const int newWidth2 = dock2->width();
QCOMPARE(oldWidth1, newWidth1);
QCOMPARE(oldWidth2, newWidth2);
layout->checkSanity();
delete fw1;
delete fw2;
}
void TestCommon::tst_restoreEmpty()
{
EnsureTopLevelsDeleted e;
// Create an empty main window, save it to disk.
auto m = createMainWindow(QSize(800, 500), MainWindowOption_None);
auto layout = m->multiSplitter();
LayoutSaver saver;
const QSize oldSize = m->size();
QVERIFY(saver.saveToFile(QStringLiteral("layout_tst_restoreEmpty.json")));
saver.restoreFromFile(QStringLiteral("layout_tst_restoreEmpty.json"));
QVERIFY(m->multiSplitter()->checkSanity());
QCOMPARE(layout->separators().size(), 0);
QCOMPARE(layout->count(), 0);
QCOMPARE(m->size(), oldSize);
QVERIFY(layout->checkSanity());
}
void TestCommon::tst_restoreCentralFrame()
{
EnsureTopLevelsDeleted e;
auto m = createMainWindow(QSize(800, 500));
auto layout = m->multiSplitter();
QCOMPARE(layout->count(), 1);
Item *item = m->dropArea()->centralFrame();
QVERIFY(item);
auto frame = static_cast<Frame *>(item->guestAsQObject());
QCOMPARE(frame->options(), FrameOption_IsCentralFrame | FrameOption_AlwaysShowsTabs);
QVERIFY(!frame->titleBar()->isVisible());
LayoutSaver saver;
QVERIFY(saver.saveToFile(QStringLiteral("layout_tst_restoreCentralFrame.json")));
QVERIFY(saver.restoreFromFile(QStringLiteral("layout_tst_restoreCentralFrame.json")));
QCOMPARE(layout->count(), 1);
item = m->dropArea()->centralFrame();
QVERIFY(item);
frame = static_cast<Frame *>(item->guestAsQObject());
QCOMPARE(frame->options(), FrameOption_IsCentralFrame | FrameOption_AlwaysShowsTabs);
QVERIFY(!frame->titleBar()->isVisible());
}
void TestCommon::tst_setFloatingSimple()
{
EnsureTopLevelsDeleted e;
auto m = createMainWindow();
auto dock1 = createDockWidget("dock1", new MyWidget("one"));
m->addDockWidget(dock1, Location_OnTop);
auto l = m->multiSplitter();
dock1->setFloating(true);
QVERIFY(l->checkSanity());
dock1->setFloating(false);
QVERIFY(l->checkSanity());
dock1->setFloating(true);
QVERIFY(l->checkSanity());
dock1->setFloating(false);
QVERIFY(l->checkSanity());
}
int main(int argc, char *argv[])
{
if (!qpaPassedAsArgument(argc, argv)) {
@@ -234,5 +538,562 @@ int main(int argc, char *argv[])
return QTest::qExec(&test, argc, argv);
}
void TestCommon::tst_doubleClose()
{
EnsureTopLevelsDeleted e;
{
// Via close()
auto dock1 = createDockWidget("hello", Qt::green);
dock1->close();
dock1->close();
delete dock1->window();
}
{
// Via the button
auto dock1 = createDockWidget("hello", Qt::green);
auto fw1 = dock1->floatingWindow();
auto t = dock1->frame()->titleBar();
t->onCloseClicked();
t->onCloseClicked();
delete dock1;
delete fw1;
}
}
void TestCommon::tst_dockInternal()
{
/**
* Here we dock relative to an existing widget, and not to the drop-area.
*/
EnsureTopLevelsDeleted e;
auto m = createMainWindow();
auto dock1 = createDockWidget("dock1", new QPushButton("one"));
auto dropArea = m->dropArea();
auto centralWidget = static_cast<Frame*>(dropArea->items()[0]->guestAsQObject());
nestDockWidget(dock1, dropArea, centralWidget, KDDockWidgets::Location_OnRight);
QVERIFY(dock1->width() < dropArea->width() - centralWidget->width());
}
void TestCommon::tst_maximizeAndRestore()
{
EnsureTopLevelsDeleted e;
auto m = createMainWindow();
auto dock1 = createDockWidget("dock1", new QPushButton("one"));
auto dock2 = createDockWidget("dock2", new QPushButton("two"));
m->addDockWidget(dock1, KDDockWidgets::Location_OnLeft);
m->addDockWidget(dock2, KDDockWidgets::Location_OnRight);
auto dropArea = m->dropArea();
QVERIFY(dropArea->checkSanity());
m->showMaximized();
Testing::waitForResize(m.get());
QVERIFY(dropArea->checkSanity());
qDebug() << "About to show normal";
m->showNormal();
Testing::waitForResize(m.get());
QVERIFY(dropArea->checkSanity());
}
void TestCommon::tst_propagateResize2()
{
// |5|1|2|
// | |3|4|
EnsureTopLevelsDeleted e;
auto m = createMainWindow();
auto dock1 = createDockWidget("dock1", new QPushButton("one"));
auto dock2 = createDockWidget("dock2", new QPushButton("two"));
m->addDockWidget(dock1, KDDockWidgets::Location_OnTop);
m->addDockWidget(dock2, KDDockWidgets::Location_OnRight, dock1);
auto dock3 = createDockWidget("dock3", new QPushButton("three"));
auto dock4 = createDockWidget("dock4", new QPushButton("four"));
m->addDockWidget(dock3, KDDockWidgets::Location_OnBottom);
m->addDockWidget(dock4, KDDockWidgets::Location_OnRight, dock3);
auto dock5 = createDockWidget("dock5", new QPushButton("five"));
m->addDockWidget(dock5, KDDockWidgets::Location_OnLeft);
auto dropArea = m->dropArea();
dropArea->checkSanity();
}
void TestCommon::tst_shutdown()
{
EnsureTopLevelsDeleted e;
auto dock = createDockWidget("doc1", Qt::green);
auto m = createMainWindow();
m->show();
QVERIFY(QTest::qWaitForWindowActive(m->windowHandle()));
delete dock->window();
}
void TestCommon::tst_28NestedWidgets_data()
{
QTest::addColumn<QVector<DockDescriptor>>("docksToCreate");
QTest::addColumn<QVector<int>>("docksToHide");
QVector<DockDescriptor> docks = {
{Location_OnLeft, -1, nullptr, AddingOption_None },
{Location_OnBottom, 0, nullptr, AddingOption_None },
{Location_OnBottom, 0, nullptr, AddingOption_None },
{Location_OnBottom, 0, nullptr, AddingOption_None },
{Location_OnBottom, 0, nullptr, AddingOption_None },
{Location_OnBottom, 0, nullptr, AddingOption_None },
{Location_OnBottom, 0, nullptr, AddingOption_None },
{Location_OnBottom, 0, nullptr, AddingOption_None },
{Location_OnBottom, 0, nullptr, AddingOption_None },
{Location_OnBottom, 0, nullptr, AddingOption_None },
{Location_OnBottom, 0, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnTop, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnLeft, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnBottom, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None }
};
QTest::newRow("28") << docks << QVector<int>{11, 0};
docks = {
{Location_OnLeft, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnTop, -1, nullptr, AddingOption_None },
{Location_OnLeft, -1, nullptr, AddingOption_None },
};
QVector<int> docksToHide;
for (int i = 0; i < docks.size(); ++i) {
docksToHide << i;
}
QTest::newRow("anchor_intersection") << docks << docksToHide;
docks = {
{Location_OnLeft, -1, nullptr, AddingOption_None },
{Location_OnBottom, -1, nullptr, AddingOption_None },
{Location_OnBottom, -1, nullptr, AddingOption_None },
{Location_OnBottom, -1, nullptr, AddingOption_None },
{Location_OnBottom, -1, nullptr, AddingOption_None },
{Location_OnBottom, -1, nullptr, AddingOption_None },
{Location_OnTop, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
};
// 2. Produced valgrind invalid reads while adding
QTest::newRow("valgrind") << docks << QVector<int>{};
docks = {
{Location_OnLeft, -1, nullptr, AddingOption_None },
{Location_OnBottom, -1, nullptr, AddingOption_None },
{Location_OnTop, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
};
QTest::newRow("bug_when_closing") << docks << QVector<int>{}; // Q_ASSERT(!isSquashed())
docks = {
{Location_OnLeft, -1, nullptr, AddingOption_None },
{Location_OnBottom, 0, nullptr, AddingOption_None },
{Location_OnBottom, 0, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnBottom, -1, nullptr, AddingOption_None },
};
QTest::newRow("bug_when_closing2") << docks << QVector<int>{}; // Tests for void KDDockWidgets::Anchor::setPosition(int, KDDockWidgets::Anchor::SetPositionOptions) Negative position -69
docks = {
{Location_OnLeft, -1, nullptr, AddingOption_None },
{Location_OnBottom, 0, nullptr, AddingOption_None },
{Location_OnBottom, 0, nullptr, AddingOption_None },
{Location_OnBottom, 0, nullptr, AddingOption_None },
{Location_OnBottom, 0, nullptr, AddingOption_None },
{Location_OnBottom, 0, nullptr, AddingOption_None },
{Location_OnBottom, 0, nullptr, AddingOption_None },
{Location_OnBottom, 0, nullptr, AddingOption_None },
{Location_OnBottom, 0, nullptr, AddingOption_None },
{Location_OnBottom, 0, nullptr, AddingOption_None },
{Location_OnBottom, 0, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnTop, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnLeft, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnBottom, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None }
};
docksToHide.clear();
for (int i = 0; i < 28; ++i) {
if (i != 16 && i != 17 && i != 18 && i != 27)
docksToHide << i;
}
QTest::newRow("bug_with_holes") << docks << docksToHide;
docks = {
{Location_OnLeft, -1, nullptr, AddingOption_StartHidden },
{Location_OnBottom, -1, nullptr, AddingOption_StartHidden },
{Location_OnBottom, -1, nullptr, AddingOption_StartHidden },
{Location_OnBottom, -1, nullptr, AddingOption_StartHidden },
{Location_OnBottom, -1, nullptr, AddingOption_StartHidden },
{Location_OnBottom, -1, nullptr, AddingOption_StartHidden },
{Location_OnBottom, -1, nullptr, AddingOption_StartHidden },
{Location_OnBottom, -1, nullptr, AddingOption_StartHidden },
{Location_OnBottom, -1, nullptr, AddingOption_StartHidden },
{Location_OnBottom, -1, nullptr, AddingOption_StartHidden },
{Location_OnBottom, -1, nullptr, AddingOption_StartHidden },
{Location_OnRight, -1, nullptr, AddingOption_StartHidden },
{Location_OnRight, -1, nullptr, AddingOption_StartHidden },
{Location_OnRight, -1, nullptr, AddingOption_StartHidden },
{Location_OnRight, -1, nullptr, AddingOption_StartHidden },
{Location_OnRight, -1, nullptr, AddingOption_StartHidden },
{Location_OnTop, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnLeft, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_StartHidden },
{Location_OnRight, -1, nullptr, AddingOption_StartHidden },
{Location_OnBottom, -1, nullptr, AddingOption_StartHidden },
{Location_OnRight, -1, nullptr, AddingOption_StartHidden },
{Location_OnRight, -1, nullptr, AddingOption_StartHidden },
{Location_OnRight, -1, nullptr, AddingOption_StartHidden },
{Location_OnRight, -1, nullptr, AddingOption_StartHidden },
{Location_OnRight, -1, nullptr, AddingOption_StartHidden },
{Location_OnLeft, 17, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None } };
docksToHide.clear();
QTest::newRow("add_as_placeholder") << docks << docksToHide;
docks = {
{Location_OnLeft, -1, nullptr, AddingOption_StartHidden },
{Location_OnBottom, -1, nullptr, AddingOption_StartHidden },
{Location_OnRight, -1, nullptr, AddingOption_StartHidden } };
QTest::newRow("add_as_placeholder_simple") << docks << docksToHide;
docks = {
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_StartHidden },
{Location_OnRight, -1, nullptr, AddingOption_StartHidden } };
docksToHide.clear();
QTest::newRow("isSquashed_assert") << docks << docksToHide;
docks = {
{Location_OnLeft, -1, nullptr, AddingOption_StartHidden },
{Location_OnTop, -1, nullptr, AddingOption_None },
{Location_OnBottom, -1, nullptr, AddingOption_StartHidden } };
docksToHide.clear();
QTest::newRow("negative_pos_warning") << docks << docksToHide;
docks = {
{Location_OnTop, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_StartHidden },
{Location_OnRight, -1, nullptr, AddingOption_None } };
docksToHide.clear();
QTest::newRow("bug") << docks << docksToHide;
docks = {
{Location_OnTop, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_StartHidden },
{Location_OnRight, -1, nullptr, AddingOption_None } };
docksToHide.clear();
QTest::newRow("bug2") << docks << docksToHide;
docks = {
{Location_OnLeft, -1, nullptr, AddingOption_StartHidden },
{Location_OnRight, -1, nullptr, AddingOption_StartHidden },
{Location_OnTop, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnLeft, -1, nullptr, AddingOption_None },
{Location_OnBottom, -1, nullptr, AddingOption_StartHidden },
{Location_OnRight, -1, nullptr, AddingOption_None } };
docksToHide.clear();
QTest::newRow("bug3") << docks << docksToHide;
}
void TestCommon::tst_28NestedWidgets()
{
QFETCH(QVector<DockDescriptor>, docksToCreate);
QFETCH(QVector<int>, docksToHide);
// Tests a case that used to cause negative anchor position when turning into placeholder
EnsureTopLevelsDeleted e;
auto m = createMainWindow(QSize(800, 500), MainWindowOption_None);
auto dropArea = m->dropArea();
MultiSplitter *layout = dropArea;
int i = 0;
for (DockDescriptor &desc : docksToCreate) {
desc.createdDock = createDockWidget(QString("%1").arg(i), new QPushButton(QString("%1").arg(i).toLatin1()), {}, false);
DockWidgetBase *relativeTo = nullptr;
if (desc.relativeToIndex != -1)
relativeTo = docksToCreate.at(desc.relativeToIndex).createdDock;
m->addDockWidget(desc.createdDock, desc.loc, relativeTo, desc.option);
QVERIFY(layout->checkSanity());
++i;
}
layout->checkSanity();
// Run the saver in these complex scenarios:
LayoutSaver saver;
const QByteArray saved = saver.serializeLayout();
QVERIFY(!saved.isEmpty());
QVERIFY(saver.restoreLayout(saved));
layout->checkSanity();
for (int i : docksToHide) {
docksToCreate.at(i).createdDock->close();
layout->checkSanity();
QTest::qWait(200);
}
layout->checkSanity();
for (int i : docksToHide) {
docksToCreate.at(i).createdDock->deleteLater();
QVERIFY(Testing::waitForDeleted(docksToCreate.at(i).createdDock));
}
layout->checkSanity();
// And hide the remaining ones
i = 0;
for (auto dock : docksToCreate) {
if (dock.createdDock && dock.createdDock->isVisible()) {
dock.createdDock->close();
QTest::qWait(200); // Wait for the docks to be closed. TODO Replace with a global event filter and wait for any resize ?
}
++i;
}
layout->checkSanity();
// Cleanup
for (auto dock : DockRegistry::self()->dockwidgets()) {
dock->deleteLater();
QVERIFY(Testing::waitForDeleted(dock));
}
}
void TestCommon::tst_closeReparentsToNull()
{
EnsureTopLevelsDeleted e;
auto dock1 = createDockWidget("1", new QPushButton("1"));
auto fw1 = dock1->window();
QVERIFY(dock1->parent() != nullptr);
dock1->close();
QVERIFY(dock1->parent() == nullptr);
delete fw1;
delete dock1;
}
void TestCommon::tst_startHidden()
{
// A really simple test for AddingOption_StartHidden
EnsureTopLevelsDeleted e;
auto m = createMainWindow(QSize(800, 500), MainWindowOption_None);
auto dock1 = createDockWidget("1", new QPushButton("1"), {}, /*show=*/false);
m->addDockWidget(dock1, Location_OnRight, nullptr, AddingOption_StartHidden);
delete dock1;
}
void TestCommon::tst_negativeAnchorPosition2()
{
// Tests that the "Out of bounds position" warning doesn't appear. Test will abort if yes.
EnsureTopLevelsDeleted e;
auto m = createMainWindow(QSize(800, 500), MainWindowOption_None);
auto dropArea = m->dropArea();
MultiSplitter *layout = dropArea;
auto dock1 = createDockWidget("1", new QPushButton("1"), {}, /*show=*/false);
auto dock2 = createDockWidget("2", new QPushButton("2"), {}, /*show=*/false);
auto dock3 = createDockWidget("3", new QPushButton("3"), {}, /*show=*/false);
m->addDockWidget(dock1, Location_OnLeft);
m->addDockWidget(dock2, Location_OnRight, nullptr, AddingOption_StartHidden);
m->addDockWidget(dock3, Location_OnRight);
QCOMPARE(layout->placeholderCount(), 1);
QCOMPARE(layout->count(), 3);
dock1->setFloating(true);
dock1->setFloating(false);
dock2->deleteLater();
layout->checkSanity();
QVERIFY(Testing::waitForDeleted(dock2));
}
void TestCommon::tst_negativeAnchorPosition3()
{
// 1. Another case, when floating a dock:
EnsureTopLevelsDeleted e;
QVector<DockDescriptor> docks = { {Location_OnLeft, -1, nullptr, AddingOption_None },
{Location_OnRight, -1, nullptr, AddingOption_None },
{Location_OnLeft, -1, nullptr, AddingOption_None },
{Location_OnBottom, -1, nullptr, AddingOption_StartHidden },
{Location_OnRight, -1, nullptr, AddingOption_None } };
auto m = createMainWindow(docks);
auto dropArea = m->dropArea();
MultiSplitter *layout = dropArea;
layout->checkSanity();
auto dock1 = docks.at(1).createdDock;
auto dock3 = docks.at(3).createdDock;
dock1->setFloating(true);
delete dock1->window();
delete dock3->window();
layout->checkSanity();
}
void TestCommon::tst_negativeAnchorPosition4()
{
// 1. Tests that we don't get a warning
// Out of bounds position= -5 ; oldPosition= 0 KDDockWidgets::Anchor(0x55e726be9090, name = "left") KDDockWidgets::MainWindow(0x55e726beb8d0)
EnsureTopLevelsDeleted e;
QVector<DockDescriptor> docks = { { Location_OnLeft, -1, nullptr, AddingOption_StartHidden },
{ Location_OnTop, -1, nullptr, AddingOption_None },
{ Location_OnRight, -1, nullptr, AddingOption_None },
{ Location_OnLeft, -1, nullptr, AddingOption_None },
{ Location_OnRight, -1, nullptr, AddingOption_None } };
auto m = createMainWindow(docks);
auto dropArea = m->dropArea();
MultiSplitter *layout = dropArea;
layout->checkSanity();
auto dock1 = docks.at(1).createdDock;
auto dock2 = docks.at(2).createdDock;
dock2->setFloating(true);
auto fw2 = dock2->floatingWindow();
dropArea->addWidget(fw2->dropArea(), Location_OnLeft, dock1->frame());
dock2->setFloating(true);
fw2 = dock2->floatingWindow();
dropArea->addWidget(fw2->dropArea(), Location_OnRight, dock1->frame());
layout->checkSanity();
docks.at(0).createdDock->deleteLater();
docks.at(4).createdDock->deleteLater();
Testing::waitForDeleted(docks.at(4).createdDock);
}
void TestCommon::tst_negativeAnchorPosition5()
{
EnsureTopLevelsDeleted e;
QVector<DockDescriptor> docks = {
{Location_OnBottom, -1, nullptr, AddingOption_StartHidden },
{Location_OnBottom, -1, nullptr, AddingOption_StartHidden },
{Location_OnBottom, -1, nullptr, AddingOption_StartHidden },
};
auto m = createMainWindow(docks);
auto dropArea = m->dropArea();
MultiSplitter *layout = dropArea;
layout->checkSanity();
auto dock0 = docks.at(0).createdDock;
auto dock1 = docks.at(1).createdDock;
dock1->show();
dock0->show();
layout->checkSanity();
// Cleanup
for (auto dock : DockRegistry::self()->dockwidgets())
dock->deleteLater();
QVERIFY(Testing::waitForDeleted(dock0));
}
void TestCommon::tst_invalidAnchorGroup()
{
// Tests a bug I got. Should not warn.
EnsureTopLevelsDeleted e;
{
auto dock1 = createDockWidget("dock1", new QPushButton("one"));
auto dock2 = createDockWidget("dock2", new QPushButton("two"));
QPointer<FloatingWindow> fw = dock2->morphIntoFloatingWindow();
nestDockWidget(dock1, fw->dropArea(), nullptr, KDDockWidgets::Location_OnTop);
dock1->close();
Testing::waitForResize(dock2);
auto layout = fw->dropArea();
layout->checkSanity();
dock2->close();
dock1->deleteLater();
dock2->deleteLater();
Testing::waitForDeleted(dock1);
}
{
// Stack 1, 2, 3, close 2, close 1
auto m = createMainWindow(QSize(800, 500), MainWindowOption_None);
auto dock1 = createDockWidget("dock1", new QPushButton("one"));
auto dock2 = createDockWidget("dock2", new QPushButton("two"));
auto dock3 = createDockWidget("dock3", new QPushButton("three"));
m->addDockWidget(dock3, Location_OnTop);
m->addDockWidget(dock2, Location_OnTop);
m->addDockWidget(dock1, Location_OnTop);
dock2->close();
dock1->close();
dock1->deleteLater();
dock2->deleteLater();
Testing::waitForDeleted(dock1);
}
}
#include "tst_common.moc"

File diff suppressed because it is too large Load Diff

View File

@@ -14,6 +14,7 @@
#include "Config.h"
#include "TitleBar_p.h"
#include "FloatingWindow_p.h"
#include "FrameworkWidgetFactory.h"
#include <QCloseEvent>
#include <QDebug>
@@ -114,7 +115,16 @@ std::unique_ptr<MainWindowBase> KDDockWidgets::Tests::createMainWindow(QVector<D
{
static int count = 0;
count++;
auto m = std::unique_ptr<MainWindowType>(new MainWindowType(QStringLiteral("MyMainWindow%1").arg(count), MainWindowOption_None));
WidgetType *parent = nullptr;
#ifdef KDDOCKWIDGETS_QTQUICK
auto view = new QQuickView(Config::self().qmlEngine(), nullptr);
view->setSource(QUrl("qrc:/main.qml"));
view->show();
parent = view->rootObject();
#endif
auto m = std::unique_ptr<MainWindowType>(new MainWindowType(QStringLiteral("MyMainWindow%1").arg(count), MainWindowOption_None, parent));
auto layout = m->multiSplitter();
m->show();
m->resize(QSize(700, 700));
@@ -226,3 +236,13 @@ void KDDockWidgets::Tests::moveMouseTo(QPoint globalDest, QWidget *receiver)
QTest::qWait(2);
}
}
void KDDockWidgets::Tests::nestDockWidget(DockWidgetBase *dock, DropArea *dropArea, Frame *relativeTo, Location location)
{
auto frame = Config::self().frameworkWidgetFactory()->createFrame();
frame->addWidget(dock);
dock->frame()->setObjectName(dock->objectName());
dropArea->addWidget(frame, location, relativeTo);
QVERIFY(dropArea->checkSanity());
}

View File

@@ -101,6 +101,9 @@ KDDockWidgets::DockWidgetBase *createDockWidget(const QString &name, QWidgetOrQu
const QString &affinityName = {});
KDDockWidgets::DockWidgetBase *createDockWidget(const QString &name, QColor color = Qt::black);
void nestDockWidget(DockWidgetBase *dock, DropArea *dropArea, Frame *relativeTo,
KDDockWidgets::Location location);
class NonClosableWidget : public QWidget
{
public:
@@ -116,7 +119,7 @@ protected:
class MyWidget : public QWidgetOrQuick
{
public:
explicit MyWidget(const QString &, QColor c);
explicit MyWidget(const QString &, QColor c = Qt::black);
~MyWidget() override;
@@ -134,6 +137,24 @@ private:
};
#ifdef KDDOCKWIDGETS_QTQUICK
// Don't want to adapt dozens of locations so it compiles for QtQuick, so just typedef.
// the fact it's a button isn't important for the tests anyway
class QPushButton : public MyWidget
{
public:
// use const char* to silence QtCreator static analizer warnings when using const char * in tst_docks.cpp
// We don't have QT_NO_CAST_FROM_ASCII and still it complains, so use an indirection so I can read tst_docks while
// porting to QtQuick without noise. Once the port is done feel free to change to QString.
explicit QPushButton(const char *name)
: MyWidget(QString::fromLatin1(name))
{
}
};
#endif
void doubleClickOn(QPoint globalPos, QWidget *receiver);
void pressOn(QPoint globalPos, QWidget *receiver);
void releaseOn(QPoint globalPos, QWidget *receiver);