Compare commits

...

124 Commits

Author SHA1 Message Date
Sergio Martins
7b460237bd wip 2020-05-03 13:59:09 +01:00
Sergio Martins
6b33e000f5 wip 2020-05-03 13:52:24 +01:00
Sergio Martins
2dcc50aaad wip 2020-05-02 16:12:44 +01:00
Sergio Martins
4f4f101b28 wip 2020-05-02 14:03:07 +01:00
Sergio Martins
52c3827e5f up 2020-05-01 19:35:51 +01:00
Sergio Martins
3b02c6f7cd wip 2020-05-01 17:27:17 +01:00
Sergio Martins
d1c7a039f1 wip 2020-05-01 14:52:41 +01:00
Sergio Martins
bbbc54a704 wip 2020-05-01 14:31:10 +01:00
Sergio Martins
f8dbd51817 wip 2020-04-30 18:15:10 +01:00
Sergio Martins
477b42b4a9 wip 2020-04-30 16:47:20 +01:00
Sergio Martins
f8c0c28717 wip 2020-04-29 22:28:03 +01:00
Sergio Martins
fccb6f0cb2 wip 2020-04-29 21:47:13 +01:00
Sergio Martins
32ff511b33 wip 2020-04-29 21:39:02 +01:00
Sergio Martins
03572617d4 wip 2020-04-29 21:35:00 +01:00
Sergio Martins
24e893dfd7 wip 2020-04-29 21:03:14 +01:00
Sergio Martins
fcdf70ed60 wip 2020-04-29 00:02:15 +01:00
Sergio Martins
948b736d95 Add Item::toVariantMap() 2020-04-28 21:22:16 +01:00
Sergio Martins
6cffb6fbb4 wip 2020-04-28 20:03:21 +01:00
Sergio Martins
f58d6285b8 wip 2020-04-28 00:11:01 +01:00
Sergio Martins
16d337a648 wip 2020-04-27 23:56:47 +01:00
Sergio Martins
3326429c05 wip 2020-04-27 23:16:03 +01:00
Sergio Martins
4dc0da5e75 wip 2020-04-27 23:12:55 +01:00
Sergio Martins
e09d66f20f wip 2020-04-27 22:40:39 +01:00
Sergio Martins
ad87e73928 wip 2020-04-27 22:30:39 +01:00
Sergio Martins
da35ac9f46 wip 2020-04-27 20:44:04 +01:00
Sergio Martins
bba7ddc4a5 wip 2020-04-27 19:46:58 +01:00
Sergio Martins
52e945c900 wip 2020-04-27 19:31:21 +01:00
Sergio Martins
64c00375c9 wip 2020-04-26 18:56:44 +01:00
Sergio Martins
03b3c4340d wip 2020-04-26 18:20:56 +01:00
Sergio Martins
de60913a88 wip 2020-04-26 16:56:34 +01:00
Sergio Martins
2e8db85d6b wip 2020-04-26 15:48:26 +01:00
Sergio Martins
301711dcbd wip 2020-04-26 15:38:33 +01:00
Sergio Martins
b2ff79997a wip 2020-04-26 14:53:58 +01:00
Sergio Martins
54b5d7fb4f wip 2020-04-25 16:26:15 +01:00
Sergio Martins
f86c45a94a wip 2020-04-25 16:12:39 +01:00
Sergio Martins
b51d1d1812 wip 2020-04-25 16:09:37 +01:00
Sergio Martins
4cc90af703 wip 2020-04-25 15:45:07 +01:00
Sergio Martins
7c72d75ca0 wip 2020-04-25 15:43:04 +01:00
Sergio Martins
0860957299 wip 2020-04-25 13:24:52 +01:00
Sergio Martins
089ffae352 wip 2020-04-25 12:30:57 +01:00
Sergio Martins
9111b6b525 wip 2020-04-25 00:04:42 +01:00
Sergio Martins
7738513851 WIP 2020-04-24 23:46:51 +01:00
Sergio Martins
949e3756e9 wip 2020-04-23 23:09:25 +01:00
Sergio Martins
9107a2f7fe wip 2020-04-23 22:49:04 +01:00
Sergio Martins
2eebecb561 wip 2020-04-23 21:39:04 +01:00
Sergio Martins
4de07d29b1 wip 2020-04-23 00:07:09 +01:00
Sergio Martins
5ffac1cd92 wip 2020-04-22 23:07:52 +01:00
Sergio Martins
7198bdf826 wip 2020-04-22 20:31:54 +01:00
Sergio Martins
a355e1b8db wip 2020-04-22 19:52:53 +01:00
Sergio Martins
1e02e6e965 wip 2020-04-22 19:36:28 +01:00
Sergio Martins
69159a15a3 wip 2020-04-22 19:29:58 +01:00
Sergio Martins
5bbc9f1d2a wip 2020-04-22 13:52:29 +01:00
Sergio Martins
118cd1b30a wip 2020-04-22 13:42:03 +01:00
Sergio Martins
4e047a25a3 wip 2020-04-22 13:25:34 +01:00
Sergio Martins
20244a5747 wip 2020-04-22 12:57:29 +01:00
Sergio Martins
b0bbd24661 wip 2020-04-22 12:50:26 +01:00
Sergio Martins
2396a27906 wip 2020-04-22 10:53:16 +01:00
Sergio Martins
713b2ed8c8 wip 2020-04-21 23:09:34 +01:00
Sergio Martins
380b8bb9a9 wip 2020-04-21 22:57:59 +01:00
Sergio Martins
e62f5b2afa wip 2020-04-21 22:51:35 +01:00
Sergio Martins
033ca3ff39 wip 2020-04-21 18:10:44 +01:00
Sergio Martins
178923f202 WIP 2020-04-20 17:23:32 +01:00
Sergio Martins
c96a5c858a wip 2020-04-15 19:41:07 +01:00
Sergio Martins
5888ae7df9 Fix examples build 2020-04-14 23:15:29 +01:00
Sergio Martins
37ab437f3f Anchor.cpp compiles 2020-04-14 07:44:44 +01:00
Sergio Martins
fec9a5ff4a wip 2020-04-13 21:47:20 +01:00
Sergio Martins
d66e88b484 wip 2020-04-13 21:40:58 +01:00
Sergio Martins
042d466a48 wip 2020-04-13 20:41:24 +01:00
Sergio Martins
69fd55dfef tests++ 2020-04-13 20:26:12 +01:00
Sergio Martins
1d553b5091 fixes++ 2020-04-13 19:54:40 +01:00
Sergio Martins
86e85d6f35 wip 2020-04-13 18:59:56 +01:00
Sergio Martins
c39805704d Fix and uncomment more tests 2020-04-13 18:21:59 +01:00
Sergio Martins
1827a5b415 Both test suits pass now
Will start uncommenting and fixing more tst_docks.cpp tests
2020-04-13 16:28:11 +01:00
Sergio Martins
5df59016c7 wip 2020-04-13 16:24:19 +01:00
Sergio Martins
eeec408879 wip 2020-04-13 16:08:30 +01:00
Sergio Martins
5dea2c7afe wip 2020-04-13 11:51:16 +01:00
Sergio Martins
7821708301 wip 2020-04-12 19:32:39 +01:00
Sergio Martins
ab58d0ec2e wip 2020-04-12 19:13:18 +01:00
Sergio Martins
ff1e2c1885 wip 2020-04-12 18:59:22 +01:00
Sergio Martins
290592bbd6 wip 2020-04-12 16:53:50 +01:00
Sergio Martins
7493cdc081 wip 2020-04-12 15:47:52 +01:00
Sergio Martins
cf85c815f4 wip 2020-04-12 15:46:07 +01:00
Sergio Martins
d5362dcf4d wip 2020-04-11 19:00:07 +01:00
Sergio Martins
56127dbeaa wip 2020-04-11 14:08:56 +01:00
Sergio Martins
adbcab2fb0 wip 2020-04-11 13:31:06 +01:00
Sergio Martins
e0924bcfa1 wip 2020-04-11 13:24:33 +01:00
Sergio Martins
2f03c11c1a wip 2020-04-10 20:56:06 +01:00
Sergio Martins
5513964f31 wip 2020-04-10 19:57:12 +01:00
Sergio Martins
6dd93c50c8 wip 2020-04-10 19:54:41 +01:00
Sergio Martins
bc84e3ef0b wip 2020-04-10 15:48:42 +01:00
Sergio Martins
da4a1e9302 wip 2020-04-10 15:45:28 +01:00
Sergio Martins
93fec759ed wip 2020-04-10 13:07:56 +01:00
Sergio Martins
d315473a54 Fix clear 2020-04-10 12:23:20 +01:00
Sergio Martins
8cd4bf1701 wip 2020-04-10 12:05:49 +01:00
Sergio Martins
95feb27a97 Remove dead code 2020-04-10 10:38:09 +01:00
Sergio Martins
8103857dbb wip 2020-04-10 10:36:17 +01:00
Sergio Martins
37afe34a22 wip 2020-04-09 00:16:08 +01:00
Sergio Martins
96d180e1c6 tst_multisplitter now passes 2020-04-07 17:52:48 +01:00
Sergio Martins
22dd3d37eb wip 2020-04-06 22:58:02 +01:00
Sergio Martins
cb592e6749 wip 2020-04-06 22:29:45 +01:00
Sergio Martins
94b47893d6 wip 2020-04-06 21:57:34 +01:00
Sergio Martins
164ccd5cd7 wip 2020-04-06 20:16:14 +01:00
Sergio Martins
2a0abe60f9 wip 2020-04-05 22:15:02 +01:00
Sergio Martins
0a80629a50 uncomment more tests 2020-04-05 15:29:59 +01:00
Sergio Martins
8c4fbd4012 start uncommenting tests 2020-04-05 15:28:07 +01:00
Sergio Martins
c03a786025 wip 2020-04-05 14:02:58 +01:00
Sergio Martins
a5a7019d5a wip 2020-04-05 13:52:06 +01:00
Sergio Martins
4be5b72208 wip 2020-04-05 13:27:21 +01:00
Sergio Martins
f6200739ff wip 2020-04-05 13:22:06 +01:00
Sergio Martins
42290b68ed Closing dock widget now works 2020-04-04 18:35:53 +01:00
Sergio Martins
4997970800 wip 2020-04-04 17:59:16 +01:00
Sergio Martins
b455ecae74 wip 2020-04-04 16:35:13 +01:00
Sergio Martins
5bd9a44324 Implement Item::turnIntoPlaceholder 2020-04-04 16:08:00 +01:00
Sergio Martins
d417837978 wip 2020-04-04 15:40:13 +01:00
Sergio Martins
acf8705590 wip 2020-04-04 14:39:45 +01:00
Sergio Martins
9a18276819 wip 2020-04-04 13:12:06 +01:00
Sergio Martins
74a748936d Fixed geometry, needs to be relative to root 2020-04-03 20:11:12 +01:00
Sergio Martins
1c16eff3e1 wip 2020-04-03 20:03:24 +01:00
Sergio Martins
6a7291080f Example builds 2020-04-03 18:44:29 +01:00
Sergio Martins
8539a9f1a4 KDDockWidgets builds now 2020-04-02 20:48:12 +01:00
Sergio Martins
4734f06046 builds 2020-04-02 19:43:36 +01:00
Sergio Martins
993ca6e9c6 Use target_compile_definitions for the no ascii cast defines
So it doesn't spread into tst_multisplitter
2020-04-01 15:23:10 +01:00
Sergio Martins
76eb54e86f WIP: Anchor.cpp now builds 2020-04-01 15:19:22 +01:00
Sergio Martins
1643f23612 WIP 2020-04-01 12:02:47 +01:00
44 changed files with 4919 additions and 6020 deletions

1
.gitignore vendored
View File

@@ -41,3 +41,4 @@ kddockwidgets_basic_quick
build-* build-*
/examples/dockwidgets/kddockwidgets_example /examples/dockwidgets/kddockwidgets_example
.cmake .cmake
mylayout.json

View File

@@ -127,7 +127,7 @@ void MyMainWindow::createDockWidgets()
addDockWidget(m_dockwidgets[3], KDDockWidgets::Location_OnBottom); addDockWidget(m_dockwidgets[3], KDDockWidgets::Location_OnBottom);
addDockWidget(m_dockwidgets[4], KDDockWidgets::Location_OnBottom); addDockWidget(m_dockwidgets[4], KDDockWidgets::Location_OnBottom);
// Tab two dock widgets toghether // Tab two dock widgets together
m_dockwidgets[3]->addDockWidgetAsTab(m_dockwidgets[5]); m_dockwidgets[3]->addDockWidgetAsTab(m_dockwidgets[5]);
// 6 is floating, as it wasn't added to the main window via MainWindow::addDockWidget(). // 6 is floating, as it wasn't added to the main window via MainWindow::addDockWidget().

View File

@@ -1,11 +1,7 @@
cmake_policy(SET CMP0043 NEW) cmake_policy(SET CMP0043 NEW)
add_definitions(-DQT_NO_CAST_TO_ASCII add_definitions(-DQT_NO_SIGNALS_SLOTS_KEYWORDS
-DQT_NO_CAST_FROM_ASCII
-DQT_NO_URL_CAST_FROM_STRING
-DQT_NO_CAST_FROM_BYTEARRAY
-DQT_NO_SIGNALS_SLOTS_KEYWORDS
-DQT_USE_QSTRINGBUILDER -DQT_USE_QSTRINGBUILDER
-DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT -DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT
-DQT_STRICT_ITERATORS -DQT_STRICT_ITERATORS
@@ -25,13 +21,9 @@ set(DOCKSLIBS_SRCS
private/ObjectViewer.cpp private/ObjectViewer.cpp
private/DropIndicatorOverlayInterface.cpp private/DropIndicatorOverlayInterface.cpp
private/indicators/ClassicIndicators.cpp private/indicators/ClassicIndicators.cpp
private/indicators/AnimatedIndicators.cpp # private/indicators/AnimatedIndicators.cpp
private/DropArea.cpp private/DropArea.cpp
private/multisplitter/Item.cpp
private/multisplitter/MultiSplitter.cpp private/multisplitter/MultiSplitter.cpp
private/multisplitter/Anchor.cpp
private/multisplitter/AnchorGroup.cpp
private/multisplitter/Separator.cpp
private/multisplitter/MultiSplitterLayout.cpp private/multisplitter/MultiSplitterLayout.cpp
private/TabWidget.cpp private/TabWidget.cpp
private/FloatingWindow.cpp private/FloatingWindow.cpp
@@ -72,7 +64,7 @@ set(DOCKS_INSTALLABLE_PRIVATE_WIDGET_INCLUDES
private/widgets/QWidgetAdapter_widgets_p.h private/widgets/QWidgetAdapter_widgets_p.h
private/widgets/TitleBarWidget_p.h private/widgets/TitleBarWidget_p.h
private/widgets/SeparatorWidget_p.h private/widgets/SeparatorWidget_p.h
private/widgets/FloatingWindowWidget_p.h private/widgets/FloatingWindowWidget_p.h
private/widgets/FrameWidget_p.h private/widgets/FrameWidget_p.h
private/widgets/TabBarWidget_p.h private/widgets/TabBarWidget_p.h
private/widgets/TabWidgetWidget_p.h private/widgets/TabWidgetWidget_p.h
@@ -119,6 +111,9 @@ else()
set(IS_CLANG_BUILD FALSE) set(IS_CLANG_BUILD FALSE)
endif() endif()
add_subdirectory(private/multisplitter)
qt5_add_resources(RESOURCES ${CMAKE_CURRENT_SOURCE_DIR}/resources.qrc) qt5_add_resources(RESOURCES ${CMAKE_CURRENT_SOURCE_DIR}/resources.qrc)
add_library(kddockwidgets SHARED ${DOCKSLIBS_SRCS} ${DOCKS_INSTALLABLE_INCLUDES} ${RESOURCES} ${RESOURCES_QUICK}) add_library(kddockwidgets SHARED ${DOCKSLIBS_SRCS} ${DOCKS_INSTALLABLE_INCLUDES} ${RESOURCES} ${RESOURCES_QUICK})
@@ -133,7 +128,13 @@ target_include_directories(kddockwidgets
${CMAKE_CURRENT_SOURCE_DIR}/private ${CMAKE_CURRENT_SOURCE_DIR}/private
) )
target_compile_definitions(kddockwidgets PRIVATE BUILDING_DOCKS_LIBRARY) target_compile_definitions(kddockwidgets PRIVATE BUILDING_DOCKS_LIBRARY
QT_NO_CAST_TO_ASCII
QT_NO_CAST_FROM_ASCII
QT_NO_URL_CAST_FROM_STRING
QT_NO_CAST_FROM_BYTEARRAY
)
if (CMAKE_COMPILER_IS_GNUCXX OR IS_CLANG_BUILD) if (CMAKE_COMPILER_IS_GNUCXX OR IS_CLANG_BUILD)
target_compile_options(kddockwidgets PRIVATE -Wshadow -Wconversion -fvisibility=hidden) target_compile_options(kddockwidgets PRIVATE -Wshadow -Wconversion -fvisibility=hidden)
@@ -145,13 +146,13 @@ endif()
if (OPTION_QTQUICK) if (OPTION_QTQUICK)
target_link_libraries(kddockwidgets Qt5::Widgets Qt5::Quick) target_link_libraries(kddockwidgets Qt5::Widgets Qt5::Quick)
else() else()
target_link_libraries(kddockwidgets Qt5::Widgets) target_link_libraries(kddockwidgets PUBLIC Qt5::Widgets PRIVATE kddockwidgets_layouting)
endif() endif()
if (NOT WIN32 AND NOT APPLE) if (NOT WIN32 AND NOT APPLE)
find_package(Qt5X11Extras) find_package(Qt5X11Extras)
target_link_libraries(kddockwidgets Qt5::X11Extras) target_link_libraries(kddockwidgets PUBLIC Qt5::X11Extras)
endif() endif()
install (TARGETS kddockwidgets install (TARGETS kddockwidgets
@@ -161,6 +162,7 @@ install (TARGETS kddockwidgets
ARCHIVE DESTINATION lib) ARCHIVE DESTINATION lib)
install (FILES ${DOCKS_INSTALLABLE_INCLUDES} DESTINATION include/kddockwidgets) install (FILES ${DOCKS_INSTALLABLE_INCLUDES} DESTINATION include/kddockwidgets)
install (FILES ${DOCKS_INSTALLABLE_PRIVATE_INCLUDES} DESTINATION include/kddockwidgets/private) install (FILES ${DOCKS_INSTALLABLE_PRIVATE_INCLUDES} DESTINATION include/kddockwidgets/private)
install (FILES private/multisplitter/Item_p.h DESTINATION include/kddockwidgets/multisplitter)
install (FILES ${DOCKS_INSTALLABLE_PRIVATE_WIDGET_INCLUDES} DESTINATION include/kddockwidgets/private/widgets) install (FILES ${DOCKS_INSTALLABLE_PRIVATE_WIDGET_INCLUDES} DESTINATION include/kddockwidgets/private/widgets)
include(CMakePackageConfigHelpers) include(CMakePackageConfigHelpers)

View File

@@ -28,6 +28,7 @@
#include "Config.h" #include "Config.h"
#include "DockRegistry_p.h" #include "DockRegistry_p.h"
#include "FrameworkWidgetFactory.h" #include "FrameworkWidgetFactory.h"
#include "Anchor_p.h"
#include <QApplication> #include <QApplication>
#include <QDebug> #include <QDebug>
@@ -67,6 +68,13 @@ Config::Config()
: d(new Private()) : d(new Private())
{ {
d->fixFlags(); d->fixFlags();
// stuff in multisplitter/ can't include the framework widget factory, so set it here
auto separatorCreator = [](Layouting::Anchor *a, QWidget *parent){
return Config::self().frameworkWidgetFactory()->createSeparator(a, parent);
};
Layouting::Anchor::setSeparatorFactoryFunc(separatorCreator);
} }
Config& Config::self() Config& Config::self()

View File

@@ -47,6 +47,7 @@
*/ */
using namespace KDDockWidgets; using namespace KDDockWidgets;
using namespace Layouting;
class DockWidgetBase::Private class DockWidgetBase::Private
{ {
@@ -544,7 +545,9 @@ void DockWidgetBase::Private::restoreToPreviousPosition()
return; return;
} }
m_lastPosition.layoutItem()->restorePlaceholder(q, m_lastPosition.m_tabIndex); MultiSplitterLayout *layout = DockRegistry::self()->layoutForItem(m_lastPosition.layoutItem());
Q_ASSERT(layout);
layout->restorePlaceholder(q, m_lastPosition.layoutItem(), m_lastPosition.m_tabIndex);
} }
void DockWidgetBase::Private::maybeRestoreToPreviousPosition() void DockWidgetBase::Private::maybeRestoreToPreviousPosition()
@@ -560,7 +563,7 @@ void DockWidgetBase::Private::maybeRestoreToPreviousPosition()
Frame *frame = q->frame(); Frame *frame = q->frame();
if (frame && frame->parentWidget() == layoutItem->parentWidget()) { if (frame && frame->parentWidget() == DockRegistry::self()->layoutForItem(layoutItem)->multiSplitter()) {
// There's a frame already. Means the DockWidget was hidden instead of closed. // There's a frame already. Means the DockWidget was hidden instead of closed.
// Nothing to do, the dock widget will simply be shown // Nothing to do, the dock widget will simply be shown
qCDebug(placeholder) << Q_FUNC_INFO << "Already had frame."; qCDebug(placeholder) << Q_FUNC_INFO << "Already had frame.";

View File

@@ -40,12 +40,15 @@ QT_BEGIN_NAMESPACE
class QAction; class QAction;
QT_END_NAMESPACE QT_END_NAMESPACE
namespace Layouting {
class Item;
}
namespace KDDockWidgets { namespace KDDockWidgets {
class Frame; class Frame;
class FloatingWindow; class FloatingWindow;
class DragController; class DragController;
class Item;
class LastPosition; class LastPosition;
class DockRegistry; class DockRegistry;
class LayoutSaver; class LayoutSaver;
@@ -333,7 +336,6 @@ private:
friend class KDDockWidgets::TabWidget; friend class KDDockWidgets::TabWidget;
friend class KDDockWidgets::TitleBar; friend class KDDockWidgets::TitleBar;
friend class KDDockWidgets::DragController; friend class KDDockWidgets::DragController;
friend class KDDockWidgets::Item;
friend class KDDockWidgets::DockRegistry; friend class KDDockWidgets::DockRegistry;
friend class KDDockWidgets::LayoutSaver; friend class KDDockWidgets::LayoutSaver;
@@ -356,7 +358,7 @@ private:
FloatingWindow *floatingWindow() const; FloatingWindow *floatingWindow() const;
///@brief adds the current layout item containing this dock widget ///@brief adds the current layout item containing this dock widget
void addPlaceholderItem(Item*); void addPlaceholderItem(Layouting::Item*);
///@brief returns the last position, just for tests. TODO Make tests just use the d-pointer. ///@brief returns the last position, just for tests. TODO Make tests just use the d-pointer.
LastPosition *lastPosition() const; LastPosition *lastPosition() const;
@@ -366,5 +368,6 @@ private:
}; };
} }
Q_DECLARE_METATYPE(KDDockWidgets::Location)
#endif #endif

View File

@@ -45,6 +45,8 @@
#endif #endif
using namespace KDDockWidgets; using namespace KDDockWidgets;
using namespace Layouting;
FrameworkWidgetFactory::~FrameworkWidgetFactory() FrameworkWidgetFactory::~FrameworkWidgetFactory()
{ {
@@ -76,7 +78,7 @@ TabWidget *DefaultWidgetFactory::createTabWidget(Frame *parent) const
return new TabWidgetWidget(parent); return new TabWidgetWidget(parent);
} }
Separator *DefaultWidgetFactory::createSeparator(Anchor *anchor, QWidgetAdapter *parent) const Layouting::Separator *DefaultWidgetFactory::createSeparator(Anchor *anchor, QWidget *parent) const
{ {
return new SeparatorWidget(anchor, parent); return new SeparatorWidget(anchor, parent);
} }

View File

@@ -32,17 +32,20 @@
* @author Sérgio Martins \<sergio.martins@kdab.com\> * @author Sérgio Martins \<sergio.martins@kdab.com\>
*/ */
namespace Layouting {
class Anchor;
class Separator;
}
namespace KDDockWidgets { namespace KDDockWidgets {
class MainWindowBase; class MainWindowBase;
class DropIndicatorOverlayInterface; class DropIndicatorOverlayInterface;
class Separator;
class FloatingWindow; class FloatingWindow;
class TabWidget; class TabWidget;
class TitleBar; class TitleBar;
class Frame; class Frame;
class DropArea; class DropArea;
class Anchor;
class TabBar; class TabBar;
/** /**
@@ -103,7 +106,7 @@ public:
/// the user to resize nested dock widgets. /// the user to resize nested dock widgets.
///@param anchor Just forward to Sepataror's constructor. ///@param anchor Just forward to Sepataror's constructor.
///@param parent Just forward to Separator's constructor. ///@param parent Just forward to Separator's constructor.
virtual Separator* createSeparator(Anchor *anchor, QWidgetAdapter *parent = nullptr) const = 0; virtual Layouting::Separator* createSeparator(Layouting::Anchor *anchor, QWidget *parent = nullptr) const = 0;
///@brief Called internally by the framework to create a FloatingWindow ///@brief Called internally by the framework to create a FloatingWindow
/// Override to provide your own FloatingWindow sub-class. If overridden then /// Override to provide your own FloatingWindow sub-class. If overridden then
@@ -135,7 +138,7 @@ public:
TitleBar *createTitleBar(FloatingWindow *) const override; TitleBar *createTitleBar(FloatingWindow *) const override;
TabBar *createTabBar(TabWidget *parent) const override; TabBar *createTabBar(TabWidget *parent) const override;
TabWidget *createTabWidget(Frame *parent) const override; TabWidget *createTabWidget(Frame *parent) const override;
Separator *createSeparator(Anchor *anchor, QWidgetAdapter *parent = nullptr) const override; Layouting::Separator *createSeparator(Layouting::Anchor *anchor, QWidget *parent = nullptr) const override;
FloatingWindow *createFloatingWindow(MainWindowBase *parent = nullptr) const override; FloatingWindow *createFloatingWindow(MainWindowBase *parent = nullptr) const override;
FloatingWindow *createFloatingWindow(Frame *frame, MainWindowBase *parent = nullptr) const override; FloatingWindow *createFloatingWindow(Frame *frame, MainWindowBase *parent = nullptr) const override;
DropIndicatorOverlayInterface *createDropIndicatorOverlay(DropArea*) const override; DropIndicatorOverlayInterface *createDropIndicatorOverlay(DropArea*) const override;

View File

@@ -66,63 +66,26 @@ namespace KDDockWidgets
}; };
Q_DECLARE_FLAGS(RestoreOptions, RestoreOption) Q_DECLARE_FLAGS(RestoreOptions, RestoreOption)
///@internal ///@internal
inline Location oppositeLocation(Location loc) inline QString locationStr(Location loc)
{ {
switch (loc) { switch (loc) {
case Location_OnLeft: case KDDockWidgets::Location_None:
return Location_OnRight; return QStringLiteral("none");
case Location_OnTop: case KDDockWidgets::Location_OnLeft:
return Location_OnBottom; return QStringLiteral("left");
case Location_OnRight: case KDDockWidgets::Location_OnTop:
return Location_OnLeft; return QStringLiteral("top");
case Location_OnBottom: case KDDockWidgets::Location_OnRight:
return Location_OnTop; return QStringLiteral("right");
default: case KDDockWidgets::Location_OnBottom:
Q_ASSERT(false); return QStringLiteral("bottom");
return Location_None; }
}
}
///@internal return QString();
inline Location adjacentLocation(Location loc) }
{
switch (loc) {
case Location_OnLeft:
return Location_OnTop;
case Location_OnTop:
return Location_OnRight;
case Location_OnRight:
return Location_OnBottom;
case Location_OnBottom:
return Location_OnLeft;
default:
Q_ASSERT(false);
return Location_None;
}
}
///@internal
inline QString locationStr(Location loc)
{
switch (loc) {
case KDDockWidgets::Location_None:
return QStringLiteral("none");
case KDDockWidgets::Location_OnLeft:
return QStringLiteral("left");
case KDDockWidgets::Location_OnTop:
return QStringLiteral("top");
case KDDockWidgets::Location_OnRight:
return QStringLiteral("right");
case KDDockWidgets::Location_OnBottom:
return QStringLiteral("bottom");
}
return QString();
}
} }
Q_DECLARE_METATYPE(KDDockWidgets::Location)
Q_DECLARE_OPERATORS_FOR_FLAGS(KDDockWidgets::FrameOptions) Q_DECLARE_OPERATORS_FOR_FLAGS(KDDockWidgets::FrameOptions)
#endif #endif

View File

@@ -34,7 +34,6 @@
#include "Logging_p.h" #include "Logging_p.h"
#include "Frame_p.h" #include "Frame_p.h"
#include "LastPosition_p.h" #include "LastPosition_p.h"
#include "multisplitter/Anchor_p.h"
#include "multisplitter/Item_p.h" #include "multisplitter/Item_p.h"
#include "FrameworkWidgetFactory.h" #include "FrameworkWidgetFactory.h"
@@ -47,44 +46,11 @@
#include <memory> #include <memory>
using namespace KDDockWidgets; using namespace KDDockWidgets;
using namespace Layouting;
QHash<QString, LayoutSaver::DockWidget::Ptr> LayoutSaver::DockWidget::s_dockWidgets; QHash<QString, LayoutSaver::DockWidget::Ptr> LayoutSaver::DockWidget::s_dockWidgets;
LayoutSaver::Layout* LayoutSaver::Layout::s_currentLayoutBeingRestored = nullptr; LayoutSaver::Layout* LayoutSaver::Layout::s_currentLayoutBeingRestored = nullptr;
static QVariantMap sizeToMap(QSize sz)
{
QVariantMap map;
map.insert(QStringLiteral("width"), sz.width());
map.insert(QStringLiteral("height"), sz.height());
return map;
}
static QVariantMap rectToMap(QRect rect)
{
QVariantMap map;
map.insert(QStringLiteral("x"), rect.x());
map.insert(QStringLiteral("y"), rect.y());
map.insert(QStringLiteral("width"), rect.width());
map.insert(QStringLiteral("height"), rect.height());
return map;
}
static QSize mapToSize(const QVariantMap &map)
{
return { map.value(QStringLiteral("width")).toInt(),
map.value(QStringLiteral("height")).toInt() };
}
static QRect mapToRect(const QVariantMap &map)
{
return QRect(map.value(QStringLiteral("x")).toInt(),
map.value(QStringLiteral("y")).toInt(),
map.value(QStringLiteral("width")).toInt(),
map.value(QStringLiteral("height")).toInt());
}
class KDDockWidgets::LayoutSaver::Private class KDDockWidgets::LayoutSaver::Private
{ {
public: public:
@@ -221,31 +187,6 @@ bool LayoutSaver::restoreLayout(const QByteArray &data)
if (data.isEmpty()) if (data.isEmpty())
return true; return true;
struct EnsureItemsAtCorrectPlace {
EnsureItemsAtCorrectPlace(LayoutSaver *ls)
: layoutSaver(ls)
{
}
~EnsureItemsAtCorrectPlace()
{
// When using RestoreOption_RelativeToMainWindow we'll have many rounding errors so the layout won't be exact.
// Make sure to run a relayout at the end
// (Using RAII to make sure it runs after Private::RAIIIsRestoring went out of scope, since "isRestoring= true" inhibits relayout
if (ensure) {
for (auto layout : DockRegistry::self()->layouts()) {
if (layoutSaver->d->matchesAffinity(layout->affinityName()))
layout->redistributeSpace();
}
}
}
bool ensure = false;
LayoutSaver *const layoutSaver;
};
EnsureItemsAtCorrectPlace ensureItemsAtCorrectPlace(this);
Private::RAIIIsRestoring isRestoring; Private::RAIIIsRestoring isRestoring;
struct FrameCleanup { struct FrameCleanup {
@@ -273,7 +214,7 @@ bool LayoutSaver::restoreLayout(const QByteArray &data)
layout.scaleSizes(); layout.scaleSizes();
// Hide all dockwidgets and unparent them from any layout before starting restore // Hide all dockwidgets and unparent them from any layout before starting restore
d->m_dockRegistry->clear(d->m_affinityNames, /*deleteStaticAnchors=*/true); d->m_dockRegistry->clear(d->m_affinityNames);
// 1. Restore main windows // 1. Restore main windows
for (const LayoutSaver::MainWindow &mw : qAsConst(layout.mainWindows)) { for (const LayoutSaver::MainWindow &mw : qAsConst(layout.mainWindows)) {
@@ -327,9 +268,6 @@ bool LayoutSaver::restoreLayout(const QByteArray &data)
} }
} }
// our raii class will run when
ensureItemsAtCorrectPlace.ensure = d->m_restoreOptions & RestoreOption_RelativeToMainWindow;
return true; return true;
} }
@@ -416,20 +354,6 @@ bool LayoutSaver::Layout::isValid() const
return true; return true;
} }
bool LayoutSaver::Layout::fillFrom(const QByteArray &serialized)
{
QDataStream ds(serialized);
ds >> this;
if (serializationVersion > KDDOCKWIDGETS_SERIALIZATION_VERSION) {
qWarning() << "Unsupported serialization version. Got=" << serializationVersion
<< "; expected equal or less than" << KDDOCKWIDGETS_SERIALIZATION_VERSION;
return false;
}
return true;
}
QByteArray LayoutSaver::Layout::toJson() const QByteArray LayoutSaver::Layout::toJson() const
{ {
QJsonDocument doc = QJsonDocument::fromVariant(toVariantMap()); QJsonDocument doc = QJsonDocument::fromVariant(toVariantMap());
@@ -518,66 +442,9 @@ LayoutSaver::MainWindow LayoutSaver::Layout::mainWindowForIndex(int index) const
return mainWindows.at(index); return mainWindows.at(index);
} }
bool LayoutSaver::Item::isValid(const LayoutSaver::MultiSplitterLayout &layout) const
{
if (!frame.isValid())
return false;
const int numAnchors = layout.anchors.size();
if (indexOfLeftAnchor < 0 || indexOfTopAnchor < 0 ||
indexOfBottomAnchor < 0 || indexOfRightAnchor < 0 ||
indexOfLeftAnchor >= numAnchors || indexOfTopAnchor >= numAnchors ||
indexOfBottomAnchor >= numAnchors || indexOfRightAnchor >= numAnchors) {
qWarning() << Q_FUNC_INFO << "Invalid anchor indexes"
<< indexOfLeftAnchor << indexOfTopAnchor
<< indexOfBottomAnchor << indexOfRightAnchor;
return false;
}
return true;
}
void LayoutSaver::Item::scaleSizes(const ScalingInfo &scalingInfo)
{
scalingInfo.applyFactorsTo(geometry);
if (!frame.isNull)
frame.scaleSizes(scalingInfo);
}
QVariantMap LayoutSaver::Item::toVariantMap() const
{
QVariantMap map;
map.insert(QStringLiteral("objectName"), objectName);
map.insert(QStringLiteral("isPlaceholder"), isPlaceholder);
map.insert(QStringLiteral("geometry"), rectToMap(geometry));
map.insert(QStringLiteral("minSize"), sizeToMap(minSize));
map.insert(QStringLiteral("indexOfLeftAnchor"), indexOfLeftAnchor);
map.insert(QStringLiteral("indexOfTopAnchor"), indexOfTopAnchor);
map.insert(QStringLiteral("indexOfRightAnchor"), indexOfRightAnchor);
map.insert(QStringLiteral("indexOfBottomAnchor"), indexOfBottomAnchor);
if (!frame.isNull)
map.insert(QStringLiteral("frame"), frame.toVariantMap());
return map;
}
void LayoutSaver::Item::fromVariantMap(const QVariantMap &map)
{
objectName = map.value(QStringLiteral("objectName")).toString();
isPlaceholder = map.value(QStringLiteral("isPlaceholder")).toBool();
geometry = mapToRect(map.value(QStringLiteral("geometry")).toMap());
minSize = mapToSize(map.value(QStringLiteral("minSize")).toMap());
indexOfLeftAnchor = map.value(QStringLiteral("indexOfLeftAnchor")).toInt();
indexOfTopAnchor = map.value(QStringLiteral("indexOfTopAnchor")).toInt();
indexOfRightAnchor = map.value(QStringLiteral("indexOfRightAnchor")).toInt();
indexOfBottomAnchor = map.value(QStringLiteral("indexOfBottomAnchor")).toInt();
frame.fromVariantMap(map.value(QStringLiteral("frame"), QVariantMap()).toMap());
}
bool LayoutSaver::Frame::isValid() const bool LayoutSaver::Frame::isValid() const
{ {
if (!isNull) if (isNull)
return true; return true;
if (!geometry.isValid()) { if (!geometry.isValid()) {
@@ -585,14 +452,21 @@ bool LayoutSaver::Frame::isValid() const
return false; return false;
} }
if (id.isEmpty()) {
qWarning() << Q_FUNC_INFO << "Invalid id";
return false;
}
if (options > 3) { if (options > 3) {
qWarning() << Q_FUNC_INFO << "Invalid options" << options; qWarning() << Q_FUNC_INFO << "Invalid options" << options;
return false; return false;
} }
if (currentTabIndex >= dockWidgets.size() || currentTabIndex < 0) { if (!dockWidgets.isEmpty()) {
qWarning() << Q_FUNC_INFO << "Invalid tab index" << currentTabIndex << dockWidgets.size(); if (currentTabIndex >= dockWidgets.size() || currentTabIndex < 0) {
return false; qWarning() << Q_FUNC_INFO << "Invalid tab index" << currentTabIndex << dockWidgets.size();
return false;
}
} }
for (auto &dw : dockWidgets) { for (auto &dw : dockWidgets) {
@@ -611,6 +485,7 @@ void LayoutSaver::Frame::scaleSizes(const ScalingInfo &scalingInfo)
QVariantMap LayoutSaver::Frame::toVariantMap() const QVariantMap LayoutSaver::Frame::toVariantMap() const
{ {
QVariantMap map; QVariantMap map;
map.insert(QStringLiteral("id"), id);
map.insert(QStringLiteral("isNull"), isNull); map.insert(QStringLiteral("isNull"), isNull);
map.insert(QStringLiteral("objectName"), objectName); map.insert(QStringLiteral("objectName"), objectName);
map.insert(QStringLiteral("geometry"), rectToMap(geometry)); map.insert(QStringLiteral("geometry"), rectToMap(geometry));
@@ -630,6 +505,7 @@ void LayoutSaver::Frame::fromVariantMap(const QVariantMap &map)
return; return;
} }
id = map.value(QStringLiteral("id")).toString();
isNull = map.value(QStringLiteral("isNull")).toBool(); isNull = map.value(QStringLiteral("isNull")).toBool();
objectName = map.value(QStringLiteral("objectName")).toString(); objectName = map.value(QStringLiteral("objectName")).toString();
geometry = mapToRect(map.value(QStringLiteral("geometry")).toMap()); geometry = mapToRect(map.value(QStringLiteral("geometry")).toMap());
@@ -674,120 +550,6 @@ void LayoutSaver::DockWidget::fromVariantMap(const QVariantMap &map)
lastPosition.fromVariantMap(map.value(QStringLiteral("lastPosition")).toMap()); lastPosition.fromVariantMap(map.value(QStringLiteral("lastPosition")).toMap());
} }
bool LayoutSaver::Anchor::isValid(const LayoutSaver::MultiSplitterLayout &layout) const
{
const bool isStatic = type != KDDockWidgets::Anchor::Type_None;
const bool isFollowing = indexOfFollowee != -1;
const int numAnchors = layout.anchors.size();
if (!geometry.isValid() && !isStatic && !isFollowing) {
qWarning() << Q_FUNC_INFO << "Invalid geometry" << geometry;
return false;
}
if (indexOfFrom < 0 || indexOfTo < 0 || indexOfFrom == indexOfTo ||
indexOfTo >= numAnchors || indexOfFrom >= numAnchors) {
qWarning() << Q_FUNC_INFO << "Invalid indexes" << indexOfFrom << indexOfTo;
return false;
}
auto &anchorTo = layout.anchors[indexOfTo];
auto &anchorFrom = layout.anchors[indexOfFrom];
if (anchorTo.orientation != anchorFrom.orientation || anchorTo.orientation == orientation) {
qWarning() << Q_FUNC_INFO << "Invalid orientation" << anchorTo.orientation << anchorFrom.orientation
<< orientation;
return false;
}
if (orientation != Qt::Vertical && orientation != Qt::Horizontal) {
qWarning() << Q_FUNC_INFO << "Invalid orientation" << orientation;
return false;
}
if (type != KDDockWidgets::Anchor::Type_None &&
type != KDDockWidgets::Anchor::Type_LeftStatic &&
type != KDDockWidgets::Anchor::Type_RightStatic &&
type != KDDockWidgets::Anchor::Type_TopStatic &&
type != KDDockWidgets::Anchor::Type_BottomStatic) {
qWarning() << Q_FUNC_INFO << "Invalid type" << type;
return false;
}
if (!isStatic && !isFollowing && (side1Items.isEmpty() || side2Items.isEmpty())) {
qWarning() << Q_FUNC_INFO << "Anchor should have items on both sides";
return false;
}
return true;
}
QVariantMap LayoutSaver::Anchor::toVariantMap() const
{
QVariantMap map;
map.insert(QStringLiteral("objectName"), objectName);
map.insert(QStringLiteral("geometry"), rectToMap(geometry));
map.insert(QStringLiteral("orientation"), orientation);
map.insert(QStringLiteral("type"), type);
map.insert(QStringLiteral("indexOfFrom"), indexOfFrom);
map.insert(QStringLiteral("indexOfTo"), indexOfTo);
map.insert(QStringLiteral("indexOfFollowee"), indexOfFollowee);
map.insert(QStringLiteral("positionPercentage"), positionPercentage);
QVariantList side1ItemsV;
QVariantList side2ItemsV;
side1ItemsV.reserve(side1Items.size());
side2ItemsV.reserve(side2Items.size());
for (int index : qAsConst(side1Items))
side1ItemsV.push_back(index);
for (int index : qAsConst(side2Items))
side2ItemsV.push_back(index);
map.insert(QStringLiteral("side1Items"), side1ItemsV);
map.insert(QStringLiteral("side2Items"), side2ItemsV);
return map;
}
void LayoutSaver::Anchor::fromVariantMap(const QVariantMap &map)
{
objectName = map.value(QStringLiteral("objectName")).toString();
geometry = mapToRect(map.value(QStringLiteral("geometry")).toMap());
orientation = map.value(QStringLiteral("orientation")).toInt();
type = map.value(QStringLiteral("type")).toInt();
indexOfFrom = map.value(QStringLiteral("indexOfFrom")).toInt();
indexOfTo = map.value(QStringLiteral("indexOfTo")).toInt();
indexOfFollowee = map.value(QStringLiteral("indexOfFollowee")).toInt();
positionPercentage = map.value(QStringLiteral("positionPercentage")).toDouble();
side1Items.clear();
side2Items.clear();
const QVariantList side1ItemsV = map.value(QStringLiteral("side1Items")).toList();
const QVariantList side2ItemsV = map.value(QStringLiteral("side2Items")).toList();
side1Items.reserve(side1ItemsV.size());
side2Items.reserve(side2ItemsV.size());
for (const QVariant &v : side1ItemsV)
side1Items.push_back(v.toInt());
for (const QVariant &v : side2ItemsV)
side2Items.push_back(v.toInt());
}
void LayoutSaver::Anchor::scaleSizes(const ScalingInfo &scalingInfo)
{
const QPoint pos = geometry.topLeft();
if (isVertical()) {
geometry.moveLeft(int(pos.x() * scalingInfo.widthFactor));
} else {
geometry.moveTop(int(pos.y() * scalingInfo.heightFactor));
}
}
bool LayoutSaver::Anchor::isVertical() const
{
return orientation == Qt::Vertical;
}
bool LayoutSaver::FloatingWindow::isValid() const bool LayoutSaver::FloatingWindow::isValid() const
{ {
if (!multiSplitterLayout.isValid()) if (!multiSplitterLayout.isValid())
@@ -893,51 +655,47 @@ void LayoutSaver::MainWindow::fromVariantMap(const QVariantMap &map)
bool LayoutSaver::MultiSplitterLayout::isValid() const bool LayoutSaver::MultiSplitterLayout::isValid() const
{ {
for (auto &item : items) { if (layout.isEmpty())
if (!item.isValid(*this)) return false;
return false;
}
for (auto &anchor : anchors) { /*if (!size.isValid()) {
if (!anchor.isValid(*this))
return false;
}
if (!size.isValid()) {
qWarning() << Q_FUNC_INFO << "Invalid size"; qWarning() << Q_FUNC_INFO << "Invalid size";
return false; return false;
} }*/
return true; return true;
} }
void LayoutSaver::MultiSplitterLayout::scaleSizes(const ScalingInfo &scalingInfo) void LayoutSaver::MultiSplitterLayout::scaleSizes(const ScalingInfo &)
{ {
scalingInfo.applyFactorsTo(/*by-ref*/size); // scalingInfo.applyFactorsTo(/*by-ref*/size);
for (LayoutSaver::Anchor &anchor : anchors) //for (LayoutSaver::Item &item : items) TODO
anchor.scaleSizes(scalingInfo); // item.scaleSizes(scalingInfo);
for (LayoutSaver::Item &item : items)
item.scaleSizes(scalingInfo);
} }
QVariantMap LayoutSaver::MultiSplitterLayout::toVariantMap() const QVariantMap LayoutSaver::MultiSplitterLayout::toVariantMap() const
{ {
QVariantMap map; QVariantMap result;
result.insert(QStringLiteral("layout"), layout);
map.insert(QStringLiteral("anchors"), toVariantList<LayoutSaver::Anchor>(anchors)); QVariantMap framesV;
map.insert(QStringLiteral("items"), toVariantList<LayoutSaver::Item>(items)); for (auto &frame : frames)
map.insert(QStringLiteral("minSize"), sizeToMap(minSize)); framesV.insert(frame.id, frame.toVariantMap());
map.insert(QStringLiteral("size"), sizeToMap(size));
return map; result.insert(QStringLiteral("frames"), framesV);
return result;
} }
void LayoutSaver::MultiSplitterLayout::fromVariantMap(const QVariantMap &map) void LayoutSaver::MultiSplitterLayout::fromVariantMap(const QVariantMap &map)
{ {
anchors = fromVariantList<LayoutSaver::Anchor>(map.value(QStringLiteral("anchors")).toList()); layout = map.value(QStringLiteral("layout")).toMap();
items = fromVariantList<LayoutSaver::Item>(map.value(QStringLiteral("items")).toList()); const QVariantMap framesV = map.value(QStringLiteral("frames")).toMap();
minSize = mapToSize(map.value(QStringLiteral("minSize")).toMap()); frames.clear();
size = mapToSize(map.value(QStringLiteral("size")).toMap()); for (const QVariant &frameV : framesV) {
LayoutSaver::Frame frame;
frame.fromVariantMap(frameV.toMap());
frames.insert(frame.id, frame);
}
} }
void LayoutSaver::LastPosition::scaleSizes(const ScalingInfo &scalingInfo) void LayoutSaver::LastPosition::scaleSizes(const ScalingInfo &scalingInfo)

View File

@@ -108,8 +108,6 @@ public:
struct DockWidget; struct DockWidget;
struct LastPosition; struct LastPosition;
struct MultiSplitterLayout; struct MultiSplitterLayout;
struct Item;
struct Anchor;
struct Frame; struct Frame;
struct Placeholder; struct Placeholder;
struct ScalingInfo; struct ScalingInfo;

View File

@@ -23,9 +23,9 @@
#include "LayoutSaver.h" #include "LayoutSaver.h"
#include "KDDockWidgets.h" #include "KDDockWidgets.h"
#include "multisplitter/Item_p.h"
#include <QRect> #include <QRect>
#include <QDataStream>
#include <QDebug> #include <QDebug>
#include <QScreen> #include <QScreen>
#include <QApplication> #include <QApplication>
@@ -33,15 +33,15 @@
#include <memory> #include <memory>
#define ANCHOR_MAGIC_MARKER "e520c60e-cf5d-4a30-b1a7-588d2c569851"
#define MULTISPLITTER_LAYOUT_MAGIC_MARKER "bac9948e-5f1b-4271-acc5-07f1708e2611" #define MULTISPLITTER_LAYOUT_MAGIC_MARKER "bac9948e-5f1b-4271-acc5-07f1708e2611"
/** /**
* Bump whenever the format changes, so we can still load old layouts. * Bump whenever the format changes, so we can still load old layouts.
* version 1: Initial version * version 1: Initial version
* version 2: Introduced MainWindow::screenSize and FloatingWindow::screenSize * version 2: Introduced MainWindow::screenSize and FloatingWindow::screenSize
* version 3: New layouting engine
*/ */
#define KDDOCKWIDGETS_SERIALIZATION_VERSION 2 #define KDDOCKWIDGETS_SERIALIZATION_VERSION 3
namespace KDDockWidgets { namespace KDDockWidgets {
@@ -194,56 +194,11 @@ struct LayoutSaver::Frame
QRect geometry; QRect geometry;
unsigned int options; unsigned int options;
int currentTabIndex; int currentTabIndex;
QString id; // for coorelation purposes
LayoutSaver::DockWidget::List dockWidgets; LayoutSaver::DockWidget::List dockWidgets;
}; };
struct LayoutSaver::Item
{
typedef QVector<LayoutSaver::Item> List;
bool isValid(const MultiSplitterLayout &) const;
/// Iterates throught the layout and patches all absolute sizes. See RestoreOption_RelativeToMainWindow.
void scaleSizes(const ScalingInfo &scalingInfo);
QVariantMap toVariantMap() const;
void fromVariantMap(const QVariantMap &map);
QString objectName;
bool isPlaceholder;
QRect geometry;
QSize minSize;
int indexOfLeftAnchor;
int indexOfTopAnchor;
int indexOfRightAnchor;
int indexOfBottomAnchor;
LayoutSaver::Frame frame;
};
struct LayoutSaver::Anchor
{
typedef QVector<LayoutSaver::Anchor> List;
bool isValid(const LayoutSaver::MultiSplitterLayout &layout) const;
QVariantMap toVariantMap() const;
void fromVariantMap(const QVariantMap &map);
void scaleSizes(const ScalingInfo &);
bool isVertical() const;
QString objectName;
QRect geometry;
double positionPercentage;
int orientation;
int type;
int indexOfFrom;
int indexOfTo;
int indexOfFollowee;
QVector<int> side1Items;
QVector<int> side2Items;
};
struct LayoutSaver::MultiSplitterLayout struct LayoutSaver::MultiSplitterLayout
{ {
bool isValid() const; bool isValid() const;
@@ -253,10 +208,8 @@ struct LayoutSaver::MultiSplitterLayout
QVariantMap toVariantMap() const; QVariantMap toVariantMap() const;
void fromVariantMap(const QVariantMap &map); void fromVariantMap(const QVariantMap &map);
LayoutSaver::Anchor::List anchors; QVariantMap layout;
LayoutSaver::Item::List items; QHash<QString, LayoutSaver::Frame> frames;
QSize minSize;
QSize size;
}; };
struct LayoutSaver::FloatingWindow struct LayoutSaver::FloatingWindow
@@ -344,7 +297,6 @@ public:
bool isValid() const; bool isValid() const;
bool fillFrom(const QByteArray &serialized);
QByteArray toJson() const; QByteArray toJson() const;
bool fromJson(const QByteArray &jsonData); bool fromJson(const QByteArray &jsonData);
QVariantMap toVariantMap() const; QVariantMap toVariantMap() const;
@@ -353,7 +305,6 @@ public:
/// Iterates throught the layout and patches all absolute sizes. See RestoreOption_RelativeToMainWindow. /// Iterates throught the layout and patches all absolute sizes. See RestoreOption_RelativeToMainWindow.
void scaleSizes(); void scaleSizes();
friend QDataStream &operator>>(QDataStream &ds, LayoutSaver::Frame *frame);
static LayoutSaver::Layout* s_currentLayoutBeingRestored; static LayoutSaver::Layout* s_currentLayoutBeingRestored;
LayoutSaver::MainWindow mainWindowForIndex(int index) const; LayoutSaver::MainWindow mainWindowForIndex(int index) const;
@@ -366,244 +317,6 @@ public:
ScreenInfo::List screenInfo; ScreenInfo::List screenInfo;
}; };
inline QDataStream &operator>>(QDataStream &ds, LayoutSaver::ScreenInfo *info)
{
ds >> info->index;
ds >> info->geometry;
ds >> info->name;
ds >> info->devicePixelRatio;
return ds;
}
inline QDataStream &operator>>(QDataStream &ds, LayoutSaver::Placeholder *p)
{
ds >> p->isFloatingWindow;
if (p->isFloatingWindow)
ds >> p->indexOfFloatingWindow;
else
ds >> p->mainWindowUniqueName;
ds >> p->itemIndex;
return ds;
}
inline QDataStream &operator>>(QDataStream &ds, LayoutSaver::Anchor *a)
{
QString marker;
ds >> marker;
if (marker != QLatin1String(ANCHOR_MAGIC_MARKER))
qWarning() << Q_FUNC_INFO << "Corrupt stream";
ds >> a->objectName;
ds >> a->geometry;
ds >> a->orientation;
ds >> a->type;
ds >> a->indexOfFrom;
ds >> a->indexOfTo;
ds >> a->indexOfFollowee;
ds >> a->side1Items;
ds >> a->side2Items;
return ds;
}
inline QDataStream &operator>>(QDataStream &ds, LayoutSaver::Frame *frame)
{
int numDockWidgets;
frame->dockWidgets.clear();
frame->isNull = false;
ds >> frame->objectName;
ds >> frame->geometry;
if (LayoutSaver::Layout::s_currentLayoutBeingRestored->serializationVersion >= 2) {
QSize sz;
ds >> sz; // deprecated field, just discard
}
ds >> frame->options;
ds >> frame->currentTabIndex;
ds >> numDockWidgets;
for (int i = 0; i < numDockWidgets; ++i) {
QString name;
ds >> name;
auto dw = LayoutSaver::DockWidget::dockWidgetForName(name);
frame->dockWidgets.push_back(dw);
}
return ds;
}
inline QDataStream &operator>>(QDataStream &ds, LayoutSaver::Item *item)
{
ds >> item->objectName;
ds >> item->isPlaceholder;
ds >> item->geometry;
ds >> item->minSize;
ds >> item->indexOfLeftAnchor;
ds >> item->indexOfTopAnchor;
ds >> item->indexOfRightAnchor;
ds >> item->indexOfBottomAnchor;
bool hasFrame;
ds >> hasFrame;
if (hasFrame) {
ds >> &item->frame;
item->frame.isNull = false;
} else {
item->frame.isNull = true;
}
return ds;
}
inline QDataStream &operator>>(QDataStream &ds, LayoutSaver::MultiSplitterLayout *l)
{
int numItems;
int numAnchors;
QString marker;
ds >> marker;
if (marker != QLatin1String(MULTISPLITTER_LAYOUT_MAGIC_MARKER))
qWarning() << Q_FUNC_INFO << "Corrupt stream, invalid magic";
ds >> l->size;
ds >> l->minSize;
ds >> numItems;
ds >> numAnchors;
l->items.clear();
l->anchors.clear();
for (int i = 0 ; i < numItems; ++i) {
LayoutSaver::Item item;
ds >> &item;
l->items.push_back(item);
}
for (int i = 0 ; i < numAnchors; ++i) {
LayoutSaver::Anchor a;
ds >> &a;
l->anchors.push_back(a);
}
return ds;
}
inline QDataStream &operator>>(QDataStream &ds, LayoutSaver::LastPosition *lp)
{
int numPlaceholders;
ds >> numPlaceholders;
lp->placeholders.clear();
for (int i = 0 ; i < numPlaceholders; ++i) {
LayoutSaver::Placeholder p;
ds >> &p;
lp->placeholders.push_back(p);
}
ds >> lp->lastFloatingGeometry;
ds >> lp->tabIndex;
ds >> lp->wasFloating;
return ds;
}
inline QDataStream &operator>>(QDataStream &ds, LayoutSaver::FloatingWindow *fw)
{
ds >> fw->parentIndex;
ds >> fw->geometry;
if (LayoutSaver::Layout::s_currentLayoutBeingRestored->serializationVersion >= 2) {
ds >> fw->screenIndex;
ds >> fw->screenSize;
}
ds >> fw->isVisible;
ds >> &fw->multiSplitterLayout;
return ds;
}
inline QDataStream &operator>>(QDataStream &ds, LayoutSaver::MainWindow *m)
{
ds >> m->uniqueName;
ds >> m->geometry;
if (LayoutSaver::Layout::s_currentLayoutBeingRestored->serializationVersion >= 2) {
ds >> m->screenIndex;
ds >> m->screenSize;
}
ds >> m->isVisible;
ds >> m->options;
ds >> &m->multiSplitterLayout;
return ds;
}
inline QDataStream &operator>>(QDataStream &ds, LayoutSaver::Layout *l)
{
LayoutSaver::DockWidget::s_dockWidgets.clear();
int numMainWindows;
int numFloatingWindows;
int numClosedDockWidgets;
int numAllDockWidgets;
int numScreenInfo;
ds >> l->serializationVersion;
ds >> numMainWindows;
l->mainWindows.clear();
for (int i = 0; i < numMainWindows; ++i) {
LayoutSaver::MainWindow m;
ds >> &m;
l->mainWindows.push_back(m);
}
ds >> numFloatingWindows;
l->floatingWindows.clear();
for (int i = 0; i < numFloatingWindows; ++i) {
LayoutSaver::FloatingWindow m;
ds >> &m;
l->floatingWindows.push_back(m);
}
ds >> numClosedDockWidgets;
l->closedDockWidgets.clear();
for (int i = 0; i < numClosedDockWidgets; ++i) {
QString name;
ds >> name;
auto dw = LayoutSaver::DockWidget::dockWidgetForName(name);
l->closedDockWidgets.push_back(dw);
}
ds >> numAllDockWidgets;
l->allDockWidgets.clear();
for (int i = 0; i < numAllDockWidgets; ++i) {
QString name;
ds >> name;
auto dw = LayoutSaver::DockWidget::dockWidgetForName(name);
ds >> &dw->lastPosition;
l->allDockWidgets.push_back(dw);
}
if (LayoutSaver::Layout::s_currentLayoutBeingRestored->serializationVersion >= 2) {
ds >> numScreenInfo;
l->screenInfo.clear();
l->screenInfo.reserve(numScreenInfo);
for (int i = 0; i < numScreenInfo; ++i) {
LayoutSaver::ScreenInfo info;
ds >> &info;
l->screenInfo.push_back(info);
}
}
return ds;
}
} }
#endif #endif

View File

@@ -231,24 +231,6 @@ DebugWindow::DebugWindow(QWidget *parent)
repaintWidgetRecursive(w); repaintWidgetRecursive(w);
}); });
button = new QPushButton(this);
button->setText(QStringLiteral("EnsureAnchorsBounded"));
layout->addWidget(button);
connect(button, &QPushButton::clicked, this, [] {
const auto layouts = DockRegistry::self()->layouts();
for (auto l : layouts)
l->ensureAnchorsBounded();
});
button = new QPushButton(this);
button->setText(QStringLiteral("RedistributeSpace"));
layout->addWidget(button);
connect(button, &QPushButton::clicked, this, [] {
const auto layouts = DockRegistry::self()->layouts();
for (auto l : layouts)
l->redistributeSpace();
});
button = new QPushButton(this); button = new QPushButton(this);
button->setText(QStringLiteral("resize by 1x1")); button->setText(QStringLiteral("resize by 1x1"));
layout->addWidget(button); layout->addWidget(button);
@@ -260,24 +242,6 @@ DebugWindow::DebugWindow(QWidget *parent)
} }
}); });
button = new QPushButton(this);
button->setText(QStringLiteral("PositionStaticAnchors()"));
layout->addWidget(button);
connect(button, &QPushButton::clicked, this, [] {
const auto layouts = DockRegistry::self()->layouts();
for (auto l : layouts)
l->positionStaticAnchors();
});
button = new QPushButton(this);
button->setText(QStringLiteral("UpdateAnchorFollowing"));
layout->addWidget(button);
connect(button, &QPushButton::clicked, this, [] {
const auto layouts = DockRegistry::self()->layouts();
for (auto l : layouts)
l->updateAnchorFollowing();
});
button = new QPushButton(this); button = new QPushButton(this);
button->setText(QStringLiteral("Raise #0 (after 3s timeout)")); button->setText(QStringLiteral("Raise #0 (after 3s timeout)"));
layout->addWidget(button); layout->addWidget(button);
@@ -289,33 +253,6 @@ DebugWindow::DebugWindow(QWidget *parent)
}); });
}); });
button = new QPushButton(this);
button->setText(QStringLiteral("Convert old layout to JSON"));
layout->addWidget(button);
connect(button, &QPushButton::clicked, this, [this] {
const QString filename = QFileDialog::getOpenFileName(this);
if (filename.isEmpty())
return;
QFile f(filename);
if (!f.open(QIODevice::ReadOnly)) {
qWarning() << "Failed to open file" << filename;
return;
}
const QByteArray oldData = f.readAll();
LayoutSaver::Layout savedLayout;
savedLayout.fillFrom(oldData);
const QByteArray jsonData = savedLayout.toJson();
QFile f2(QStringLiteral("%1.json").arg(filename));
if (!f2.open(QIODevice::WriteOnly)) {
qWarning() << "Failed to open file for writing" << filename;
return;
}
f2.write(jsonData);
});
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
button = new QPushButton(this); button = new QPushButton(this);
button->setText(QStringLiteral("Dump native windows")); button->setText(QStringLiteral("Dump native windows"));

View File

@@ -24,6 +24,7 @@
#include "DebugWindow_p.h" #include "DebugWindow_p.h"
#include "LastPosition_p.h" #include "LastPosition_p.h"
#include "multisplitter/MultiSplitterLayout_p.h" #include "multisplitter/MultiSplitterLayout_p.h"
#include "multisplitter/MultiSplitter_p.h"
#include "quick/QmlTypes.h" #include "quick/QmlTypes.h"
#include <QPointer> #include <QPointer>
@@ -32,6 +33,7 @@
#include <QWindow> #include <QWindow>
using namespace KDDockWidgets; using namespace KDDockWidgets;
using namespace Layouting;
DockRegistry::DockRegistry(QObject *parent) DockRegistry::DockRegistry(QObject *parent)
: QObject(parent) : QObject(parent)
@@ -77,6 +79,22 @@ bool DockRegistry::isProcessingAppQuitEvent() const
return m_isProcessingAppQuitEvent; return m_isProcessingAppQuitEvent;
} }
MultiSplitterLayout *DockRegistry::layoutForItem(const Item *item) const
{
if (auto ms = qobject_cast<MultiSplitter*>(item->hostWidget()))
return ms->multiSplitterLayout();
return nullptr;
}
bool DockRegistry::itemIsInMainWindow(const Item *item) const
{
if (auto layout = layoutForItem(item))
return layout->multiSplitter()->isInMainWindow();
return false;
}
DockRegistry *DockRegistry::self() DockRegistry *DockRegistry::self()
{ {
static QPointer<DockRegistry> s_dockRegistry; static QPointer<DockRegistry> s_dockRegistry;
@@ -295,7 +313,7 @@ QVector<QWidget *> DockRegistry::topLevels(bool excludeFloatingDocks) const
return windows; return windows;
} }
void DockRegistry::clear(bool deleteStaticAnchors) void DockRegistry::clear()
{ {
for (auto dw : qAsConst(m_dockWidgets)) { for (auto dw : qAsConst(m_dockWidgets)) {
dw->forceClose(); dw->forceClose();
@@ -303,17 +321,17 @@ void DockRegistry::clear(bool deleteStaticAnchors)
} }
for (auto mw : qAsConst(m_mainWindows)) for (auto mw : qAsConst(m_mainWindows))
mw->multiSplitterLayout()->clear(deleteStaticAnchors); mw->multiSplitterLayout()->clear();
qCDebug(restoring) << Q_FUNC_INFO << "; dockwidgets=" << m_dockWidgets.size() qCDebug(restoring) << Q_FUNC_INFO << "; dockwidgets=" << m_dockWidgets.size()
<< "; nestedwindows=" << m_nestedWindows.size(); << "; nestedwindows=" << m_nestedWindows.size();
} }
void DockRegistry::clear(QStringList affinities, bool deleteStaticAnchors) void DockRegistry::clear(QStringList affinities)
{ {
if (affinities.isEmpty()) { if (affinities.isEmpty()) {
// Just clear everything // Just clear everything
clear(deleteStaticAnchors); clear();
return; return;
} }
@@ -329,7 +347,7 @@ void DockRegistry::clear(QStringList affinities, bool deleteStaticAnchors)
for (auto mw : qAsConst(m_mainWindows)) { for (auto mw : qAsConst(m_mainWindows)) {
if (affinities.contains(mw->affinityName())) { if (affinities.contains(mw->affinityName())) {
mw->multiSplitterLayout()->clear(deleteStaticAnchors); mw->multiSplitterLayout()->clear();
} }
} }
} }

View File

@@ -102,12 +102,12 @@ public:
* @brief Closes all dock widgets, destroys all FloatingWindow, Item and Anchors. * @brief Closes all dock widgets, destroys all FloatingWindow, Item and Anchors.
* This is called before restoring a layout. * This is called before restoring a layout.
*/ */
void clear(bool deleteStaticAnchors = false); void clear();
/** /**
* @brief Closes all dock widgets, destroys all FloatingWindow, Item and Anchors with the specified affinities. * @brief Closes all dock widgets, destroys all FloatingWindow, Item and Anchors with the specified affinities.
*/ */
void clear(QStringList affinities, bool deleteStaticAnchors = false); void clear(QStringList affinities);
/** /**
* @brief Ensures that all floating DockWidgets have a FloatingWindow as a window. * @brief Ensures that all floating DockWidgets have a FloatingWindow as a window.
@@ -137,6 +137,12 @@ public:
*/ */
bool isProcessingAppQuitEvent() const; bool isProcessingAppQuitEvent() const;
// TODO: docs
MultiSplitterLayout* layoutForItem(const Layouting::Item *) const;
// TODO: docs
bool itemIsInMainWindow(const Layouting::Item *) const;
protected: protected:
bool eventFilter(QObject *watched, QEvent *event) override; bool eventFilter(QObject *watched, QEvent *event) override;
private: private:

View File

@@ -32,6 +32,7 @@
#include "WindowBeingDragged_p.h" #include "WindowBeingDragged_p.h"
using namespace KDDockWidgets; using namespace KDDockWidgets;
using namespace Layouting;
/** /**
* @file * @file
@@ -59,23 +60,11 @@ int DropArea::numFrames() const
return m_layout->count(); return m_layout->count();
} }
Anchor::List DropArea::nonStaticAnchors(bool includePlaceholders) const
{
auto anchors = m_layout->anchors();
Anchor::List result;
for (Anchor *anchor : anchors) {
if (!anchor->isStatic() && !(!includePlaceholders && anchor->isFollowing()))
result << anchor;
}
return result;
}
Frame *DropArea::frameContainingPos(QPoint globalPos) const Frame *DropArea::frameContainingPos(QPoint globalPos) const
{ {
const ItemList &items = m_layout->items(); const Item::List &items = m_layout->items();
for (Item *item : items) { for (Item *item : items) {
auto frame = item->frame(); auto frame = static_cast<Frame*>(item->frame());
if (!frame || !frame->isVisible()) { if (!frame || !frame->isVisible()) {
continue; continue;
} }
@@ -89,7 +78,7 @@ Frame *DropArea::frameContainingPos(QPoint globalPos) const
Item *DropArea::centralFrame() const Item *DropArea::centralFrame() const
{ {
for (Item *item : m_layout->items()) { for (Item *item : m_layout->items()) {
if (auto f = item->frame()) { if (auto f = static_cast<Frame*>(item->frame())) {
if (f->isCentralFrame()) if (f->isCentralFrame())
return item; return item;
} }
@@ -142,19 +131,16 @@ void DropArea::addDockWidget(DockWidgetBase *dw, Location location, DockWidgetBa
void DropArea::debug_updateItemNamesForGammaray() void DropArea::debug_updateItemNamesForGammaray()
{ {
for (Item *item : m_layout->items()) { for (Item *item : m_layout->items()) {
if (auto frame = item->frame()) { if (auto frame = static_cast<Frame*>(item->frame())) {
if (!frame->dockWidgets().isEmpty()) if (!frame->dockWidgets().isEmpty())
frame->setObjectName(frame->dockWidgets().at(0)->uniqueName()); frame->setObjectName(frame->dockWidgets().at(0)->uniqueName());
} }
} }
for (Anchor *a : m_layout->anchors())
a->debug_updateItemNames();
} }
bool DropArea::checkSanity(MultiSplitterLayout::AnchorSanityOption o) bool DropArea::checkSanity()
{ {
return m_layout->checkSanity(o); return m_layout->checkSanity();
} }
bool DropArea::contains(DockWidgetBase *dw) const bool DropArea::contains(DockWidgetBase *dw) const

View File

@@ -38,6 +38,11 @@ DropAreaWithCentralFrame::~DropAreaWithCentralFrame()
Frame* DropAreaWithCentralFrame::createCentralFrame(MainWindowOptions options) Frame* DropAreaWithCentralFrame::createCentralFrame(MainWindowOptions options)
{ {
return (options & MainWindowOption_HasCentralFrame) ? Config::self().frameworkWidgetFactory()->createFrame(nullptr, FrameOptions() | FrameOption_IsCentralFrame | FrameOption_AlwaysShowsTabs) Frame *frame = nullptr;
: nullptr; if (options & MainWindowOption_HasCentralFrame) {
frame = Config::self().frameworkWidgetFactory()->createFrame(nullptr, FrameOptions() | FrameOption_IsCentralFrame | FrameOption_AlwaysShowsTabs);
frame->setObjectName(QStringLiteral("central frame"));
}
return frame;
} }

View File

@@ -58,15 +58,14 @@ public:
bool drop(QWidgetOrQuick *droppedwindow, KDDockWidgets::Location location, Frame *relativeTo); bool drop(QWidgetOrQuick *droppedwindow, KDDockWidgets::Location location, Frame *relativeTo);
int numFrames() const; int numFrames() const;
Anchor::List nonStaticAnchors(bool includePlaceholders = false) const;
Frame *frameContainingPos(QPoint globalPos) const; Frame *frameContainingPos(QPoint globalPos) const;
Item *centralFrame() const; Layouting::Item *centralFrame() const;
DropIndicatorOverlayInterface *dropIndicatorOverlay() const { return m_dropIndicatorOverlay; } DropIndicatorOverlayInterface *dropIndicatorOverlay() const { return m_dropIndicatorOverlay; }
void addDockWidget(DockWidgetBase *, KDDockWidgets::Location location, DockWidgetBase *relativeTo, AddingOption option = {}); void addDockWidget(DockWidgetBase *, KDDockWidgets::Location location, DockWidgetBase *relativeTo, AddingOption option = {});
void debug_updateItemNamesForGammaray(); void debug_updateItemNamesForGammaray();
bool checkSanity(MultiSplitterLayout::AnchorSanityOption o = MultiSplitterLayout::AnchorSanity_All); bool checkSanity();
bool contains(DockWidgetBase *) const; bool contains(DockWidgetBase *) const;
QString affinityName() const; QString affinityName() const;

View File

@@ -25,6 +25,7 @@
#include "QWidgetAdapter.h" #include "QWidgetAdapter.h"
#include "Frame_p.h" #include "Frame_p.h"
#include "KDDockWidgets.h" #include "KDDockWidgets.h"
#include "multisplitter/Item_p.h"
namespace KDDockWidgets { namespace KDDockWidgets {

View File

@@ -280,6 +280,7 @@ bool FloatingWindow::beingDeleted() const
if (m_beingDeleted) if (m_beingDeleted)
return true; return true;
// TODO: Confusing logic
for (Frame *f : frames()) { for (Frame *f : frames()) {
if (!f->beingDeletedLater()) if (!f->beingDeletedLater())
return false; return false;

View File

@@ -46,6 +46,7 @@
static int s_dbg_numFrames = 0; static int s_dbg_numFrames = 0;
using namespace KDDockWidgets; using namespace KDDockWidgets;
using namespace Layouting;
namespace KDDockWidgets { namespace KDDockWidgets {
static FrameOptions actualOptions(FrameOptions options) static FrameOptions actualOptions(FrameOptions options)
@@ -293,7 +294,7 @@ void Frame::restoreToPreviousPosition()
return; return;
} }
m_layoutItem->restorePlaceholder(this); m_layoutItem->restore(this);
} }
int Frame::currentTabIndex() const int Frame::currentTabIndex() const
@@ -420,6 +421,11 @@ QString Frame::affinityName() const
} }
} }
QWidget *Frame::asWidget()
{
return this;
}
DockWidgetBase *Frame::dockWidgetAt(int index) const DockWidgetBase *Frame::dockWidgetAt(int index) const
{ {
return qobject_cast<DockWidgetBase *>(m_tabWidget->dockwidgetAt(index)); return qobject_cast<DockWidgetBase *>(m_tabWidget->dockwidgetAt(index));
@@ -493,6 +499,9 @@ bool Frame::event(QEvent *e)
Frame *Frame::deserialize(const LayoutSaver::Frame &f) Frame *Frame::deserialize(const LayoutSaver::Frame &f)
{ {
if (!f.isValid())
return nullptr;
auto frame = Config::self().frameworkWidgetFactory()->createFrame(/*parent=*/nullptr, FrameOptions(f.options)); auto frame = Config::self().frameworkWidgetFactory()->createFrame(/*parent=*/nullptr, FrameOptions(f.options));
frame->setObjectName(f.objectName); frame->setObjectName(f.objectName);
@@ -519,6 +528,7 @@ LayoutSaver::Frame Frame::serialize() const
frame.geometry = geometry(); frame.geometry = geometry();
frame.options = options(); frame.options = options();
frame.currentTabIndex = currentTabIndex(); frame.currentTabIndex = currentTabIndex();
frame.id = QString::number(qint64(this)); // for coorelation purposes
for (DockWidgetBase *dock : docks) for (DockWidgetBase *dock : docks)
frame.dockWidgets.push_back(dock->serialize()); frame.dockWidgets.push_back(dock->serialize());

View File

@@ -31,6 +31,7 @@
#include "docks_export.h" #include "docks_export.h"
#include "QWidgetAdapter.h" #include "QWidgetAdapter.h"
#include "LayoutSaver_p.h" #include "LayoutSaver_p.h"
#include "multisplitter/Item_p.h"
#include <QWidget> #include <QWidget>
#include <QVector> #include <QVector>
@@ -43,7 +44,6 @@ class TitleBar;
class TabWidget; class TabWidget;
class DropArea; class DropArea;
class DockWidgetBase; class DockWidgetBase;
class Item;
class FloatingWindow; class FloatingWindow;
/** /**
@@ -57,6 +57,7 @@ class FloatingWindow;
* to a FloatingWindow. * to a FloatingWindow.
*/ */
class DOCKS_EXPORT Frame : public QWidgetAdapter class DOCKS_EXPORT Frame : public QWidgetAdapter
, public Layouting::GuestInterface
{ {
Q_OBJECT Q_OBJECT
@@ -178,11 +179,8 @@ public:
///@brief Called when a dock widget child @p w is hidden ///@brief Called when a dock widget child @p w is hidden
void onDockWidgetHidden(DockWidgetBase *w); void onDockWidgetHidden(DockWidgetBase *w);
///@brief sets the layout item that either contains this Frame in the layout or is a placeholder
void setLayoutItem(Item *item);
///@brief returns the layout item that either contains this Frame in the layout or is a placeholder ///@brief returns the layout item that either contains this Frame in the layout or is a placeholder
Item *layoutItem() const; Layouting::Item *layoutItem() const;
///@brief For tests-only. Returns the number of Frame instances in the whole application. ///@brief For tests-only. Returns the number of Frame instances in the whole application.
static int dbg_numFrames(); static int dbg_numFrames();
@@ -204,6 +202,12 @@ public:
QString affinityName() const; QString affinityName() const;
///@brief sets the layout item that either contains this Frame in the layout or is a placeholder
void setLayoutItem(Layouting::Item *item) override;
///@brief Overriden from GuestInterface
QWidget *asWidget() override;
Q_SIGNALS: Q_SIGNALS:
void currentDockWidgetChanged(KDDockWidgets::DockWidgetBase *); void currentDockWidgetChanged(KDDockWidgets::DockWidgetBase *);
void numDockWidgetsChanged(); void numDockWidgetsChanged();
@@ -223,7 +227,7 @@ private:
TitleBar *const m_titleBar; TitleBar *const m_titleBar;
DropArea *m_dropArea = nullptr; DropArea *m_dropArea = nullptr;
const FrameOptions m_options; const FrameOptions m_options;
QPointer<Item> m_layoutItem; QPointer<Layouting::Item> m_layoutItem;
bool m_beingDeleted = false; bool m_beingDeleted = false;
QMetaObject::Connection m_visibleWidgetCountChangedConnection; QMetaObject::Connection m_visibleWidgetCountChangedConnection;
}; };

View File

@@ -32,6 +32,7 @@
#include <algorithm> #include <algorithm>
using namespace KDDockWidgets; using namespace KDDockWidgets;
using namespace Layouting;
LastPosition::~LastPosition() LastPosition::~LastPosition()
{ {
@@ -46,7 +47,7 @@ void LastPosition::addPlaceholderItem(Item *placeholder)
if (containsPlaceholder(placeholder)) if (containsPlaceholder(placeholder))
return; return;
if (placeholder->isInMainWindow()) { if (DockRegistry::self()->itemIsInMainWindow(placeholder)) {
// 2. If we have a MainWindow placeholder we don't need nothing else // 2. If we have a MainWindow placeholder we don't need nothing else
removePlaceholders(); removePlaceholders();
} else { } else {
@@ -82,7 +83,7 @@ Item *LastPosition::layoutItem() const
// In the future we might want to restore it to FloatingWindows. // In the future we might want to restore it to FloatingWindows.
for (const auto &itemref : m_placeholders) { for (const auto &itemref : m_placeholders) {
if (itemref->item->isInMainWindow()) if (DockRegistry::self()->itemIsInMainWindow(itemref->item))
return itemref->item; return itemref->item;
} }
@@ -101,7 +102,7 @@ bool LastPosition::containsPlaceholder(Item *item) const
void LastPosition::removePlaceholders(const MultiSplitterLayout *layout) void LastPosition::removePlaceholders(const MultiSplitterLayout *layout)
{ {
m_placeholders.erase(std::remove_if(m_placeholders.begin(), m_placeholders.end(), [layout] (const std::unique_ptr<ItemRef> &itemref) { m_placeholders.erase(std::remove_if(m_placeholders.begin(), m_placeholders.end(), [layout] (const std::unique_ptr<ItemRef> &itemref) {
return itemref->item->layout() == layout; return DockRegistry::self()->layoutForItem(itemref->item) == layout;
}), m_placeholders.end()); }), m_placeholders.end());
} }
@@ -110,7 +111,7 @@ void LastPosition::removeNonMainWindowPlaceholders()
auto it = m_placeholders.begin(); auto it = m_placeholders.begin();
while (it != m_placeholders.end()) { while (it != m_placeholders.end()) {
ItemRef *itemref = it->get(); ItemRef *itemref = it->get();
if (!itemref->item->isInMainWindow()) if (!DockRegistry::self()->itemIsInMainWindow(itemref->item))
it = m_placeholders.erase(it); it = m_placeholders.erase(it);
else else
++it; ++it;
@@ -154,7 +155,7 @@ void LastPosition::deserialize(const LayoutSaver::LastPosition &lp)
layout = mainWindow->multiSplitterLayout(); layout = mainWindow->multiSplitterLayout();
} }
const ItemList &items = layout->items(); const Item::List &items = layout->items();
if (itemIndex < items.size()) { if (itemIndex < items.size()) {
Item *item = items.at(itemIndex); Item *item = items.at(itemIndex);
addPlaceholderItem(item); addPlaceholderItem(item);
@@ -179,7 +180,7 @@ LayoutSaver::LastPosition LastPosition::serialize() const
LayoutSaver::Placeholder p; LayoutSaver::Placeholder p;
Item *item = itemRef->item; Item *item = itemRef->item;
MultiSplitterLayout *layout = item->layout(); MultiSplitterLayout *layout = DockRegistry::self()->layoutForItem(item);
const int itemIndex = layout->items().indexOf(item); const int itemIndex = layout->items().indexOf(item);
auto fw = layout->multiSplitter()->floatingWindow(); auto fw = layout->multiSplitter()->floatingWindow();

View File

@@ -31,16 +31,19 @@
#include "multisplitter/Item_p.h" #include "multisplitter/Item_p.h"
#include "Logging_p.h" #include "Logging_p.h"
#include "LayoutSaver_p.h" #include "LayoutSaver_p.h"
#include "QWidgetAdapter.h"
#include <QPointer> #include <QPointer>
#include <memory> #include <memory>
namespace KDDockWidgets { namespace KDDockWidgets {
class MultiSplitterLayout;
// Just a RAII class so we don't forget to unref // Just a RAII class so we don't forget to unref
struct ItemRef struct ItemRef
{ {
ItemRef(const QMetaObject::Connection &conn, Item *it) ItemRef(const QMetaObject::Connection &conn, Layouting::Item *it)
: item(it) : item(it)
, guard(it) , guard(it)
, connection(conn) , connection(conn)
@@ -56,8 +59,8 @@ struct ItemRef
} }
} }
Item *const item; Layouting::Item *const item;
const QPointer<Item> guard; const QPointer<Layouting::Item> guard;
const QMetaObject::Connection connection; const QMetaObject::Connection connection;
private: private:
Q_DISABLE_COPY(ItemRef) Q_DISABLE_COPY(ItemRef)
@@ -105,13 +108,13 @@ public:
bool m_wasFloating = false; bool m_wasFloating = false;
///@brief Adds the last layout item where the dock widget was (or is) ///@brief Adds the last layout item where the dock widget was (or is)
void addPlaceholderItem(Item *placeholder); void addPlaceholderItem(Layouting::Item *placeholder);
QWidgetOrQuick *window() const; QWidgetOrQuick *window() const;
Item* layoutItem() const; Layouting::Item* layoutItem() const;
bool containsPlaceholder(Item*) const; bool containsPlaceholder(Layouting::Item*) const;
void removePlaceholders() { m_clearing = true; m_placeholders.clear(); m_clearing = false;} void removePlaceholders() { m_clearing = true; m_placeholders.clear(); m_clearing = false;}
const std::vector<std::unique_ptr<ItemRef>>& placeholders() const { return m_placeholders; } const std::vector<std::unique_ptr<ItemRef>>& placeholders() const { return m_placeholders; }
@@ -123,7 +126,7 @@ public:
void removeNonMainWindowPlaceholders(); void removeNonMainWindowPlaceholders();
///@brief removes the Item @p placeholder ///@brief removes the Item @p placeholder
void removePlaceholder(Item *placeholder); void removePlaceholder(Layouting::Item *placeholder);
void dumpDebug() void dumpDebug()
{ {

View File

@@ -19,13 +19,8 @@
*/ */
#include "Anchor_p.h" #include "Anchor_p.h"
#include "MultiSplitterLayout_p.h"
#include "MultiSplitter_p.h"
#include "Logging_p.h" #include "Logging_p.h"
#include "LayoutSaver.h"
#include "Config.h"
#include "Separator_p.h" #include "Separator_p.h"
#include "FrameworkWidgetFactory.h"
#include <QRubberBand> #include <QRubberBand>
#include <QApplication> #include <QApplication>
@@ -36,161 +31,63 @@
#endif #endif
using namespace KDDockWidgets; using namespace KDDockWidgets;
using namespace Layouting;
bool Anchor::s_isResizing = false; bool Anchor::s_isResizing = false;
const QString Anchor::s_magicMarker = QStringLiteral("e520c60e-cf5d-4a30-b1a7-588d2c569851"); Anchor* Anchor::s_separatorBeingDragged = nullptr;
Anchor::Anchor(Qt::Orientation orientation, MultiSplitterLayout *multiSplitter, Type type) static SeparatorFactoryFunc s_separatorFactoryFunc = nullptr;
: QObject(multiSplitter->multiSplitter())
, m_orientation(orientation) static Separator* createSeparator(Anchor *a, QWidget *parent)
, m_type(type) {
, m_layout(multiSplitter) if (s_separatorFactoryFunc)
, m_separatorWidget(Config::self().frameworkWidgetFactory()->createSeparator(this, multiSplitter->multiSplitter())) return s_separatorFactoryFunc(a, parent);
, m_lazyResize(Config::self().flags() & Config::Flag_LazyResize)
, m_lazyResizeRubberBand(m_lazyResize ? new QRubberBand(QRubberBand::Line, multiSplitter->multiSplitter()) : nullptr) return new Separator(a, parent);
}
Anchor::Anchor(ItemContainer *parentContainer, Qt::Orientation orientation,
Options options, QWidget *hostWidget)
: QObject(hostWidget)
, m_orientation(orientation)
, m_hostWidget(hostWidget)
, m_separatorWidget(createSeparator(this, m_hostWidget))
, m_options(options)
, m_lazyResizeRubberBand((options & Option::LazyResize) ? new QRubberBand(QRubberBand::Line, hostWidget) : nullptr)
, m_parentContainer(parentContainer)
{ {
multiSplitter->insertAnchor(this);
connect(this, &QObject::objectNameChanged, m_separatorWidget, &QObject::setObjectName); connect(this, &QObject::objectNameChanged, m_separatorWidget, &QObject::setObjectName);
} }
Anchor::~Anchor() Anchor::~Anchor()
{ {
m_separatorWidget->setEnabled(false); delete m_separatorWidget;
m_separatorWidget->deleteLater(); if (s_separatorBeingDragged == this)
qCDebug(multisplittercreation) << "~Anchor; this=" << this << "; m_to=" << m_to << "; m_from=" << m_from; s_separatorBeingDragged = nullptr;
m_layout->removeAnchor(this);
for (Item *item : items(Side1))
item->anchorGroup().setAnchor(nullptr, m_orientation, Side1);
for (Item *item : items(Side2))
item->anchorGroup().setAnchor(nullptr, m_orientation, Side2);
} }
void Anchor::setFrom(Anchor *from) QWidget *Anchor::hostWidget() const
{ {
if (from->orientation() == orientation() || from == this) { return m_hostWidget;
qWarning() << "Anchor::setFrom: Invalid from" << from->orientation() << m_orientation
<< from << this;
return;
}
if (m_from)
disconnect(m_from, &Anchor::positionChanged, this, &Anchor::updateSize);
m_from = from;
connect(from, &Anchor::positionChanged, this, &Anchor::updateSize);
updateSize();
Q_EMIT fromChanged();
}
void Anchor::setTo(Anchor *to)
{
Q_ASSERT(to);
if (to->orientation() == orientation() || to == this) {
qWarning() << "Anchor::setFrom: Invalid to" << to->orientation() << m_orientation
<< to << this;
return;
}
if (m_to)
disconnect(m_to, &Anchor::positionChanged, this, &Anchor::updateSize);
m_to = to;
connect(to, &Anchor::positionChanged, this, &Anchor::updateSize);
updateSize();
Q_EMIT toChanged();
}
void Anchor::updateSize()
{
if (isValid()) {
if (isVertical()) {
setGeometry(QRect(position(), m_from->geometry().bottom() + 1, thickness(), length()));
} else {
setGeometry(QRect(m_from->geometry().right() + 1, position(), length(), thickness()));
}
}
qCDebug(anchors) << "Anchor::updateSize" << this << geometry();
} }
void Anchor::setGeometry(QRect r) void Anchor::setGeometry(QRect r)
{ {
if (r != m_geometry) { if (r != m_geometry) {
if (position() < 0) { if (position() < 0) {
qCDebug(anchors) << Q_FUNC_INFO << "Old position was negative" << position() << "; new=" << r; qCDebug(separators) << Q_FUNC_INFO << "Old position was negative" << position() << "; new=" << r;
} }
m_geometry = r; m_geometry = r;
m_separatorWidget->setGeometry(r); m_separatorWidget->setGeometry(r);
m_separatorWidget->setVisible(true);
Q_EMIT geometryChanged(r);
} }
} }
void Anchor::updateItemSizes() bool Anchor::isVertical() const
{ {
if (!m_initialized) { return m_orientation == Qt::Vertical;
// setPosition() hasn't been called yet, don't bother
return;
}
if (LayoutSaver::restoreInProgress()) {
// Nothing to do. The LayoutSaver is setting up the whole layout.
return;
}
qCDebug(anchors) << Q_FUNC_INFO << this << "; o=" << orientation();
int position = this->position() + m_positionOffset;
for (Item *item : qAsConst(m_side2Items)) {
QRect geo = item->geometry();
const QPoint topLeft = isVertical() ? QPoint(position + thickness(), item->y())
: QPoint(item->x(), position + thickness());
geo.setTopLeft(topLeft);
if (!item->isPlaceholder())
item->setGeometry(geo);
}
position = this->position() - m_positionOffset;
for (Item *item : qAsConst(m_side1Items)) {
QRect geo = item->geometry();
// -1 as the widget is right next to the anchor, and not on top
const QPoint bottomRight = isVertical() ? QPoint(position - 1, geo.bottom())
: QPoint(geo.right(), position - 1);
geo.setBottomRight(bottomRight);
if (!item->isPlaceholder()) {
item->setGeometry(geo);
}
}
}
void Anchor::debug_updateItemNames()
{
// I call this in the unit-tests, when running them on gammaray
m_debug_side1ItemNames.clear();
m_debug_side2ItemNames.clear();
for (Item *item : qAsConst(m_side1Items))
m_debug_side1ItemNames += item->objectName() + QStringLiteral("; ");
for (Item *item : qAsConst(m_side2Items))
m_debug_side2ItemNames += item->objectName() + QStringLiteral("; ");
Q_EMIT debug_itemNamesChanged();
}
QString Anchor::debug_side1ItemNames() const
{
return m_debug_side1ItemNames;
}
QString Anchor::debug_side2ItemNames() const
{
return m_debug_side2ItemNames;
} }
Qt::Orientation Anchor::orientation() const Qt::Orientation Anchor::orientation() const
@@ -198,484 +95,36 @@ Qt::Orientation Anchor::orientation() const
return m_orientation; return m_orientation;
} }
void Anchor::setPosition(int p, SetPositionOptions options) void Anchor::setGeometry(int pos, int pos2, int length)
{ {
qCDebug(anchors) << Q_FUNC_INFO << this << "; visible=" QRect newGeo = m_geometry;
<< m_separatorWidget->isVisible() << "; p=" << p;
const int max = m_layout->length(orientation()) - Anchor::thickness(true);
const bool outOfBounds = max != -1 && (p < 0 || p > max);
if (outOfBounds) {
if (m_layout->isRestoringPlaceholder() || m_layout->isAddingItem() || m_layout->isResizing()) {
// Don't do anything here, it will call ensureAnchorsBounded() when finished
return;
} else if (!LayoutSaver::restoreInProgress()) {
m_layout->dumpDebug();
qWarning() << Q_FUNC_INFO << "Out of bounds position=" << p
<< "; oldPosition=" << position()
<< "; this=" << this
<< "; size=" << m_layout->size()
<< "; max=" << max
<< m_layout->multiSplitter()->window();
}
}
m_initialized = true;
if (position() == p) {
updateItemSizes();
return;
}
if (isVertical()) { if (isVertical()) {
m_geometry.moveLeft(p); // The separator itself is horizontal
newGeo.setSize(QSize(length, Item::separatorThickness()));
newGeo.moveTo(pos2, pos);
} else { } else {
m_geometry.moveTop(p); // The separator itself is vertical
newGeo.setSize(QSize(Item::separatorThickness(), length));
newGeo.moveTo(pos, pos2);
} }
/** setGeometry(newGeo);
* If we're in the middle of a resize then remember the relative positions, so we can do
* a redistribution so that relatively all widgets occupy the same amount
*/
const bool recalculatePercentage = !(options & SetPositionOption_DontRecalculatePercentage) && !m_layout->isResizing();
m_separatorWidget->move(p);
if (recalculatePercentage) {
// We keep the percentage, so we don't constantly recalculate it during a resize, which introduces rounding errors
updatePositionPercentage();
}
// Note: Position can be slightly negative if the main window isn't big enougn to host the new size.
// In that case the window will be resized shortly after
//Q_ASSERT(p >= 0); - commented out, as it's normal
Q_EMIT positionChanged(position());
updateItemSizes();
}
void Anchor::updatePositionPercentage()
{
const int layoutLength = m_layout->length(m_orientation);
m_positionPercentage = (position() * 1.0) / layoutLength;
if (position() > layoutLength) {
// This warning makes the unit-tests fail if some invalid m_positionPercentage ever appears.
// Bug fixed now though.
qWarning() << Q_FUNC_INFO << "Weird position percentage" << m_positionPercentage
<< position() << layoutLength;
}
} }
int Anchor::position() const int Anchor::position() const
{ {
const QPoint topLeft = m_geometry.topLeft(); const QPoint topLeft = m_geometry.topLeft();
return isVertical() ? topLeft.x() : topLeft.y(); return isVertical() ? topLeft.y() : topLeft.x();
}
void Anchor::setVisible(bool v)
{
m_separatorWidget->setVisible(v);
if (v) {
m_separatorWidget->setGeometry(m_geometry);
}
}
int Anchor::minPosition() const
{
const int smallestSqueeze = smallestAvailableItemSqueeze(Side1);
return position() - smallestSqueeze;
}
int Anchor::smallestAvailableItemSqueeze(Anchor::Side side) const
{
int smallest = 0;
bool firstElement = true;
for (Item *item : items(side)) {
const int length = item->length(m_orientation);
const int minLength = item->minLength(m_orientation);
const int availableSqueeze = length - minLength;
if (availableSqueeze < smallest || firstElement) {
smallest = availableSqueeze;
firstElement = false;
}
}
return smallest;
}
void Anchor::ensureBounded()
{
// TODO: Probably delete this unused method. It was used in the old days before discovering it
// was flawed: Separators being in between bounds doesn't imply that all min sizes are being
// Honoured. Use MultiSplitterLayout::ensureItemsMinSize() instead
if (!isStatic() && !isFollowing()) {
const QPair<int,int> bounds = m_layout->boundPositionsForAnchor(this);
if (position() < bounds.first) {
setPosition(bounds.first);
} else if (position() > bounds.second) {
setPosition(bounds.second);
}
}
for (Item *item : items(Side2)) {
item->anchorAtSide(Side2, orientation())->ensureBounded();
}
}
int Anchor::length() const
{
Q_ASSERT(m_to);
Q_ASSERT(m_from);
return m_to->position() - m_from->position();
}
bool Anchor::isValid() const
{
return m_to && m_from && m_to != m_from && m_to != this && m_from != this;
}
int Anchor::thickness() const
{
return isVertical() ? m_separatorWidget->width()
: m_separatorWidget->height();
}
bool Anchor::hasItems(Anchor::Side side) const
{
switch (side) {
case Side1:
return !m_side1Items.isEmpty();
case Side2:
return !m_side2Items.isEmpty();
default:
Q_ASSERT(false);
return false;
}
}
bool Anchor::onlyHasPlaceholderItems(Anchor::Side side) const
{
auto &items = side == Side1 ? m_side1Items
: m_side2Items;
for (Item *item : items) {
if (!item->isPlaceholder())
return false;
}
return true;
}
bool Anchor::hasNonPlaceholderItems(Anchor::Side side) const
{
auto &items = side == Side1 ? m_side1Items
: m_side2Items;
for (Item *item : items) {
if (!item->isPlaceholder())
return true;
}
return false;
}
bool Anchor::containsItem(const Item *item, Anchor::Side side) const
{
switch (side) {
case Side1:
return m_side1Items.contains(const_cast<Item *>(item));
case Side2:
return m_side2Items.contains(const_cast<Item *>(item));
default:
Q_ASSERT(false);
return false;
}
}
bool Anchor::isStaticOrFollowsStatic() const
{
if (isStatic())
return true;
return m_followee && m_followee->isStaticOrFollowsStatic();
}
const ItemList Anchor::items(Anchor::Side side) const
{
switch (side) {
case Side1:
return m_side1Items;
case Side2:
return m_side2Items;
default:
Q_ASSERT(false);
return {};
}
}
void Anchor::consume(Anchor *other)
{
QPointer<Anchor> otherp = other; // Just to check if it wasn't deleted meanwhile. Which doesn't happen, but we silence a clang-tidy warning this way.
consume(other, Side1);
if (otherp)
consume(other, Side2);
}
void Anchor::consume(Anchor *other, Side side)
{
auto items = other->items(side);
other->removeItems(side);
addItems(items, side);
if (other->isUnneeded()) {
// Before deleting an unneeded anchor, we must check if there's anchors following it, and make them follow us instead
Anchor::List anchorsFollowingOther = m_layout->anchorsFollowing(other);
for (Anchor *follower : anchorsFollowingOther) {
if (follower != this)
follower->setFollowee(this);
}
delete other;
}
}
void Anchor::swapItems(Anchor *other)
{
auto other1 = other->m_side1Items;
auto other2 = other->m_side2Items;
auto my1 = m_side1Items;
auto my2 = m_side2Items;
removeAllItems();
other->removeAllItems();
other->addItems(my1, Side1);
other->addItems(my2, Side2);
addItems(other1, Side1);
addItems(other2, Side1);
}
void Anchor::removeAllItems()
{
removeItems(Side1);
removeItems(Side2);
}
/** static */
Anchor *Anchor::createFrom(Anchor *other, Item *relativeTo)
{
Q_ASSERT(other);
auto anchor = new Anchor(other->orientation(), other->m_layout);
anchor->setFrom(other->m_from);
anchor->setTo(other->m_to);
if (relativeTo) {
if (other->containsItem(relativeTo, Side1)) {
other->removeItem(relativeTo);
anchor->addItem(relativeTo, Side1);
} else if (other->containsItem(relativeTo, Side2)) {
other->removeItem(relativeTo);
anchor->addItem(relativeTo, Side2);
} else {
Q_ASSERT(false);
}
} else {
auto other1 = other->m_side1Items;
auto other2 = other->m_side2Items;
other->removeAllItems();
anchor->addItems(other1, Side1);
anchor->addItems(other2, Side2);
}
return anchor;
}
void Anchor::setPositionOffset(int value)
{
if (value != m_positionOffset) {
m_positionOffset = value;
updateItemSizes();
}
} }
bool Anchor::isBeingDragged() const bool Anchor::isBeingDragged() const
{ {
return m_layout->anchorBeingDragged() == this; return s_separatorBeingDragged == this;
} }
int Anchor::cumulativeMinLength(Anchor::Side side) const bool Anchor::lazyResizeEnabled() const
{ {
if (isStatic() && isEmpty()) { return m_options & Option::LazyResize;
// There's no widget, but minimum is the space occupied by left+right anchors (or top+bottom).
const int staticAnchorThickness = Anchor::thickness(/*static=*/true);
if ((side == Side2 && (m_type & (Type_LeftStatic | Type_TopStatic))) ||
(side == Side1 && (m_type & (Type_RightStatic | Type_BottomStatic))))
return 2 * staticAnchorThickness;
}
const CumulativeMin result = cumulativeMinLength_recursive(side);
const int numNonStaticAnchors = result.numItems >= 2 ? result.numItems - 1
: 0;
int r = Anchor::thickness(isStatic()) + Anchor::thickness(true)
+ numNonStaticAnchors*Anchor::thickness(false)
+ result.minLength;
return r;
}
Anchor::CumulativeMin Anchor::cumulativeMinLength_recursive(Anchor::Side side) const
{
const auto items = this->items(side);
CumulativeMin result = { 0, 0 };
for (auto item : items) {
Anchor *oppositeAnchor = item->anchorAtSide(side, orientation());
if (!oppositeAnchor) {
// Shouldn't happen. But don't assert as this might be being called from a dumpDebug()
qWarning() << Q_FUNC_INFO << "Null opposite anchor";
return {0, 0};
}
CumulativeMin candidateMin = { 0, 0 };
if (!item->isPlaceholder()) {
candidateMin.numItems++;
candidateMin.minLength = item->minLength(orientation());
}
candidateMin += oppositeAnchor->cumulativeMinLength_recursive(side);
if (candidateMin.minLength >= result.minLength) {
result = candidateMin;
}
}
return result;
}
void Anchor::setFollowee(Anchor *followee)
{
Q_ASSERT(this != followee);
if (m_followee == followee)
return;
qCDebug(placeholder) << Q_FUNC_INFO << "follower="
<< this << "; followee=" << followee;
if (m_followee) {
disconnect(m_followee, &Anchor::positionChanged, this, &Anchor::onFolloweePositionChanged);
disconnect(m_followee, &Anchor::thicknessChanged, this, &Anchor::setThickness);
disconnect(m_followeeDestroyedConnection);
}
m_followee = followee;
setThickness();
if (m_followee) {
Q_ASSERT(orientation() == m_followee->orientation());
setVisible(false);
setPosition(m_followee->position());
connect(m_followee, &Anchor::positionChanged, this, &Anchor::onFolloweePositionChanged);
connect(m_followee, &Anchor::thicknessChanged, this, &Anchor::setThickness);
m_followeeDestroyedConnection = connect(m_followee, &QObject::destroyed, this, [this] {
setFollowee(nullptr);
});
} else {
setVisible(true);
}
Q_EMIT followeeChanged();
}
const Anchor::List Anchor::followers() const
{
Anchor::List result;
for (Anchor *a : m_layout->anchors()) {
if (a->followee() == this)
result.push_back(a);
}
return result;
}
Anchor *Anchor::endFollowee() const
{
Anchor *a = m_followee;
while (a) {
if (!a->followee())
return a;
a = a->followee();
}
return nullptr;
}
bool Anchor::findAnchor(Anchor *anchor, Anchor::Side side) const
{
if (!anchor)
return false;
Q_ASSERT(anchor != this);
Q_ASSERT(anchor->orientation() == orientation());
for (Item *item : items(side)) {
Anchor *a = item->anchorAtSide(side, orientation());
if (anchor == a)
return true;
if (a->findAnchor(anchor, side))
return true;
}
return false;
}
Anchor *Anchor::findNearestAnchorWithItems(Anchor::Side side) const
{
Anchor *candidate = nullptr;
for (Item *item : items(side)) {
Anchor *a = item->anchorAtSide(side, orientation());
if (!a->hasNonPlaceholderItems(side))
a = a->findNearestAnchorWithItems(side);
if (!candidate || (side == Side1 && a->position() > candidate->position()) || (side == Side2 && a->position() < candidate->position()) ) {
candidate = a;
}
}
if (!candidate)
candidate = m_layout->staticAnchor(side, orientation());
Q_ASSERT(candidate->isStatic() || candidate->hasNonPlaceholderItems(side));
return candidate;
}
void Anchor::clear()
{
m_side1Items.clear();
m_side2Items.clear();
}
void Anchor::onFolloweePositionChanged(int pos)
{
Q_ASSERT(isFollowing());
setPosition(pos);
}
int Anchor::thickness(bool staticAnchor)
{
return Config::self().separatorThickness(staticAnchor);
}
void Anchor::setLayout(MultiSplitterLayout *layout)
{
m_layout->removeAnchor(this);
m_layout = layout;
setParent(layout->multiSplitter());
m_separatorWidget->setParent(layout->multiSplitter());
m_layout->insertAnchor(this);
m_layout->setAnchorBeingDragged(nullptr);
} }
Separator *Anchor::separatorWidget() const Separator *Anchor::separatorWidget() const
@@ -683,26 +132,6 @@ Separator *Anchor::separatorWidget() const
return m_separatorWidget; return m_separatorWidget;
} }
void Anchor::setThickness()
{
const int value = isFollowing() ? m_followee->thickness()
: thickness(isStatic());
const int oldValue = thickness();
if (value != oldValue) {
if (isVertical()) {
m_separatorWidget->setFixedWidth(value);
m_geometry.setWidth(value);
} else {
m_separatorWidget->setFixedHeight(value);
m_geometry.setHeight(value);
}
Q_EMIT thicknessChanged();
}
}
void Anchor::setLazyPosition(int pos) void Anchor::setLazyPosition(int pos)
{ {
if (m_lazyPosition != pos) { if (m_lazyPosition != pos) {
@@ -721,67 +150,29 @@ void Anchor::setLazyPosition(int pos)
int Anchor::position(QPoint p) const int Anchor::position(QPoint p) const
{ {
return isVertical() ? p.x() : p.y(); return isVertical() ? p.y() : p.x();
} }
void Anchor::addItem(Item *item, Anchor::Side side) void Anchor::setPosition(int p)
{ {
Q_ASSERT(side != Side_None); QRect geo = m_geometry;
auto &items = (side == Side1) ? m_side1Items : m_side2Items; QPoint pt = geo.topLeft();
if (!items.contains(item)) { if (isVertical())
items << item; pt.setY(p);
item->anchorGroup().setAnchor(this, orientation(), side); else
Q_EMIT itemsChanged(side); pt.setX(p);
updateItemSizes();
}
}
void Anchor::addItems(const ItemList &list, Side side) geo.moveTopLeft(pt);
{ setGeometry(geo);
for (Item *item : list)
addItem(item, side);
}
void Anchor::removeItem(Item *item)
{
if (m_side1Items.removeOne(item)) {
item->anchorGroup().setAnchor(nullptr, orientation(), Side1);
Q_EMIT itemsChanged(Side1);
} else {
if (m_side2Items.removeOne(item)) {
item->anchorGroup().setAnchor(nullptr, orientation(), Side2);
Q_EMIT itemsChanged(Side2);
}
}
}
void Anchor::removeItems(Side side)
{
const auto &items = this->items(side);
for (Item *item : items)
removeItem(item);
}
Anchor::Side Anchor::oppositeSide(Side side)
{
switch (side) {
case Side1:
return Side2;
case Side2:
return Side1;
default:
Q_ASSERT(false);
return Side_None;
}
} }
void Anchor::onMousePress() void Anchor::onMousePress()
{ {
s_isResizing = true; s_separatorBeingDragged = this;
m_layout->setAnchorBeingDragged(this);
qCDebug(anchors) << "Drag started";
if (m_lazyResize) { qCDebug(separators) << "Drag started";
if (lazyResizeEnabled()) {
setLazyPosition(position()); setLazyPosition(position());
m_lazyResizeRubberBand->show(); m_lazyResizeRubberBand->show();
} }
@@ -789,22 +180,21 @@ void Anchor::onMousePress()
void Anchor::onMouseReleased() void Anchor::onMouseReleased()
{ {
if (m_lazyResize) { if (m_lazyResizeRubberBand) {
m_lazyResizeRubberBand->hide(); m_lazyResizeRubberBand->hide();
setPosition(m_lazyPosition); setPosition(m_lazyPosition);
} }
s_isResizing = false; s_separatorBeingDragged = nullptr;
m_layout->setAnchorBeingDragged(nullptr);
} }
void Anchor::onMouseMoved(QPoint pt) void Anchor::onMouseMoved(QPoint pt)
{ {
if (!isBeingDragged() || isStatic()) if (!isBeingDragged())
return; return;
if (!(qApp->mouseButtons() & Qt::LeftButton)) { if (!(qApp->mouseButtons() & Qt::LeftButton)) {
qCDebug(mouseevents) << Q_FUNC_INFO << "Ignoring spurious mouse event. Someone ate our ReleaseEvent"; qCDebug(separators) << Q_FUNC_INFO << "Ignoring spurious mouse event. Someone ate our ReleaseEvent";
onMouseReleased(); onMouseReleased();
return; return;
} }
@@ -820,18 +210,19 @@ void Anchor::onMouseMoved(QPoint pt)
#endif #endif
const int positionToGoTo = position(pt); const int positionToGoTo = position(pt);
auto bounds = m_layout->boundPositionsForAnchor(this); const int minPos = m_parentContainer->minPosForSeparator_global(this);
const int maxPos = m_parentContainer->maxPosForSeparator_global(this);
if (positionToGoTo < bounds.first || positionToGoTo > bounds.second) { qDebug() << "foo" << minPos << positionToGoTo << maxPos;
// qDebug() << "Out of bounds" << bounds.first << bounds.second << positionToGoTo << "; currentPos" << position() << "; window size" << window()->size();
if (positionToGoTo < minPos || positionToGoTo > maxPos)
return; return;
}
m_lastMoveDirection = positionToGoTo < position() ? Side1 m_lastMoveDirection = positionToGoTo < position() ? Side1
: (positionToGoTo > position() ? Side2 : (positionToGoTo > position() ? Side2
: Side_None); // Side_None shouldn't happen though. : Side2); // Last case shouldn't happen though.
if (m_lazyResize) if (/*m_lazyResize*/ false) // TODO
setLazyPosition(positionToGoTo); setLazyPosition(positionToGoTo);
else else
setPosition(positionToGoTo); setPosition(positionToGoTo);
@@ -839,11 +230,15 @@ void Anchor::onMouseMoved(QPoint pt)
void Anchor::onWidgetMoved(int p) void Anchor::onWidgetMoved(int p)
{ {
if (m_layout->anchorBeingDragged() != this) // We only care if it's being dragged by mouse if (!isResizing()) // We only care if it's being dragged by mouse
return; return;
setPosition(p);
}
setPosition(p); QRect Anchor::geometry() const
{
return m_geometry;
} }
bool Anchor::isResizing() bool Anchor::isResizing()
@@ -851,61 +246,7 @@ bool Anchor::isResizing()
return s_isResizing; return s_isResizing;
} }
void Anchor::setSeparatorFactoryFunc(SeparatorFactoryFunc func)
Anchor *Anchor::deserialize(const LayoutSaver::Anchor &a, MultiSplitterLayout *layout)
{ {
auto anchor = new Anchor(Qt::Orientation(a.orientation), layout, Anchor::Type(a.type)); s_separatorFactoryFunc = func;
anchor->setObjectName(a.objectName);
anchor->setGeometry(a.geometry);
anchor->m_positionPercentage = a.positionPercentage;
anchor->setProperty("indexFrom", a.indexOfFrom);
anchor->setProperty("indexTo", a.indexOfTo);
anchor->setProperty("indexFolowee", a.indexOfFollowee);
ItemList side1Items;
ItemList side2Items;
const ItemList allItems = layout->items();
side1Items.reserve(a.side1Items.size());
for (int index : qAsConst(a.side1Items)) {
side1Items.push_back(allItems.at(index));
}
side2Items.reserve(a.side2Items.size());
for (int index : qAsConst(a.side2Items)) {
side2Items.push_back(allItems.at(index));
}
anchor->m_side1Items = side1Items;
anchor->m_side2Items = side2Items;
anchor->m_initialized = true;
return anchor;
}
LayoutSaver::Anchor Anchor::serialize() const
{
LayoutSaver::Anchor a;
const Anchor::List allAnchors = m_layout->anchors();
const ItemList allItems = m_layout->items();
a.objectName = objectName();
a.type = type();
a.geometry = geometry();
a.orientation = orientation();
a.indexOfFrom = allAnchors.indexOf(from());
a.indexOfTo = allAnchors.indexOf(to());
a.indexOfFollowee = followee() ? allAnchors.indexOf(followee()) : -1;
a.positionPercentage = m_positionPercentage;
a.side1Items.clear();
a.side1Items.reserve(this->side1Items().size());
for (Item *item : this->side1Items())
a.side1Items.push_back(allItems.indexOf(item));
a.side2Items.clear();
a.side2Items.reserve(this->side2Items().size());
for (Item *item : this->side2Items())
a.side2Items.push_back(allItems.indexOf(item));
return a;
} }

View File

@@ -1,468 +0,0 @@
/*
This file is part of KDDockWidgets.
Copyright (C) 2018-2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Author: Sérgio Martins <sergio.martins@kdab.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "AnchorGroup_p.h"
#include "Anchor_p.h"
#include "MultiSplitterLayout_p.h"
#include "MultiSplitter_p.h"
#include "Logging_p.h"
using namespace KDDockWidgets;
AnchorGroup::AnchorGroup(MultiSplitterLayout *l)
: layout(l)
{
}
int AnchorGroup::width() const
{
return right->position() - left->position();
}
int AnchorGroup::height() const
{
return bottom->position() - top->position();
}
bool AnchorGroup::containsAnchor(Anchor *anchor) const
{
return anchor == left || anchor == top || anchor == right || anchor == bottom;
}
bool AnchorGroup::containsAnchor(Anchor *anchor, Anchor::Side side) const
{
if (side == Anchor::Side1)
return anchor == left || anchor == top;
return anchor == right || anchor == bottom;
}
QSize AnchorGroup::availableSize() const
{
const int leftBound = left->isStatic() ? left->position()
: layout->boundPositionForAnchor(left, Anchor::Side1);
const int rightBound = right->isStatic() ? right->position()
: layout->boundPositionForAnchor(right, Anchor::Side2);
const int topBound = top->isStatic() ? top->position()
: layout->boundPositionForAnchor(top, Anchor::Side1);
const int bottomBound = bottom->isStatic() ? bottom->position()
: layout->boundPositionForAnchor(bottom, Anchor::Side2);
return QSize(rightBound - leftBound - left->thickness(),
bottomBound - topBound - top->thickness());
}
QSize AnchorGroup::itemSize() const
{
return QSize(right->position() - left->position() - left->thickness(),
bottom->position() - top->position() - top->thickness());
}
int AnchorGroup::itemSize(Qt::Orientation o) const
{
return o == Qt::Vertical ? itemSize().width()
: itemSize().height();
}
bool AnchorGroup::hasAvailableSizeFor(QSize needed, Qt::Orientation orientation) const
{
const QSize available = availableSize();
return orientation == Qt::Vertical ? available.width() >= needed.width()
: available.height() >= needed.height();
}
AnchorGroup AnchorGroup::outterGroup() const
{
AnchorGroup group(layout);
group.left = left->hasNonPlaceholderItems(Anchor::Side1) ? left
: left->findNearestAnchorWithItems(Anchor::Side1);
group.top = top->hasNonPlaceholderItems(Anchor::Side1) ? top
: top->findNearestAnchorWithItems(Anchor::Side1);
group.right = right->hasNonPlaceholderItems(Anchor::Side2) ? right
: right->findNearestAnchorWithItems(Anchor::Side2);
group.bottom = bottom->hasNonPlaceholderItems(Anchor::Side2) ? bottom
: bottom->findNearestAnchorWithItems(Anchor::Side2);
return group;
}
Anchor *AnchorGroup::oppositeAnchor(Anchor *a) const
{
if (a == left)
return right;
if (a == right)
return left;
if (a == top)
return bottom;
if (a == bottom)
return top;
return nullptr;
}
Anchor *AnchorGroup::createAnchorFrom(Location fromAnchorLocation, Item *relativeTo)
{
Anchor *other = anchor(fromAnchorLocation);
Q_ASSERT(other);
auto anchor = new Anchor(other->orientation(), other->m_layout);
if (anchor->isVertical()) {
anchor->setFrom(top);
anchor->setTo(bottom);
} else {
anchor->setFrom(left);
anchor->setTo(right);
}
if (relativeTo) {
if (other->containsItem(relativeTo, Anchor::Side1)) {
other->removeItem(relativeTo);
anchor->addItem(relativeTo, Anchor::Side1);
} else if (other->containsItem(relativeTo, Anchor::Side2)) {
other->removeItem(relativeTo);
anchor->addItem(relativeTo, Anchor::Side2);
} else {
Q_ASSERT(false);
}
} else {
auto other1 = other->m_side1Items;
auto other2 = other->m_side2Items;
other->removeAllItems();
anchor->addItems(other1, Anchor::Side1);
anchor->addItems(other2, Anchor::Side2);
}
return anchor;
}
Anchor *AnchorGroup::anchor(Location loc) const
{
switch (loc) {
case KDDockWidgets::Location_OnLeft:
return left;
case KDDockWidgets::Location_OnTop:
return top;
case KDDockWidgets::Location_OnRight:
return right;
case KDDockWidgets::Location_OnBottom:
return bottom;
default:
Q_ASSERT(false);
return nullptr;
}
}
Anchor *AnchorGroup::anchorAtDirection(Anchor::Side side, Qt::Orientation orientation) const
{
const bool isSide1 = side == Anchor::Side1;
if (orientation == Qt::Vertical) {
return isSide1 ? right : left;
} else {
return isSide1 ? bottom : top;
}
}
Anchor *AnchorGroup::anchorAtSide(Anchor::Side side, Qt::Orientation orientation) const
{
const bool isSide1 = side == Anchor::Side1;
if (orientation == Qt::Vertical) {
return isSide1 ? left: right;
} else {
return isSide1 ? top : bottom;
}
}
void AnchorGroup::setAnchor(Anchor *anchor, Location loc)
{
switch (loc) {
case KDDockWidgets::Location_OnLeft:
left = anchor;
break;
case KDDockWidgets::Location_OnTop:
top = anchor;
break;
case KDDockWidgets::Location_OnRight:
right = anchor;
break;
case KDDockWidgets::Location_OnBottom:
bottom = anchor;
break;
default:
Q_ASSERT(false);
}
}
bool AnchorGroup::anchorIsFollowingInwards(Anchor *anchor) const
{
if (!anchor)
return false;
if (anchor == left && left->findAnchor(left->endFollowee(), Anchor::Side2))
return true;
if (anchor == top && top->findAnchor(top->endFollowee(), Anchor::Side2))
return true;
if (anchor == right && right->findAnchor(right->endFollowee(), Anchor::Side1))
return true;
if (anchor == bottom && bottom->findAnchor(bottom->endFollowee(), Anchor::Side1))
return true;
return false;
}
QDebug AnchorGroup::debug(QDebug d) const
{
d << "AnchorGroup: this=" << ((void*)this) << "\n; top=" << top << "; left=" << left
<< "\n ; right=" << right << "; bottom=" << bottom
<< "\n ; valid=" << isValid()
<< anchorIsFollowingInwards(left) << anchorIsFollowingInwards(top)
<< anchorIsFollowingInwards(right) << anchorIsFollowingInwards(bottom)
<< (left ? left->followee() : nullptr)
<< "\n";
return d;
}
const Anchor::List AnchorGroup::anchorsFollowingInwards() const
{
Anchor::List result;
if (anchorIsFollowingInwards(left))
result.push_back(left);
if (anchorIsFollowingInwards(top))
result.push_back(top);
if (anchorIsFollowingInwards(right)) {
result.push_back(right);
Q_ASSERT(!result.contains(left));
}
if (anchorIsFollowingInwards(bottom)) {
result.push_back(bottom);
Q_ASSERT(!result.contains(top));
}
Q_ASSERT(result.size() <= 2);
return result;
}
const Anchor::List AnchorGroup::anchorsNotFollowingInwards() const
{
Anchor::List result = anchors();
for (Anchor *a : anchorsFollowingInwards())
result.removeOne(a);
return result;
}
const Anchor::List AnchorGroup::anchors() const
{
return { left, top, right, bottom };
}
Anchor::Side AnchorGroup::sideForAnchor(Anchor *a) const
{
if (a == left || a == top)
return Anchor::Side1;
return Anchor::Side2;
}
bool AnchorGroup::isStatic() const
{
return top->isStatic() && bottom->isStatic() && left->isStatic() && right->isStatic();
}
bool AnchorGroup::isStaticOrFollowsStatic() const
{
return top->isStaticOrFollowsStatic() && bottom->isStaticOrFollowsStatic()
&& left->isStaticOrFollowsStatic() && right->isStaticOrFollowsStatic();
}
void AnchorGroup::updateItemSizes()
{
// Sets the geometry of the items that are inside this group
left->updateItemSizes();
top->updateItemSizes();
right->updateItemSizes();
bottom->updateItemSizes();
}
void AnchorGroup::setAnchor(Anchor *a, Qt::Orientation orientation, Anchor::Side side)
{
const bool isSide1 = side == Anchor::Side1;
if (orientation == Qt::Vertical) {
if (isSide1)
right = a;
else
left = a;
} else {
if (isSide1)
bottom = a;
else
top = a;
}
}
Anchor *AnchorGroup::adjacentAnchor(Anchor *other) const
{
if (other == top)
return right;
if (other == right)
return bottom;
if (other == bottom)
return left;
if (other == left)
return top;
return nullptr;
}
QPair<Anchor *, Anchor *> AnchorGroup::adjacentAnchors(Anchor *anchor) const
{
if (anchor == left || anchor == right) {
return { top, bottom };
} else if (anchor == top || anchor == bottom) {
return { left, right };
} else {
return {};
}
}
void AnchorGroup::addItem(Item *item)
{
// Dropping a single dockwidget, without any nesting
left->addItem(item, Anchor::Side2);
top->addItem(item, Anchor::Side2);
right->addItem(item, Anchor::Side1);
bottom->addItem(item, Anchor::Side1);
}
void AnchorGroup::addItem(MultiSplitterLayout *sourceMultiSplitter)
{
// Here we rip all the widgets and anchors from the source multisplitter into the receiving multisplitter
// preserving the layout between source widgets. Then we delete the source splitter, as all its
// content has bene integrated into ours
// To prevent the source splitter from deleting the anchors once the widgets are reparented
sourceMultiSplitter->m_beingMergedIntoAnotherMultiSplitter = true;
// Reparent the widgets:
for (Item *sourceItem : sourceMultiSplitter->items()) {
sourceItem->setLayout(layout);
sourceItem->setVisible(true);
}
// Reparent the inner anchors, they're ours now
for (Anchor *anchor : sourceMultiSplitter->anchors()) {
if (!anchor->isStatic()) {
const qreal positionPercentage = anchor->positionPercentage();
anchor->setLayout(layout);
anchor->setVisible(true);
if (anchor->from()->isStatic()) {
if (anchor->isVertical()) {
anchor->setFrom(top);
} else {
anchor->setFrom(left);
}
}
if (anchor->to()->isStatic()) {
if (anchor->isVertical()) {
anchor->setTo(bottom);
} else {
anchor->setTo(right);
}
}
// And update their position
qreal newPos = 0;
if (anchor->isVertical()) {
newPos = left->position() + (width() * positionPercentage);
} else {
newPos = top->position() + (height() * positionPercentage);
}
const QPair<int,int> bounds = layout->boundPositionsForAnchor(anchor);
anchor->setPosition(qBound(bounds.first, static_cast<int>(newPos), bounds.second));
}
}
AnchorGroup sourceAnchorGroup = sourceMultiSplitter->staticAnchorGroup();
Q_ASSERT(sourceAnchorGroup.isValid());
top->consume(sourceAnchorGroup.top);
bottom->consume(sourceAnchorGroup.bottom);
left->consume(sourceAnchorGroup.left);
right->consume(sourceAnchorGroup.right);
delete sourceMultiSplitter->multiSplitter(); // Delete MultiSplitter and MultiSplitterLayout
}
void AnchorGroup::removeItem(Item *item)
{
left->removeItem(item);
right->removeItem(item);
bottom->removeItem(item);
top->removeItem(item);
if (left->isUnneeded()) {
layout->updateAnchorsFromTo(left, right);
const int leftPosition = left->position();
right->consume(left, Anchor::Side1);
if (!right->isUnneeded() && !right->isStatic()) {
// Make use of the extra space, so it's fair
right->setPosition(right->position() - ((right->position() - leftPosition) / 2));
}
}
if (right->isUnneeded()) {
layout->updateAnchorsFromTo(right, left);
left->consume(right, Anchor::Side2);
}
if (top->isUnneeded()) {
layout->updateAnchorsFromTo(top, bottom);
const int topPosition = top->position();
bottom->consume(top, Anchor::Side1);
if (!bottom->isUnneeded() && !bottom->isStatic()) {
// Make use of the extra space, so it's fair
bottom->setPosition(bottom->position() - ((bottom->position() - topPosition) / 2));
}
}
if (bottom->isUnneeded()) {
layout->updateAnchorsFromTo(bottom, top);
top->consume(bottom, Anchor::Side2);
}
}

View File

@@ -1,126 +0,0 @@
/*
This file is part of KDDockWidgets.
Copyright (C) 2018-2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Author: Sérgio Martins <sergio.martins@kdab.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KD_MULTISPLITTER_ANCHORGROUP_P_H
#define KD_MULTISPLITTER_ANCHORGROUP_P_H
#include "docks_export.h"
#include "KDDockWidgets.h"
#include "Anchor_p.h"
#include <QDebug>
namespace KDDockWidgets {
class MultiSplitterLayout;
class Anchor;
class Item;
struct DOCKS_EXPORT_FOR_UNIT_TESTS AnchorGroup
{
///@brief contructs an invalid group
AnchorGroup() = default;
explicit AnchorGroup(MultiSplitterLayout *);
void addItem(Item *item);
void addItem(MultiSplitterLayout *);
void removeItem(Item *item);
bool isValid() const { return top && left && bottom && right; }
int width() const;
int height() const;
///@brief returns whether this group contains @p anchor
bool containsAnchor(Anchor *anchor) const;
///@brief returns whether this group contains @p anchor at Side @p side
///If side is Side1, then anchor must be equal to left or top, otherwise top or bottom
bool containsAnchor(Anchor *anchor, Anchor::Side side) const;
/**
* @brief Returns the max available size in this group
* This is the size of the widget when you push all anchors outwards
*/
QSize availableSize() const;
/**
* @brief Returns the size of an item that would be inside these 4 anchors
*/
QSize itemSize() const;
/**
* @brief Similar to @ref itemSize(), but returns the width if @p o is Qt::Vertical, otherwise
* the height
*/
int itemSize(Qt::Orientation o) const;
/**
* @brief Returns whether @ref availableSize is bigger or equal than @ref needed
*/
bool hasAvailableSizeFor(QSize needed, Qt::Orientation orientation) const;
/// Returns the group formed by the Anchors that actually have items on their outter side
AnchorGroup outterGroup() const;
Anchor *oppositeAnchor(Anchor*) const;
Anchor *createAnchorFrom(KDDockWidgets::Location fromAnchorLocation, Item *relativeTo);
void setAnchor(Anchor *a, Qt::Orientation orientation, Anchor::Side side);
Anchor *adjacentAnchor(Anchor*) const;
QPair<Anchor*,Anchor*> adjacentAnchors(Anchor*) const;
Anchor *anchor(KDDockWidgets::Location) const;
Anchor *anchorAtDirection(Anchor::Side side, Qt::Orientation orientation) const;
Anchor *anchorAtSide(Anchor::Side side, Qt::Orientation orientation) const;
void setAnchor(Anchor *anchor, KDDockWidgets::Location);
bool anchorIsFollowingInwards(Anchor*) const;
const Anchor::List anchorsFollowingInwards() const;
const Anchor::List anchorsNotFollowingInwards() const;
const Anchor::List anchors() const;
Anchor::Side sideForAnchor(Anchor*) const;
bool isStatic() const;
bool isStaticOrFollowsStatic() const;
void updateItemSizes();
Anchor *top = nullptr;
Anchor *left = nullptr;
Anchor *bottom = nullptr;
Anchor *right = nullptr;
MultiSplitterLayout *layout;
QDebug debug(QDebug d) const;
};
}
inline QDebug operator<< (QDebug d, KDDockWidgets::AnchorGroup *group)
{
// out-of-line as it needs to include MultiSplitterLayout
return group->debug(d);
}
#endif

View File

@@ -21,8 +21,7 @@
#ifndef KD_MULTISPLITTER_ANCHOR_P_H #ifndef KD_MULTISPLITTER_ANCHOR_P_H
#define KD_MULTISPLITTER_ANCHOR_P_H #define KD_MULTISPLITTER_ANCHOR_P_H
#include "docks_export.h" #include "Item_p.h"
#include "LayoutSaver_p.h"
#include <QObject> #include <QObject>
#include <QPointer> #include <QPointer>
@@ -34,325 +33,83 @@ class QRubberBand;
QT_END_NAMESPACE QT_END_NAMESPACE
namespace KDDockWidgets { namespace KDDockWidgets {
class Item;
class MultiSplitterLayout; class MultiSplitterLayout;
class Separator; }
typedef QVector<Item*> ItemList; namespace Layouting {
/** class Anchor : public QObject // clazy:exclude=ctor-missing-parent-argument
* @brief An anchor is the vertical or horizontal (@ref orientation()) line that has an handle
* so you can resize widgets with your mouse.
*
* A MultiSplitter comes with 4 static anchors (@ref isStatic()), that represent the top, left, right
* and bottom borders. A static anchor means it can't change position, doesn't display the handle and
* will have the same lifetime has the MultiSplitter.
*
* Each anchor has two properties indicating in which anchor it starts and where it ends, @ref from(), to().
* For example, the top static horizontal anchor starts at the left anchor and ends at the right static anchor.
* If this anchor is vertical, then from()/to() return horizontal anchors, and vice-versa.
*
* An anchor has a length, which is to()->pos() - from()->pos(). The length of a vertical anchor is,
* thus, its vertical extent (Likewise for horizontal anchors).
*
* An anchor controls two groups of widgets: side1 and side2 widgets. When an anchor is dragged with mouse
* it will resize those widgets. The widgets always start or end at the position where the anchor lives.
* For vertical anchors, side1 means "the widgets at its left" and side2 means "the widgets at its right",
* Same principle for horizontal anchors, but for top/bottom instead.
* Static anchors only have 1 side with widgets. For example the left static anchor only has widgets at its
* right, so side1Widgets is empty.
* Non-static anchors, always have side1 and side2 widgets. If not then they are considered unneeded
* and are deleted.
*
* Example:
*
* +--------------------+
* | | |
* | | |
* | | |
* | Foo | Bar |
* | | |
* | | |
* +--------------------+
*
* In the above example we have 5 anchors. 4 of them are static (left, right, top, bottom) and there's
* a non-static one, in the middle. It's vertical, and can be dragged left and right, resizing its
* side1Widgets (Foo) and side2Widgets (Bar). This non-static anchors has from=top anchor, and to=bottom anchor.
*
*/
class DOCKS_EXPORT_FOR_UNIT_TESTS Anchor : public QObject // clazy:exclude=ctor-missing-parent-argument
{ {
Q_OBJECT Q_OBJECT
// properties for GammaRay Q_PROPERTY(QRect geometry READ geometry NOTIFY geometryChanged)
Q_PROPERTY(KDDockWidgets::ItemList side1Items READ side1Items NOTIFY itemsChanged)
Q_PROPERTY(KDDockWidgets::ItemList side2Items READ side2Items NOTIFY itemsChanged)
Q_PROPERTY(QString debug_side1ItemNames READ debug_side1ItemNames NOTIFY debug_itemNamesChanged)
Q_PROPERTY(QString debug_side2ItemNames READ debug_side2ItemNames NOTIFY debug_itemNamesChanged)
Q_PROPERTY(Anchor* from READ from WRITE setFrom NOTIFY fromChanged)
Q_PROPERTY(Anchor* to READ to WRITE setTo NOTIFY toChanged)
Q_PROPERTY(int position READ position WRITE setPosition NOTIFY positionChanged)
Q_PROPERTY(Qt::Orientation orientation READ orientation CONSTANT) Q_PROPERTY(Qt::Orientation orientation READ orientation CONSTANT)
Q_PROPERTY(Anchor *followee READ followee NOTIFY followeeChanged)
public: public:
///@brief represents the Anchor type
///An anchor can be of 2 types:
/// - Normal: Anchor that can be resized via mouse
/// - static: this is the top, left, right, bottom borders of the main window. They are called static because they don't move.
enum Type {
Type_None = 0, ///< The anchor is normal, and can be resized.
Type_LeftStatic = 1, ///< The anchor is static and represents the left mainwindow margin
Type_RightStatic = 2, ///< The anchor is static and represents the right mainwindow margin
Type_TopStatic = 4, ///< The anchor is static and represents the top mainwindow margin
Type_BottomStatic = 8, ///< The anchor is static and represents the bottom mainwindow margin
Type_Static = Type_TopStatic | Type_LeftStatic | Type_RightStatic | Type_BottomStatic ///< The anchor is static, one of the 4 previous ones
};
Q_ENUM(Type)
enum Side { enum class Option {
Side_None = 0, None = 0,
Side1, LazyResize
Side2
}; };
Q_ENUM(Side) Q_DECLARE_FLAGS(Options, Option);
enum SetPositionOption {
SetPositionOption_None = 0,
SetPositionOption_DontRecalculatePercentage = 1
};
Q_DECLARE_FLAGS(SetPositionOptions, SetPositionOption)
typedef QVector<Anchor *> List; typedef QVector<Anchor *> List;
explicit Anchor(Qt::Orientation orientation, MultiSplitterLayout *multiSplitter, Type = Type_None); explicit Anchor(ItemContainer *parentContainer, Qt::Orientation orientation,
Options options, QWidget *hostWidget);
~Anchor() override; ~Anchor() override;
static Anchor* deserialize(const LayoutSaver::Anchor &, MultiSplitterLayout *layout);
LayoutSaver::Anchor serialize() const;
void setFrom(Anchor *); QWidget *hostWidget() const;
Anchor *from() const { return m_from; }
Anchor *to() const { return m_to; }
void setTo(Anchor *);
Qt::Orientation orientation() const; Qt::Orientation orientation() const;
void addItem(Item *, Side); void setGeometry(int pos, int pos2, int length);
void addItems(const ItemList &list, Side);
void removeItem(Item *w);
void removeItems(Side);
bool isVertical() const { return m_orientation == Qt::Vertical; }
void setPosition(int p, SetPositionOptions = SetPositionOption_None);
void updatePositionPercentage();
int position() const; int position() const;
void setVisible(bool);
qreal positionPercentage() const { return m_positionPercentage; }
void ensureBounded();
/**
* @brief Sets the new layout. Called when we're dropping a source layout into a target one.
* The target one will steal the separators of the source one.
*/
void setLayout(MultiSplitterLayout *);
///@brief returns the separator widget ///@brief returns the separator widget
Separator* separatorWidget() const; Separator* separatorWidget() const;
/**
* Returns how far left or top an anchor can go and still respecting it's Side1 widgets min-size.
* This function doesn't count with shifting other anchors, for that use MultiSplitterLayout::boundPositionsForAnchor()
* which is is recursive and returns the bounds after simulating that intermediary anchors to the left/top were
* also resized (each still respecting widgets min sizes though).
*/
int minPosition() const;
/**
* A squeeze is a widget's width (or height for horizontal anchors) minus its minimum width.
* This function iterates through all widgets of the specified side and returns the minimum
* available squeeze.
*/
int smallestAvailableItemSqueeze(Anchor::Side) const;
/**
* @brief The length of this anchor. The distance between @ref from and @ref to.
* @return the anchor's length
*/
int length() const;
/**
* @brief Checks if this anchor is valid. It's valid if @ref from and @ref to are non-null, and not the same.
* @return true if this anchor is valid.
*/
bool isValid() const;
/**
* @brief The width of a vertical anchor, or height of an horizontal anchor.
*/
int thickness() const;
/**
* @brief Checks if this Anchor is static.
* @return true if this Anchor is static.
*/
bool isStatic() const { return m_type & Type_Static; }
bool isUnneeded() const { return !isStatic() && (!hasItems(Side1) || !hasItems(Side2)); }
bool isEmpty() const { return !hasItems(Side1) && !hasItems(Side2); }
bool hasItems(Side) const;
bool hasNonPlaceholderItems(Side) const;
bool onlyHasPlaceholderItems(Anchor::Side side) const;
/**
* @brief Returns whether this Anchor should follow another one. That happens if one of it's side is empty or only has placeholders
* Also, it can't be a static anchor.
*/
bool shouldFollow() const { return !isStatic() && (onlyHasPlaceholderItems(Side1) || onlyHasPlaceholderItems(Side2)); }
bool containsItem(const Item *w, Side side) const;
bool isStaticOrFollowsStatic() const;
const ItemList items(Side side) const;
const ItemList side1Items() const { return m_side1Items; }
const ItemList side2Items() const { return m_side2Items; }
void consume(Anchor *other);
void consume(Anchor *other, Side);
void swapItems(Anchor *other);
void removeAllItems();
static Anchor *createFrom(Anchor *other, Item *relativeTo = nullptr);
void setPositionOffset(int); void setPositionOffset(int);
bool isBeingDragged() const; bool isBeingDragged() const;
Type type() const { return m_type; } bool lazyResizeEnabled() const;
int cumulativeMinLength(Anchor::Side side) const;
/**
* @brief Makes this separator follow another one. This one will be made invisible.
* Used when the item in the layout is just a placeholder remembering a previous dock widget position.
* Pass nullptr do make it not follow and visible again.
*/
void setFollowee(Anchor *);
/**
* @brief getter for the followee
*/
Anchor *followee() const { return m_followee; }
/**
* @brief Returns the list of anchors following this one.
*/
const List followers() const;
/**
* @brief Returns the last followee in the chain.
*/
Anchor *endFollowee() const;
/**
* @brief Recursively looks for an anchor in the whole layout but only looking at side @p side
*
* This allows us to know if there's an anchor on the top or left of us (side1) or right or bottom
* (side2), in the whole layout.
*
* Returns false if @p anchor is nullptr
*/
bool findAnchor(Anchor *anchor, Side side) const;
/**
* @brief Returns the nearest Anchor with non-placeholder items on side @p side
* If nothing is found then returns the static anchor on that side
*/
Anchor *findNearestAnchorWithItems(Side side) const;
///@brief removes the side1 and side2 items. Doesn't delete them
void clear();
static int thickness(bool staticAnchor);
static Anchor::Side oppositeSide(Side side);
void onFolloweePositionChanged(int pos);
bool isFollowing() const { return m_followee != nullptr; }
void onMousePress(); void onMousePress();
void onMouseReleased(); void onMouseReleased();
void onMouseMoved(QPoint pt); void onMouseMoved(QPoint pt);
void onWidgetMoved(int p); void onWidgetMoved(int p);
QRect geometry() const;
bool isVertical() const;
///@brief Returns whether we're dragging a separator. Can be useful for the app to stop other work while we're not in the final size ///@brief Returns whether we're dragging a separator. Can be useful for the app to stop other work while we're not in the final size
static bool isResizing(); static bool isResizing();
static void setSeparatorFactoryFunc(SeparatorFactoryFunc);
private:
struct CumulativeMin {
int minLength;
int numItems;
CumulativeMin& operator+=(CumulativeMin other) {
minLength += other.minLength;
numItems += other.numItems;
return *this;
}
};
CumulativeMin cumulativeMinLength_recursive(Anchor::Side side) const;
void setThickness();
void setLazyPosition(int);
Q_SIGNALS: Q_SIGNALS:
void positionChanged(int pos); void geometryChanged(QRect);
void itemsChanged(Anchor::Side);
void fromChanged();
void toChanged();
void debug_itemNamesChanged();
void followeeChanged();
void thicknessChanged();
public: private:
int position(QPoint) const; void setLazyPosition(int);
void updateSize();
void updateItemSizes();
void debug_updateItemNames();
QString debug_side1ItemNames() const;
QString debug_side2ItemNames() const;
void setGeometry(QRect); void setGeometry(QRect);
QRect geometry() const { return m_geometry; } int position(QPoint) const;
void setPosition(int p);
const Qt::Orientation m_orientation; const Qt::Orientation m_orientation;
ItemList m_side1Items;
ItemList m_side2Items;
QPointer<Anchor> m_from;// QPointer just so we can assert. They should never be null.
QPointer<Anchor> m_to;
const Type m_type;
qreal m_positionPercentage = 0.0; // Should be between 0 and 1
// Only set when anchor is moved through mouse. Side1 if going towards left or top, Side2 otherwise. // Only set when anchor is moved through mouse. Side1 if going towards left or top, Side2 otherwise.
Side m_lastMoveDirection = Side_None; Layouting::Side m_lastMoveDirection = Side1;
MultiSplitterLayout *m_layout = nullptr; QWidget *const m_hostWidget;
bool m_showingSide1Rubberband = false;
bool m_showingSide2Rubberband = false;
bool m_initialized = false;
static bool s_isResizing; static bool s_isResizing;
static const QString s_magicMarker; // Just to validate serialize is symmetric to deserialize static Anchor* s_separatorBeingDragged;
// For when being animated. They are not displayed at their pos, but with an offset.
int m_positionOffset = 0;
QString m_debug_side1ItemNames;
QString m_debug_side2ItemNames;
Separator *const m_separatorWidget; Separator *const m_separatorWidget;
QRect m_geometry; QRect m_geometry;
Anchor *m_followee = nullptr;
QMetaObject::Connection m_followeeDestroyedConnection;
const bool m_lazyResize;
int m_lazyPosition = 0; int m_lazyPosition = 0;
const Options m_options;
QRubberBand *const m_lazyResizeRubberBand; QRubberBand *const m_lazyResizeRubberBand;
ItemContainer *const m_parentContainer;
}; };
} }
Q_DECLARE_METATYPE(KDDockWidgets::ItemList)
Q_DECLARE_METATYPE(KDDockWidgets::Item*)
#endif #endif

View File

@@ -0,0 +1,22 @@
set(MULTISPLITTER_SRCS
Anchor.cpp
Anchor_p.h
Item.cpp
Item_p.h
Logging.cpp
Logging_p.h
Separator.cpp
Separator_p.h
)
add_library(kddockwidgets_layouting ${MULTISPLITTER_SRCS})
target_link_libraries(kddockwidgets_layouting Qt5::Core Qt5::Widgets)
add_subdirectory(tests)
target_include_directories(kddockwidgets_layouting
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
)

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
/* /*
This file is part of KDDockWidgets. This file is part of KDDockWidgets.
Copyright (C) 2018-2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Copyright (C) 2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Author: Sérgio Martins <sergio.martins@kdab.com> Author: Sérgio Martins <sergio.martins@kdab.com>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
@@ -18,177 +18,577 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef KD_MULTISPLITTER_ITEM_P_H #pragma once
#define KD_MULTISPLITTER_ITEM_P_H
#include "docks_export.h"
#include "Anchor_p.h"
#include "QWidgetAdapter.h"
#include "LayoutSaver_p.h"
#include <QRect>
#include <QObject> #include <QObject>
#include <QVector>
#include <QRect>
#include <QVariant>
#include <QWidget> // TODO: remove
#include <QDebug>
#include <QVariant>
/** #include <memory>
* @brief Implements an item that you put into a multi-splitter.
* For now it just wraps a KDDockWidgets::Frame, but could eventually be used in QML.
*/
namespace KDDockWidgets {
struct AnchorGroup; #define KDDOCKWIDGETS_MIN_WIDTH 80
class MultiSplitterLayout; #define KDDOCKWIDGETS_MIN_HEIGHT 90
class Frame;
class DockWidgetBase;
class TestDocks;
struct GeometryDiff class TestMultiSplitter;
{
explicit GeometryDiff(QRect oldGeo, QRect newGeo)
: leftDiff(newGeo.left() - oldGeo.left())
, topDiff(newGeo.top() - oldGeo.top())
, rightDiff(newGeo.right() - oldGeo.right())
, bottomDiff(newGeo.bottom() - oldGeo.bottom())
, onlyOneSideChanged([this]{
int numChanged = 0;
if (leftDiff != 0)
numChanged++;
if (topDiff != 0)
numChanged++;
if (rightDiff != 0)
numChanged++;
if (bottomDiff != 0)
numChanged++;
return numChanged == 1;
}()) // Lambda just so we can have onlyOneChanged as const
{
}
// Orientation of the Anchor that provoked the geometry diff namespace Layouting {
Qt::Orientation orientation() const
{
if (leftDiff || rightDiff)
return Qt::Vertical;
return Qt::Horizontal; class ItemContainer;
} class Item;
class Anchor;
class Separator;
int delta() const typedef Separator* (*SeparatorFactoryFunc)(Layouting::Anchor*, QWidget *parent);
{
// Since we only use GeometryDiff when only 1 side changed, just sum them all
return leftDiff + rightDiff + topDiff + bottomDiff;
}
int signess() const enum Location {
{ Location_None,
return delta() > 0 ? 1: -1; Location_OnLeft, ///> Left docking location
} Location_OnTop, ///> Top docking location
Location_OnRight, ///> Right docking location
const int leftDiff; Location_OnBottom ///> Bottom docking location
const int topDiff;
const int rightDiff;
const int bottomDiff;
const bool onlyOneSideChanged;
}; };
class DOCKS_EXPORT_FOR_UNIT_TESTS Item : public QObject // clazy:exclude=ctor-missing-parent-argument enum AddingOption {
AddingOption_None = 0, ///> No option set
AddingOption_StartHidden ///< Don't show the dock widget when adding it
};
///@internal
inline Location oppositeLocation(Location loc)
{
switch (loc) {
case Location_OnLeft:
return Location_OnRight;
case Location_OnTop:
return Location_OnBottom;
case Location_OnRight:
return Location_OnLeft;
case Location_OnBottom:
return Location_OnTop;
default:
Q_ASSERT(false);
return Location_None;
}
}
///@internal
inline Location adjacentLocation(Location loc)
{
switch (loc) {
case Location_OnLeft:
return Location_OnTop;
case Location_OnTop:
return Location_OnRight;
case Location_OnRight:
return Location_OnBottom;
case Location_OnBottom:
return Location_OnLeft;
default:
Q_ASSERT(false);
return Location_None;
}
}
enum Side {
Side1,
Side2
};
enum class GrowthStrategy {
BothSidesEqually
};
inline Qt::Orientation oppositeOrientation(Qt::Orientation o) {
return o == Qt::Vertical ? Qt::Horizontal
: Qt::Vertical;
}
inline int pos(QPoint p, Qt::Orientation o) {
return o == Qt::Vertical ? p.y()
: p.x();
}
inline int length(QSize sz, Qt::Orientation o) {
return o == Qt::Vertical ? sz.height()
: sz.width();
}
inline bool locationIsVertical(Location loc)
{
return loc == Location_OnTop || loc == Location_OnBottom;
}
inline bool locationIsHorizontal(Location loc)
{
return !locationIsVertical(loc);
}
inline bool locationIsSide1(Location loc)
{
return loc == Location_OnLeft || loc == Location_OnTop;
}
inline bool locationIsSide2(Location loc)
{
return loc == Location_OnRight || loc == Location_OnBottom;
}
inline QRect adjustedRect(QRect r, Qt::Orientation o, int p1, int p2)
{
if (o == Qt::Vertical) {
r.adjust(0, p1, 0, p2);
} else {
r.adjust(p1, 0, p2, 0);
}
return r;
}
inline QVariantMap sizeToMap(QSize sz)
{
QVariantMap map;
map.insert(QStringLiteral("width"), sz.width());
map.insert(QStringLiteral("height"), sz.height());
return map;
}
inline QVariantMap rectToMap(QRect rect)
{
QVariantMap map;
map.insert(QStringLiteral("x"), rect.x());
map.insert(QStringLiteral("y"), rect.y());
map.insert(QStringLiteral("width"), rect.width());
map.insert(QStringLiteral("height"), rect.height());
return map;
}
inline QSize mapToSize(const QVariantMap &map)
{
return { map.value(QStringLiteral("width")).toInt(),
map.value(QStringLiteral("height")).toInt() };
}
inline QRect mapToRect(const QVariantMap &map)
{
return QRect(map.value(QStringLiteral("x")).toInt(),
map.value(QStringLiteral("y")).toInt(),
map.value(QStringLiteral("width")).toInt(),
map.value(QStringLiteral("height")).toInt());
}
inline Qt::Orientation orientationForLocation(Location loc)
{
switch (loc) {
case Location_OnLeft:
case Location_OnRight:
return Qt::Horizontal;
case Location_None:
case Location_OnTop:
case Location_OnBottom:
return Qt::Vertical;
}
return Qt::Vertical;
}
inline Side sideForLocation(Location loc)
{
switch (loc) {
case Location_OnLeft:
case Location_OnTop:
return Side::Side1;
case Location_OnRight:
case Location_OnBottom:
return Side::Side2;
default:
return Side::Side1;
}
}
struct SizingInfo {
QSize size() const {
return geometry.size();
}
int length(Qt::Orientation o) const {
return Layouting::length(size(), o);
}
int minLength(Qt::Orientation o) const {
return Layouting::length(minSize, o);
}
int availableLength(Qt::Orientation o) const {
return qMax(0, length(o) - minLength(o));
}
int missingLength(Qt::Orientation o) const {
return qMax(0, minLength(o) - length(o));
}
QPoint pos() const {
return geometry.topLeft();
}
int position(Qt::Orientation o) const {
return Layouting::pos(pos(), o);
}
int edge(Qt::Orientation o) const {
return o == Qt::Vertical ? geometry.bottom()
: geometry.right();
}
void setLength(int l, Qt::Orientation o) {
if (o == Qt::Vertical) {
geometry.setHeight(l);
} else {
geometry.setWidth(l);
}
}
void incrementLength(int byAmount, Qt::Orientation o) {
setLength(length(o) + byAmount, o);
}
void setOppositeLength(int l, Qt::Orientation o) {
setLength(l, oppositeOrientation(o));
}
void setPos(int p, Qt::Orientation o) {
if (o == Qt::Vertical)
geometry.moveTop(p);
else
geometry.moveLeft(p);
}
bool isNull() const {
return geometry.isNull();
}
void setGeometry(QRect geo) {
geometry = geo;
}
QVariantMap toVariantMap() const;
void fromVariantMap(const QVariantMap &);
typedef QVector<SizingInfo> List;
QRect geometry;
QSize minSize = QSize(40, 40); // TODO: Hardcoded
QSize maxSize = QSize(16777215, 16777215); // TODO: Not supported yet
double percentageWithinParent = 0.0;
bool isBeingInserted = false;
};
class GuestInterface
{
public:
virtual void setLayoutItem(Item *) = 0;
virtual QWidget *asWidget() = 0;
};
class Item : public QObject
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(bool isPlaceholder READ isPlaceholder NOTIFY isPlaceholderChanged) Q_PROPERTY(int x READ x NOTIFY xChanged)
Q_PROPERTY(int y READ y NOTIFY yChanged)
Q_PROPERTY(int width READ width NOTIFY widthChanged)
Q_PROPERTY(int height READ height NOTIFY heightChanged)
Q_PROPERTY(QRect geometry READ geometry NOTIFY geometryChanged) Q_PROPERTY(QRect geometry READ geometry NOTIFY geometryChanged)
Q_PROPERTY(QSize minimumSize READ minimumSize NOTIFY minimumSizeChanged) Q_PROPERTY(bool isContainer READ isContainer CONSTANT)
public: public:
typedef QVector<Item*> List;
/// @brief constructs a new layout item to show @p Frame in the layout @layout explicit Item(QWidget *hostWidget, ItemContainer *parent = nullptr);
/// @param frame This is never nullptr.
/// @param layout This is never nullptr.
explicit Item(Frame *frame, MultiSplitterLayout *layout);
/// @brief Constructor overload used when restoring a layout and the Item is a placeholder (no frame)
explicit Item(MultiSplitterLayout *layout);
/// @brief Destroys its frame too.
~Item() override; ~Item() override;
static Item* deserialize(const LayoutSaver::Item &, MultiSplitterLayout *layout); bool isRoot() const;
LayoutSaver::Item serialize() const; virtual int visibleCount_recursive() const;
virtual void insertItem(Item *item, Location, AddingOption = AddingOption_None);
/**
* @brief No widget can have a minimum size smaller than this, regardless of their minimum size.
*/
static QSize hardcodedMinimumSize();
int x() const; int x() const;
int y() const; int y() const;
QPoint pos() const;
int position(Qt::Orientation) const;
QSize size() const;
int width() const; int width() const;
int height() const; int height() const;
bool isVisible() const; QSize size() const;
void setVisible(bool); void setSize(QSize);
QPoint pos() const;
void setGeometry(QRect); int pos(Qt::Orientation) const;
void ensureMinSize(Qt::Orientation orientation, Anchor::Side);
void ensureMinSize(Qt::Orientation orientation);
void beginBlockPropagateGeo();
void endBlockPropagateGeo();
QRect geometry() const; QRect geometry() const;
bool eventFilter(QObject *, QEvent *) override;
Frame* frame() const; bool isContainer() const;
QWidgetOrQuick *window() const; bool isWidget() const { return !isContainer(); }
QWidgetOrQuick *parentWidget() const;
MultiSplitterLayout *layout() const; Qt::Orientation orientation() const;
void setLayout(MultiSplitterLayout *w); // TODO: Make the widget children of this one? static int separatorThickness();
[[nodiscard]] virtual bool checkSanity();
/** void setParentContainer(ItemContainer *parent); // TODO: Make private
* Returns the width of the widget if orientation is Vertical, the height otherwise. ItemContainer *parentContainer() const;
*/ void setPos(QPoint); // TODO: Make private
void setPos(int pos, Qt::Orientation);
int position(Qt::Orientation) const;
const ItemContainer *asContainer() const;
ItemContainer *asContainer();
void setMinSize(QSize);
void setMaxSize(QSize);
virtual QSize minSize() const;
virtual QSize maxSize() const;
virtual void resize(QSize newSize);
int minLength(Qt::Orientation) const;
void setLength(int length, Qt::Orientation);
virtual void setLength_recursive(int length, Qt::Orientation);
int length(Qt::Orientation) const; int length(Qt::Orientation) const;
int minLength(Qt::Orientation orientation) const; int availableLength(Qt::Orientation) const;
Anchor *anchorAtSide(Anchor::Side side, Qt::Orientation orientation) const;
Anchor *anchor(const GeometryDiff &) const;
AnchorGroup& anchorGroup();
const AnchorGroup& anchorGroup() const;
QSize minimumSize() const;
bool isPlaceholder() const; bool isPlaceholder() const;
void setIsPlaceholder(bool);
/**
* @brief Returns whether this item lives in a @ref MainWindow, as opposed to a @ref FloatingWindow
*/
bool isInMainWindow() const;
///@brief turns the placeholder into a normal Item again showing @p dockWidget virtual bool isVisible() const;
void restorePlaceholder(DockWidgetBase *dockWidget, int tabIndex); virtual void setIsVisible(bool);
virtual void setGeometry_recursive(QRect rect);
virtual void dumpLayout(int level = 0);
void setGeometry(QRect rect);
SizingInfo m_sizingInfo;
QSize missingSize() const;
int missingLength(Qt::Orientation) const;
bool isBeingInserted() const;
void setBeingInserted(bool);
ItemContainer *root() const;
QRect mapToRoot(QRect) const;
QPoint mapToRoot(QPoint) const;
int mapToRoot(int p, Qt::Orientation) const;
QPoint mapFromRoot(QPoint) const;
QRect mapFromRoot(QRect) const;
QPoint mapFromParent(QPoint) const;
int mapFromRoot(int p, Qt::Orientation) const;
///@brief turns the placeholder into a normal item again QWidget *frame() const { return m_guest ? m_guest->asWidget() : nullptr; } // TODO: rename
/// This overload is called when the Frame has more than 1 tab, otherwise we just use the DockWidget overload GuestInterface *guest() const { return m_guest; }
void restorePlaceholder(Frame *frame); void setFrame(GuestInterface *);
QWidget *window() const {
/** return m_guest ? frame()->window() : nullptr;
* @brief Checks if the minSize is correct. }
* The parent widget got a QEvent::LayoutRequest, so the Frame might have changed its constraints.
*/
void onLayoutRequest() const;
void ref(); void ref();
void unref(); void unref();
int refCount() const; // for tests int refCount() const;
Q_SIGNALS:
void frameChanged();
void geometryChanged();
void isPlaceholderChanged();
void minimumSizeChanged();
private:
friend KDDockWidgets::TestDocks;
QSize actualMinSize() const; // The min size, regardless if it's a placeholder or not, so we can save the actual value while LayoutSaver::saveLayout
void restoreSizes(QSize minSize, QRect geometry); // Just for LayoutSaver::restore
class Private; QWidget *hostWidget() const;
Private *const d; void restore(GuestInterface *guest);
virtual void setHostWidget(QWidget *);
virtual void updateWidgetGeometries();
virtual QVariantMap toVariantMap() const;
virtual void fillFromVariantMap(const QVariantMap &map, const QHash<QString, GuestInterface*> &widgets);
static Item* createFromVariantMap(QWidget *hostWidget, ItemContainer *parent,
const QVariantMap &map, const QHash<QString, GuestInterface *> &widgets);
Q_SIGNALS:
void geometryChanged();
void xChanged();
void yChanged();
void widthChanged();
void heightChanged();
void visibleChanged(Item *thisItem, bool visible);
void minSizeChanged(Item *thisItem);
protected:
friend class ::TestMultiSplitter;
explicit Item(bool isContainer, QWidget *hostWidget, ItemContainer *parent);
const bool m_isContainer;
ItemContainer *m_parent = nullptr;
private Q_SLOTS:
void onWidgetLayoutRequested();
private:
void turnIntoPlaceholder();
bool eventFilter(QObject *o, QEvent *event) override;
int m_refCount = 0;
void updateObjectName();
void onWidgetDestroyed();
bool m_isVisible = false;
bool m_destroying = false; // TODO: Remove and check if unit-tests pass
QWidget * m_hostWidget = nullptr;
GuestInterface *m_guest = nullptr;
}; };
class ItemContainer : public Item {
Q_OBJECT
Q_PROPERTY(QVariantList items READ items NOTIFY itemsChanged)
public:
struct LengthOnSide {
int length = 0;
int minLength = 0;
int available() const {
return qMax(0, length - minLength);
}
int missing() const {
return qMax(0, minLength - length);
}
};
explicit ItemContainer(QWidget *hostWidget, ItemContainer *parent);
explicit ItemContainer(QWidget *parent);
void insertItem(Item *item, int index);
[[nodiscard]] bool checkSanity() override;
void scheduleCheckSanity() const;
bool hasOrientation() const;
int numChildren() const;
int numVisibleChildren() const;
bool hasChildren() const;
bool hasVisibleChildren() const;
int indexOfVisibleChild(const Item *) const;
void removeItem(Item *, bool hardRemove = true);
bool isEmpty() const;
void setGeometry_recursive(QRect rect) override;
ItemContainer *convertChildToContainer(Item *leaf);
void insertItem(Item *item, Location, AddingOption = AddingOption_None) override;
bool hasOrientationFor(Location) const;
Item::List children() const;
Item::List visibleChildren(bool includeBeingInserted = false) const;
int usableLength() const;
bool hasSingleVisibleItem() const;
bool contains(const Item *item) const;
bool contains_recursive(const Item *item) const;
void setChildren(const Item::List children, Qt::Orientation o);
void setOrientation(Qt::Orientation);
QSize minSize() const override;
QSize maxSize() const override;
void resize(QSize newSize) override;
int length() const;
QRect rect() const;
QVariantList items() const;
void dumpLayout(int level = 0) override;
void updateChildPercentages();
void updateChildPercentages_recursive();
void restoreChild(Item *);
void updateWidgetGeometries() override;
int oppositeLength() const;
///@brief Grows the side1Neighbour to the right and the side2Neighbour to the left
///So they occupy the empty space that's between them (or bottom/top if Qt::Vertical).
///This is useful when an Item is removed. Its neighbours will occupy its space.
///side1Neighbour or side2Neighbour are allowed to be null, in which case the non-null one
///will occupy the entire space.
void growNeighbours(Item *side1Neighbour, Item *side2Neighbour);
///@brief grows an item by @p amount. It calculates how much to grow on side1 and on side2
///Then calls growItem(item, side1Growth, side2Growth) which will effectively grow it,
///and shrink the neighbours which are donating the size.
void growItem(Item *, int amount, GrowthStrategy, bool accountForNewSeparator = false);
void growItem(int index, SizingInfo::List &sizes, int missing, GrowthStrategy, bool accountForNewSeparator = false);
///@brief Shrinks the neighbours of the item at @p index
///
/// The neighbours at the left/top of the item, will be shrunk by @p side1Amount, while the items
/// at right/bottom will be shrunk by @p side2Amount.
/// Squeezes all the neighbours (not just the immediate ones).
void shrinkNeighbours(int index, SizingInfo::List &sizes, int side1Amount, int side2Amount);
Item *visibleNeighbourFor(const Item *item, Side side) const;
QSize availableSize() const;
int availableLength() const;
LengthOnSide lengthOnSide(const SizingInfo::List &sizes, int fromIndex, Side, Qt::Orientation) const;
int minLength(int fromIndex, Side, Qt::Orientation) const;
int neighboursLengthFor(const Item *item, Side, Qt::Orientation) const;
int neighboursLengthFor_recursive(const Item *item, Side, Qt::Orientation) const;
int neighboursMinLengthFor(const Item *item, Side, Qt::Orientation) const;
int neighboursMinLengthFor_recursive(const Item *item, Side, Qt::Orientation) const;
int neighbourSeparatorWaste(const Item *item, Side, Qt::Orientation) const;
int neighbourSeparatorWaste_recursive(const Item *item, Side, Qt::Orientation) const;
int availableOnSide(Item *child, Side) const;
QSize missingSizeFor(Item *item, Qt::Orientation) const;
void onChildMinSizeChanged(Item *child);
void onChildVisibleChanged(Item *child, bool visible);
void updateSizeConstraints();
SizingInfo::List sizingInfosPerNeighbour(Item *item, Side) const;
SizingInfo::List sizes(bool ignoreBeingInserted = false) const;
QVector<int> calculateSqueezes(SizingInfo::List::ConstIterator begin, SizingInfo::List::ConstIterator end, int needed) const;
QRect suggestedDropRect(QSize minSize, const Item *relativeTo, Location) const;
void positionItems();
void positionItems(SizingInfo::List &sizes);
bool isResizing() const { return m_isResizing; }
void clear();
Item* itemForFrame(const QWidget *w) const; // TODO: Rename
int visibleCount_recursive() const override;
int count_recursive() const;
Item *itemAt(QPoint p) const;
Item *itemAt_recursive(QPoint p) const;
Item::List items_recursive() const;
void setHostWidget(QWidget *) override;
void setIsVisible(bool) override;
bool isVisible() const override;
void setLength_recursive(int length, Qt::Orientation) override;
void applyGeometries(const SizingInfo::List &sizes);
void applyPositions(const SizingInfo::List &sizes);
Qt::Orientation orientation() const;
bool isVertical() const;
bool isHorizontal() const;
int indexOf(Anchor *) const;
int minPosForSeparator(Anchor *) const;
int maxPosForSeparator(Anchor *) const;
int minPosForSeparator_global(Anchor *) const;
int maxPosForSeparator_global(Anchor *) const;
void deleteSeparators_recursive();
void updateSeparators_recursive();
QVariantMap toVariantMap() const override;
void fillFromVariantMap(const QVariantMap &map, const QHash<QString, GuestInterface *> &widgets) override;
Q_SIGNALS:
void itemsChanged();
void numVisibleItemsChanged(int);
void numItemsChanged();
public:
Item::List m_children;
bool m_isResizing = false;
bool m_blockUpdatePercentages = false;
QVector<Layouting::Anchor*> separators_recursive() const;
Qt::Orientation m_orientation = Qt::Vertical;
private:
void updateWidgets_recursive();
/// Returns the positions that each separator should have (x position if Qt::Horizontal, y otherwise)
QVector<int> requiredSeparatorPositions() const;
void updateSeparators();
void deleteSeparators();
Anchor* separatorAt(int p) const;
QVector<double> childPercentages() const;
mutable bool m_checkSanityScheduled = false;
QVector<Layouting::Anchor*> m_separators;
bool m_convertingItemToContainer = false;
};
/**
* Returns the widget's min size
*/
inline QSize widgetMinSize(const QWidget *w)
{
const int minW = w->minimumWidth() > 0 ? w->minimumWidth()
: w->minimumSizeHint().width();
const int minH = w->minimumHeight() > 0 ? w->minimumHeight()
: w->minimumSizeHint().height();
return QSize(minW, minH).expandedTo(Item::hardcodedMinimumSize());
} }
#endif inline int widgetMinLength(const QWidget *w, Qt::Orientation o) {
return length(widgetMinSize(w), o);
}
}

View File

@@ -0,0 +1,23 @@
/*
This file is part of KDDockWidgets.
Copyright (C) 2018-2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Author: Sérgio Martins <sergio.martins@kdab.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Logging_p.h"
Q_LOGGING_CATEGORY(separators, "kdab.docks.separators", QtWarningMsg)

View File

@@ -0,0 +1,29 @@
/*
This file is part of KDDockWidgets.
Copyright (C) 2018-2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Author: Sérgio Martins <sergio.martins@kdab.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KD_DOCKWIDGETS_MULTISPLITTER_LOGGING_P_H
#define KD_DOCKWIDGETS_MULTISPLITTER_LOGGING_P_H
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(separators)
#endif

View File

@@ -29,7 +29,7 @@
#include "MultiSplitter_p.h" #include "MultiSplitter_p.h"
#include "MultiSplitterLayout_p.h" #include "MultiSplitterLayout_p.h"
#include "Logging_p.h" #include "../Logging_p.h" // TODO
#include "MainWindowBase.h" #include "MainWindowBase.h"
#include "FloatingWindow_p.h" #include "FloatingWindow_p.h"
#include "LayoutSaver.h" #include "LayoutSaver.h"

File diff suppressed because it is too large Load Diff

View File

@@ -34,7 +34,6 @@
#include "../Frame_p.h" #include "../Frame_p.h"
#include "Anchor_p.h" #include "Anchor_p.h"
#include "AnchorGroup_p.h"
#include "docks_export.h" #include "docks_export.h"
#include "KDDockWidgets.h" #include "KDDockWidgets.h"
#include "Item_p.h" #include "Item_p.h"
@@ -45,59 +44,11 @@
namespace KDDockWidgets { namespace KDDockWidgets {
class MultiSplitter; class MultiSplitter;
class Length;
namespace Debug { namespace Debug {
class DebugWindow; class DebugWindow;
} }
/**
* Returns the width of the widget if orientation is Vertical, the height otherwise.
*/
template <typename T>
inline int widgetLength(const T *w, Qt::Orientation orientation)
{
return (orientation == Qt::Vertical) ? w->width() : w->height();
}
inline int lengthFromSize(QSize sz, Qt::Orientation orientation)
{
return orientation == Qt::Vertical ? sz.width()
: sz.height();
}
inline Anchor::Side sideForLocation(Location loc)
{
switch (loc) {
case KDDockWidgets::Location_OnLeft:
case KDDockWidgets::Location_OnTop:
return Anchor::Side1;
case KDDockWidgets::Location_OnRight:
case KDDockWidgets::Location_OnBottom:
return Anchor::Side2;
default:
break;
}
return Anchor::Side_None;
}
inline Qt::Orientation orientationForLocation(Location loc)
{
switch (loc) {
case KDDockWidgets::Location_OnLeft:
case KDDockWidgets::Location_OnRight:
return Qt::Vertical;
case KDDockWidgets::Location_OnTop:
case KDDockWidgets::Location_OnBottom:
return Qt::Horizontal;
default:
break;
}
return Qt::Vertical;
}
/** /**
* A MultiSplitter is like a QSplitter but supports mixing vertical and horizontal splitters in * A MultiSplitter is like a QSplitter but supports mixing vertical and horizontal splitters in
* any combination. * any combination.
@@ -124,11 +75,6 @@ public:
explicit MultiSplitterLayout(MultiSplitter *parent); explicit MultiSplitterLayout(MultiSplitter *parent);
~MultiSplitterLayout() override; ~MultiSplitterLayout() override;
/**
* @brief No widget can have a minimum size smaller than this, regardless of their minimum size.s
*/
static QSize hardcodedMinimumSize();
/** /**
* @brief returns the widget that this layout manages * @brief returns the widget that this layout manages
*/ */
@@ -152,17 +98,17 @@ public:
/** /**
* @brief Adds the dockwidget but it stays hidden until an explicit show() * @brief Adds the dockwidget but it stays hidden until an explicit show()
*/ */
void addAsPlaceholder(DockWidgetBase *dw, KDDockWidgets::Location location, Item *relativeTo = nullptr); void addAsPlaceholder(DockWidgetBase *dw, KDDockWidgets::Location location, Layouting::Item *relativeTo = nullptr);
/** /**
* @brief Removes an item from this MultiSplitter. * @brief Removes an item from this MultiSplitter.
*/ */
void removeItem(Item *item); void removeItem(Layouting::Item *item);
/** /**
* @brief Returns true if this layout contains the specified item. * @brief Returns true if this layout contains the specified item.
*/ */
bool contains(const Item *) const; bool contains(const Layouting::Item *) const;
/** /**
* @brief Returns true if this layout contains the specified frame. * @brief Returns true if this layout contains the specified frame.
@@ -172,20 +118,20 @@ public:
/** /**
* @brief Returns the visible Item at pos @p p. * @brief Returns the visible Item at pos @p p.
*/ */
Item *itemAt(QPoint p) const; Layouting::Item *itemAt(QPoint p) const;
/** /**
* @brief Removes all Items, Anchors and Frames docked in this layout. * @brief Removes all Items, Anchors and Frames docked in this layout.
* DockWidgets are closed but not deleted. * DockWidgets are closed but not deleted.
*/ */
void clear(bool alsoDeleteStaticAnchors = false); void clear();
/** /**
* @brief Returns the number of Item objects in this layout. * @brief Returns the number of Item objects in this layout.
* This includes non-visible (placeholder) Items too. * This includes non-visible (placeholder) Items too.
* @sa visibleCount * @sa visibleCount
*/ */
int count() const { return m_items.size(); } int count() const { return m_rootItem->count_recursive(); }
/** /**
* @brief Returns the number of visible Items in this layout. * @brief Returns the number of visible Items in this layout.
@@ -201,11 +147,6 @@ public:
*/ */
int placeholderCount() const; int placeholderCount() const;
/**
* @brief Returns true if count is 0.
*/
bool isEmpty() const { return m_items.isEmpty(); }
/** /**
* @brief Returns whether there's non placeholder items. * @brief Returns whether there's non placeholder items.
*/ */
@@ -219,7 +160,9 @@ public:
/** /**
* @brief The list of items in this layout. * @brief The list of items in this layout.
*/ */
const ItemList items() const; const Layouting::Item::List items() const;
Layouting::ItemContainer *rootItem() const;
/** /**
* Called by the indicators, so they draw the drop rubber band at the correct place. * Called by the indicators, so they draw the drop rubber band at the correct place.
@@ -227,38 +170,17 @@ public:
* Excludes the Anchor thickness, result is actually smaller than what needed. In other words, * Excludes the Anchor 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. * the result will be exactly the same as the geometry the widget will get.
*/ */
QRect rectForDrop(const QWidgetOrQuick *widget, KDDockWidgets::Location location, const Item *relativeTo) const; QRect rectForDrop(const QWidgetOrQuick *widget, KDDockWidgets::Location location, const Layouting::Item *relativeTo) const;
bool deserialize(const LayoutSaver::MultiSplitterLayout &); bool deserialize(const LayoutSaver::MultiSplitterLayout &);
LayoutSaver::MultiSplitterLayout serialize() const; LayoutSaver::MultiSplitterLayout serialize() const;
void setAnchorBeingDragged(Anchor *); void setAnchorBeingDragged(Layouting::Anchor *);
Anchor *anchorBeingDragged() const { return m_anchorBeingDragged; } Layouting::Anchor *anchorBeingDragged() const { return m_anchorBeingDragged; }
bool anchorIsBeingDragged() const { return m_anchorBeingDragged != nullptr; } bool anchorIsBeingDragged() const { return m_anchorBeingDragged != nullptr; }
///@brief returns list of separators ///@brief returns list of separators
const Anchor::List anchors() const { return m_anchors; } Layouting::Anchor::List anchors() const;
/**
* @brief Returns the list of anchors that are following @p followee
*/
Anchor::List anchorsFollowing(Anchor *followee) const;
///@brief returns the number of anchors that are following others, just for tests.
int numAchorsFollowing() const;
///@brief returns the number of anchors that are following others, just for tests.
int numVisibleAnchors() const;
///@brief returns either the left, top, right or bottom separator, depending on the @p type
Anchor *staticAnchor(Anchor::Type type) const;
Anchor *staticAnchor(Anchor::Side side, Qt::Orientation orientation) const;
///@brief a function that all code paths adding Items will call.
///It's mostly for code reuse, so we don't duplicate what's done here. But it's also nice to
///have a central place that we know will be called
void addItems_internal(const ItemList &, bool updateConstraints = true, bool emitSignal = true);
/** /**
* @brief Updates the min size of this layout. * @brief Updates the min size of this layout.
@@ -288,45 +210,30 @@ public:
* @brief returns the contents width. * @brief returns the contents width.
* Usually it's the same width as the respective parent MultiSplitter. * Usually it's the same width as the respective parent MultiSplitter.
*/ */
int width() const { return m_size.width(); } int width() const { return size().width(); }
/** /**
* @brief returns the contents height. * @brief returns the contents height.
* Usually it's the same height as the respective parent MultiSplitter. * Usually it's the same height as the respective parent MultiSplitter.
*/ */
int height() const { return m_size.height(); } int height() const { return size().height(); }
/** /**
* @brief returns the layout's minimum size * @brief returns the layout's minimum size
* @ref setMinimumSize * @ref setMinimumSize
*/ */
QSize minimumSize() const { return m_minSize; } QSize minimumSize() const;
/** /**
* @brief getter for the size * @brief getter for the size
*/ */
QSize size() const { return m_size; } QSize size() const { return m_rootItem->size(); }
// For debug/hardening // For debug/hardening
bool validateInputs(QWidgetOrQuick *widget, KDDockWidgets::Location location, const Frame *relativeToFrame, AddingOption option) const; bool validateInputs(QWidgetOrQuick *widget, KDDockWidgets::Location location, const Frame *relativeToFrame, AddingOption option) const;
// For debug/hardening // For debug/hardening
enum AnchorSanityOption { bool checkSanity() const;
AnchorSanity_Normal = 0,
AnchorSanity_Intersections = 1,
AnchorSanity_WidgetMinSizes = 2,
AnchorSanity_WidgetInvalidSizes = 4,
AnchorSanity_Followers = 8,
AnchorSanity_WidgetGeometry = 16,
AnchorSanity_Visibility = 32,
AnchorSanity_All = AnchorSanity_Intersections | AnchorSanity_WidgetMinSizes | AnchorSanity_WidgetInvalidSizes | AnchorSanity_Followers | AnchorSanity_WidgetGeometry | AnchorSanity_Visibility
};
Q_ENUM(AnchorSanityOption)
bool checkSanity(AnchorSanityOption o = AnchorSanity_All) const;
void maybeCheckSanity();
void restorePlaceholder(Item *item);
/** /**
* @brief Removes unneeded placeholder items when adding new frames. * @brief Removes unneeded placeholder items when adding new frames.
@@ -343,7 +250,7 @@ public:
/** /**
* @brief returns the Item that holds @p frame in this layout * @brief returns the Item that holds @p frame in this layout
*/ */
Item *itemForFrame(const Frame *frame) const; Layouting::Item *itemForFrame(const Frame *frame) const;
/** /**
* @brief returns the frames contained in @p frameOrMultiSplitter * @brief returns the frames contained in @p frameOrMultiSplitter
@@ -362,58 +269,21 @@ public:
*/ */
QVector<DockWidgetBase*> dockWidgets() const; QVector<DockWidgetBase*> dockWidgets() const;
/** void restorePlaceholder(DockWidgetBase *dw, Layouting::Item *, int tabIndex);
* @brief Creates an AnchorGroup suited for adding a dockwidget to @location relative to @relativeToItem
*
* Returns the AnchorGroup and a new Anchor, if it was needed.
* If relativeTo is null then it returns the static anchor group.
*/
QPair<AnchorGroup, Anchor *> createTargetAnchorGroup(Location location, Item *relativeToItem);
struct Length {
Length() = default;
Length(int side1, int side2)
: side1Length(side1)
, side2Length(side2)
{}
int side1Length = 0;
int side2Length = 0;
int length() const { return side1Length + side2Length; }
void setLength(int newLength)
{
// Sets the new length, preserving proportion
side1Length = int(side1Factor() * newLength);
side2Length = newLength - side1Length;
}
bool isNull() const
{
return length() <= 0;
}
private:
qreal side1Factor() const
{
return (1.0 * side1Length) / length();
}
};
Q_SIGNALS: Q_SIGNALS:
///@brief emitted when the number of widgets changes ///@brief emitted when the number of widgets changes
///@param count the new widget count void widgetCountChanged();
void widgetCountChanged(int count);
void visibleWidgetCountChanged(int count); void visibleWidgetCountChanged(int count);
///@brief emitted when a widget is added ///@brief emitted when a widget is added
///@param item the item containing the new widget ///@param item the item containing the new widget
void widgetAdded(KDDockWidgets::Item *item); void widgetAdded(Layouting::Item *item);
///@brief emitted when a widget is removed ///@brief emitted when a widget is removed
///@param item the item containing the removed widget ///@param item the item containing the removed widget
void widgetRemoved(KDDockWidgets::Item *item); void widgetRemoved(Layouting::Item *item);
///@brief emitted right before dumping debug ///@brief emitted right before dumping debug
///@sa dumpDebug ///@sa dumpDebug
@@ -428,35 +298,14 @@ Q_SIGNALS:
void minimumSizeChanged(QSize); void minimumSizeChanged(QSize);
public: public:
bool eventFilter(QObject *o, QEvent *e) override; Layouting::Anchor::List anchors(Qt::Orientation, bool includeStatic = false, bool includePlaceholders = true) const;
AnchorGroup anchorsForPos(QPoint pos) const;
AnchorGroup staticAnchorGroup() const;
Anchor::List anchors(Qt::Orientation, bool includeStatic = false, bool includePlaceholders = true) const;
Anchor *newAnchor(AnchorGroup &group, KDDockWidgets::Location location);
friend QDebug operator<<(QDebug d, const AnchorGroup &group);
static const QString s_magicMarker;
void ensureAnchorsBounded();
private: private:
friend struct AnchorGroup;
friend class Item;
friend class Anchor;
friend class TestDocks; friend class TestDocks;
friend class KDDockWidgets::Debug::DebugWindow; friend class KDDockWidgets::Debug::DebugWindow;
friend class LayoutSaver; friend class LayoutSaver;
struct AnchorBounds {
Anchor *side1;
Anchor *side2;
};
std::pair<int,int> boundInterval(int newPos1, Anchor* anchor1, int newPos2, Anchor *anchor2) const;
void blockItemPropagateGeo(bool block); void blockItemPropagateGeo(bool block);
/**
* @brief overload called by the first one. Split-out so it's easier to unit-test the math
*/
QRect rectForDrop(Length lengthForDrop, Location location, QRect relativeToRect) const;
/** /**
* @brief setter for the minimum size * @brief setter for the minimum size
* @ref minimumSize * @ref minimumSize
@@ -465,57 +314,10 @@ private:
void emitVisibleWidgetCountChanged(); void emitVisibleWidgetCountChanged();
/**
* @brief Returns the size that the widget will get when dropped at this specific location.
*
* When location is Left or Right then the length represents a width, otherwise an height.
* This function is also called to know the size of the rubberband when hovering over a location.
*/
MultiSplitterLayout::Length lengthForDrop(const QWidgetOrQuick *widget, KDDockWidgets::Location location,
const Item *relativeTo) const;
/**
* @brief Ensures that this layout's size is enough for dropping @p widget to @p location,
* relative to @p relativeToItem.
*
* It may increase size or do notying, never decrease.
*/
void ensureEnoughSize(const QWidgetOrQuick *widget, KDDockWidgets::Location location,
const Item *relativeToItem);
void insertAnchor(Anchor *);
void removeAnchor(Anchor *);
/**
* Returns the min or max position that an anchor can go to (due to minimum size restriction on the widgets).
* For example, if the anchor is vertical and direction is Side1 then it returns the minimum x
* that the anchor can have. If direction is Side2 then it returns the maximum width. If horizontal
* then the height.
*/
int boundPositionForAnchor(Anchor *, Anchor::Side direction) const;
/**
* Similar to boundPositionForAnchor, but returns both the min and the max width (or height)
*/
QPair<int, int> boundPositionsForAnchor(Anchor *) const;
/**
* @brief similar to @ref boundPositionsForAnchor but returns for all anchors
*/
QHash<Anchor*, QPair<int,int>> boundPositionsForAllAnchors() const;
/** Returns how much is available for the new drop. It already counts with the space for new anchor that will be created.
* So it returns this layout's width() (or height), minus the minimum-sizes of all widgets, minus the thickness of all anchors
* minus the thickness of the anchor that would be created.
**/
Length availableLengthForDrop(KDDockWidgets::Location location, const Item *relativeTo) const;
/** /**
* @brief Like @ref availableLengthForDrop but just returns the total available width or height (depending on @p orientation) * @brief Like @ref availableLengthForDrop but just returns the total available width or height (depending on @p orientation)
* So no need to receive any location. * So no need to receive any location.
* @param orientation If Qt::Vertical then returns the available width. Height otherwise. * @param orientation If Qt::Vertical then returns the available height. Width otherwise.
*/ */
int availableLengthForOrientation(Qt::Orientation orientation) const; int availableLengthForOrientation(Qt::Orientation orientation) const;
@@ -525,126 +327,24 @@ private:
*/ */
QSize availableSize() const; QSize availableSize() const;
/**
* @brief Increases the layout size if @ref availableSize is less than @needed
*/
void ensureHasAvailableSize(QSize needed);
/**
* Removes the widgets associated with oldAnchor and gives them to newAnchor.
* Called when removing a widget results in unneeded anchors.
*/
void updateAnchorsFromTo(Anchor *oldAnchor, Anchor *newAnchor);
void clearAnchorsFollowing();
void updateAnchorFollowing(const AnchorGroup &groupBeingRemoved = {});
QHash<Anchor *, Anchor *> anchorsShouldFollow() const;
/**
* Positions the static anchors at their correct places. Called when the MultiSplitter is resized.
* left and top anchor are at position 0, while right/bottom are at position= width/height.
* (Approx, due to styling margins and whatnot)
*/
void positionStaticAnchors();
/**
* When this MultiSplitter is resized, it gives or steals the less/extra space evenly through
* all widgets.
**/
void redistributeSpace();
void redistributeSpace(QSize oldSize, QSize newSize);
void redistributeSpace_recursive(Anchor *fromAnchor, int minAnchorPos);
/**
* Returns the width (if orientation = Horizontal), or height that is occupied by anchors.
* For example, an horizontal anchor has 2 or 3 px of width, so that's space that can't be
* occupied by child widgets.
*/
int wastedSpacing(Qt::Orientation) const;
/**
* Called by addWidget().
*
* When adding a widget to a layout, it will steal space from the widgets on the left (or top) (@p direction being Anchor::Side1),
* and from the widgets on the right (or bottom) (@p direction being Anchor::Side2).
*
* @param delta the amount of space we're stealing in the specified side
* @param fromAnchor The anchor we're starting from
* @param direction if we're going left/top (Side1) or right/bottom (Side2)
*/
void propagateResize(int delta, Anchor *fromAnchor, Anchor::Side direction);
// Helper function for propagateResize()
void collectPaths(QVector<Anchor::List> &paths, Anchor *fromAnchor, Anchor::Side direction);
// convenience for the unit-tests
// Moves the widget's bottom or right anchor, to resize it.
void resizeItem(Frame *frame, int newSize, Qt::Orientation);
void ensureItemsMinSize();
///@brief returns whether we're inside setSize(); ///@brief returns whether we're inside setSize();
bool isResizing() const { return m_resizing; } bool isResizing() const { return m_resizing; }
bool isRestoringPlaceholder() const { return m_restoringPlaceholder; } bool isRestoringPlaceholder() const { return m_restoringPlaceholder; }
bool isAddingItem() const { return m_addingItem; }
QString affinityName() const; QString affinityName() const;
MultiSplitter *const m_multiSplitter; MultiSplitter *const m_multiSplitter;
Anchor::List m_anchors;
Anchor *m_leftAnchor = nullptr;
Anchor *m_topAnchor = nullptr;
Anchor *m_rightAnchor = nullptr;
Anchor *m_bottomAnchor = nullptr;
ItemList m_items;
bool m_inCtor = true; bool m_inCtor = true;
bool m_inDestructor = false; bool m_inDestructor = false;
bool m_beingMergedIntoAnotherMultiSplitter = false; bool m_beingMergedIntoAnotherMultiSplitter = false; // TODO
bool m_restoringPlaceholder = false; bool m_restoringPlaceholder = false;
bool m_resizing = false; bool m_resizing = false;
bool m_addingItem = false;
QSize m_minSize = QSize(0, 0); QPointer<Layouting::Anchor> m_anchorBeingDragged;
AnchorGroup m_staticAnchorGroup; Layouting::ItemContainer *m_rootItem = nullptr;
QPointer<Anchor> m_anchorBeingDragged;
QSize m_size;
}; };
inline QDebug operator<<(QDebug d, const AnchorGroup &group) {
d << "AnchorGroup: top=" << group.top << "; left=" << group.left
<< "; right=" << group.right << "; bottom=" << group.bottom;
return d;
} }
/**
* Returns the widget's min-width if orientation is Vertical, the min-height otherwise.
*/
inline int widgetMinLength(const QWidgetOrQuick *w, Qt::Orientation orientation)
{
int min = 0;
if (orientation == Qt::Vertical) {
if (w->minimumWidth() > 0)
min = w->minimumWidth();
else
min = w->minimumSizeHint().width();
min = qMax(MultiSplitterLayout::hardcodedMinimumSize().width(), min);
} else {
if (w->minimumHeight() > 0)
min = w->minimumHeight();
else
min = w->minimumSizeHint().height();
min = qMax(MultiSplitterLayout::hardcodedMinimumSize().height(), min);
}
return qMax(min, 0);
}
}
Q_DECLARE_METATYPE(KDDockWidgets::MultiSplitterLayout::Length)
#endif #endif

View File

@@ -19,24 +19,20 @@
*/ */
#include "Separator_p.h" #include "Separator_p.h"
#include "multisplitter/MultiSplitterLayout_p.h" #include "Anchor_p.h"
#include "multisplitter/Anchor_p.h"
#include "Logging_p.h" #include "Logging_p.h"
#include "Item_p.h"
using namespace KDDockWidgets; #include <QMouseEvent>
Separator::Separator(KDDockWidgets::Anchor *anchor, QWidgetAdapter *parent) using namespace Layouting;
: QWidgetAdapter(parent)
Separator::Separator(Layouting::Anchor *anchor, QWidget *hostWidget)
: QWidget(hostWidget)
, m_anchor(anchor) , m_anchor(anchor)
{ {
Q_ASSERT(anchor); Q_ASSERT(anchor);
setVisible(true); setVisible(true);
const int thickness = Anchor::thickness(isStatic());
if (isVertical())
setFixedWidth(thickness);
else
setFixedHeight(thickness);
} }
bool Separator::isVertical() const bool Separator::isVertical() const
@@ -44,39 +40,26 @@ bool Separator::isVertical() const
return m_anchor->isVertical(); return m_anchor->isVertical();
} }
bool Separator::isStatic() const
{
return m_anchor->isStatic();
}
int Separator::position() const
{
return isVertical() ? x() : y();
}
void Separator::onMousePress()
{
Q_ASSERT(!m_anchor->isFollowing());
m_anchor->onMousePress();
}
void Separator::onMouseMove(QPoint globalPos)
{
Q_ASSERT(!m_anchor->isFollowing());
m_anchor->onMouseMoved(parentWidget()->mapFromGlobal(globalPos));
}
void Separator::onMouseRelease()
{
Q_ASSERT(!m_anchor->isFollowing());
m_anchor->onMouseReleased();
}
void Separator::move(int p) void Separator::move(int p)
{ {
if (isVertical()) { if (isVertical()) {
QWidgetAdapter::move(p, y()); QWidget::move(x(), p);
} else { } else {
QWidgetAdapter::move(x(), p); QWidget::move(p, y());
} }
} }
void Separator::mousePressEvent(QMouseEvent *)
{
m_anchor->onMousePress();
}
void Separator::mouseMoveEvent(QMouseEvent *ev)
{
m_anchor->onMouseMoved(parentWidget()->mapFromGlobal(ev->globalPos()));
}
void Separator::mouseReleaseEvent(QMouseEvent *)
{
m_anchor->onMouseReleased();
}

View File

@@ -21,35 +21,31 @@
#ifndef KD_MULTISPLITTER_SEPARATOR_P_H #ifndef KD_MULTISPLITTER_SEPARATOR_P_H
#define KD_MULTISPLITTER_SEPARATOR_P_H #define KD_MULTISPLITTER_SEPARATOR_P_H
#include "docks_export.h" //#include "docks_export.h" TODO
#include "QWidgetAdapter.h"
#include <QWidget>
#include <QPointer> #include <QPointer>
namespace KDDockWidgets { namespace Layouting {
class Anchor; class Anchor;
class DOCKS_EXPORT Separator : public QWidgetAdapter class /*DOCKS_EXPORT*/ Separator : public QWidget
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(bool isVertical READ isVertical CONSTANT) Q_PROPERTY(bool isVertical READ isVertical CONSTANT)
Q_PROPERTY(bool isStatic READ isStatic CONSTANT)
//Q_PROPERTY(int position READ position NOTIFY positionChanged)
public: public:
explicit Separator(Anchor *anchor, QWidgetAdapter *parent = nullptr); explicit Separator(Layouting::Anchor *anchor, QWidget *hostWidget);
bool isVertical() const; bool isVertical() const;
bool isStatic() const;
int position() const;
void move(int p); void move(int p);
const QPointer<Anchor> anchor() const { return m_anchor; }
protected: protected:
void onMousePress() override; const QPointer<Layouting::Anchor> anchor() const { return m_anchor; }
void onMouseMove(QPoint globalPos) override; void mousePressEvent(QMouseEvent *) override;
void onMouseRelease() override; void mouseMoveEvent(QMouseEvent *) override;
void mouseReleaseEvent(QMouseEvent *) override;
private: private:
const QPointer<Anchor> m_anchor; // QPointer so we don't dereference invalid point in paintEvent() when Anchor is deleted. const QPointer<Layouting::Anchor> m_anchor; // QPointer so we don't dereference invalid point in paintEvent() when Anchor is deleted.
}; };
} }

View File

@@ -0,0 +1,5 @@
find_package(Qt5Test)
add_executable(tst_multisplitter tst_multisplitter.cpp)
target_link_libraries(tst_multisplitter kddockwidgets_layouting Qt5::Test)

File diff suppressed because it is too large Load Diff

View File

@@ -19,7 +19,6 @@
*/ */
#include "SeparatorWidget_p.h" #include "SeparatorWidget_p.h"
#include "multisplitter/MultiSplitterLayout_p.h"
#include "multisplitter/Anchor_p.h" #include "multisplitter/Anchor_p.h"
#include "Logging_p.h" #include "Logging_p.h"
@@ -27,8 +26,9 @@
#include <QStyleOption> #include <QStyleOption>
using namespace KDDockWidgets; using namespace KDDockWidgets;
using namespace Layouting;
SeparatorWidget::SeparatorWidget(KDDockWidgets::Anchor *anchor, QWidgetAdapter *parent) SeparatorWidget::SeparatorWidget(Layouting::Anchor *anchor, QWidget *parent)
: Separator(anchor, parent) : Separator(anchor, parent)
{ {
setMouseTracking(true); setMouseTracking(true);
@@ -45,7 +45,7 @@ void SeparatorWidget::paintEvent(QPaintEvent *)
opt.palette = palette(); opt.palette = palette();
opt.rect = rect(); opt.rect = rect();
opt.state = QStyle::State_None; opt.state = QStyle::State_None;
if (isVertical()) if (!isVertical())
opt.state |= QStyle::State_Horizontal; opt.state |= QStyle::State_Horizontal;
if (isEnabled()) if (isEnabled())
@@ -56,16 +56,14 @@ void SeparatorWidget::paintEvent(QPaintEvent *)
void SeparatorWidget::enterEvent(QEvent *) void SeparatorWidget::enterEvent(QEvent *)
{ {
qCDebug(anchors) << Q_FUNC_INFO << anchor() << isEnabled() << this; qCDebug(anchors) << Q_FUNC_INFO << anchor() << this;
if (!anchor()) if (!anchor())
return; return;
if (!isStatic()) { if (isVertical())
if (isVertical()) setCursor(Qt::SizeVerCursor);
setCursor(Qt::SizeHorCursor); else
else setCursor(Qt::SizeHorCursor);
setCursor(Qt::SizeVerCursor);
}
} }
void SeparatorWidget::leaveEvent(QEvent *) void SeparatorWidget::leaveEvent(QEvent *)

View File

@@ -21,6 +21,7 @@
#ifndef KD_MULTISPLITTER_SEPARATORWIDGET_P_H #ifndef KD_MULTISPLITTER_SEPARATORWIDGET_P_H
#define KD_MULTISPLITTER_SEPARATORWIDGET_P_H #define KD_MULTISPLITTER_SEPARATORWIDGET_P_H
#include "docks_export.h"
#include "multisplitter/Separator_p.h" #include "multisplitter/Separator_p.h"
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
@@ -29,11 +30,11 @@ QT_END_NAMESPACE
namespace KDDockWidgets { namespace KDDockWidgets {
class DOCKS_EXPORT SeparatorWidget : public Separator class DOCKS_EXPORT SeparatorWidget : public Layouting::Separator
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit SeparatorWidget(Anchor *anchor, QWidgetAdapter *parent = nullptr); explicit SeparatorWidget(Layouting::Anchor *anchor, QWidget *parent = nullptr);
protected: protected:
void paintEvent(QPaintEvent *) override; void paintEvent(QPaintEvent *) override;

File diff suppressed because it is too large Load Diff

View File

@@ -97,7 +97,6 @@ DockWidgetBase *KDDockWidgets::Tests::createDockWidget(const QString &name, QWid
dock->activateWindow(); dock->activateWindow();
Q_ASSERT(dock->window()); Q_ASSERT(dock->window());
if (QTest::qWaitForWindowActive(dock->window()->windowHandle(), 200)) { if (QTest::qWaitForWindowActive(dock->window()->windowHandle(), 200)) {
qDebug() << dock->window();
return dock; return dock;
} }
return nullptr; return nullptr;