Compare commits

...

117 Commits

Author SHA1 Message Date
Sergio Martins
61079be42d Add example of advanced custom titlebar
Run with: ./bin/kddockwidgets_example -p

The titlebar knows which dock widgets it contains and which one
is the current.

In some cases there's no QTabBar bellow it, like when it's a FloatingWindow
titlebar and there's only 1 nested frame.
2022-03-12 13:11:44 +00:00
Sergio Martins
743dbc0718 Added dockWidgetInserted|Removed signals to TabBarWidget
Since QTabBar doesn't have them.
Useful for custom tab bars
2022-03-12 12:47:26 +00:00
Sergio Martins
fd4588de0f TitleBarWidget: Mark members as protected
So custom titlebars have more power
2022-03-12 12:31:53 +00:00
Sergio Martins
6ba10cfe12 Added TitleBar::tabBar()
Useful for people writing custom titlebar's with style that depends
on the current tab
2022-03-11 18:21:49 +00:00
Sergio Martins
096176dc72 Don't dereference potentially nullptr 2022-03-10 18:37:28 +00:00
Sergio Martins
25b04d7ed8 MDI: Fix another case of showing resize handles wrong
the logic for 'y' only makes sense if 'x' is bounded and vice-versa
2022-03-10 10:55:17 +00:00
Sergio Martins
e345e89c35 MDI: Fix case where resize cursor would be shown for frame bellow
Was already fixed a few days ago, but this is the case for nested
mdi.
2022-03-09 19:08:29 +00:00
Sergio Martins
a97663294c example: Allow to test propagation of close event in the mdi example 2022-03-09 17:18:11 +00:00
Sergio Martins
bb4cf802f5 examples: Remove more duplicate MyWidget.cpp/h files
share with main example instead
2022-03-09 16:42:15 +00:00
Sergio Martins
701069617c example: Allow to test propagation of close event in the mdi example 2022-03-09 16:30:26 +00:00
Sergio Martins
a8c50f1876 example: Remove duplicate minimal-mdi/MyWidget.cpp
It was using the cpp from the main example but the header from
minimal-mdi. Instead, share both the impl and the header
2022-03-09 16:27:25 +00:00
Sergio Martins
61cca1e5ec Forward QCloseEvent to the MDI widgets too
Fixes case where docked MDI widgets were not able to block a close
2022-03-09 16:11:55 +00:00
Sergio Martins
7db9938b85 Fix MainWindow not propagating close events to docked widgets
Nested FloatingWindows already supported it, so make it consistent.

Personal take: In a non-docking world, users can override their
main window close event and prevent a close, to save a document
or such. However, in a docking world, the main window developer
won't know which widgets are docked, so forwarding needs to happen,
as some might have documents to save.
2022-03-09 15:47:02 +00:00
Sergio Martins
01cc915734 Further move onCloseEvent to base class
So that it can be reused by MDI layouts too
2022-03-09 14:58:35 +00:00
Sergio Martins
3454b67a45 Refactor: Move close event logic from FloatingWindow to DropArea
So main window can use it too
2022-03-09 14:39:20 +00:00
Sergio Martins
21765efbac example: Added --blocks-close-event
Makes dock widget #0 reject the close event, meaning it won't close.
2022-03-09 12:46:20 +00:00
Allen Winter
60a1e46453 CMakeLists.txt - fix setting KDDockWidgets_DEPS
broken with last commit
2022-03-08 12:06:53 -05:00
Allen Winter
8b8ef7f2b0 CMakeLists.txt - find COMPONENTS to search for Qt modules 2022-03-08 12:01:13 -05:00
Sergio Martins
c0e8fe3869 Update ChangeLog regarding #44 and #96 being fixed 2022-03-07 16:38:40 +00:00
Sergio Martins
524dff9105 Don't try to restore to previous position if there isn't any 2022-03-07 16:36:38 +00:00
Sergio Martins
0099a19a82 Make redocking floating windows with tabs possible
TitleBar::isFloating() was returning false, while it should
have returned true.

Fixed by making isFloating() simpler and dumb. isFloating() should
only say if it belongs to a floating window or not. It shouldn't
contain any logic about whether button should be visible or not.

There's already logic elsewhere that will hide the float button
in case there's nesting.

Fixes issue #96 and #44
2022-03-07 15:56:15 +00:00
Sergio Martins
e6b8636e88 MDI: Fix resize cursor appearing for frames that had others on top 2022-03-04 11:34:35 +00:00
Sergio Martins
86419fd979 MDI: Fixed closing dock widget in MDI would close main window
We only close the window when the window is a FloatingWindow and
it's the last frame
2022-03-03 17:52:13 +00:00
Eism
220471f746 Corrected the updating of normal geometry for window 2022-02-14 15:34:01 +00:00
Sergio Martins
412860abac tests: Use QT_NO_KEYWORDS too 2022-02-12 14:43:37 +00:00
Sergio Martins
2eeb4aac27 Fix Item_p.h being exposed in public API
Forward declare Item instead
2022-02-12 14:37:19 +00:00
Sergio Martins
bfb2ec701e cmake: Add a dedicated ASAN preset
It's not doing well on Windows, so make the default be a non-asan
build
2022-02-11 22:55:54 +00:00
Sergio Martins
54bf24d5d4 Added Config::setDropIndicatorAllowedFunc() 2022-02-11 19:49:23 +00:00
Sergio Martins
76cbb760ed Minor refactoring before introducing DropIndicatorAllowedFunc
Deals with all the false case first
2022-02-11 19:07:28 +00:00
Sergio Martins
4824a398ab Move DropIndicatorOverlayInterface::DropLocation enum to namespace scope
It's public now
2022-02-11 18:14:26 +00:00
Sergio Martins
a502a8250b Fix possible nullptr dereference 2022-02-11 17:42:09 +00:00
Sergio Martins
85fb4ff671 indicators: Prepare the visibility to be more granular
Currently either all or none inner indicators can be hidden/shown,
and same of the outter.

We'll soon allow some of them to be hidden, selectively, by the
client app.
2022-02-11 17:37:26 +00:00
Sergio Martins
6db3ccc87f .gitignore: Add a few clangd files 2022-02-11 17:10:22 +00:00
Sergio Martins
5811cab164 segmented indicators: Fix potential bugs due to decoupled drop types
The returned QVector was indexed by the enum values, but the enum
is flag based now, no longer sequential. Instead of depending
on ordering of the enum, let's instead return a QHash where the
type is coupled with the corresponding QPolygon already
2022-02-11 17:08:30 +00:00
Sergio Martins
1387c2f573 segmented indicators: Use the base class logic
Instead of repeating it
2022-02-11 16:58:12 +00:00
Sergio Martins
79cc347cd8 Refactor: Move indicator visibility logic into base class 2022-02-11 16:42:07 +00:00
Sergio Martins
e62bde3152 Improve documentation for MainWindowOption_HasCentralWidget
Fixes issue #272
2022-02-11 14:36:19 +00:00
Sergio Martins
81abb3cea5 Fix build on OpenBSD
Fixes #265
2022-02-11 14:31:00 +00:00
Sergio Martins
ecd3c20adf Fix build with Qt5+C++20
Error was:
qvector.h:532:18: error: use of overloaded operator '!=' is ambiguous (with operand types 'int *' and 'QTypedArrayData<int>::iterator')
        while (i != d->begin())
2022-02-10 11:01:14 +00:00
Sergio Martins
755d53432b Fix "drag to detach" MDI windows when in nested MDI mode
We were deleting the draggable, causing the drag to stop
2022-02-04 17:54:43 +00:00
Sergio Martins
e00a552bf8 Add DragController::currentStateChanged() signal 2022-02-04 17:52:54 +00:00
Sergio Martins
faf93fe597 Add DragController::isIdle() 2022-02-04 17:48:42 +00:00
Sergio Martins
4f8aac7df3 Fix floating windows not restoring to previous position
The dock widget wrapper that we deleted had that info. We need
to preserve it.
2022-02-04 16:57:44 +00:00
Sergio Martins
719803ecfa Fix a test with offscreen QPA
We were pressing on pos 6,6 to start a drag but that triggered
a window resize instead.
2022-02-04 16:10:13 +00:00
Sergio Martins
f0ef24383b Debug++ 2022-02-04 16:10:13 +00:00
Sergio Martins
4f8b174a8d Fixed Frame::isFloating() for the nested MDI case 2022-02-04 15:17:11 +00:00
Sergio Martins
675b166956 Added the last crash fix to the ChangeLog 2022-02-04 15:11:17 +00:00
Sergio Martins
b13ba1e42e Fix crash due to use of native widgets on Windows
Qt has corner cases when all of its widgets are native widgets.
This particular crash was a loop between QWidget::create()
and QWidget::createWinId().
2022-02-02 13:21:14 +00:00
Sergio Martins
481dae64c3 nested_mdi: Fix floating windows not going back to their previous location
When floating a mdi window that was nested in a drop area, clicking the
float button should put it back to the MDI area.
2022-01-30 02:55:02 +00:00
Sergio Martins
6b04e20a7e Add some doxygen 2022-01-30 02:40:53 +00:00
Sergio Martins
8a3ee35993 Fix firstParentOfType() going through different windows 2022-01-30 02:15:58 +00:00
Sergio Martins
146c656e29 Minor: Use OOP, add ItemRef::isInMainWindow() 2022-01-29 21:43:41 +00:00
Sergio Martins
a190e2dfbf Fix -Wshadow warning 2022-01-29 20:57:19 +00:00
Sergio Martins
d0daff6771 vscode: Add an entry to run the mdi with dock widgets example 2022-01-29 20:15:29 +00:00
Sergio Martins
721795b113 Remove LastPosition struct, move its members to Position
It was an unneeded indirection that didn't provide added semantics
2022-01-29 19:29:03 +00:00
Sergio Martins
40231b7fae Minor coding style 2022-01-29 19:02:17 +00:00
Sergio Martins
1d0300ecc8 Fix another unused variable 2022-01-26 00:14:01 +00:00
Sergio Martins
ce20628555 Remove unused variable - fixes Werror build 2022-01-25 23:23:54 +00:00
Sergio Martins
d0dcac6b03 Make DockWidgetBase::setMDIPosition|setMDISize support nesting
They now honour Option_MDINestable.
Before they would bail out assuming they weren't in a MDI area.
Now they look further up the hierarchy to find our MDIArea, if any.
2022-01-25 20:14:04 +00:00
Sergio Martins
2b1aa44eff Fix crash when floating nested mdi 2022-01-24 19:18:55 +00:00
Sergio Martins
ae42dffcb1 Update Changelog regarding new nested MDI feature 2022-01-24 18:48:00 +00:00
Sergio Martins
21adfe06ad Add an example for nested docking within MDI 2022-01-24 18:31:10 +00:00
Sergio Martins
7de26139a2 Restore source-compat for TabWidgetWidget() 2022-01-24 15:47:06 +00:00
Sergio Martins
894ff9fea0 Minor: Fix overridden signature 2022-01-24 15:34:37 +00:00
Sergio Martins
942c462586 Fix release build 2022-01-24 15:27:24 +00:00
Sergio Martins
128645693c nested_mdi: Fix DnD over dock widgets docked in MDI
Dragging was always detecting the main window's drop indicator
overlay. Make it transparent for mouse events, so that
QWidget::childAt() doesn't pick it, and we can see the inner
drop areas.
2022-01-23 23:31:50 +00:00
Sergio Martins
86bceb4c48 nested mdi: Fix detaching inner dock widgets
Only the outter-most MDI frame is dragged in MDI mode. The inner ones
are dragged in normal docking mode, they become real floating windows.
2022-01-23 21:46:37 +00:00
Sergio Martins
ea64aae861 tests++ 2022-01-23 21:46:37 +00:00
Sergio Martins
d1645dff73 tests: Remove unneeded sleep 2022-01-23 21:46:37 +00:00
Sergio Martins
a72e018f3b nested mdi: Also test floating a nested MDI 2022-01-23 21:46:37 +00:00
Sergio Martins
66b0ba8902 Added MDIArea::frames() 2022-01-23 21:46:37 +00:00
Sergio Martins
18457d80aa nested mdi: Get rid of unneeded drop area mdi wrapper when floating
When floating, the FloatingWindow has its own DropArea for nesting.
We delete the redundant level of wrappers when floating.
2022-01-23 21:46:37 +00:00
Sergio Martins
95c12dbd4c nested mdi: Fix floating picking the wrong title bar
Use DockWidget::titleBar() which will always travel the hierarchy
and pick the first visible title bar. We have more nesting now.
2022-01-23 21:46:37 +00:00
Sergio Martins
c7682a3524 Use QScopedValueRollback, it's more expressive 2022-01-23 21:46:37 +00:00
Sergio Martins
4922363e71 Simplify expression 2022-01-23 21:46:37 +00:00
Sergio Martins
9f6ec0244f nested mdi: More tests for closing docks 2022-01-23 21:46:37 +00:00
Sergio Martins
747e987f28 test++ 2022-01-23 21:46:37 +00:00
Sergio Martins
68e01c70ee nested mdi: Delete wrappers once we close the last nested DW 2022-01-23 21:46:37 +00:00
Sergio Martins
296b2a3370 nested mdi: Fix MDI's frame title not being updated
Before we didn't need to update it because there was only 1
dock widget. But now, if there's more than one we need to set the
application's name instead of the dock widget's name as title.
2022-01-23 21:46:37 +00:00
Sergio Martins
074bc26be9 Added DropArea::mdiDockWidgetWrapper() 2022-01-23 21:46:37 +00:00
Sergio Martins
f8e6ecf821 Remove DropArea::setIsMDIWrapper(), use ctor instead
This propery is meant to be set only once, so enforce it via
compiler.
2022-01-23 21:46:37 +00:00
Sergio Martins
3e70a2cc71 nested mdi: Added DockWidgetBase::Private::isMDIWrapper 2022-01-23 21:46:37 +00:00
Sergio Martins
65ced9604f nested mdi: Fixed title bar visibility
When there's only one docked widget we only show 1 title bar.
Just like happens with a FloatingWindow
2022-01-23 21:46:37 +00:00
Sergio Martins
45b0536c6a Added DropArea::hasSingleFrame()
For readability
2022-01-23 21:46:37 +00:00
Sergio Martins
8c8f5a8fda Introduce Option_MDINestable
Should allow MDI dock widgets to also accept drops.
Befor, each MDI "window" only had 1 dock widget, but now each
MDI "window" a layout of dock widgets.

This is implemented by nesting the actual dock widget inside
a wrapper drop area. This drop area gives the drop support.

There's still bugs and more tests to fix before merging.
2022-01-23 21:46:37 +00:00
Sergio Martins
22a9ce2596 Merge branch '1.5' 2022-01-21 12:01:38 +00:00
Mauro Persano
f13f0129d4 Add API to set center widget margins on MainWindow
We can't change the margins by subclassing MainWindow and overriding
centerWidgetMargins, since the margins are initialized in MainWindow's
constructor. This adds public API to MainWindow to allow us to change
the margins later on.
2022-01-20 22:37:36 +00:00
Sergio Martins
22ffc6c7ea Fix QtQuick build 2022-01-20 22:37:36 +00:00
Mauro Persano
d1767b5534 Don't render frame for central persistent widget
When the main window has a central persistent widget, make sure the
containing tab widget doesn't render a frame around it.
2022-01-20 22:08:17 +00:00
Allen Winter
2fbe4f872e Merge branch '1.5' 2022-01-20 08:15:23 -05:00
Allen Winter
9c17b44ad7 Merge branch '1.5' 2022-01-19 16:23:47 -05:00
Sergio Martins
16c43b8c24 Make cmake config file honour KDDockWidgets_X11EXTRAS 2022-01-12 09:55:02 +00:00
Allen Winter
8391d85d48 appveyor.yml - add libxkbcommon on ubuntu for qt6 2022-01-09 10:17:39 -05:00
Allen Winter
e59e5d7a71 appveyor.yml - add builders for Qt6 2022-01-09 09:50:58 -05:00
Sergio Martins
aa3c3272ee Fix examples build on macOS and Windows
For some reason a X11 dependency was commited by mistake.
2022-01-08 18:39:16 +00:00
Sergio Martins
1d8fad245a Fix build with 5.12
This is a special request from a customer who can't upgrade right
now.

Min is still 5.15 in CMakeLists.
2022-01-07 17:55:53 +00:00
Sergio Martins
789c531f3d Fix parent of the MDIArea layout
Fixes tests on QtQuick. On QtWidgets it would get reparented
so tests already passed.
2022-01-07 17:45:37 +00:00
Sergio Martins
5b87fb4435 Add an examples/mdi_with_docking/ example 2022-01-07 17:31:20 +00:00
Sergio Martins
402f0b9d90 Add a fwd header for MDIArea.h 2022-01-07 17:27:45 +00:00
Sergio Martins
37567d3980 Introduce MDIArea, a widget that can host MDI dock widgets
This is a public wrapper to MDILayoutWidget. The latter is private
and has internals we don't want to expose. Instead create a public
class with a thin API.

You no longer need to create a MainWindowMDI to have MDI support.
You can now have a normal MainWindow (with normal docking) and
add some dock widget that as a MDIArea as widget.
2022-01-07 17:13:16 +00:00
Sergio Martins
6cef4cea2c Port frameParent() to firstParentOfType() 2022-01-07 14:59:33 +00:00
Sergio Martins
aea2bf971b Add TestDocks::tst_mdi_mixed_with_docking
Tests that we can dock a MDI Layout.
Meaning the main window would support both docking and MDI.
This basic test passes. But there's still a lot of bugs to fix
2022-01-07 12:50:25 +00:00
Sergio Martins
00a0e455e7 Make MDILayoutWidget::addDockWidget's initial opt param optional
It's seldom needed
2022-01-07 12:41:24 +00:00
Sergio Martins
d5c7fbfedd Install MDILayoutWidget_p.h too 2022-01-07 12:32:39 +00:00
Sergio Martins
bea6c09494 CMake: Add a clang -ftime-trace preset
Just for profiling build times.
2022-01-06 13:54:34 +00:00
Allen Winter
92d0d74641 src/private/FloatingWindow.cpp - on Windows, define NOMINMAX
else windows.h will define the max() macro which doesn't
play nice with the std::numeric_limits::max()

Issue #266
2021-12-27 07:56:00 -05:00
Sergio Martins
4d4f2a0183 Merge branch '1.5' 2021-12-22 17:07:24 +00:00
Sergio Martins
64b5564f99 README: Clarify that for QtQuick we suggest Qt6 2021-12-22 16:08:55 +00:00
Sergio Martins
66cc9ddc03 Updated ChangeLog
Closes #259
2021-12-22 15:39:00 +00:00
Eism
336f1146d3 Fixed the restoration of geometry when user closed maximized window 2021-12-22 15:31:26 +00:00
Mauro Persano
29b1a434c4 Make lazy resize rubber bands optionally top-level
To workaround MFC bug
2021-12-14 11:32:45 +00:00
Sergio Martins
b20ce0895b X11: Add support for robust z-order detection
Qt doesn't provide any way to know which window is directly
bellow the cursor. Generally this is fine since floating windows
are always on top of the main window. However, if Qt::Tool is removed,
then they can be behind, and KDDW can't know which window to overlay
the drop indicators onto.

This patch uses XLib's XQueryTree to solve this.
This is still experimental, hence disabled by default.

For now it's disabled by default, since it's experimental.
You can turn it on by passing -DKDDockWidgets_XLib=ON

Fixes bug #256
2021-11-25 20:59:16 +00:00
Sergio Martins
e67f55af51 Minor: Added Utils::isXCB() 2021-11-25 17:22:58 +00:00
Allen Winter
f1410948f8 Merge branch '1.5' 2021-11-24 16:51:38 -05:00
Allen Winter
d832750eb7 CMakeLists.txt - increase min Qt to version 5.15 2021-11-18 09:07:36 -05:00
Sergio Martins
77392709e2 Merge branch '1.5' 2021-11-18 14:03:00 +00:00
Allen Winter
6fd0b4ddee CMakeLists.txt, Changelog - this will be version 1.6 2021-11-15 09:55:47 -05:00
89 changed files with 2153 additions and 737 deletions

2
.gitignore vendored
View File

@@ -64,3 +64,5 @@ kddockwidgets_minimal_example
*.sln
*.dir
.vscode
/.cache
/compile_commands.json

View File

@@ -90,10 +90,10 @@ endif()
set(${PROJECT_NAME}_VERSION_MAJOR 1)
set(${PROJECT_NAME}_VERSION_MINOR 5)
set(${PROJECT_NAME}_VERSION_PATCH 1)
set(${PROJECT_NAME}_VERSION_PATCH 95)
set(${PROJECT_NAME}_VERSION ${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}.${${PROJECT_NAME}_VERSION_PATCH})
set(PROJECT_VERSION ${${PROJECT_NAME}_VERSION}) #PROJECT_VERSION is needed by some ECM modules
set(${PROJECT_NAME}_SOVERSION "1.5")
set(${PROJECT_NAME}_SOVERSION "1.6")
include(FeatureSummary)
@@ -132,22 +132,18 @@ endif()
if(${PROJECT_NAME}_QT6)
set(Qt_VERSION_MAJOR 6)
set(QT_MIN_VERSION "6.0.0")
find_package(Qt6Widgets ${QT_MIN_VERSION} REQUIRED)
find_package(Qt6Test ${QT_MIN_VERSION} REQUIRED)
set(${PROJECT_NAME}_LIBRARY_QTID "-qt6")
else()
set(Qt_VERSION_MAJOR 5)
set(QT_MIN_VERSION "5.15")
find_package(Qt5Widgets ${QT_MIN_VERSION} REQUIRED)
find_package(Qt5Test ${QT_MIN_VERSION} REQUIRED)
set(${PROJECT_NAME}_LIBRARY_QTID "")
endif()
find_package(Qt${Qt_VERSION_MAJOR} ${QT_MIN_VERSION} NO_MODULE REQUIRED COMPONENTS Widgets Test)
include(KDQtInstallPaths) #to set QT_INSTALL_FOO variables
set(${PROJECT_NAME}_DEPS "widgets")
if(${PROJECT_NAME}_QTQUICK)
find_package(Qt${Qt_VERSION_MAJOR}Quick)
find_package(Qt${Qt_VERSION_MAJOR}QuickControls2)
find_package(Qt${Qt_VERSION_MAJOR} NO_MODULE REQUIRED COMPONENTS Quick QuickControls2)
add_definitions(-DKDDOCKWIDGETS_QTQUICK)
set(${PROJECT_NAME}_DEPS "${${PROJECT_NAME}_DEPS} quick quickcontrols2")
else()
@@ -217,11 +213,13 @@ if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT APPLE) OR
set(sanitizers_enabled FALSE)
endif()
# cannot enable this for clang + sanitizers
if(NOT sanitizers_enabled OR NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
# Do not allow undefined symbols, even in non-symbolic shared libraries
set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--no-undefined ${CMAKE_SHARED_LINKER_FLAGS}")
set(CMAKE_MODULE_LINKER_FLAGS "-Wl,--no-undefined ${CMAKE_MODULE_LINKER_FLAGS}")
if(APPLE OR LINUX)
# cannot enable this for clang + sanitizers
if(NOT sanitizers_enabled OR NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
# Do not allow undefined symbols, even in non-symbolic shared libraries
set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--no-undefined ${CMAKE_SHARED_LINKER_FLAGS}")
set(CMAKE_MODULE_LINKER_FLAGS "-Wl,--no-undefined ${CMAKE_MODULE_LINKER_FLAGS}")
endif()
endif()
endif()
@@ -298,8 +296,10 @@ if(${PROJECT_NAME}_EXAMPLES)
add_subdirectory(examples/dockwidgets)
add_subdirectory(examples/minimal)
add_subdirectory(examples/minimal-mdi)
add_subdirectory(examples/mdi_with_docking)
set_compiler_flags(kddockwidgets_example)
set_compiler_flags(kddockwidgets_minimal_example)
set_compiler_flags(kddockwidgets_mdi_with_docking_example)
endif()
endif()

View File

@@ -9,7 +9,6 @@
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"KDDockWidgets_DEVELOPER_MODE": "ON",
"ECM_ENABLE_SANITIZERS" : "'address;undefined'",
"CMAKE_EXPORT_COMPILE_COMMANDS" : "ON",
"KDDockWidgets_FUZZER" : "OFF"
},
@@ -18,16 +17,17 @@
}
},
{
"name": "dev-gammaray",
"displayName": "dev-gammaray",
"description": "A Gammaray friendly build. (No ASAN)",
"name": "dev-asan",
"displayName": "dev-asan",
"description": "An ASAN/UBSAN build",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build-dev-gammaray",
"binaryDir": "${sourceDir}/build-dev-asan",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"KDDockWidgets_DEVELOPER_MODE": "ON",
"CMAKE_EXPORT_COMPILE_COMMANDS" : "ON",
"KDDockWidgets_FUZZER" : "OFF"
"KDDockWidgets_FUZZER" : "OFF",
"ECM_ENABLE_SANITIZERS" : "'address;undefined'"
},
"warnings" : {
"uninitialized" : true
@@ -164,6 +164,23 @@
"displayName": "dev6",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build-dev6",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"KDDockWidgets_QT6": "ON",
"KDDockWidgets_DEVELOPER_MODE": "ON",
"CMAKE_EXPORT_COMPILE_COMMANDS" : "ON",
"KDDockWidgets_FUZZER" : "OFF",
"CMAKE_PREFIX_PATH" : "$env{QT6_DIR}"
},
"environment": {
"PATH": "$env{QT6_DIR}/bin:$penv{PATH}"
}
},
{
"name": "dev-asan6",
"displayName": "dev-asan6",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build-dev-asan6",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"KDDockWidgets_QT6": "ON",
@@ -295,6 +312,51 @@
"QML2_IMPORT_PATH" : "$env{QT6_DIR}/imports:$env{QT6_DIR}/qml",
"LD_LIBRARY_PATH" : "$env{QT6_DIR}/lib"
}
},
{
"name": "dev-time-trace",
"displayName": "dev-time-trace",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build-dev-time-trace",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"KDDockWidgets_DEVELOPER_MODE": "ON",
"CMAKE_EXPORT_COMPILE_COMMANDS" : "ON",
"KDDockWidgets_FUZZER" : "OFF",
"CMAKE_C_FLAGS_INIT" : "-ftime-trace",
"CMAKE_CXX_FLAGS_INIT": "-ftime-trace"
},
"warnings" : {
"uninitialized" : true
},
"environment": {
"CC": "clang",
"CXX": "clang++",
"CCACHE_DISABLE" : "ON"
}
},
{
"name": "dev6-time-trace",
"displayName": "dev6-time-trace",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build-dev6-time-trace",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"KDDockWidgets_DEVELOPER_MODE": "ON",
"CMAKE_EXPORT_COMPILE_COMMANDS" : "ON",
"KDDockWidgets_FUZZER" : "OFF",
"KDDockWidgets_QT6" : "ON",
"CMAKE_C_FLAGS_INIT" : "-ftime-trace",
"CMAKE_CXX_FLAGS_INIT": "-ftime-trace"
},
"warnings" : {
"uninitialized" : true
},
"environment": {
"CC": "clang",
"CXX": "clang++",
"CCACHE_DISABLE" : "ON"
}
}
],
"buildPresets": [

View File

@@ -1,3 +1,13 @@
* v1.6.0 (unreleased)
- Fixed restoring of normal geometry when closing a maximized window (#259)
- Experimental support for docking into dock widgets which are in a MDI area.
- Fixed potential crash involving infinite loop between QWidget::create() and QWidget::createWinId()
- Moved DropIndicatorOverlayInterface::DropLocation enum to KDDockWidgets namespace scope
- Added Config::setDropIndicatorAllowedFunc() and corresponding example
(kddockwidgets_example --hide-certain-docking-indicators)
- Fixed case where unfloating wouldn't restore to the main window (#44 and #96)
- Fixed MainWindow not propagating close events to docked widgets
* v1.5.1 (unreleased)
- X11: Improved detecting which window is under the cursor, by using native X11 API

View File

@@ -1,8 +1,11 @@
Supported Qt versions and toolchains
=====================================
KDDockWidgets for QtQuick requires a C++17 capable compiler and either
Qt >= 5.15.2 or Qt >= 6.2.1
KDDockWidgets for QtQuick requires a C++17 capable compiler and Qt >= 6.2.1.
Qt 5.15.2 will probably also work, but it's not built and tested by KDAB CI, we
advise users to move to Qt6 as soon as possible.
TROUBLESHOOTING

View File

@@ -156,8 +156,9 @@ your application whenever updating KDDW.
Supported Qt versions and toolchains
=====================================
KDDockWidgets requires Qt 5.15.x or Qt6 >= 6.2.
====================================
KDDockWidgets requires a C++17 capable compiler and Qt 5.15.x or Qt6 >= 6.2
For QtQuick support see [README-QtQuick.md](README-QtQuick.md).
Styling

View File

@@ -19,7 +19,7 @@ skip_tags: false
# Build worker image
image:
- Ubuntu
- Ubuntu2004
- macos
- Visual Studio 2019
@@ -40,19 +40,24 @@ configuration:
- Release
- Debug
environment:
matrix:
- useqt6: False
- useqt6: True
install:
- sh: if [ "`uname -s`" = "Darwin" ]; then brew install ninja; else sudo apt-get -y install mesa-common-dev libglu1-mesa-dev; fi
- sh: if [ "`uname -s`" = "Darwin" ]; then brew install ninja; else sudo apt-get -y update; sudo apt-get -y install mesa-common-dev libglu1-mesa-dev libxkbcommon-dev libxkbcommon-x11-dev; fi
before_build:
- cmd: call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat"
- cmd: set PATH=C:\Qt\5.15\msvc2019_64;%PATH%
- sh: if [ "`uname -s`" = "Darwin" ]; then export PATH=$HOME/Qt/5.15/clang_64/bin:$PATH; else export PATH=$HOME/Qt/5.15/gcc_64/bin:$PATH; fi
- cmd: set PATH=C:\Qt\6.2\msvc2019_64;C:\Qt\5.15\msvc2019_64;%PATH%
- sh: if [ "`uname -s`" = "Darwin" ]; then export PATH=$HOME/Qt/6.1/macos/bin:$HOME/Qt/5.15/clang_64/bin:$PATH; else export PATH=$HOME/Qt/6.2/gcc_64/bin:$HOME/Qt/5.15/gcc_64/bin:$PATH; fi
build_script:
- mkdir build
- cd build
- cmd: cmake -G Ninja -DCMAKE_BUILD_TYPE=%CONFIGURATION% -DKDDockWidgets_TESTS=True -DKDDockWidgets_EXAMPLES=True -DKDDockWidgets_DEVELOPER_MODE=True ..
- sh: cmake -G Ninja -DCMAKE_BUILD_TYPE=$CONFIGURATION -DKDDockWidgets_TESTS=True -DKDDockWidgets_EXAMPLES=True -DKDDockWidgets_DEVELOPER_MODE=True ..
- cmd: cmake -G Ninja -DCMAKE_BUILD_TYPE=%CONFIGURATION% -DKDDockWidgets_QT6=%useqt6% -DKDDockWidgets_TESTS=True -DKDDockWidgets_EXAMPLES=True -DKDDockWidgets_DEVELOPER_MODE=True ..
- sh: cmake -G Ninja -DCMAKE_BUILD_TYPE=$CONFIGURATION -DKDDockWidgets_QT6=$useqt6 -DKDDockWidgets_TESTS=True -DKDDockWidgets_EXAMPLES=True -DKDDockWidgets_DEVELOPER_MODE=True ..
- cmake --build .
- cmd: cmake --build . --target install
- sh: sudo cmake --build . --target install

View File

@@ -47,6 +47,17 @@
"stopAtEntry": false,
"externalConsole": false
},
{
"name": "gdb-kddockwidgets_mdi_with_docking_example",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/build-dev/bin/kddockwidgets_mdi_with_docking_example",
"args": [],
"cwd": "${workspaceFolder}",
"MIMode": "gdb",
"stopAtEntry": false,
"externalConsole": false
},
{
"name": "gdb-tst_docks",
"type": "cppdbg",

View File

@@ -15,6 +15,7 @@
#include <kddockwidgets/FrameworkWidgetFactory.h>
#include <kddockwidgets/private/TabWidget_p.h>
#include <kddockwidgets/private/FloatingWindow_p.h>
#include <kddockwidgets/private/widgets/FrameWidget_p.h>
#include <kddockwidgets/private/widgets/TabBarWidget_p.h>
#include <kddockwidgets/private/widgets/TabWidgetWidget_p.h>
@@ -28,6 +29,7 @@
class MyTitleBar : public KDDockWidgets::TitleBarWidget
{
public:
bool m_isSpecialType = false;
explicit MyTitleBar(KDDockWidgets::Frame *frame)
: KDDockWidgets::TitleBarWidget(frame)
{
@@ -45,24 +47,73 @@ public:
void init()
{
setFixedHeight(60);
if (auto tb = tabBar()) {
if (auto tbWidget = qobject_cast<KDDockWidgets::TabBarWidget *>(tb->asWidget())) {
// 1. Be notified when dock widgets are added/removed from the tabbar and when current changes
connect(tbWidget, &KDDockWidgets::TabBarWidget::dockWidgetInserted, this, &MyTitleBar::updateType);
connect(tbWidget, &KDDockWidgets::TabBarWidget::dockWidgetRemoved, this, &MyTitleBar::updateType);
connect(tbWidget, &KDDockWidgets::TabBarWidget::currentChanged, this, &MyTitleBar::updateType);
}
}
if (KDDockWidgets::FloatingWindow *fw = floatingWindow()) {
// 2. Floating Windows with more than 1 Frame (more than 1 tabbar) won't have a special titlebar.
// during runtime a FloatingWindow can have frames removed/added, so update its title bar type.
connect(fw, &KDDockWidgets::FloatingWindow::numFramesChanged, this, &MyTitleBar::updateType);
}
}
void paintEvent(QPaintEvent *) override
void updateType()
{
QPainter p(this);
QPen pen(Qt::black);
const QColor focusedBackgroundColor = Qt::yellow;
const QColor backgroundColor = focusedBackgroundColor.darker(115);
QBrush brush(isFocused() ? focusedBackgroundColor : backgroundColor);
pen.setWidth(4);
p.setPen(pen);
p.setBrush(brush);
p.drawRect(rect().adjusted(4, 4, -4, -4));
QFont f = qApp->font();
f.setPixelSize(30);
f.setBold(true);
p.setFont(f);
p.drawText(QPoint(10,40), title());
m_isSpecialType = false;
if (tabBar() != nullptr) { // Will be null for floating windows with several frames.
const auto dws = dockWidgets();
for (auto dw : dws) {
// 3. If this TitleBar contains dock widget #1 or #2 it's special and will have custom painting
if (dw->uniqueName() == QLatin1String("DockWidget #1") ||
dw->uniqueName() == QLatin1String("DockWidget #2")) {
m_isSpecialType = true;
break;
}
}
QWidget::update();
}
}
void paintEvent(QPaintEvent *ev) override
{
KDDockWidgets::DockWidgetBase *currentDW = nullptr;
if (auto tb = tabBar()) {
if (auto tbWidget = qobject_cast<KDDockWidgets::TabBarWidget *>(tb->asWidget())) {
// 4. Know the current tab, so it can influence the title bar
currentDW = tbWidget->currentDockWidget();
}
}
if (m_isSpecialType) {
QPainter p(this);
QPen pen(Qt::black);
const bool isDw2 = currentDW && currentDW->uniqueName() == QLatin1String("DockWidget #2");
const QColor focusedBackgroundColor = isDw2 ? Qt::cyan : Qt::yellow;
const QColor backgroundColor = focusedBackgroundColor.darker(115);
QBrush brush(isFocused() ? focusedBackgroundColor : backgroundColor);
pen.setWidth(4);
p.setPen(pen);
p.setBrush(brush);
p.drawRect(rect().adjusted(4, 4, -4, -4));
QFont f = qApp->font();
f.setPixelSize(30);
f.setBold(true);
p.setFont(f);
p.drawText(QPoint(10, 40), title());
} else {
KDDockWidgets::TitleBarWidget::paintEvent(ev);
}
}
};

View File

@@ -47,6 +47,7 @@ static MyWidget *newMyWidget()
MyMainWindow::MyMainWindow(const QString &uniqueName, KDDockWidgets::MainWindowOptions options,
bool dockWidget0IsNonClosable, bool nonDockableDockWidget9, bool restoreIsRelative,
bool maxSizeForDockWidget8, bool dockwidget5DoesntCloseBeforeRestore,
bool dock0BlocksCloseEvent,
const QString &affinityName, QWidget *parent)
: MainWindow(uniqueName, options, parent)
, m_dockWidget0IsNonClosable(dockWidget0IsNonClosable)
@@ -54,6 +55,7 @@ MyMainWindow::MyMainWindow(const QString &uniqueName, KDDockWidgets::MainWindowO
, m_restoreIsRelative(restoreIsRelative)
, m_maxSizeForDockWidget8(maxSizeForDockWidget8)
, m_dockwidget5DoesntCloseBeforeRestore(dockwidget5DoesntCloseBeforeRestore)
, m_dock0BlocksCloseEvent(dock0BlocksCloseEvent)
{
auto menubar = menuBar();
auto fileMenu = new QMenu(QStringLiteral("File"), this);
@@ -190,6 +192,9 @@ KDDockWidgets::DockWidgetBase *MyMainWindow::newDockWidget()
myWidget->setMaximumSize(200, 200);
}
if (count == 0 && m_dock0BlocksCloseEvent)
myWidget->blockCloseEvent();
dock->setWidget(myWidget);
if (dock->options() & KDDockWidgets::DockWidget::Option_NotDockable) {

View File

@@ -20,7 +20,7 @@ class MyMainWindow : public KDDockWidgets::MainWindow
public:
explicit MyMainWindow(const QString &uniqueName, KDDockWidgets::MainWindowOptions options,
bool dockWidget0IsNonClosable, bool nonDockableDockWidget9, bool restoreIsRelative,
bool maxSizeForDockWidget8, bool dockwidget5DoesntCloseBeforeRestore,
bool maxSizeForDockWidget8, bool dockwidget5DoesntCloseBeforeRestore, bool dock0BlocksCloseEvent,
const QString &affinityName = {}, // Usually not needed. Just here to show the feature.
QWidget *parent = nullptr);
~MyMainWindow() override;
@@ -34,5 +34,6 @@ private:
const bool m_restoreIsRelative;
const bool m_maxSizeForDockWidget8;
const bool m_dockwidget5DoesntCloseBeforeRestore;
const bool m_dock0BlocksCloseEvent;
KDDockWidgets::DockWidget::List m_dockwidgets;
};

View File

@@ -15,6 +15,7 @@
#include <QDebug>
#include <QFile>
#include <QLineEdit>
#include <QCloseEvent>
static QHash<QString, QImage> s_images; /// clazy:exclude=non-pod-global-static
@@ -70,6 +71,20 @@ void MyWidget::drawLogo(QPainter &p)
p.drawImage(targetLogoRect, m_logo, m_logo.rect());
}
void MyWidget::blockCloseEvent()
{
m_blocksCloseEvent = true;
}
void MyWidget::closeEvent(QCloseEvent *ev)
{
if (m_blocksCloseEvent) {
ev->ignore();
} else {
QWidget::closeEvent(ev);
}
}
MyWidget1::MyWidget1(MyWidget::QWidget *parent)
: MyWidget(QStringLiteral(":/assets/triangles.png"), QStringLiteral(":/assets/KDAB_bubble_white.png"), parent)
{

View File

@@ -26,10 +26,16 @@ class MyWidget : public QWidget
public:
explicit MyWidget(const QString &backgroundFile, const QString &logoFile, QWidget *parent = nullptr);
~MyWidget();
// These two are just for demonstrating how to block the close event, if desired
void blockCloseEvent();
void closeEvent(QCloseEvent *) override;
protected:
void drawLogo(QPainter &);
QImage m_background;
QImage m_logo;
bool m_blocksCloseEvent = false;
};
class MyWidget1 : public MyWidget

View File

@@ -13,6 +13,7 @@
#include "MyMainWindow.h"
#include "MyFrameworkWidgetFactory.h"
#include <algorithm>
#include <kddockwidgets/Config.h>
#include <QStyleFactory>
@@ -119,6 +120,10 @@ int main(int argc, char **argv)
QCoreApplication::translate("main", "DockWidget #5 won't be closed before a restore. Illustrates LayoutSaverOption::DontCloseBeforeRestore"));
parser.addOption(dontCloseBeforeRestore);
QCommandLineOption blockCloseEvent("block-close-event",
QCoreApplication::translate("main", "DockWidget #0 will block close events"));
parser.addOption(blockCloseEvent);
QCommandLineOption showButtonsInTabBarIfTitleBarHidden("show-buttons-in-tabbar-if-titlebar-hidden",
QCoreApplication::translate("main", "If we're not using title bars we'll still show the close and float button in the tab bar"));
parser.addOption(showButtonsInTabBarIfTitleBarHidden);
@@ -131,6 +136,10 @@ int main(int argc, char **argv)
QCoreApplication::translate("main", "Allow switching tabs via context menu in tabs area"));
parser.addOption(ctxtMenuOnTabs);
QCommandLineOption hideCertainDockingIndicators("hide-certain-docking-indicators",
QCoreApplication::translate("main", "Illustrates usage of Config::setDropIndicatorAllowedFunc()"));
parser.addOption(hideCertainDockingIndicators);
#if defined(DOCKS_DEVELOPER_MODE)
parser.addOption(centralFrame);
@@ -253,6 +262,24 @@ int main(int argc, char **argv)
return 1;
}
if (parser.isSet(hideCertainDockingIndicators)) {
// Here we exemplify adding a restriction to "Dock Widget 8"
// Dock widget 8 will only be allowed to dock to the outter areasa
auto func = [](KDDockWidgets::DropLocation location,
const KDDockWidgets::DockWidgetBase::List &source,
const KDDockWidgets::DockWidgetBase::List &target) {
Q_UNUSED(target); // When dragging into a tab, 'target' would have the list of already tabbed dock widgets
const bool isDraggingDW8 = std::find_if(source.cbegin(), source.cend(), [] (KDDockWidgets::DockWidgetBase *dw) {
return dw->uniqueName() == QLatin1String("DockWidget #8");
}) != source.cend();
return (location & KDDockWidgets::DropLocation_Outter) || !isDraggingDW8;
};
KDDockWidgets::Config::self().setDropIndicatorAllowedFunc(func);
}
KDDockWidgets::Config::self().setFlags(flags);
const bool nonClosableDockWidget0 = parser.isSet(nonClosableDockWidget);
@@ -261,6 +288,7 @@ int main(int argc, char **argv)
const bool maxSizeForDockWidget8 = parser.isSet(maxSizeOption);
const bool dontCloseDockWidget5BeforeRestore = parser.isSet(dontCloseBeforeRestore);
const bool usesMainWindowsWithAffinity = parser.isSet(multipleMainWindows);
const bool dock0BlocksCloseEvent = parser.isSet(blockCloseEvent);
#ifdef KDDOCKWIDGETS_SUPPORTS_NESTED_MAINWINDOWS
const bool usesDockableMainWindows = parser.isSet(dockableMainWindows);
@@ -270,7 +298,7 @@ int main(int argc, char **argv)
MyMainWindow mainWindow(QStringLiteral("MyMainWindow"), options, nonClosableDockWidget0,
nonDockableDockWidget9, restoreIsRelative, maxSizeForDockWidget8,
dontCloseDockWidget5BeforeRestore);
dontCloseDockWidget5BeforeRestore, dock0BlocksCloseEvent);
mainWindow.setWindowTitle("Main Window 1");
mainWindow.resize(1200, 1200);
mainWindow.show();
@@ -289,7 +317,7 @@ int main(int argc, char **argv)
auto mainWindow2 = new MyMainWindow(QStringLiteral("MyMainWindow-2"), options,
nonClosableDockWidget0, nonDockableDockWidget9,
restoreIsRelative, maxSizeForDockWidget8,
dontCloseDockWidget5BeforeRestore, affinity);
dontCloseDockWidget5BeforeRestore, dock0BlocksCloseEvent, affinity);
if (affinity.isEmpty())
mainWindow2->setWindowTitle("Main Window 2");
else
@@ -303,7 +331,7 @@ int main(int argc, char **argv)
const QString affinity = QStringLiteral("Inner-DockWidgets-2");
auto dockableMainWindow = new MyMainWindow(QStringLiteral("MyMainWindow-2"), options,
false, false, restoreIsRelative, false,
false, affinity);
false, false, affinity);
dockableMainWindow->setAffinities({ affinity });

View File

@@ -0,0 +1,40 @@
#
# This file is part of KDDockWidgets.
#
# SPDX-FileCopyrightText: 2019-2021 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
# Author: Sergio Martins <sergio.martins@kdab.com>
#
# SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
#
# Contact KDAB at <info@kdab.com> for commercial licensing options.
#
cmake_minimum_required(VERSION 3.7)
project(kddockwidgets_mdi_with_docking_example)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_INCLUDE_CURRENT_DIRS ON)
if(NOT TARGET kddockwidgets)
# This will look for Qt, do find_package yourself manually before
# if you want to look for a specific Qt version for instance.
find_package(KDDockWidgets REQUIRED)
endif()
set(RESOURCES_EXAMPLE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/../dockwidgets/resources_example.qrc)
# Just to reuse MyWidget.h
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../dockwidgets/)
add_executable(kddockwidgets_mdi_with_docking_example
main.cpp
../dockwidgets/MyWidget.cpp
${RESOURCES_EXAMPLE_SRC}
)
target_link_libraries(kddockwidgets_mdi_with_docking_example
PRIVATE
KDAB::kddockwidgets
)

View File

@@ -0,0 +1,95 @@
/*
This file is part of KDDockWidgets.
SPDX-FileCopyrightText: 2019-2021 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
Author: Sérgio Martins <sergio.martins@kdab.com>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
Contact KDAB at <info@kdab.com> for commercial licensing options.
*/
#include "MyWidget.h"
#include <kddockwidgets/DockWidget.h>
#include <kddockwidgets/MainWindow.h>
#include <kddockwidgets/MDIArea.h>
#include <QStyleFactory>
#include <QApplication>
#include <QCommandLineParser>
// clazy:excludeall=qstring-allocations
int main(int argc, char **argv)
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
#endif
QApplication app(argc, argv);
app.setOrganizationName(QStringLiteral("KDAB"));
app.setApplicationName(QStringLiteral("App supporting both docking and a MDI area"));
QCommandLineParser parser;
parser.setApplicationDescription("KDDockWidgets MDI mixed with normal docking");
parser.addHelpOption();
QCommandLineOption nestedDocking("n", QCoreApplication::translate("main", "The MDI dock widgets will serve as drop areas, allowing for further nesting"));
parser.addOption(nestedDocking);
parser.process(app);
// Fusion looks better in general, but feel free to change
qApp->setStyle(QStyleFactory::create(QStringLiteral("Fusion")));
// # 1. Create our main window
KDDockWidgets::MainWindow mainWindow(QStringLiteral("MyMainWindow"), KDDockWidgets::MainWindowOption_HasCentralWidget);
mainWindow.setWindowTitle("Main Window");
mainWindow.resize(1600, 1200);
mainWindow.show();
// # 2. Create a dock widget, it needs a unique name
auto dock1 = new KDDockWidgets::DockWidget(QStringLiteral("MyDock1"));
auto widget1 = new MyWidget1();
dock1->setWidget(widget1);
auto dock2 = new KDDockWidgets::DockWidget(QStringLiteral("MyDock2"));
auto widget2 = new MyWidget2();
dock2->setWidget(widget2);
// # 3. Dock them
mainWindow.addDockWidget(dock1, KDDockWidgets::Location_OnLeft, nullptr, KDDockWidgets::InitialOption(QSize(300, 0)));
mainWindow.addDockWidget(dock2, KDDockWidgets::Location_OnBottom, nullptr, KDDockWidgets::InitialOption(QSize(0, 300)));
KDDockWidgets::DockWidgetBase::Options options = {};
if (parser.isSet(nestedDocking)) {
options |= KDDockWidgets::DockWidgetBase::Option_MDINestable;
}
// 4. Create our MDI widgets, which will go into the MDI area
auto mdiWidget1 = new KDDockWidgets::DockWidget(QStringLiteral("MDI widget1"), options);
mdiWidget1->setWidget(new MyWidget1());
auto mdiWidget2 = new KDDockWidgets::DockWidget(QStringLiteral("MDI widget2"), options);
mdiWidget2->setWidget(new MyWidget2());
auto mdiWidget3 = new KDDockWidgets::DockWidget(QStringLiteral("MDI widget3"), options);
auto widget3 = new MyWidget3();
mdiWidget3->setWidget(widget3);
// Just for my personal testing: Overkill to add an option
// widget3->blockCloseEvent();
auto mdiArea = new KDDockWidgets::MDIArea();
mainWindow.setPersistentCentralWidget(mdiArea);
mdiArea->addDockWidget(mdiWidget1, QPoint(10, 10));
mdiArea->addDockWidget(mdiWidget2, QPoint(50, 50));
mdiArea->addDockWidget(mdiWidget3, QPoint(110, 110));
return app.exec();
}

View File

@@ -24,6 +24,9 @@ endif()
set(RESOURCES_EXAMPLE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/../dockwidgets/resources_example.qrc)
# Just to reuse MyWidget.h
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../dockwidgets/)
add_executable(kddockwidgets_minimal_mdi_example
main.cpp
../dockwidgets/MyWidget.cpp

View File

@@ -1,116 +0,0 @@
/*
This file is part of KDDockWidgets.
SPDX-FileCopyrightText: 2019-2022 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
Author: Sérgio Martins <sergio.martins@kdab.com>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
Contact KDAB at <info@kdab.com> for commercial licensing options.
*/
#include "MyWidget.h"
#include <QPainter>
#include <QDebug>
#include <QFile>
#include <QLineEdit>
static QHash<QString, QImage> s_images; /// clazy:exclude=non-pod-global-static
MyWidget::MyWidget(const QString &backgroundFile, const QString &logoFile, QWidget *parent)
: QWidget(parent)
{
if (!backgroundFile.isEmpty()) {
auto it = s_images.find(backgroundFile);
if (it == s_images.end())
it = s_images.insert(backgroundFile, QImage(backgroundFile));
m_background = it.value();
}
if (!logoFile.isEmpty()) {
auto it = s_images.find(logoFile);
if (it == s_images.end())
it = s_images.insert(logoFile, QImage(logoFile));
m_logo = it.value();
}
setFocusPolicy(Qt::StrongFocus);
#if 0
// Uncomment to show focus propagation working
new QLineEdit(this);
auto l2 = new QLineEdit(this);
l2->move(0, 100);
setFocusProxy(l2);
#endif
}
MyWidget::~MyWidget()
{
}
void MyWidget::drawLogo(QPainter &p)
{
if (m_logo.isNull())
return;
const qreal ratio = m_logo.height() / (m_logo.width() * 1.0);
const int maxWidth = int(0.80 * size().width());
const int maxHeight = int(0.80 * size().height());
const int proposedHeight = int(maxWidth * ratio);
const int width = proposedHeight <= maxHeight ? maxWidth
: int(maxHeight / ratio);
const int height = int(width * ratio);
QRect targetLogoRect(0,0, width, height);
targetLogoRect.moveCenter(rect().center() + QPoint(0, -int(size().height() * 0.00)));
p.drawImage(targetLogoRect, m_logo, m_logo.rect());
}
MyWidget1::MyWidget1(MyWidget::QWidget *parent)
: MyWidget(QStringLiteral(":/assets/triangles.png"), QStringLiteral(":/assets/KDAB_bubble_white.png"), parent)
{
}
void MyWidget1::paintEvent(QPaintEvent *)
{
QPainter p(this);
p.fillRect(rect(), QColor(0xCC, 0xCC, 0xCC));
p.drawImage(m_background.rect(), m_background, m_background.rect());
drawLogo(p);
}
MyWidget2::MyWidget2(MyWidget::QWidget *parent)
: MyWidget(QString(), QStringLiteral(":/assets/KDAB_bubble_blue.png"), parent)
{
}
void MyWidget2::paintEvent(QPaintEvent *)
{
QPainter p(this);
p.fillRect(rect(), Qt::white);
drawLogo(p);
}
MyWidget3::MyWidget3(MyWidget::QWidget *parent)
: MyWidget(QStringLiteral(":/assets/base.png"), QStringLiteral(":/assets/KDAB_bubble_fulcolor.png"), parent)
, m_triangle(QImage(QStringLiteral(":/assets/tri.png")))
{
}
void MyWidget3::paintEvent(QPaintEvent *)
{
QPainter p(this);
p.fillRect(rect(), QColor(0xD5, 0xD5, 0xD5));
p.drawImage(m_background.rect(), m_background, m_background.rect());
const QRect targetRect = QRect({ width() - m_triangle.width(), height() - m_triangle.height() }, m_triangle.size());
p.drawImage(targetRect, m_triangle, m_triangle.rect());
drawLogo(p);
}

View File

@@ -1,65 +0,0 @@
/*
This file is part of KDDockWidgets.
SPDX-FileCopyrightText: 2019-2022 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
Author: Sérgio Martins <sergio.martins@kdab.com>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
Contact KDAB at <info@kdab.com> for commercial licensing options.
*/
#ifndef EXAMPLEDOCKABLEWIDGET_H
#define EXAMPLEDOCKABLEWIDGET_H
#pragma once
#include <QWidget>
QT_BEGIN_NAMESPACE
class QPainter;
QT_END_NAMESPACE
class MyWidget : public QWidget
{
Q_OBJECT
public:
MyWidget() = default;
explicit MyWidget(const QString &backgroundFile, const QString &logoFile, QWidget *parent = nullptr);
~MyWidget();
protected:
void drawLogo(QPainter &);
QImage m_background;
QImage m_logo;
};
class MyWidget1 : public MyWidget
{
Q_OBJECT
public:
explicit MyWidget1(QWidget *parent = nullptr);
protected:
void paintEvent(QPaintEvent*) override;
};
class MyWidget2 : public MyWidget
{
Q_OBJECT
public:
explicit MyWidget2(QWidget *parent = nullptr);
protected:
void paintEvent(QPaintEvent*) override;
};
class MyWidget3 : public MyWidget
{
Q_OBJECT
public:
explicit MyWidget3(QWidget *parent = nullptr);
protected:
void paintEvent(QPaintEvent*) override;
QImage m_triangle;
};
#endif

View File

@@ -53,6 +53,9 @@ int main(int argc, char **argv)
auto widget3 = new MyWidget3();
dock3->setWidget(widget3);
// Just for my personal testing: Overkill to add an option
// widget3->blockCloseEvent();
// # 3. Dock them
mainWindow.addDockWidget(dock1, QPoint(10, 10));
mainWindow.addDockWidget(dock2, QPoint(50, 50));

View File

@@ -35,6 +35,8 @@ set(DOCKSLIBS_SRCS
MainWindowBase.h
MainWindowMDI.cpp
MainWindowMDI.h
MDIArea.cpp
MDIArea.h
LayoutSaver.cpp
LayoutSaver.h
private/LayoutSaver_p.h
@@ -126,6 +128,7 @@ set(DOCKS_INSTALLABLE_PRIVATE_INCLUDES
private/WidgetResizeHandler_p.h
private/DockRegistry_p.h
private/TabWidget_p.h
private/MDILayoutWidget_p.h
)
set(DOCKS_INSTALLABLE_PRIVATE_WIDGET_INCLUDES
@@ -219,6 +222,7 @@ else()
${DOCKS_INSTALLABLE_INCLUDES}
MainWindow.h
DockWidget.h
MDIArea.h
)
endif()
@@ -295,7 +299,7 @@ if(CMAKE_COMPILER_IS_GNUCXX OR IS_CLANG_BUILD)
endif()
if(${PROJECT_NAME}_QTQUICK)
target_link_libraries(kddockwidgets PUBLIC Qt${Qt_VERSION_MAJOR}::Widgets Qt${Qt_VERSION_MAJOR}::Quick Qt${Qt_VERSION_MAJOR}::QuickControls2)
target_link_libraries(kddockwidgets PUBLIC Qt${Qt_VERSION_MAJOR}::Widgets Qt${Qt_VERSION_MAJOR}::Quick Qt${Qt_VERSION_MAJOR}::QuickControls2 PRIVATE Qt${Qt_VERSION_MAJOR}::GuiPrivate)
else()
target_link_libraries(kddockwidgets PUBLIC Qt${Qt_VERSION_MAJOR}::Widgets PRIVATE Qt${Qt_VERSION_MAJOR}::WidgetsPrivate)
endif()

View File

@@ -19,6 +19,7 @@
#include "Config.h"
#include "private/multisplitter/MultiSplitterConfig.h"
#include "private/multisplitter/Widget.h"
#include "private/multisplitter/Item_p.h"
#include "private/DockRegistry_p.h"
#include "private/Utils_p.h"
#include "private/DragController_p.h"
@@ -54,6 +55,7 @@ public:
DockWidgetFactoryFunc m_dockWidgetFactoryFunc = nullptr;
MainWindowFactoryFunc m_mainWindowFactoryFunc = nullptr;
TabbingAllowedFunc m_tabbingAllowedFunc = nullptr;
DropIndicatorAllowedFunc m_dropIndicatorAllowedFunc = nullptr;
FrameworkWidgetFactory *m_frameworkWidgetFactory = nullptr;
Flags m_flags = Flag_Default;
InternalFlags m_internalFlags = InternalFlag_None;
@@ -180,6 +182,16 @@ TabbingAllowedFunc Config::tabbingAllowedFunc() const
return d->m_tabbingAllowedFunc;
}
void Config::setDropIndicatorAllowedFunc(DropIndicatorAllowedFunc func)
{
d->m_dropIndicatorAllowedFunc = func;
}
DropIndicatorAllowedFunc Config::dropIndicatorAllowedFunc() const
{
return d->m_dropIndicatorAllowedFunc;
}
void Config::setAbsoluteWidgetMinSize(QSize size)
{
if (!DockRegistry::self()->isEmpty(/*excludeBeingDeleted=*/false)) {

View File

@@ -20,6 +20,7 @@
#define KD_DOCKWIDGETS_CONFIG_H
#include "docks_export.h"
#include "KDDockWidgets.h"
#include <qglobal.h>
@@ -37,6 +38,22 @@ class FrameworkWidgetFactory;
typedef KDDockWidgets::DockWidgetBase *(*DockWidgetFactoryFunc)(const QString &name);
typedef KDDockWidgets::MainWindowBase *(*MainWindowFactoryFunc)(const QString &name);
/// @brief Function to allow more granularity to disallow where widgets are dropped
///
/// By default, widgets can be dropped to the outter and inner left/right/top/bottom
/// and center. The client app can however provide a lambda via setDropIndicatorAllowedFunc
/// to block (by returning false) any specific locations they desire.
///
/// @param location The drop indicator location to allow or disallow
/// @param source The dock widgets being dragged
/// @param target The dock widgets within an existing docked tab group
/// @return true if the docking is allowed.
/// @sa setDropIndicatorAllowedFunc
typedef bool (*DropIndicatorAllowedFunc)(DropLocation location,
const QVector<DockWidgetBase *> &source,
const QVector<DockWidgetBase *> &target);
/// @deprecated Use DropIndicatorAllowedFunc instead.
/// @brief Function to allow the user more granularity to disallow dock widgets to tab together
/// @param source The dock widgets being dragged
/// @param target The dock widgets within an existing docked tab group
@@ -203,6 +220,8 @@ public:
bool dropIndicatorsInhibited() const;
/**
* @deprecated Use setDropIndicatorAllowedFunc() instead, and catch the DropLocation_Center case.
*
* @brief Allows the user to intercept a docking attempt to center (tabbed) and disallow it.
*
* Whenever the user tries to tab two widgets together, the framework will call @p func. If
@@ -219,8 +238,10 @@ public:
* // disallows dockFoo to be tabbed with dockBar.
* return !(source.contains(dockFoo) && target.contains(dockBar));
* }
* @endcode
*
* KDDockWidgets::Config::self()->setTabbingAllowedFunc(func);
*
* @endcode
*/
void setTabbingAllowedFunc(TabbingAllowedFunc func);
@@ -229,6 +250,37 @@ public:
///@sa setTabbingAllowedFunc().
TabbingAllowedFunc tabbingAllowedFunc() const;
/**
* @brief Allows the client app to disallow certain docking indicators.
*
* For example, let's assume the app doesn't want to show outter indicators for a certain
* dock widget.
*
* @code
* #include <kddockwidgets/Config.h>
* (...)
*
* auto func = [] (KDDockWidgets::DropLocation loc,
* const KDDockWidgets::DockWidgetBase::List &source,
* const KDDockWidgets::DockWidgetBase::List &target)
* {
* // disallows dockFoo to be docked to outter areas
* return !((loc & KDDockWidgets::DropLocation_Outter) && source.contains(dockFoo));
* }
*
* KDDockWidgets::Config::self()->setDropIndicatorAllowedFunc(func);
*
* @endcode
*
* Run "kddockwidgets_example --hide-certain-docking-indicators" to see this in action.
*/
void setDropIndicatorAllowedFunc(DropIndicatorAllowedFunc func);
///@brief Used internally by the framework. Returns the function which was passed to setDropIndicatorAllowedFunc()
///By default it's nullptr.
///@sa setDropIndicatorAllowedFunc().
DropIndicatorAllowedFunc dropIndicatorAllowedFunc() const;
///@brief Sets the minimum size a dock widget can have.
/// Widgets can still provide their own min-size and it will be respected, however it can never be
/// smaller than this one.

View File

@@ -180,7 +180,7 @@ bool DockWidgetBase::setFloating(bool floats)
{
const bool alreadyFloating = isFloating();
if ((floats && alreadyFloating) || (!floats && !alreadyFloating))
if (floats == alreadyFloating)
return true; // Nothing to do
if (!floats && (Config::self().internalFlags() & Config::InternalFlag_DontShowWhenUnfloatingHiddenWindow) && !isVisible()) {
@@ -205,10 +205,10 @@ bool DockWidgetBase::setFloating(bool floats)
frame->detachTab(this);
} else {
d->frame()->titleBar()->makeWindow();
titleBar()->makeWindow();
}
auto lastGeo = d->lastPositions().lastFloatingGeometry();
auto lastGeo = d->lastPosition()->lastFloatingGeometry();
if (lastGeo.isValid()) {
if (auto fw = floatingWindow())
fw->setSuggestedGeometry(lastGeo, SuggestedGeometryHint_PreserveCenter);
@@ -237,6 +237,17 @@ QString DockWidgetBase::uniqueName() const
QString DockWidgetBase::title() const
{
if (d->isMDIWrapper()) {
// It's just a wrapper to help implementing Option_MDINestable. Return the title of the real dock widget we're hosting.
auto dropAreaGuest = qobject_cast<DropArea*>(widget());
Q_ASSERT(dropAreaGuest);
if (dropAreaGuest->hasSingleFrame()) {
return dropAreaGuest->frames().constFirst()->title();
} else {
return qApp->applicationName();
}
}
return d->title;
}
@@ -370,7 +381,7 @@ QStringList DockWidgetBase::affinities() const
void DockWidgetBase::show()
{
if (isWindow() && (d->m_lastPositions.wasFloating() || !d->m_lastPositions.isValid())) {
if (isWindow() && (d->m_lastPosition->wasFloating() || !d->m_lastPosition->isValid())) {
// Create the FloatingWindow already, instead of waiting for the show event.
// This reduces flickering on some platforms
d->morphIntoFloatingWindow();
@@ -465,7 +476,7 @@ bool DockWidgetBase::isInSideBar() const
bool DockWidgetBase::hasPreviousDockedLocation() const
{
return d->m_lastPositions.isValid();
return d->m_lastPosition->isValid();
}
QSize DockWidgetBase::lastOverlayedSize() const
@@ -488,7 +499,7 @@ void DockWidgetBase::setFloatingGeometry(QRect geometry)
if (isOpen() && isFloating()) {
window()->setGeometry(geometry);
} else {
d->m_lastPositions.setLastFloatingGeometry(geometry);
d->m_lastPosition->setLastFloatingGeometry(geometry);
}
}
@@ -498,7 +509,7 @@ FloatingWindow *DockWidgetBase::Private::morphIntoFloatingWindow()
return fw; // Nothing to do
if (q->isWindow()) {
QRect geo = m_lastPositions.lastFloatingGeometry();
QRect geo = m_lastPosition->lastFloatingGeometry();
if (geo.isNull()) {
geo = q->geometry();
@@ -532,8 +543,75 @@ void DockWidgetBase::Private::maybeMorphIntoFloatingWindow()
MDILayoutWidget *DockWidgetBase::Private::mdiLayout() const
{
if (auto mw = mainWindow())
return mw->mdiLayoutWidget();
auto p = const_cast<QObject *>(q->parent());
while (p) {
if (qobject_cast<const QWindow*>(p)) {
// Ignore QObject hierarchies spanning though multiple windows
return nullptr;
}
if (qobject_cast<LayoutWidget*>(p)) {
// We found a layout
if (auto mdiLayout = qobject_cast<MDILayoutWidget*>(p)) {
// And it's MDI
return mdiLayout;
} else if (auto dropArea = qobject_cast<DropArea*>(p)) {
// It's a DropArea. But maybe it's a drop area that's just helping
// making the MDI windows accept drops (Option_MDINestable)
if (!dropArea->isMDIWrapper())
return nullptr;
// It's a MDI wrapper, keep looking up.
}
}
p = p->parent();
}
return nullptr;
}
bool DockWidgetBase::Private::isMDIWrapper() const
{
return mdiDropAreaWrapper() != nullptr;
}
DropArea *DockWidgetBase::Private::mdiDropAreaWrapper() const
{
if (auto dropAreaGuest = qobject_cast<DropArea *>(q->widget())) {
if (dropAreaGuest->isMDIWrapper())
return dropAreaGuest;
}
return nullptr;
}
DockWidgetBase *DockWidgetBase::Private::mdiDockWidgetWrapper() const
{
if (isMDIWrapper()) {
// We are the wrapper
return q;
}
auto p = const_cast<QObject *>(q->parent());
while (p) {
if (qobject_cast<const QWindow*>(p)) {
// Ignore QObject hierarchies spanning though multiple windows
return nullptr;
}
if (qobject_cast<LayoutWidget*>(p)) {
if (auto dropArea = qobject_cast<DropArea*>(p)) {
if (dropArea->isMDIWrapper())
return dropArea->mdiDockWidgetWrapper();
}
return nullptr;
}
p = p->parent();
}
return nullptr;
}
@@ -603,7 +681,7 @@ void DockWidgetBase::Private::updateFloatAction()
QScopedValueRollback<bool> recursionGuard(m_updatingFloatAction, true); // Guard against recursiveness
if (q->isFloating()) {
floatAction->setEnabled(m_lastPositions.isValid());
floatAction->setEnabled(m_lastPosition->isValid());
floatAction->setChecked(true);
floatAction->setToolTip(tr("Dock"));
} else {
@@ -646,7 +724,7 @@ void DockWidgetBase::Private::close()
&& q->isVisible()) { // only user-closing is interesting to save the geometry
// We check for isVisible so we don't save geometry if you call close() on an already closed
// dock widget
m_lastPositions.setLastFloatingGeometry(q->window()->geometry());
m_lastPosition->setLastFloatingGeometry(q->window()->geometry());
}
saveTabIndex();
@@ -669,14 +747,14 @@ void DockWidgetBase::Private::close()
bool DockWidgetBase::Private::restoreToPreviousPosition()
{
if (!m_lastPositions.isValid())
if (!m_lastPosition->isValid())
return false;
Layouting::Item *item = m_lastPositions.lastItem();
Layouting::Item *item = m_lastPosition->lastItem();
LayoutWidget *layout = DockRegistry::self()->layoutForItem(item);
Q_ASSERT(layout);
layout->restorePlaceholder(q, item, m_lastPositions.lastTabIndex());
layout->restorePlaceholder(q, item, m_lastPosition->lastTabIndex());
return true;
}
@@ -684,14 +762,14 @@ void DockWidgetBase::Private::maybeRestoreToPreviousPosition()
{
// This is called when we get a QEvent::Show. Let's see if we have to restore it to a previous position.
if (!m_lastPositions.isValid())
if (!m_lastPosition->isValid())
return;
Layouting::Item *layoutItem = m_lastPositions.lastItem();
Layouting::Item *layoutItem = m_lastPosition->lastItem();
if (!layoutItem)
return; // nothing to do, no last position
if (m_lastPositions.wasFloating())
if (m_lastPosition->wasFloating())
return; // Nothing to do, it was floating before, now it'll just get visible
Frame *frame = this->frame();
@@ -721,7 +799,7 @@ int DockWidgetBase::Private::currentTabIndex() const
void DockWidgetBase::Private::saveTabIndex()
{
m_lastPositions.saveTabIndex(currentTabIndex(), q->isFloating());
m_lastPosition->saveTabIndex(currentTabIndex(), q->isFloating());
}
void DockWidgetBase::Private::show()
@@ -826,14 +904,26 @@ int DockWidgetBase::userType() const
void DockWidgetBase::setMDIPosition(QPoint pos)
{
if (MDILayoutWidget *layout = d->mdiLayout())
layout->moveDockWidget(this, pos);
if (MDILayoutWidget *layout = d->mdiLayout()) {
if (auto wrapperDW = d->mdiDockWidgetWrapper()) {
// Case of using Option_MDINestable. We need to layout the actual top level DW
layout->moveDockWidget(wrapperDW, pos);
} else {
layout->moveDockWidget(this, pos);
}
}
}
void DockWidgetBase::setMDISize(QSize size)
{
if (MDILayoutWidget *layout = d->mdiLayout())
layout->resizeDockWidget(this, size);
if (MDILayoutWidget *layout = d->mdiLayout()) {
if (auto wrapperDW = d->mdiDockWidgetWrapper()) {
// Case of using Option_MDINestable. We need to layout the actual top level DW
layout->resizeDockWidget(wrapperDW, size);
} else {
layout->resizeDockWidget(this, size);
}
}
}
void DockWidgetBase::setMDIZ(int z)
@@ -917,12 +1007,12 @@ DockWidgetBase::Private::Private(const QString &dockName, DockWidgetBase::Option
void DockWidgetBase::Private::addPlaceholderItem(Layouting::Item *item)
{
Q_ASSERT(item);
m_lastPositions.addPosition(item);
m_lastPosition->addPlaceholderItem(item);
}
LastPositions &DockWidgetBase::Private::lastPositions()
Position::Ptr &DockWidgetBase::Private::lastPosition()
{
return m_lastPositions;
return m_lastPosition;
}
Frame *DockWidgetBase::Private::frame() const
@@ -940,6 +1030,6 @@ void DockWidgetBase::Private::saveLastFloatingGeometry()
{
if (q->isFloating() && q->isVisible()) {
// It's getting docked, save last floating position
lastPositions().setLastFloatingGeometry(q->window()->geometry());
lastPosition()->setLastFloatingGeometry(q->window()->geometry());
}
}

View File

@@ -76,7 +76,10 @@ public:
Option_None = 0, ///< No option, the default
Option_NotClosable = 1, ///< The DockWidget can't be closed on the [x], only programmatically
Option_NotDockable = 2, ///< The DockWidget can't be docked, it's always floating
Option_DeleteOnClose = 4 ///< Deletes the DockWidget when closed
Option_DeleteOnClose = 4, ///< Deletes the DockWidget when closed
Option_MDINestable = 8 ///< EXPERIMENTAL. When this dock widget is being shown in a MDI area it will also allow other dock widgets to be dropped to its sides and tabbed
/// Usually Each MDI "window" corresponds to one DockWidget, with this option each "window" will have a layout with 1 or more dock widgets
/// Run "kddockwidgets_mdi_with_docking_example -n" to see it in action
};
Q_DECLARE_FLAGS(Options, Option)
Q_ENUM(Options);
@@ -529,6 +532,7 @@ private:
friend class MultiSplitter;
friend class LayoutWidget;
friend class MDILayoutWidget;
friend class FloatingWindow;
friend class Frame;
friend class DropArea;
friend class ::TestDocks;

View File

@@ -75,9 +75,9 @@ TabBar *DefaultWidgetFactory::createTabBar(TabWidget *parent) const
return new TabBarWidget(parent);
}
TabWidget *DefaultWidgetFactory::createTabWidget(Frame *parent) const
TabWidget *DefaultWidgetFactory::createTabWidget(Frame *parent, TabWidgetOptions options) const
{
return new TabWidgetWidget(parent);
return new TabWidgetWidget(parent, options);
}
Layouting::Separator *DefaultWidgetFactory::createSeparator(Layouting::Widget *parent) const
@@ -189,7 +189,7 @@ TabBar *DefaultWidgetFactory::createTabBar(TabWidget *parent) const
return new TabBarQuick(parent);
}
TabWidget *DefaultWidgetFactory::createTabWidget(Frame *parent) const
TabWidget *DefaultWidgetFactory::createTabWidget(Frame *parent, TabWidgetOptions) const
{
return new TabWidgetQuick(parent);
}

View File

@@ -97,7 +97,7 @@ public:
///@brief Called internally by the framework to create a TabWidget
/// Override to provide your own TabWidget sub-class.
///@param parent Just forward to TabWidget's constructor.
virtual TabWidget *createTabWidget(Frame *parent) const = 0;
virtual TabWidget *createTabWidget(Frame *parent, TabWidgetOptions options = TabWidgetOption_None) const = 0;
///@brief Called internally by the framework to create a TabBar
/// Override to provide your own TabBar sub-class.
@@ -167,7 +167,7 @@ public:
Frame *createFrame(QWidgetOrQuick *parent, FrameOptions) const override;
TitleBar *createTitleBar(Frame *) const override;
TitleBar *createTitleBar(FloatingWindow *) const override;
TabWidget *createTabWidget(Frame *parent) const override;
TabWidget *createTabWidget(Frame *parent, TabWidgetOptions = TabWidgetOption_None) const override;
TabBar *createTabBar(TabWidget *parent) const override;
Layouting::Separator *createSeparator(Layouting::Widget *parent = nullptr) const override;
FloatingWindow *createFloatingWindow(MainWindowBase *parent = nullptr) const override;

View File

@@ -57,7 +57,7 @@ enum MainWindowOption
MainWindowOption_HasCentralFrame = 1, ///> Makes the MainWindow always have a central frame, for tabbing documents
MainWindowOption_MDI = 2, ///> The layout will be MDI. DockWidgets can have arbitrary positions, not restricted by any layout
MainWindowOption_HasCentralWidget = 4 | MainWindowOption_HasCentralFrame, ///> Similar to MainWindowOption_HasCentralFrame but
///> you'll have a central widget which can't be detached (Similar to regular QMainWindow).
///> you'll have a central widget which can't be detached (Similar to regular QMainWindow). @sa MainWindowBase::setPersistentCentralWidget()
};
Q_DECLARE_FLAGS(MainWindowOptions, MainWindowOption)
Q_ENUM_NS(MainWindowOptions)
@@ -229,6 +229,24 @@ enum class TitleBarButtonType
};
Q_ENUM_NS(TitleBarButtonType)
///@brief Enum describing the different drop indicator types
enum DropLocation
{
DropLocation_None = 0,
DropLocation_Left = 1,
DropLocation_Top = 2,
DropLocation_Right = 4,
DropLocation_Bottom = 8,
DropLocation_Center = 16,
DropLocation_OutterLeft = 32,
DropLocation_OutterTop = 64,
DropLocation_OutterRight = 128,
DropLocation_OutterBottom = 256,
DropLocation_Inner = DropLocation_Left | DropLocation_Right | DropLocation_Top | DropLocation_Bottom,
DropLocation_Outter = DropLocation_OutterLeft | DropLocation_OutterRight | DropLocation_OutterTop | DropLocation_OutterBottom
};
Q_ENUM_NS(DropLocation)
///@internal
inline Qt5Qt6Compat::qhashtype qHash(SideBarLocation loc, Qt5Qt6Compat::qhashtype seed)
{
@@ -254,6 +272,7 @@ enum CursorPosition
Q_DECLARE_FLAGS(CursorPositions, CursorPosition)
Q_ENUM_NS(CursorPosition)
///@internal
enum FrameOption
{
@@ -266,6 +285,15 @@ enum FrameOption
Q_DECLARE_FLAGS(FrameOptions, FrameOption)
Q_ENUM_NS(FrameOptions)
///@internal
enum TabWidgetOption
{
TabWidgetOption_None = 0,
TabWidgetOption_DocumentMode = 1 ///> Enables QTabWidget::documentMode()
};
Q_DECLARE_FLAGS(TabWidgetOptions, TabWidgetOption)
Q_ENUM_NS(TabWidgetOptions)
///@internal
inline QString locationStr(Location loc)
{

View File

@@ -16,7 +16,7 @@ if (@KDDockWidgets_QTQUICK@)
find_dependency(Qt@Qt_VERSION_MAJOR@Quick REQUIRED)
endif()
if (NOT WIN32 AND NOT APPLE AND NOT EMSCRIPTEN AND NOT @KDDockWidgets_QT6@)
if (NOT WIN32 AND NOT APPLE AND NOT EMSCRIPTEN AND NOT @KDDockWidgets_QT6@ AND @KDDockWidgets_X11EXTRAS@)
find_dependency(Qt5X11Extras REQUIRED)
endif()

View File

@@ -22,6 +22,7 @@
#include "DockWidgetBase.h"
#include "FrameworkWidgetFactory.h"
#include "private/multisplitter/Item_p.h"
#include "private/LayoutSaver_p.h"
#include "private/DockRegistry_p.h"
#include "private/DockWidgetBase_p.h"
@@ -30,6 +31,7 @@
#include "private/LayoutWidget_p.h"
#include "private/Logging_p.h"
#include "private/Position_p.h"
#include "private/Utils_p.h"
#include <qmath.h>
#include <QDebug>
@@ -176,7 +178,7 @@ QByteArray LayoutSaver::serializeLayout() const
for (DockWidgetBase *dockWidget : dockWidgets) {
if (d->matchesAffinity(dockWidget->affinities())) {
auto dw = dockWidget->d->serialize();
dw->lastPosition = dockWidget->d->lastPositions().serialize();
dw->lastPosition = dockWidget->d->lastPosition()->serialize();
layout.allDockWidgets.push_back(dw);
}
}
@@ -289,7 +291,7 @@ bool LayoutSaver::restoreLayout(const QByteArray &data)
if (DockWidgetBase *dockWidget =
d->m_dockRegistry->dockByName(dw->uniqueName, DockRegistry::DockByNameFlag::ConsultRemapping)) {
dockWidget->d->lastPositions().deserialize(dw->lastPosition);
dockWidget->d->lastPosition()->deserialize(dw->lastPosition);
} else {
qWarning() << Q_FUNC_INFO << "Couldn't find dock widget" << dw->uniqueName;
}
@@ -339,13 +341,19 @@ void LayoutSaver::Private::deserializeWindowGeometry(const T &saved, QWidgetOrQu
// Not simply calling QWidget::setGeometry() here.
// For QtQuick we need to modify the QWindow's geometry.
auto windowGeometry = saved.geometry;
::FloatingWindow::ensureRectIsOnScreen(windowGeometry);
QRect geometry = saved.geometry;
if (!isNormalWindowState(saved.windowState)) {
// The window will be maximized. We first set its geometry to normal
// Later it's maximized and will remember this value
geometry = saved.normalGeometry;
}
::FloatingWindow::ensureRectIsOnScreen(geometry);
if (topLevel->isWindow()) {
topLevel->setGeometry(windowGeometry);
topLevel->setGeometry(geometry);
} else {
KDDockWidgets::Private::setTopLevelGeometry(windowGeometry, topLevel);
KDDockWidgets::Private::setTopLevelGeometry(geometry, topLevel);
}
topLevel->setVisible(saved.isVisible);
@@ -851,6 +859,7 @@ QVariantMap LayoutSaver::MainWindow::toVariantMap() const
map.insert(QStringLiteral("multiSplitterLayout"), multiSplitterLayout.toVariantMap());
map.insert(QStringLiteral("uniqueName"), uniqueName);
map.insert(QStringLiteral("geometry"), Layouting::rectToMap(geometry));
map.insert(QStringLiteral("normalGeometry"), Layouting::rectToMap(normalGeometry));
map.insert(QStringLiteral("screenIndex"), screenIndex);
map.insert(QStringLiteral("screenSize"), Layouting::sizeToMap(screenSize));
map.insert(QStringLiteral("isVisible"), isVisible);
@@ -872,6 +881,7 @@ void LayoutSaver::MainWindow::fromVariantMap(const QVariantMap &map)
multiSplitterLayout.fromVariantMap(map.value(QStringLiteral("multiSplitterLayout")).toMap());
uniqueName = map.value(QStringLiteral("uniqueName")).toString();
geometry = Layouting::mapToRect(map.value(QStringLiteral("geometry")).toMap());
normalGeometry = Layouting::mapToRect(map.value(QStringLiteral("normalGeometry")).toMap());
screenIndex = map.value(QStringLiteral("screenIndex")).toInt();
screenSize = Layouting::mapToSize(map.value(QStringLiteral("screenSize")).toMap());
isVisible = map.value(QStringLiteral("isVisible")).toBool();
@@ -1016,6 +1026,23 @@ void LayoutSaver::Placeholder::fromVariantMap(const QVariantMap &map)
mainWindowUniqueName = map.value(QStringLiteral("mainWindowUniqueName")).toString();
}
static QScreen *screenForMainWindow(MainWindowBase *mw)
{
// Workaround for 5.12 which doesn't have QWidget::screen().
#ifdef KDDOCKWIDGETS_QTQUICK
return mw->screen();
#endif
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
if (QWindow *window = mw->window()->windowHandle())
return window->screen();
return nullptr;
#else
return mw->screen();
#endif
}
LayoutSaver::ScalingInfo::ScalingInfo(const QString &mainWindowId, QRect savedMainWindowGeo, int screenIndex)
{
auto mainWindow = DockRegistry::self()->mainWindowByName(mainWindowId);
@@ -1034,7 +1061,7 @@ LayoutSaver::ScalingInfo::ScalingInfo(const QString &mainWindowId, QRect savedMa
return;
}
const int currentScreenIndex = qApp->screens().indexOf(mainWindow->screen());
const int currentScreenIndex = qApp->screens().indexOf(screenForMainWindow(mainWindow));
this->mainWindowName = mainWindowId;
this->savedMainWindowGeometry = savedMainWindowGeo;

103
src/MDIArea.cpp Normal file
View File

@@ -0,0 +1,103 @@
/*
This file is part of KDDockWidgets.
SPDX-FileCopyrightText: 2020-2021 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
Author: Sérgio Martins <sergio.martins@kdab.com>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
Contact KDAB at <info@kdab.com> for commercial licensing options.
*/
#include "MDIArea.h"
#include "DockWidgetBase.h"
#include "private/MDILayoutWidget_p.h"
#include "private/DropAreaWithCentralFrame_p.h"
#ifdef KDDOCKWIDGETS_QTWIDGETS
# include "DockWidget.h"
# include <QVBoxLayout>
#else
# include "DockWidgetQuick.h"
#endif
#include <QGuiApplication>
#include <QCloseEvent>
using namespace KDDockWidgets;
class MDIArea::Private
{
public:
Private(QWidgetOrQuick *parent)
: layoutWidget(new MDILayoutWidget(parent))
{
}
~Private()
{
delete layoutWidget;
}
MDILayoutWidget *const layoutWidget;
};
MDIArea::MDIArea(QWidgetOrQuick *parent)
: QWidgetAdapter(parent)
, d(new Private(this))
{
#ifdef KDDOCKWIDGETS_QTWIDGETS
auto vlay = new QVBoxLayout(this);
vlay->setContentsMargins({});
vlay->addWidget(d->layoutWidget);
#else
QWidgetAdapter::makeItemFillParent(d->layoutWidget);
#endif
}
MDIArea::~MDIArea()
{
delete d;
}
void MDIArea::addDockWidget(DockWidgetBase *dw, QPoint localPt, InitialOption addingOption)
{
if (dw->options() & DockWidgetBase::Option_MDINestable) {
// We' wrap it with a drop area, so we can drag other dock widgets over this one and dock
auto wrapperDW = new DockWidgetType(QStringLiteral("%1-mdiWrapper").arg(dw->uniqueName()));
auto dropAreaWrapper = new DropArea(wrapperDW, /*isMDIWrapper= */ true);
dropAreaWrapper->addDockWidget(dw, Location_OnBottom, nullptr);
wrapperDW->setWidget(dropAreaWrapper);
dw = wrapperDW;
}
d->layoutWidget->addDockWidget(dw, localPt, addingOption);
}
void MDIArea::moveDockWidget(DockWidgetBase *dw, QPoint pos)
{
d->layoutWidget->moveDockWidget(dw, pos);
}
void MDIArea::resizeDockWidget(DockWidgetBase *dw, QSize size)
{
d->layoutWidget->resizeDockWidget(dw, size);
}
QList<Frame *> MDIArea::frames() const
{
return d->layoutWidget->frames();
}
void MDIArea::onCloseEvent(QCloseEvent *e)
{
e->accept(); // Accepted by default (will close unless ignored)
const Frame::List frames = this->frames();
for (Frame *frame : frames) {
qApp->sendEvent(frame, e);
if (!e->isAccepted())
break; // Stop when the first frame prevents closing
}
}

67
src/MDIArea.h Normal file
View File

@@ -0,0 +1,67 @@
/*
This file is part of KDDockWidgets.
SPDX-FileCopyrightText: 2020-2021 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
Author: Sérgio Martins <sergio.martins@kdab.com>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
Contact KDAB at <info@kdab.com> for commercial licensing options.
*/
#ifndef KDDOCKWIDGETS_MDI_AREA_H
#define KDDOCKWIDGETS_MDI_AREA_H
#include "kddockwidgets/docks_export.h"
#include "KDDockWidgets.h"
#include "QWidgetAdapter.h"
#include <QWidget>
namespace KDDockWidgets {
class MDILayoutWidget;
class DockWidgetBase;
class Frame;
/**
* @brief MDIArea allows to host dock widget in MDI mode.
* This is an alternative to using a full blown MainWindowMDI.
* The use case is when you already have a MainWindow (doing normal docking) and you
* want to add an area where you want to use MDI dock widgets. Instead of docking a MainWindowMDI,
* you'd just use an MDIArea, and avoid having nested main windows.
*
* See examples/mdi_with_docking/.
*/
class DOCKS_EXPORT MDIArea : public QWidgetAdapter
{
Q_OBJECT
public:
explicit MDIArea(QWidgetOrQuick *parent = nullptr);
~MDIArea();
/// @brief docks the dock widgets into this MDI area, at the specified position
void addDockWidget(DockWidgetBase *dw, QPoint localPt, InitialOption addingOption = {});
/// @brief Moves a dock widget @p dw to point @p pos
void moveDockWidget(DockWidgetBase *dw, QPoint pos);
/// @brief Sets the size of dock widget @p dw to @p size
void resizeDockWidget(DockWidgetBase *dw, QSize size);
/// @brief Returns the list of frames in this MDI Area
/// Each Frame object represents a 'window' emebedded in the MDI Area
QList<Frame *> frames() const;
/// @brief Forwards the close event to the MDI dock widgets, aborts closing if at least one
/// dock widget doesn't allow it
void onCloseEvent(QCloseEvent *) override;
private:
class Private;
Private *const d;
};
}
#endif

View File

@@ -72,7 +72,8 @@ public:
void updateMargins()
{
m_layout->setContentsMargins(q->centerWidgetMargins());
const qreal factor = logicalDpiFactor(q);
m_layout->setContentsMargins(m_centerWidgetMargins * factor);
}
MainWindow *const q;
@@ -80,6 +81,7 @@ public:
QHash<SideBarLocation, SideBar *> m_sideBars;
MyCentralWidget *const m_centralWidget;
QHBoxLayout *const m_layout;
QMargins m_centerWidgetMargins = { 1, 5, 1, 1 };
};
MyCentralWidget::~MyCentralWidget()
@@ -139,11 +141,23 @@ void MainWindow::resizeEvent(QResizeEvent *ev)
QMargins MainWindow::centerWidgetMargins() const
{
const QMargins margins = { 1, 5, 1, 1 };
return margins * logicalDpiFactor(this);
return d->m_centerWidgetMargins;
}
void MainWindow::setCenterWidgetMargins(const QMargins &margins)
{
if (d->m_centerWidgetMargins == margins)
return;
d->m_centerWidgetMargins = margins;
d->updateMargins();
}
QRect MainWindow::centralAreaGeometry() const
{
return centralWidget()->geometry();
}
void MainWindow::closeEvent(QCloseEvent *ev)
{
onCloseEvent(ev);
}

View File

@@ -50,9 +50,15 @@ public:
///@brief returns the sidebar for the specified location
SideBar *sideBar(SideBarLocation) const override;
protected:
void resizeEvent(QResizeEvent *) override;
//@brief returns the margins for the contents widget
QMargins centerWidgetMargins() const override;
//@brief sets the margins for the contents widgets
void setCenterWidgetMargins(const QMargins &margins);
protected:
void closeEvent(QCloseEvent *) override;
void resizeEvent(QResizeEvent *) override;
QRect centralAreaGeometry() const override;
private:

View File

@@ -30,6 +30,7 @@
#include "private/DropAreaWithCentralFrame_p.h"
#include "private/LayoutSaver_p.h"
#include "private/DockWidgetBase_p.h"
#include "private/multisplitter/Item_p.h"
// Or we can have a createDockWidget() in the factory
#ifdef KDDOCKWIDGETS_QTQUICK
@@ -38,6 +39,8 @@
# include "DockWidget.h"
#endif
#include <QCloseEvent>
using namespace KDDockWidgets;
static LayoutWidget *createLayoutWidget(MainWindowBase *mainWindow, MainWindowOptions options)
@@ -547,7 +550,7 @@ void MainWindowBase::overlayOnSideBar(DockWidgetBase *dw)
auto frame = Config::self().frameworkWidgetFactory()->createFrame(this, FrameOption_IsOverlayed);
d->m_overlayedDockWidget = dw;
frame->addWidget(dw);
d->updateOverlayGeometry(dw->d->lastPositions().lastOverlayedGeometry(sb->location()).size());
d->updateOverlayGeometry(dw->d->lastPosition()->lastOverlayedGeometry(sb->location()).size());
frame->setAllowedResizeSides(d->allowedResizeSides(sb->location()));
frame->QWidgetAdapter::show();
@@ -576,7 +579,7 @@ void MainWindowBase::clearSideBarOverlay(bool deleteFrame)
}
const SideBarLocation loc = d->m_overlayedDockWidget->sideBarLocation();
d->m_overlayedDockWidget->d->lastPositions().setLastOverlayedGeometry(
d->m_overlayedDockWidget->d->lastPosition()->setLastOverlayedGeometry(
loc, frame->QWidgetAdapter::geometry());
frame->unoverlay();
@@ -739,6 +742,7 @@ LayoutSaver::MainWindow MainWindowBase::serialize() const
m.options = options();
m.geometry = windowGeometry();
m.normalGeometry = normalGeometry();
m.isVisible = isVisible();
m.uniqueName = uniqueName();
m.screenIndex = screenNumberForWidget(this);
@@ -789,3 +793,8 @@ QWidgetOrQuick *MainWindowBase::persistentCentralWidget() const
return nullptr;
}
void MainWindowBase::onCloseEvent(QCloseEvent *e)
{
d->m_layoutWidget->onCloseEvent(e);
}

View File

@@ -223,6 +223,7 @@ public:
QRect windowGeometry() const;
protected:
void onCloseEvent(QCloseEvent *);
void setUniqueName(const QString &uniqueName);
void onResized(QResizeEvent *); // Because QtQuick doesn't have resizeEvent()
virtual QMargins centerWidgetMargins() const = 0;

View File

@@ -0,0 +1,12 @@
/*
This file is part of KDDockWidgets.
SPDX-FileCopyrightText: 2020-2021 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
Author: Sergio Martins <sergio.martins@kdab.com>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
Contact KDAB at <info@kdab.com> for commercial licensing options.
*/
#include "../../MDIArea.h"

View File

@@ -0,0 +1,12 @@
/*
This file is part of KDDockWidgets.
SPDX-FileCopyrightText: 2020-2021 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
Author: Sergio Martins <sergio.martins@kdab.com>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
Contact KDAB at <info@kdab.com> for commercial licensing options.
*/
#include "../../../private/MDILayoutWidget_p.h"

View File

@@ -23,6 +23,7 @@
#include "Utils_p.h"
#include "WidgetResizeHandler_p.h"
#include "WindowBeingDragged_p.h"
#include "multisplitter/Item_p.h"
#include <QPointer>
#include <QDebug>
@@ -283,8 +284,9 @@ LayoutWidget *DockRegistry::layoutForItem(const Layouting::Item *item) const
bool DockRegistry::itemIsInMainWindow(const Layouting::Item *item) const
{
if (auto layout = layoutForItem(item))
return layout->isInMainWindow();
if (LayoutWidget *layout = layoutForItem(item)) {
return layout->isInMainWindow(/*honoursNesting=*/ true);
}
return false;
}
@@ -675,7 +677,7 @@ void DockRegistry::clear(const DockWidgetBase::List &dockWidgets,
for (auto dw : qAsConst(dockWidgets)) {
if (affinities.isEmpty() || affinitiesMatch(affinities, dw->affinities())) {
dw->forceClose();
dw->d->lastPositions().removePlaceholders();
dw->d->lastPosition()->removePlaceholders();
}
}
@@ -711,7 +713,7 @@ bool DockRegistry::eventFilter(QObject *watched, QEvent *event)
}
} else if (event->type() == QEvent::MouseButtonPress) {
// When clicking on a MDI Frame we raise the window
if (Frame *f = parentFrame(watched)) {
if (Frame *f = firstParentOfType<Frame>(watched)) {
if (f->isMDI())
f->raise();
}

View File

@@ -191,13 +191,13 @@ public:
*/
MainWindowBase::List mainWindowsWithAffinity(const QStringList &affinities) const;
// TODO: docs
/// @brief Returns the LayoutWidget where the specified item is in
LayoutWidget *layoutForItem(const Layouting::Item *) const;
// TODO: docs
/// @brief Returns whether the item is in a main window.
/// Nesting is honoured. (MDIArea inside DropArea inside MainWindow, for example)
bool itemIsInMainWindow(const Layouting::Item *) const;
bool affinitiesMatch(const QStringList &affinities1, const QStringList &affinities2) const;
/// @brief Returns a list of all known main window unique names

View File

@@ -80,7 +80,7 @@ public:
void addPlaceholderItem(Layouting::Item *);
///@brief returns the last position, just for tests.
LastPositions &lastPositions();
Position::Ptr &lastPosition();
void forceClose();
QPoint defaultCenterPosForFloating();
@@ -136,6 +136,17 @@ public:
/// @brief Returns the mdi layout this dock widget is in, if any.
MDILayoutWidget *mdiLayout() const;
/// @brief Returns if this is an helper DockWidget created automatically to host a drop area inside MDI
/// This is only used by the DockWidget::Option_MDINestable feature
bool isMDIWrapper() const;
/// @brief If this dock widget is an MDI wrapper (isMDIWrapper()), then returns the wrapper drop area
DropArea *mdiDropAreaWrapper() const;
/// @brief If this dock widget is inside a drop area nested in MDI then returns the wrapper dock widget
/// This goes up the hierarchy, while mdiDropAreaWrapper goes down.
DockWidgetBase *mdiDockWidgetWrapper() const;
const QString name;
QStringList affinities;
QString title;
@@ -147,7 +158,7 @@ public:
const LayoutSaverOptions layoutSaverOptions;
QAction *const toggleAction;
QAction *const floatAction;
LastPositions m_lastPositions;
Position::Ptr m_lastPosition = std::make_shared<Position>();
bool m_isPersistentCentralDockWidget = false;
bool m_processingToggleAction = false;
bool m_updatingToggleAction = false;

View File

@@ -143,6 +143,8 @@ void MinimalStateMachine::setCurrentState(State *state)
if (state)
state->onEntry();
Q_EMIT currentStateChanged();
}
}
@@ -592,26 +594,26 @@ DragController::DragController(QObject *parent)
{
qCDebug(creation) << "DragController()";
auto stateNone = new StateNone(this);
m_stateNone = new StateNone(this);
auto statepreDrag = new StatePreDrag(this);
auto stateDragging = isWayland() ? new StateDraggingWayland(this)
: new StateDragging(this);
m_stateDraggingMDI = new StateInternalMDIDragging(this);
stateNone->addTransition(this, &DragController::mousePressed, statepreDrag);
statepreDrag->addTransition(this, &DragController::dragCanceled, stateNone);
m_stateNone->addTransition(this, &DragController::mousePressed, statepreDrag);
statepreDrag->addTransition(this, &DragController::dragCanceled, m_stateNone);
statepreDrag->addTransition(this, &DragController::manhattanLengthMove, stateDragging);
statepreDrag->addTransition(this, &DragController::manhattanLengthMoveMDI, m_stateDraggingMDI);
stateDragging->addTransition(this, &DragController::dragCanceled, stateNone);
stateDragging->addTransition(this, &DragController::dropped, stateNone);
stateDragging->addTransition(this, &DragController::dragCanceled, m_stateNone);
stateDragging->addTransition(this, &DragController::dropped, m_stateNone);
m_stateDraggingMDI->addTransition(this, &DragController::dragCanceled, stateNone);
m_stateDraggingMDI->addTransition(this, &DragController::dragCanceled, m_stateNone);
m_stateDraggingMDI->addTransition(this, &DragController::mdiPopOut, stateDragging);
if (usesFallbackMouseGrabber())
enableFallbackMouseGrabber();
setCurrentState(stateNone);
setCurrentState(m_stateNone);
}
DragController *DragController::instance()
@@ -647,6 +649,11 @@ bool DragController::isInClientDrag() const
return isDragging() && !m_nonClientDrag;
}
bool DragController::isIdle() const
{
return activeState() == m_stateNone;
}
void DragController::grabMouseFor(QWidgetOrQuick *target)
{
if (isWayland())
@@ -788,6 +795,12 @@ static QWidgetOrQuick *qtTopLevelForHWND(HWND hwnd)
// Case not supported for QtQuick.
const QWidgetList widgets = qApp->topLevelWidgets();
for (QWidget *widget : widgets) {
if (!widget->windowHandle()) {
// Don't call winId on windows that don't have it, as that will force all its childrens to have it,
// and that's not very stable. a top level might not have one because it's being destroyed, or because
// it's a top-level just because it has't been reparented I guess.
continue;
}
if (hwnd == ( HWND )widget->winId()) {
return widget;
}

View File

@@ -26,6 +26,7 @@
namespace KDDockWidgets {
class StateBase;
class StateNone;
class StateInternalMDIDragging;
class DropArea;
class Draggable;
@@ -59,6 +60,9 @@ public:
State *currentState() const;
void setCurrentState(State *);
Q_SIGNALS:
void currentStateChanged();
private:
State *m_currentState = nullptr;
};
@@ -85,6 +89,7 @@ public:
bool isDragging() const;
bool isInNonClientDrag() const;
bool isInClientDrag() const;
bool isIdle() const;
void grabMouseFor(QWidgetOrQuick *);
void releaseMouse(QWidgetOrQuick *);
@@ -133,6 +138,7 @@ private:
DropArea *m_currentDropArea = nullptr;
bool m_nonClientDrag = false;
FallbackMouseGrabber *m_fallbackMouseGrabber = nullptr;
StateNone *m_stateNone = nullptr;
StateInternalMDIDragging *m_stateDraggingMDI = nullptr;
};

View File

@@ -22,8 +22,7 @@
#include "Logging_p.h"
#include "MainWindowBase.h"
#include "Utils_p.h"
// #include "indicators/AnimatedIndicators_p.h"
#include "multisplitter/Item_p.h"
#include "WindowBeingDragged_p.h"
using namespace KDDockWidgets;
@@ -34,8 +33,9 @@ using namespace KDDockWidgets;
*
* @author Sérgio Martins \<sergio.martins@kdab.com\>
*/
DropArea::DropArea(QWidgetOrQuick *parent)
DropArea::DropArea(QWidgetOrQuick *parent, bool isMDIWrapper)
: MultiSplitter(parent)
, m_isMDIWrapper(isMDIWrapper)
, m_dropIndicatorOverlay(Config::self().frameworkWidgetFactory()->createDropIndicatorOverlay(this))
{
qCDebug(creation) << "DropArea";
@@ -46,6 +46,24 @@ DropArea::DropArea(QWidgetOrQuick *parent)
qWarning() << "Dropping not implement for QtQuick on Wayland yet!";
#endif
}
if (m_isMDIWrapper) {
connect(this, &MultiSplitter::visibleWidgetCountChanged, this, [this] {
auto dw = mdiDockWidgetWrapper();
if (!dw) {
qWarning() << Q_FUNC_INFO << "Unexpected null wrapper dock widget";
return;
}
if (visibleCount() > 0) {
// The title of our MDI frame will need to change to the app name if we have more than 1 dock widget nested
Q_EMIT dw->titleChanged(dw->title());
} else {
// Our wrapeper isn't needed anymore
dw->deleteLater();
}
});
}
}
DropArea::~DropArea()
@@ -156,6 +174,11 @@ bool DropArea::hasSingleFloatingFrame() const
return frames.size() == 1 && frames.first()->isFloating();
}
bool DropArea::hasSingleFrame() const
{
return visibleCount() == 1;
}
QStringList DropArea::affinities() const
{
if (auto mw = mainWindow()) {
@@ -178,14 +201,14 @@ void DropArea::layoutParentContainerEqually(DockWidgetBase *dw)
layoutEqually(item->parentBoxContainer());
}
DropIndicatorOverlayInterface::DropLocation DropArea::hover(WindowBeingDragged *draggedWindow, QPoint globalPos)
DropLocation DropArea::hover(WindowBeingDragged *draggedWindow, QPoint globalPos)
{
if (Config::self().dropIndicatorsInhibited() || !validateAffinity(draggedWindow))
return DropIndicatorOverlayInterface::DropLocation_None;
return DropLocation_None;
if (!m_dropIndicatorOverlay) {
qWarning() << Q_FUNC_INFO << "The frontend is missing a drop indicator overlay";
return DropIndicatorOverlayInterface::DropLocation_None;
return DropLocation_None;
}
Frame *frame = frameContainingPos(globalPos); // Frame is nullptr if MainWindowOption_HasCentralFrame isn't set
@@ -194,13 +217,13 @@ DropIndicatorOverlayInterface::DropLocation DropArea::hover(WindowBeingDragged *
return m_dropIndicatorOverlay->hover(globalPos);
}
static bool isOutterLocation(DropIndicatorOverlayInterface::DropLocation location)
static bool isOutterLocation(DropLocation location)
{
switch (location) {
case DropIndicatorOverlayInterface::DropLocation_OutterLeft:
case DropIndicatorOverlayInterface::DropLocation_OutterTop:
case DropIndicatorOverlayInterface::DropLocation_OutterRight:
case DropIndicatorOverlayInterface::DropLocation_OutterBottom:
case DropLocation_OutterLeft:
case DropLocation_OutterTop:
case DropLocation_OutterRight:
case DropLocation_OutterBottom:
return true;
default:
return false;
@@ -216,7 +239,7 @@ bool DropArea::drop(WindowBeingDragged *droppedWindow, QPoint globalPos)
return false;
}
if (m_dropIndicatorOverlay->currentDropLocation() == DropIndicatorOverlayInterface::DropLocation_None) {
if (m_dropIndicatorOverlay->currentDropLocation() == DropLocation_None) {
qCDebug(hovering) << "DropArea::drop: bailing out, drop location = none";
return false;
}
@@ -236,7 +259,7 @@ bool DropArea::drop(WindowBeingDragged *droppedWindow, QPoint globalPos)
}
bool DropArea::drop(WindowBeingDragged *draggedWindow, Frame *acceptingFrame,
DropIndicatorOverlayInterface::DropLocation droploc)
DropLocation droploc)
{
FloatingWindow *droppedWindow = draggedWindow ? draggedWindow->floatingWindow()
: nullptr;
@@ -247,7 +270,8 @@ bool DropArea::drop(WindowBeingDragged *draggedWindow, Frame *acceptingFrame,
// With Wayland we delay the floating window until we drop it.
// Ofc, we could just dock the dockwidget without the temporary FloatingWindow, but this way we reuse
// 99% of the rest of the code, without adding more wayland special cases
droppedWindow = draggedWindow->draggable()->makeWindow()->floatingWindow();
droppedWindow = draggedWindow ? draggedWindow->draggable()->makeWindow()->floatingWindow()
: nullptr;
if (!droppedWindow) {
// Doesn't happen
qWarning() << Q_FUNC_INFO << "Wayland: Expected window" << draggedWindow;
@@ -263,19 +287,19 @@ bool DropArea::drop(WindowBeingDragged *draggedWindow, Frame *acceptingFrame,
// variable isn't used
switch (droploc) {
case DropIndicatorOverlayInterface::DropLocation_Left:
case DropIndicatorOverlayInterface::DropLocation_Top:
case DropIndicatorOverlayInterface::DropLocation_Bottom:
case DropIndicatorOverlayInterface::DropLocation_Right:
case DropLocation_Left:
case DropLocation_Top:
case DropLocation_Bottom:
case DropLocation_Right:
result = drop(droppedWindow, DropIndicatorOverlayInterface::multisplitterLocationFor(droploc), acceptingFrame);
break;
case DropIndicatorOverlayInterface::DropLocation_OutterLeft:
case DropIndicatorOverlayInterface::DropLocation_OutterTop:
case DropIndicatorOverlayInterface::DropLocation_OutterRight:
case DropIndicatorOverlayInterface::DropLocation_OutterBottom:
case DropLocation_OutterLeft:
case DropLocation_OutterTop:
case DropLocation_OutterRight:
case DropLocation_OutterBottom:
result = drop(droppedWindow, DropIndicatorOverlayInterface::multisplitterLocationFor(droploc), nullptr);
break;
case DropIndicatorOverlayInterface::DropLocation_Center:
case DropLocation_Center:
qCDebug(hovering) << "Tabbing" << droppedWindow << "into" << acceptingFrame;
if (!validateAffinity(droppedWindow, acceptingFrame))
return false;
@@ -361,3 +385,16 @@ bool DropArea::validateAffinity(T *window, Frame *acceptingFrame) const
return true;
}
bool DropArea::isMDIWrapper() const
{
return m_isMDIWrapper;
}
DockWidgetBase *DropArea::mdiDockWidgetWrapper() const
{
if (m_isMDIWrapper)
return qobject_cast<DockWidgetBase *>(QWidgetAdapter::parent());
return nullptr;
}

View File

@@ -41,11 +41,11 @@ class DOCKS_EXPORT DropArea : public MultiSplitter
{
Q_OBJECT
public:
explicit DropArea(QWidgetOrQuick *parent);
explicit DropArea(QWidgetOrQuick *parent, bool isMDIWrapper = false);
~DropArea();
void removeHover();
DropIndicatorOverlayInterface::DropLocation hover(WindowBeingDragged *draggedWindow, QPoint globalPos);
DropLocation hover(WindowBeingDragged *draggedWindow, QPoint globalPos);
///@brief Called when a user drops a widget via DND
bool drop(WindowBeingDragged *droppedWindow, QPoint globalPos);
Frame::List frames() const;
@@ -64,9 +64,19 @@ public:
/// Implies it's in a FloatingWindow and that it has only one dock widget
bool hasSingleFloatingFrame() const;
/// Returns whether this drop area has only 1 frame.
/// See further explanation in FloatingWindow::hasSingleFrame()
bool hasSingleFrame() const;
QStringList affinities() const;
void layoutParentContainerEqually(DockWidgetBase *);
/// When DockWidget::Option_MDINestable is used, docked MDI dock widgets will be wrapped inside a DropArea, so they accept drops
/// This DropArea is created implicitly while docking, and this function will return true
bool isMDIWrapper() const;
/// Returns the helper dock widget for implementing DockWidget::Option_MDINestable.
DockWidgetBase *mdiDockWidgetWrapper() const;
private:
Q_DISABLE_COPY(DropArea)
friend class Frame;
@@ -77,12 +87,13 @@ private:
template<typename T>
bool validateAffinity(T *, Frame *acceptingFrame = nullptr) const;
bool drop(WindowBeingDragged *draggedWindow, Frame *acceptingFrame, DropIndicatorOverlayInterface::DropLocation);
bool drop(WindowBeingDragged *draggedWindow, Frame *acceptingFrame, DropLocation);
bool drop(QWidgetOrQuick *droppedwindow, KDDockWidgets::Location location, Frame *relativeTo);
Frame *frameContainingPos(QPoint globalPos) const;
void updateFloatingActions();
bool m_inDestructor = false;
const bool m_isMDIWrapper;
QString m_affinityName;
DropIndicatorOverlayInterface *m_dropIndicatorOverlay = nullptr;
};

View File

@@ -14,6 +14,8 @@
#include "Frame_p.h"
#include "DropArea_p.h"
#include "DockRegistry_p.h"
#include "DragController_p.h"
#include "Config.h"
using namespace KDDockWidgets;
@@ -24,6 +26,9 @@ DropIndicatorOverlayInterface::DropIndicatorOverlayInterface(DropArea *dropArea)
setVisible(false);
setObjectName(QStringLiteral("DropIndicatorOverlayInterface"));
// Set transparent for mouse events so that topLevel->childAt() never returns the drop indicator overlay
setAttribute(Qt::WA_TransparentForMouseEvents);
connect(DockRegistry::self(), &DockRegistry::dropIndicatorsInhibitedChanged, this,
[this](bool inhibited) {
if (inhibited)
@@ -81,35 +86,88 @@ bool DropIndicatorOverlayInterface::isHovered() const
return m_draggedWindowIsHovering;
}
DropIndicatorOverlayInterface::DropLocation DropIndicatorOverlayInterface::currentDropLocation() const
DropLocation DropIndicatorOverlayInterface::currentDropLocation() const
{
return m_currentDropLocation;
}
KDDockWidgets::Location DropIndicatorOverlayInterface::multisplitterLocationFor(DropIndicatorOverlayInterface::DropLocation dropLoc)
KDDockWidgets::Location DropIndicatorOverlayInterface::multisplitterLocationFor(DropLocation dropLoc)
{
switch (dropLoc) {
case KDDockWidgets::DropIndicatorOverlayInterface::DropLocation_None:
case DropLocation_None:
return KDDockWidgets::Location_None;
case KDDockWidgets::DropIndicatorOverlayInterface::DropLocation_Left:
case KDDockWidgets::DropIndicatorOverlayInterface::DropLocation_OutterLeft:
case DropLocation_Left:
case DropLocation_OutterLeft:
return KDDockWidgets::Location_OnLeft;
case KDDockWidgets::DropIndicatorOverlayInterface::DropLocation_OutterTop:
case KDDockWidgets::DropIndicatorOverlayInterface::DropLocation_Top:
case DropLocation_OutterTop:
case DropLocation_Top:
return KDDockWidgets::Location_OnTop;
case KDDockWidgets::DropIndicatorOverlayInterface::DropLocation_OutterRight:
case KDDockWidgets::DropIndicatorOverlayInterface::DropLocation_Right:
case DropLocation_OutterRight:
case DropLocation_Right:
return KDDockWidgets::Location_OnRight;
case KDDockWidgets::DropIndicatorOverlayInterface::DropLocation_OutterBottom:
case KDDockWidgets::DropIndicatorOverlayInterface::DropLocation_Bottom:
case DropLocation_OutterBottom:
case DropLocation_Bottom:
return KDDockWidgets::Location_OnBottom;
case KDDockWidgets::DropIndicatorOverlayInterface::DropLocation_Center:
case DropLocation_Center:
return KDDockWidgets::Location_None;
case DropLocation_Inner:
case DropLocation_Outter:
qWarning() << Q_FUNC_INFO << "Unexpected drop location" << dropLoc;
break;
}
return KDDockWidgets::Location_None;
}
bool DropIndicatorOverlayInterface::dropIndicatorVisible(DropLocation dropLoc) const
{
if (dropLoc == DropLocation_None)
return false;
WindowBeingDragged *windowBeingDragged = DragController::instance()->windowBeingDragged();
if (!windowBeingDragged)
return false;
const DockWidgetBase::List source = windowBeingDragged->dockWidgets();
const DockWidgetBase::List target = m_hoveredFrame ? m_hoveredFrame->dockWidgets()
: DockWidgetBase::List();
const bool isInner = dropLoc & DropLocation_Inner;
const bool isOutter = dropLoc & DropLocation_Outter;
if (isInner) {
if (!m_hoveredFrame)
return false;
} else if (isOutter) {
// If there's only 1 frame in the layout, the outer indicators are redundant, as they do the same thing as the internal ones.
// But there might be another window obscuring our target, so it's useful to show the outer indicators in this case
const bool isTheOnlyFrame = m_hoveredFrame && m_hoveredFrame->isTheOnlyFrame();
if (isTheOnlyFrame && !DockRegistry::self()->isProbablyObscured(m_hoveredFrame->window()->windowHandle(), windowBeingDragged))
return false;
} else if (dropLoc == DropLocation_Center) {
if (!m_hoveredFrame)
return false;
if (auto tabbingAllowedFunc = Config::self().tabbingAllowedFunc()) {
if (!tabbingAllowedFunc(source, target))
return false;
}
// Only allow to dock to center if the affinities match
if (!DockRegistry::self()->affinitiesMatch(m_hoveredFrame->affinities(), windowBeingDragged->affinities()) && m_hoveredFrame->isDockable())
return false;
} else {
qWarning() << Q_FUNC_INFO << "Unknown drop indicator location" << dropLoc;
return false;
}
if (auto dropIndicatorAllowedFunc = Config::self().dropIndicatorAllowedFunc()) {
if (!dropIndicatorAllowedFunc(dropLoc, source, target))
return false;
}
return true;
}
void DropIndicatorOverlayInterface::onFrameDestroyed()
{
setHoveredFrame(nullptr);
@@ -119,7 +177,7 @@ void DropIndicatorOverlayInterface::onHoveredFrameChanged(Frame *)
{
}
void DropIndicatorOverlayInterface::setCurrentDropLocation(DropIndicatorOverlayInterface::DropLocation location)
void DropIndicatorOverlayInterface::setCurrentDropLocation(DropLocation location)
{
if (m_currentDropLocation != location) {
m_currentDropLocation = location;
@@ -127,7 +185,7 @@ void DropIndicatorOverlayInterface::setCurrentDropLocation(DropIndicatorOverlayI
}
}
DropIndicatorOverlayInterface::DropLocation DropIndicatorOverlayInterface::hover(QPoint globalPos)
DropLocation DropIndicatorOverlayInterface::hover(QPoint globalPos)
{
return hover_impl(globalPos);
}
@@ -143,5 +201,5 @@ void DropIndicatorOverlayInterface::setHoveredFrameRect(QRect rect)
void DropIndicatorOverlayInterface::removeHover()
{
setWindowBeingDragged(false);
setCurrentDropLocation(DropIndicatorOverlayInterface::DropLocation_None);
setCurrentDropLocation(DropLocation_None);
}

View File

@@ -25,25 +25,8 @@ class DOCKS_EXPORT DropIndicatorOverlayInterface : public QWidgetAdapter
{
Q_OBJECT
Q_PROPERTY(QRect hoveredFrameRect READ hoveredFrameRect NOTIFY hoveredFrameRectChanged)
Q_PROPERTY(KDDockWidgets::DropIndicatorOverlayInterface::DropLocation currentDropLocation READ currentDropLocation NOTIFY currentDropLocationChanged)
Q_PROPERTY(KDDockWidgets::DropLocation currentDropLocation READ currentDropLocation NOTIFY currentDropLocationChanged)
public:
enum DropLocation
{
DropLocation_None = 0,
DropLocation_Left,
DropLocation_Top,
DropLocation_Right,
DropLocation_Bottom,
DropLocation_Center,
DropLocation_OutterLeft,
DropLocation_OutterTop,
DropLocation_OutterRight,
DropLocation_OutterBottom,
DropLocation_First = DropLocation_Left,
DropLocation_Last = DropLocation_OutterBottom,
};
Q_ENUM(DropLocation)
explicit DropIndicatorOverlayInterface(DropArea *dropArea);
void setHoveredFrame(Frame *);
@@ -55,9 +38,9 @@ public:
{
return m_hoveredFrame;
}
void setCurrentDropLocation(DropIndicatorOverlayInterface::DropLocation location);
void setCurrentDropLocation(DropLocation location);
KDDockWidgets::DropIndicatorOverlayInterface::DropLocation hover(QPoint globalPos);
KDDockWidgets::DropLocation hover(QPoint globalPos);
/// Clears and hides drop indicators
void removeHover();
@@ -66,6 +49,9 @@ public:
/// The return is in global coordinates
virtual QPoint posForIndicator(DropLocation) const = 0;
/// @brief Returns whether the specified drop indicator should be visible
virtual bool dropIndicatorVisible(DropLocation) const;
static KDDockWidgets::Location multisplitterLocationFor(DropLocation);
Q_SIGNALS:
@@ -80,7 +66,7 @@ private:
DropLocation m_currentDropLocation = DropLocation_None;
protected:
virtual DropIndicatorOverlayInterface::DropLocation hover_impl(QPoint globalPos) = 0;
virtual DropLocation hover_impl(QPoint globalPos) = 0;
virtual void onHoveredFrameChanged(Frame *);
virtual void updateVisibility() {};

View File

@@ -22,6 +22,9 @@
#include "FrameworkWidgetFactory.h"
#include "DragController_p.h"
#include "LayoutSaver_p.h"
#include "DockWidgetBase_p.h"
#include "multisplitter/Item_p.h"
#include <QCloseEvent>
#include <QScopedValueRollback>
@@ -144,12 +147,52 @@ FloatingWindow::FloatingWindow(QRect suggestedGeometry, MainWindowBase *parent)
FloatingWindow::FloatingWindow(Frame *frame, QRect suggestedGeometry, MainWindowBase *parent)
: FloatingWindow(suggestedGeometry, hackFindParentHarder(frame, parent))
{
m_disableSetVisible = true;
// Adding a widget will trigger onFrameCountChanged, which triggers a setVisible(true).
// The problem with setVisible(true) will forget about or requested geometry and place the window at 0,0
// So disable the setVisible(true) call while in the ctor.
m_dropArea->addWidget(frame, KDDockWidgets::Location_OnTop, {});
m_disableSetVisible = false;
QScopedValueRollback<bool> guard(m_disableSetVisible, true);
if (frame->hasNestedMDIDockWidgets()) {
// When using DockWidget::MDINestable, the docked MDI widget is wrapped by a drop area so we can drop things into it.
// When floating it, we can delete that helper drop area, as FloatingWindow already has one
if (frame->dockWidgetCount() == 0) {
// doesn't happen
qWarning() << Q_FUNC_INFO << "Unexpected empty frame";
return;
}
DockWidgetBase *dwMDIWrapper = frame->dockWidgetAt(0);
DropArea *dropAreaMDIWrapper = dwMDIWrapper->d->mdiDropAreaWrapper();
if (dropAreaMDIWrapper->hasSingleFrame()) {
Frame *innerFrame = dropAreaMDIWrapper->frames().constFirst();
if (innerFrame->hasSingleDockWidget()) {
// When pressing the unfloat button, the dock widgets gets docked to the previous
// position it was at. DockWidgetBase::Private::m_lastPosition stores that location,
// however, when having nested MDI, we have an extra Dock Widget, the wrapper, and it
// contains the last position. So, when floating, we need to transfer that and not lose it.
DockWidgetBase *dw = innerFrame->dockWidgetAt(0);
dw->d->lastPosition() = dwMDIWrapper->d->lastPosition();
}
}
m_dropArea->addMultiSplitter(dropAreaMDIWrapper, Location_OnTop);
dwMDIWrapper->setVisible(false);
if (!DragController::instance()->isIdle()) {
// We're dragging a MDI window and we reached the border, detaching it, and making it float. We can't delete the wrapper frame just yet,
// as that would delete the title bar which is currently being dragged. Delete it once the drag finishes
connect(DragController::instance(), &DragController::currentStateChanged, dwMDIWrapper, [dwMDIWrapper] {
if (DragController::instance()->isIdle())
delete dwMDIWrapper;
});
} else {
dwMDIWrapper->deleteLater();
}
} else {
// Adding a widget will trigger onFrameCountChanged, which triggers a setVisible(true).
// The problem with setVisible(true) will forget about or requested geometry and place the window at 0,0
// So disable the setVisible(true) call while in the ctor.
m_dropArea->addWidget(frame, KDDockWidgets::Location_OnTop, {});
}
}
FloatingWindow::~FloatingWindow()
@@ -321,7 +364,7 @@ bool FloatingWindow::anyNonDockable() const
bool FloatingWindow::hasSingleFrame() const
{
return m_dropArea->visibleCount() == 1;
return m_dropArea->hasSingleFrame();
}
bool FloatingWindow::hasSingleDockWidget() const
@@ -451,14 +494,7 @@ void FloatingWindow::onCloseEvent(QCloseEvent *e)
return;
}
e->accept(); // Accepted by default (will close unless ignored)
const Frame::List frames = this->frames();
for (Frame *frame : frames) {
qApp->sendEvent(frame, e);
if (!e->isAccepted())
break; // Stop when the first frame prevents closing
}
m_dropArea->onCloseEvent(e);
}
bool FloatingWindow::deserialize(const LayoutSaver::FloatingWindow &fw)
@@ -466,12 +502,6 @@ bool FloatingWindow::deserialize(const LayoutSaver::FloatingWindow &fw)
if (dropArea()->deserialize(fw.multiSplitterLayout)) {
updateTitleBarVisibility();
if (fw.normalGeometry.isValid() && !isNormalWindowState(fw.windowState)) {
// Restore QWidgetPrivate's normalGeometry (no public API in QWidget)
setNormalGeometry(fw.normalGeometry);
}
// And show it:
if (fw.windowState & Qt::WindowMaximized) {
showMaximized();
} else if (fw.windowState & Qt::WindowMinimized) {

View File

@@ -32,6 +32,7 @@
#include "WidgetResizeHandler_p.h"
#include "MDILayoutWidget_p.h"
#include "DropAreaWithCentralFrame_p.h"
#include "multisplitter/Item_p.h"
#include <QCloseEvent>
#include <QTimer>
@@ -50,12 +51,22 @@ static FrameOptions actualOptions(FrameOptions options)
return options;
}
static TabWidgetOptions tabWidgetOptions(FrameOptions options)
{
if (options & FrameOption_NonDockable) {
/// If we can't tab things into this Frame then let's not draw the QTabWidget frame either
return TabWidgetOption_DocumentMode;
}
return TabWidgetOption_None;
}
}
Frame::Frame(QWidgetOrQuick *parent, FrameOptions options, int userType)
: LayoutGuestWidget(parent)
, FocusScope(this)
, m_tabWidget(Config::self().frameworkWidgetFactory()->createTabWidget(this))
, m_tabWidget(Config::self().frameworkWidgetFactory()->createTabWidget(this, tabWidgetOptions(options)))
, m_titleBar(Config::self().frameworkWidgetFactory()->createTitleBar(this))
, m_options(actualOptions(options))
, m_userType(userType)
@@ -330,6 +341,9 @@ void Frame::updateTitleBarVisibility()
} else if (FloatingWindow *fw = floatingWindow()) {
// If there's nested frames then show each Frame's title bar
visible = !fw->hasSingleFrame();
} else if (isMDIWrapper()) {
auto dropArea = this->mdiDropAreaWrapper();
visible = !dropArea->hasSingleFrame();
} else {
visible = true;
}
@@ -373,6 +387,10 @@ TitleBar *Frame::actualTitleBar() const
// If there's nested frames then show each Frame's title bar
if (fw->hasSingleFrame())
return fw->titleBar();
} else if (auto mdiDropArea = mdiDropAreaWrapper()) {
if (mdiDropArea->hasSingleFrame()) {
return mdiFrame()->titleBar();
}
}
return titleBar();
@@ -536,7 +554,7 @@ void Frame::setLayoutItem(Layouting::Item *item)
dw->d->addPlaceholderItem(item);
} else {
for (DockWidgetBase *dw : dockWidgets())
dw->d->lastPositions().removePlaceholders();
dw->d->lastPosition()->removePlaceholders();
}
}
@@ -620,7 +638,7 @@ void Frame::unoverlay()
bool Frame::isFloating() const
{
if (isInMainWindow())
if (isInMainWindow() || isMDI())
return false;
return isTheOnlyFrame();
@@ -836,11 +854,55 @@ bool Frame::isMDI() const
return mdiLayoutWidget() != nullptr;
}
bool Frame::isMDIWrapper() const
{
return mdiDropAreaWrapper() != nullptr;
}
Frame *Frame::mdiFrame() const
{
if (auto dwWrapper = mdiDockWidgetWrapper()) {
return dwWrapper->d->frame();
}
return nullptr;
}
DockWidgetBase *Frame::mdiDockWidgetWrapper() const
{
if (auto dropArea = mdiDropAreaWrapper()) {
return qobject_cast<DockWidgetBase *>(dropArea->QWidgetAdapter::parent());
}
return nullptr;
}
DropArea *Frame::mdiDropAreaWrapper() const
{
auto dropArea = qobject_cast<DropArea *>(QWidgetAdapter::parent());
if (dropArea && dropArea->isMDIWrapper())
return dropArea;
return nullptr;
}
MDILayoutWidget *Frame::mdiLayoutWidget() const
{
return qobject_cast<MDILayoutWidget *>(m_layoutWidget);
}
bool Frame::hasNestedMDIDockWidgets() const
{
if (!isMDI() || dockWidgetCount() == 0)
return false;
if (dockWidgetCount() != 1) {
qWarning() << Q_FUNC_INFO << "Expected a single dock widget wrapper as frame child";
return false;
}
return dockWidgetAt(0)->d->isMDIWrapper();
}
int Frame::userType() const
{
return m_userType;

View File

@@ -285,9 +285,27 @@ public:
/// Usually no, unless you're using an MDI main window
bool isMDI() const;
/// @brief Returns whether this frame was created automatically just for the purpose of supporting DockWidget::Option_MDINestable
bool isMDIWrapper() const;
/// @brief If this is an MDI wrapper frame, return the DockWidget MDI wrapper
/// @sa isMDIWrapper
DockWidgetBase *mdiDockWidgetWrapper() const;
/// @brief If this is an MDI wrapper frame, return the DropArea MDI wrapper
/// @sa isMDIWrapper
DropArea *mdiDropAreaWrapper() const;
/// @brief If this frame is an MDI wrapper, returns the MDI frame. That is the frame you actually drag inside the MDI area
Frame *mdiFrame() const;
/// @brief Returns the MDI layout. Or nullptr if this frame isn't in a MDI layout
MDILayoutWidget *mdiLayoutWidget() const;
/// @brief If this frame is a MDI frame (isMDI() == true), returns whether it contains nested dock widgets (DockWidget::Option_MDINestable)
/// @sa isMDI()
bool hasNestedMDIDockWidgets() const;
/// @brief See DockWidgetBase::userType()
int userType() const;

View File

@@ -284,6 +284,7 @@ public:
QString uniqueName;
QStringList affinities;
QRect geometry;
QRect normalGeometry;
int screenIndex;
QSize screenSize; // for relative-size restoring
bool isVisible;

View File

@@ -17,6 +17,9 @@
#include "FrameworkWidgetFactory.h"
#include "MainWindowBase.h"
#include "Position_p.h"
#include "Utils_p.h"
#include "multisplitter/Item_p.h"
using namespace KDDockWidgets;
@@ -33,21 +36,30 @@ LayoutWidget::~LayoutWidget()
DockRegistry::self()->unregisterLayout(this);
}
bool LayoutWidget::isInMainWindow() const
bool LayoutWidget::isInMainWindow(bool honourNesting) const
{
return mainWindow() != nullptr;
return mainWindow(honourNesting) != nullptr;
}
MainWindowBase *LayoutWidget::mainWindow() const
MainWindowBase *LayoutWidget::mainWindow(bool honourNesting) const
{
if (auto pw = QWidgetAdapter::parentWidget()) {
// Note that if pw is a FloatingWindow then pw->parentWidget() can be a MainWindow too, as
// it's parented
if (pw->objectName() == QLatin1String("MyCentralWidget"))
return qobject_cast<MainWindowBase *>(pw->parentWidget());
// QtQuick doesn't support nesting yet
honourNesting = honourNesting && kddwUsesQtWidgets();
if (auto mw = qobject_cast<MainWindowBase *>(pw))
return mw;
if (honourNesting) {
// This layout might be a MDIArea, nested in DropArea, which is main window.
return firstParentOfType<MainWindowBase>(this);
} else {
if (auto pw = QWidgetAdapter::parentWidget()) {
// Note that if pw is a FloatingWindow then pw->parentWidget() can be a MainWindow too, as
// it's parented
if (pw->objectName() == QLatin1String("MyCentralWidget"))
return qobject_cast<MainWindowBase *>(pw->parentWidget());
if (auto mw = qobject_cast<MainWindowBase *>(pw))
return mw;
}
}
return nullptr;
@@ -129,7 +141,7 @@ void LayoutWidget::unrefOldPlaceholders(const Frame::List &framesBeingAdded) con
{
for (Frame *frame : framesBeingAdded) {
for (DockWidgetBase *dw : frame->dockWidgets()) {
dw->d->lastPositions().removePlaceholders(this);
dw->d->lastPosition()->removePlaceholders(this);
}
}
}
@@ -290,3 +302,15 @@ LayoutSaver::MultiSplitter LayoutWidget::serialize() const
return l;
}
void LayoutWidget::onCloseEvent(QCloseEvent *e)
{
e->accept(); // Accepted by default (will close unless ignored)
const Frame::List frames = this->frames();
for (Frame *frame : frames) {
qApp->sendEvent(frame, e);
if (!e->isAccepted())
break; // Stop when the first frame prevents closing
}
}

View File

@@ -33,6 +33,7 @@
namespace Layouting {
class Item;
class ItemContainer;
class Separator;
class Widget_qwidget;
}
@@ -62,8 +63,13 @@ public:
explicit LayoutWidget(QWidgetOrQuick *parent = nullptr);
~LayoutWidget() override;
bool isInMainWindow() const;
MainWindowBase *mainWindow() const;
/// @brief Returns whether this layout is in a MainWindow
/// @param honourNesting If true, then we'll count DropAreas/MDIAreas which are nested into DropAreas/MDIAreas as inside the main window.
/// otherwise, only direct parenting is considered
bool isInMainWindow(bool honourNesting = false) const;
MainWindowBase *mainWindow(bool honourNesting = false) const;
FloatingWindow *floatingWindow() const;
/**
@@ -183,6 +189,8 @@ public:
virtual bool deserialize(const LayoutSaver::MultiSplitter &);
LayoutSaver::MultiSplitter serialize() const;
void onCloseEvent(QCloseEvent *) override;
protected:
void setRootItem(Layouting::ItemContainer *root);
/**

View File

@@ -107,7 +107,8 @@ void MDILayoutWidget::resizeDockWidget(Frame *frame, QSize size)
Layouting::Item *item = itemForFrame(frame);
if (!item) {
qWarning() << Q_FUNC_INFO << "Frame not found in the layout" << frame;
qWarning() << Q_FUNC_INFO << "Frame not found in the layout" << frame << frame->isMDI()
<< frame->isMDIWrapper();
return;
}

View File

@@ -36,7 +36,7 @@ public:
~MDILayoutWidget() override;
/// @brief docks the dock widgets into this MDI area, at the specified position
void addDockWidget(DockWidgetBase *dw, QPoint localPt, InitialOption addingOption);
void addDockWidget(DockWidgetBase *dw, QPoint localPt, InitialOption addingOption = {});
/// @brief Moves a dock widget @p dw to point @p pos
void moveDockWidget(DockWidgetBase *dw, QPoint pos);

View File

@@ -32,6 +32,7 @@
#include "Position_p.h"
#include "WindowBeingDragged_p.h"
#include "multisplitter/Widget.h"
#include "multisplitter/Item_p.h"
#include <QScopedValueRollback>

View File

@@ -20,6 +20,7 @@
#include "FloatingWindow_p.h"
#include "LayoutSaver_p.h"
#include "LayoutWidget_p.h"
#include "multisplitter/Item_p.h"
#include <algorithm>
@@ -47,7 +48,7 @@ void Position::addPlaceholderItem(Layouting::Item *placeholder)
removeNonMainWindowPlaceholders();
}
// Make sure our list only contains valid placeholders. We save the result so we can disconnect from the lambda, since the Item might outlive LastPosition
// Make sure our list only contains valid placeholders. We save the result so we can disconnect from the lambda, since the Item might outlive Position
QMetaObject::Connection connection = QObject::connect(placeholder, &QObject::destroyed, placeholder, [this, placeholder] {
removePlaceholder(placeholder);
});
@@ -66,7 +67,7 @@ Layouting::Item *Position::layoutItem() const
// In the future we might want to restore it to FloatingWindows.
for (const auto &itemref : m_placeholders) {
if (DockRegistry::self()->itemIsInMainWindow(itemref->item))
if (itemref->isInMainWindow())
return itemref->item;
}
@@ -101,7 +102,7 @@ void Position::removeNonMainWindowPlaceholders()
auto it = m_placeholders.begin();
while (it != m_placeholders.end()) {
ItemRef *itemref = it->get();
if (!DockRegistry::self()->itemIsInMainWindow(itemref->item))
if (!itemref->isInMainWindow())
it = m_placeholders.erase(it);
else
++it;
@@ -121,6 +122,9 @@ void Position::removePlaceholder(Layouting::Item *placeholder)
void Position::deserialize(const LayoutSaver::Position &lp)
{
m_lastFloatingGeometry = lp.lastFloatingGeometry;
m_lastOverlayedGeometries = lp.lastOverlayedGeometries;
for (const auto &placeholder : qAsConst(lp.placeholders)) {
LayoutWidget *layout;
int itemIndex = placeholder.itemIndex;
@@ -190,6 +194,9 @@ LayoutSaver::Position Position::serialize() const
l.tabIndex = m_tabIndex;
l.wasFloating = m_wasFloating;
l.lastFloatingGeometry = lastFloatingGeometry();
l.lastOverlayedGeometries = m_lastOverlayedGeometries;
return l;
}
@@ -209,17 +216,7 @@ ItemRef::~ItemRef()
}
}
LayoutSaver::Position LastPositions::serialize()
bool ItemRef::isInMainWindow() const
{
LayoutSaver::Position result = lastPosition->serialize();
result.lastFloatingGeometry = lastFloatingGeometry();
result.lastOverlayedGeometries = m_lastOverlayedGeometries;
return result;
}
void LastPositions::deserialize(const LayoutSaver::Position &p)
{
m_lastFloatingGeometry = p.lastFloatingGeometry;
m_lastOverlayedGeometries = p.lastOverlayedGeometries;
lastPosition->deserialize(p);
return DockRegistry::self()->itemIsInMainWindow(item);
}

View File

@@ -41,9 +41,11 @@ class LayoutWidget;
// Just a RAII class so we don't forget to unref
struct ItemRef
{
ItemRef(const QMetaObject::Connection &conn, Layouting::Item *it);
explicit ItemRef(const QMetaObject::Connection &, Layouting::Item *);
~ItemRef();
bool isInMainWindow() const;
Layouting::Item *const item;
const QPointer<Layouting::Item> guard;
const QMetaObject::Connection connection;
@@ -106,6 +108,7 @@ public:
bool containsPlaceholder(Layouting::Item *) const;
void removePlaceholders();
/// @brief Returns the last places where the dock widget was or is
const std::vector<std::unique_ptr<ItemRef>> &placeholders() const
{
return m_placeholders;
@@ -120,26 +123,10 @@ public:
///@brief removes the Item @p placeholder
void removePlaceholder(Layouting::Item *placeholder);
private:
friend inline QDebug operator<<(QDebug, const KDDockWidgets::Position::Ptr &);
// The last places where this dock widget was (or is), so it can be restored when setFloating(false) or show() is called.
std::vector<std::unique_ptr<ItemRef>> m_placeholders;
bool m_clearing = false; // to prevent re-entrancy
};
struct LastPositions
{
// TODO: Support multiple old positions, one per main window
bool isValid() const
void saveTabIndex(int tabIndex, bool isFloating)
{
return lastPosition->isValid();
}
void addPosition(Layouting::Item *item)
{
lastPosition->addPlaceholderItem(item);
m_tabIndex = tabIndex;
m_wasFloating = isFloating;
}
void setLastFloatingGeometry(QRect geo)
@@ -149,7 +136,7 @@ struct LastPositions
bool wasFloating() const
{
return lastPosition->m_wasFloating;
return m_wasFloating;
}
QRect lastFloatingGeometry() const
@@ -157,6 +144,16 @@ struct LastPositions
return m_lastFloatingGeometry;
}
Layouting::Item *lastItem() const
{
return layoutItem();
}
int lastTabIndex() const
{
return m_tabIndex;
}
QRect lastOverlayedGeometry(SideBarLocation loc) const
{
return m_lastOverlayedGeometries.value(loc);
@@ -167,47 +164,14 @@ struct LastPositions
m_lastOverlayedGeometries[loc] = rect;
}
LayoutSaver::Position serialize();
void deserialize(const LayoutSaver::Position &p);
Layouting::Item *lastItem() const
{
return lastPosition->layoutItem();
}
Layouting::Item::List layoutItems() const
{
Layouting::Item::List items;
return items;
}
void saveTabIndex(int tabIndex, bool isFloating)
{
lastPosition->m_tabIndex = tabIndex;
lastPosition->m_wasFloating = isFloating;
}
void removePlaceholders() const
{
lastPosition->removePlaceholders();
}
void removePlaceholders(const LayoutWidget *hostWidget) const
{
lastPosition->removePlaceholders(hostWidget);
}
int lastTabIndex() const
{
return lastPosition->m_tabIndex;
}
private:
friend inline QDebug operator<<(QDebug, const KDDockWidgets::Position::Ptr &);
// The last places where this dock widget was (or is), so it can be restored when setFloating(false) or show() is called.
std::vector<std::unique_ptr<ItemRef>> m_placeholders;
QRect m_lastFloatingGeometry;
QHash<SideBarLocation, QRect> m_lastOverlayedGeometries;
friend inline QDebug operator<<(QDebug d, const KDDockWidgets::LastPositions &);
Position::Ptr lastPosition = std::make_shared<Position>();
bool m_clearing = false; // to prevent re-entrancy
};
inline QDebug operator<<(QDebug d, const KDDockWidgets::Position::Ptr &p)
@@ -219,12 +183,6 @@ inline QDebug operator<<(QDebug d, const KDDockWidgets::Position::Ptr &p)
return d;
}
inline QDebug operator<<(QDebug d, const KDDockWidgets::LastPositions &p)
{
d << p.lastPosition << "; lastFloatingGeometry=" << p.m_lastFloatingGeometry;
return d;
}
}
#endif

View File

@@ -18,6 +18,8 @@
#include "FrameworkWidgetFactory.h"
#include "Config.h"
#include "MainWindowBase.h"
#include "MDILayoutWidget_p.h"
#include "TabWidget_p.h"
#include <QTimer>
#include <QWindowStateChangeEvent>
@@ -100,8 +102,24 @@ MainWindowBase *TitleBar::mainWindow() const
bool TitleBar::isMDI() const
{
if (auto mw = mainWindow())
return mw->isMDI();
QObject *p = const_cast<TitleBar *>(this);
while (p) {
if (qobject_cast<const QWindow*>(p)) {
// Ignore QObject hierarchies spanning though multiple windows
return false;
}
if (qobject_cast<MDILayoutWidget *>(p))
return true;
if (qobject_cast<DropArea *>(p)) {
// Note that the TitleBar can be inside a DropArea that's inside a MDIArea
// so we need this additional check
return false;
}
p = p->parent();
}
return false;
}
@@ -321,7 +339,7 @@ void TitleBar::onCloseClicked()
qWarning() << Q_FUNC_INFO << "Frame with no dock widgets";
}
} else {
if (m_frame->isTheOnlyFrame() && !m_frame->isInMainWindow()) {
if (m_frame->isTheOnlyFrame() && m_frame->isInFloatingWindow()) {
m_frame->window()->close();
} else {
m_frame->close();
@@ -349,7 +367,7 @@ void TitleBar::onCloseClicked()
bool TitleBar::isFloating() const
{
if (m_floatingWindow)
return m_floatingWindow->hasSingleDockWidget(); // Debatable! Maybe it's always floating.
return true;
if (m_frame)
return m_frame->isFloating();
@@ -393,9 +411,16 @@ void TitleBar::onFloatClicked()
// Case 2: Multiple dockwidgets are tabbed together and floating
// TODO: Just reuse the whole frame and put it back. The frame currently doesn't remember the position in the main window
// so use an hack for now
for (auto dock : qAsConst(dockWidgets)) {
dock->setFloating(true);
dock->setFloating(false);
if (!dockWidgets.isEmpty()) { // could be empty during destruction, maybe
if (!dockWidgets.constFirst()->hasPreviousDockedLocation()) {
// Don't attempt, there's no previous docked location
return;
}
for (auto dock : qAsConst(dockWidgets)) {
dock->setFloating(true);
dock->setFloating(false);
}
}
}
} else {
@@ -474,3 +499,20 @@ bool TitleBar::isWindow() const
{
return m_floatingWindow != nullptr;
}
TabBar *TitleBar::tabBar() const
{
if (m_floatingWindow && m_floatingWindow->hasSingleFrame()) {
if (Frame *frame = m_floatingWindow->singleFrame()) {
return frame->tabWidget()->tabBar();
} else {
// Shouldn't happen
qWarning() << Q_FUNC_INFO << "Expected a frame";
}
} else if (m_frame) {
return m_frame->tabWidget()->tabBar();
}
return nullptr;
}

View File

@@ -34,6 +34,7 @@ namespace KDDockWidgets {
class DockWidgetBase;
class Frame;
class Button;
class TabBar;
class DOCKS_EXPORT TitleBar : public QWidgetAdapter, public Draggable
{
@@ -63,7 +64,7 @@ public:
DockWidgetBase *singleDockWidget() const override;
///@brief Returns true if the dock widget which has this title bar is floating
///@brief Returns true if this title-bar is the title bar of a floating window
bool isFloating() const;
///@brief the list of dockwidgets under this TitleBar.
@@ -96,6 +97,11 @@ public:
///@brief toggle floating
Q_INVOKABLE bool onDoubleClicked();
///@brief Returns the tab bar which is under this title bar.
///It's only nullptr for the case of having a Floating Window with more than one nested Frame
TabBar* tabBar() const;
///@brief getter for m_frame
Frame *frame() const
{
@@ -112,9 +118,8 @@ public:
///Returns nullptr otherwise
MainWindowBase *mainWindow() const;
/// @brief Returns if this title bar is in a main window in MDI mode
/// By default false. Only relevant if your main window was constructed with the
/// MainWindowOption_MDI option
/// @brief Returns if this title bar belongs to a dock widget which is in MDI mode (inside a MDI area)
/// For example in a main window created with MainWindowOption_MDI option
bool isMDI() const override;
/// @brief updates the close button enabled state

View File

@@ -362,13 +362,25 @@ inline bool scalingFactorIsSupported(qreal factor)
#endif
}
/// @brief Returns the parent frame which the specified object is in, if any
inline Frame *parentFrame(QObject *from)
template <typename T>
inline T* firstParentOfType(const QObject *child)
{
auto p = from;
auto p = const_cast<QObject *>(child);
while (p) {
if (auto frame = qobject_cast<Frame *>(p))
return frame;
if (auto candidate = qobject_cast<T *>(p))
return candidate;
if (qobject_cast<const QWindow*>(p)) {
// Ignore QObject hierarchies spanning though multiple windows
return nullptr;
}
#ifdef KDDOCKWIDGETS_QTWIDGETS
// Ignore QObject hierarchies spanning though multiple windows
if (auto w = qobject_cast<QWidget *>(p))
if (w->isWindow())
return nullptr;
#endif
p = p->parent();
}
@@ -376,6 +388,33 @@ inline Frame *parentFrame(QObject *from)
return nullptr;
}
template <typename T>
inline T* lastParentOfType(const QObject *child)
{
auto p = const_cast<QObject *>(child);
T* result = nullptr;
while (p) {
if (auto candidate = qobject_cast<T *>(p))
result = candidate;
if (qobject_cast<const QWindow*>(p)) {
// Ignore QObject hierarchies spanning though multiple windows
return result;
}
#ifdef KDDOCKWIDGETS_QTWIDGETS
// Ignore QObject hierarchies spanning though multiple windows
if (auto w = qobject_cast<QWidget *>(p))
if (w->isWindow())
return result;
#endif
p = p->parent();
}
return result;
}
};
#endif

View File

@@ -87,8 +87,31 @@ bool WidgetResizeHandler::eventFilter(QObject *o, QEvent *e)
if (!widget)
return false;
if (m_isTopLevelWindowResizer && (!widget->isTopLevel() || o != mTarget))
return false;
if (m_isTopLevelWindowResizer) {
if (!widget->isTopLevel() || o != mTarget)
return false;
} else if (isMDI()) {
// Each Frame has a WidgetResizeHandler instance.
// mTarget is the Frame we want to resize.
// but 'o' might not be mTarget, because we're using a global event filter.
// The global event filter is required because we allow the cursor to be outside the frame, a few pixels
// so we have a nice resize margin.
// Here we deal with the case where our mTarget, let's say "Frame 1" is on top of "Frame 2" but cursor
// is near "Frame 2"'s margins, and would show resize cursor.
// We only want to continue if the cursor is near the margins of our own frame (mTarget)
auto frame = firstParentOfType<Frame>(widget);
if (frame && frame->isMDIWrapper()) {
// We don't care about the inner Option_MDINestable helper frame
frame = frame->mdiFrame();
}
if (frame && frame != mTarget) {
const bool areSiblings = frame->QWidgetAdapter::parentWidget() == mTarget->parentWidget();
if (areSiblings)
return false;
}
}
switch (e->type()) {
case QEvent::MouseButtonPress: {
@@ -497,15 +520,20 @@ CursorPosition WidgetResizeHandler::cursorPosition(QPoint globalPos) const
const int margin = widgetResizeHandlerMargin();
QFlags<CursorPosition>::Int result = CursorPosition_Undefined;
if (qAbs(x) <= margin)
result |= CursorPosition_Left;
else if (qAbs(x - (mTarget->width() - margin)) <= margin)
result |= CursorPosition_Right;
if (qAbs(y) <= margin)
result |= CursorPosition_Top;
else if (qAbs(y - (mTarget->height() - margin)) <= margin)
result |= CursorPosition_Bottom;
if (y >= -margin && y <= mTarget->height() + margin) {
if (qAbs(x) <= margin)
result |= CursorPosition_Left;
else if (qAbs(x - (mTarget->width() - margin)) <= margin)
result |= CursorPosition_Right;
}
if (x >= -margin && x <= mTarget->width() + margin) {
if (qAbs(y) <= margin)
result |= CursorPosition_Top;
else if (qAbs(y - (mTarget->height() - margin)) <= margin)
result |= CursorPosition_Bottom;
}
// Filter out sides we don't allow
result = result & mAllowedResizeSides;

View File

@@ -45,29 +45,59 @@ ClassicIndicators::~ClassicIndicators()
delete m_indicatorWindow;
}
DropIndicatorOverlayInterface::DropLocation ClassicIndicators::hover_impl(QPoint globalPos)
DropLocation ClassicIndicators::hover_impl(QPoint globalPos)
{
return m_indicatorWindow->hover(globalPos);
}
QPoint ClassicIndicators::posForIndicator(DropIndicatorOverlayInterface::DropLocation loc) const
QPoint ClassicIndicators::posForIndicator(DropLocation loc) const
{
return m_indicatorWindow->posForIndicator(loc);
}
bool ClassicIndicators::innerIndicatorsVisible() const
bool ClassicIndicators::innerLeftIndicatorVisible() const
{
return m_innerIndicatorsVisible;
return dropIndicatorVisible(DropLocation_Left);
}
bool ClassicIndicators::outterIndicatorsVisible() const
bool ClassicIndicators::innerRightIndicatorVisible() const
{
return m_outterIndicatorsVisible;
return dropIndicatorVisible(DropLocation_Right);
}
bool ClassicIndicators::innerTopIndicatorVisible() const
{
return dropIndicatorVisible(DropLocation_Top);
}
bool ClassicIndicators::innerBottomIndicatorVisible() const
{
return dropIndicatorVisible(DropLocation_Bottom);
}
bool ClassicIndicators::outterLeftIndicatorVisible() const
{
return dropIndicatorVisible(DropLocation_OutterLeft);
}
bool ClassicIndicators::outterRightIndicatorVisible() const
{
return dropIndicatorVisible(DropLocation_OutterRight);
}
bool ClassicIndicators::outterTopIndicatorVisible() const
{
return dropIndicatorVisible(DropLocation_OutterTop);
}
bool ClassicIndicators::outterBottomIndicatorVisible() const
{
return dropIndicatorVisible(DropLocation_OutterBottom);
}
bool ClassicIndicators::tabIndicatorVisible() const
{
return m_tabIndicatorVisible;
return dropIndicatorVisible(DropLocation_Center);
}
bool ClassicIndicators::onResize(QSize)
@@ -82,40 +112,13 @@ void ClassicIndicators::updateVisibility()
m_indicatorWindow->updatePositions();
m_indicatorWindow->setVisible(true);
updateWindowPosition();
updateIndicatorsVisibility(true);
raiseIndicators();
} else {
m_rubberBand->setVisible(false);
m_indicatorWindow->setVisible(false);
updateIndicatorsVisibility(false);
}
}
void ClassicIndicators::updateIndicatorsVisibility(bool visible)
{
const bool isTheOnlyFrame = m_hoveredFrame && m_hoveredFrame->isTheOnlyFrame();
m_innerIndicatorsVisible = visible && m_hoveredFrame;
WindowBeingDragged *windowBeingDragged = DragController::instance()->windowBeingDragged();
// If there's only 1 frame in the layout, the outer indicators are redundant, as they do the same thing as the internal ones.
// But there might be another window obscuring our target, so it's useful to show the outer indicators in this case
m_outterIndicatorsVisible = visible && (!isTheOnlyFrame || DockRegistry::self()->isProbablyObscured(m_hoveredFrame->window()->windowHandle(), windowBeingDragged));
// Only allow to dock to center if the affinities match
auto tabbingAllowedFunc = Config::self().tabbingAllowedFunc();
m_tabIndicatorVisible = m_innerIndicatorsVisible && windowBeingDragged && DockRegistry::self()->affinitiesMatch(m_hoveredFrame->affinities(), windowBeingDragged->affinities()) && m_hoveredFrame->isDockable();
if (m_tabIndicatorVisible && tabbingAllowedFunc) {
const DockWidgetBase::List source = windowBeingDragged->dockWidgets();
const DockWidgetBase::List target = m_hoveredFrame->dockWidgets();
m_tabIndicatorVisible = tabbingAllowedFunc(source, target);
}
Q_EMIT innerIndicatorsVisibleChanged();
Q_EMIT outterIndicatorsVisibleChanged();
Q_EMIT tabIndicatorVisibleChanged();
Q_EMIT indicatorsVisibleChanged();
}
void ClassicIndicators::raiseIndicators()
@@ -123,31 +126,31 @@ void ClassicIndicators::raiseIndicators()
m_indicatorWindow->raise();
}
KDDockWidgets::Location locationToMultisplitterLocation(ClassicIndicators::DropLocation location)
KDDockWidgets::Location locationToMultisplitterLocation(DropLocation location)
{
switch (location) {
case DropIndicatorOverlayInterface::DropLocation_Left:
case DropLocation_Left:
return KDDockWidgets::Location_OnLeft;
case DropIndicatorOverlayInterface::DropLocation_Top:
case DropLocation_Top:
return KDDockWidgets::Location_OnTop;
case DropIndicatorOverlayInterface::DropLocation_Right:
case DropLocation_Right:
return KDDockWidgets::Location_OnRight;
case DropIndicatorOverlayInterface::DropLocation_Bottom:
case DropLocation_Bottom:
return KDDockWidgets::Location_OnBottom;
case DropIndicatorOverlayInterface::DropLocation_OutterLeft:
case DropLocation_OutterLeft:
return KDDockWidgets::Location_OnLeft;
case DropIndicatorOverlayInterface::DropLocation_OutterTop:
case DropLocation_OutterTop:
return KDDockWidgets::Location_OnTop;
case DropIndicatorOverlayInterface::DropLocation_OutterRight:
case DropLocation_OutterRight:
return KDDockWidgets::Location_OnRight;
case DropIndicatorOverlayInterface::DropLocation_OutterBottom:
case DropLocation_OutterBottom:
return KDDockWidgets::Location_OnBottom;
default:
return KDDockWidgets::Location_None;
}
}
void ClassicIndicators::setDropLocation(ClassicIndicators::DropLocation location)
void ClassicIndicators::setDropLocation(DropLocation location)
{
setCurrentDropLocation(location);

View File

@@ -17,41 +17,43 @@ using namespace KDDockWidgets;
namespace KDDockWidgets {
static QString iconName(DropIndicatorOverlayInterface::DropLocation loc, bool active)
static QString iconName(DropLocation loc, bool active)
{
QString suffix = active ? QStringLiteral("_active")
: QString();
QString name;
switch (loc) {
case DropIndicatorOverlayInterface::DropLocation_Center:
case DropLocation_Center:
name = QStringLiteral("center");
break;
case DropIndicatorOverlayInterface::DropLocation_Left:
case DropLocation_Left:
name = QStringLiteral("inner_left");
break;
case DropIndicatorOverlayInterface::DropLocation_Right:
case DropLocation_Right:
name = QStringLiteral("inner_right");
break;
case DropIndicatorOverlayInterface::DropLocation_Bottom:
case DropLocation_Bottom:
name = QStringLiteral("inner_bottom");
break;
case DropIndicatorOverlayInterface::DropLocation_Top:
case DropLocation_Top:
name = QStringLiteral("inner_top");
break;
case DropIndicatorOverlayInterface::DropLocation_OutterLeft:
case DropLocation_OutterLeft:
name = QStringLiteral("outter_left");
break;
case DropIndicatorOverlayInterface::DropLocation_OutterBottom:
case DropLocation_OutterBottom:
name = QStringLiteral("outter_bottom");
break;
case DropIndicatorOverlayInterface::DropLocation_OutterRight:
case DropLocation_OutterRight:
name = QStringLiteral("outter_right");
break;
case DropIndicatorOverlayInterface::DropLocation_OutterTop:
case DropLocation_OutterTop:
name = QStringLiteral("outter_top");
break;
case DropIndicatorOverlayInterface::DropLocation_None:
case DropLocation_None:
case DropLocation_Inner:
case DropLocation_Outter:
return QString();
}
@@ -83,7 +85,7 @@ void Indicator::setHovered(bool hovered)
if (hovered) {
q->setDropLocation(m_dropLocation);
} else if (q->currentDropLocation() == m_dropLocation) {
q->setDropLocation(DropIndicatorOverlayInterface::DropLocation_None);
q->setDropLocation(DropLocation_None);
}
}
}
@@ -117,15 +119,15 @@ static Qt::WindowFlags flagsForIndicatorWindow()
IndicatorWindow::IndicatorWindow(ClassicIndicators *classicIndicators_)
: QWidget(parentForIndicatorWindow(classicIndicators_), flagsForIndicatorWindow())
, classicIndicators(classicIndicators_)
, m_center(new Indicator(classicIndicators, this, DropIndicatorOverlayInterface::DropLocation_Center)) // Each indicator is not a top-level. Otherwise there's noticeable delay.
, m_left(new Indicator(classicIndicators, this, DropIndicatorOverlayInterface::DropLocation_Left))
, m_right(new Indicator(classicIndicators, this, DropIndicatorOverlayInterface::DropLocation_Right))
, m_bottom(new Indicator(classicIndicators, this, DropIndicatorOverlayInterface::DropLocation_Bottom))
, m_top(new Indicator(classicIndicators, this, DropIndicatorOverlayInterface::DropLocation_Top))
, m_outterLeft(new Indicator(classicIndicators, this, DropIndicatorOverlayInterface::DropLocation_OutterLeft))
, m_outterRight(new Indicator(classicIndicators, this, DropIndicatorOverlayInterface::DropLocation_OutterRight))
, m_outterBottom(new Indicator(classicIndicators, this, DropIndicatorOverlayInterface::DropLocation_OutterBottom))
, m_outterTop(new Indicator(classicIndicators, this, DropIndicatorOverlayInterface::DropLocation_OutterTop))
, m_center(new Indicator(classicIndicators, this, DropLocation_Center)) // Each indicator is not a top-level. Otherwise there's noticeable delay.
, m_left(new Indicator(classicIndicators, this, DropLocation_Left))
, m_right(new Indicator(classicIndicators, this, DropLocation_Right))
, m_bottom(new Indicator(classicIndicators, this, DropLocation_Bottom))
, m_top(new Indicator(classicIndicators, this, DropLocation_Top))
, m_outterLeft(new Indicator(classicIndicators, this, DropLocation_OutterLeft))
, m_outterRight(new Indicator(classicIndicators, this, DropLocation_OutterRight))
, m_outterBottom(new Indicator(classicIndicators, this, DropLocation_OutterBottom))
, m_outterTop(new Indicator(classicIndicators, this, DropLocation_OutterTop))
{
setWindowFlag(Qt::FramelessWindowHint, true);
@@ -136,37 +138,39 @@ IndicatorWindow::IndicatorWindow(ClassicIndicators *classicIndicators_)
setAttribute(Qt::WA_TranslucentBackground);
connect(classicIndicators, &ClassicIndicators::innerIndicatorsVisibleChanged,
connect(classicIndicators, &ClassicIndicators::indicatorsVisibleChanged,
this, &IndicatorWindow::updateIndicatorVisibility);
connect(classicIndicators, &ClassicIndicators::outterIndicatorsVisibleChanged,
connect(classicIndicators, &ClassicIndicators::indicatorsVisibleChanged,
this, &IndicatorWindow::updateIndicatorVisibility);
m_indicators << m_center << m_left << m_right << m_top << m_bottom
<< m_outterBottom << m_outterTop << m_outterLeft << m_outterRight;
}
Indicator *IndicatorWindow::indicatorForLocation(DropIndicatorOverlayInterface::DropLocation loc) const
Indicator *IndicatorWindow::indicatorForLocation(DropLocation loc) const
{
switch (loc) {
case DropIndicatorOverlayInterface::DropLocation_Center:
case DropLocation_Center:
return m_center;
case DropIndicatorOverlayInterface::DropLocation_Left:
case DropLocation_Left:
return m_left;
case DropIndicatorOverlayInterface::DropLocation_Right:
case DropLocation_Right:
return m_right;
case DropIndicatorOverlayInterface::DropLocation_Bottom:
case DropLocation_Bottom:
return m_bottom;
case DropIndicatorOverlayInterface::DropLocation_Top:
case DropLocation_Top:
return m_top;
case DropIndicatorOverlayInterface::DropLocation_OutterLeft:
case DropLocation_OutterLeft:
return m_outterLeft;
case DropIndicatorOverlayInterface::DropLocation_OutterBottom:
case DropLocation_OutterBottom:
return m_outterBottom;
case DropIndicatorOverlayInterface::DropLocation_OutterRight:
case DropLocation_OutterRight:
return m_outterRight;
case DropIndicatorOverlayInterface::DropLocation_OutterTop:
case DropLocation_OutterTop:
return m_outterTop;
case DropIndicatorOverlayInterface::DropLocation_None:
case DropLocation_None:
case DropLocation_Outter:
case DropLocation_Inner:
return nullptr;
}
@@ -195,26 +199,23 @@ void IndicatorWindow::resizeEvent(QResizeEvent *ev)
void IndicatorWindow::updateIndicatorVisibility()
{
for (Indicator *indicator : { m_left, m_right, m_bottom, m_top })
indicator->setVisible(classicIndicators->innerIndicatorsVisible());
for (Indicator *indicator : { m_outterTop, m_outterLeft, m_outterRight, m_outterBottom })
indicator->setVisible(classicIndicators->outterIndicatorsVisible());
m_center->setVisible(classicIndicators->tabIndicatorVisible());
for (Indicator *indicator : { m_left, m_right, m_bottom, m_top,
m_outterTop, m_outterLeft, m_outterRight, m_outterBottom,
m_center })
indicator->setVisible(classicIndicators->dropIndicatorVisible(indicator->m_dropLocation));
updateMask();
}
QPoint IndicatorWindow::posForIndicator(DropIndicatorOverlayInterface::DropLocation loc) const
QPoint IndicatorWindow::posForIndicator(DropLocation loc) const
{
Indicator *indicator = indicatorForLocation(loc);
return indicator->mapToGlobal(indicator->rect().center());
}
DropIndicatorOverlayInterface::DropLocation IndicatorWindow::hover(QPoint globalPos)
DropLocation IndicatorWindow::hover(QPoint globalPos)
{
DropIndicatorOverlayInterface::DropLocation loc = DropIndicatorOverlayInterface::DropLocation_None;
DropLocation loc = DropLocation_None;
for (Indicator *indicator : qAsConst(m_indicators)) {
if (indicator->isVisible()) {
@@ -249,7 +250,7 @@ void IndicatorWindow::updatePositions()
}
}
Indicator::Indicator(ClassicIndicators *classicIndicators, IndicatorWindow *parent, ClassicIndicators::DropLocation location)
Indicator::Indicator(ClassicIndicators *classicIndicators, IndicatorWindow *parent, DropLocation location)
: QWidget(parent)
, q(classicIndicators)
, m_dropLocation(location)
@@ -295,11 +296,11 @@ IndicatorWindow::IndicatorWindow(KDDockWidgets::ClassicIndicators *classicIndica
}
}
DropIndicatorOverlayInterface::DropLocation IndicatorWindow::hover(QPoint pt)
DropLocation IndicatorWindow::hover(QPoint pt)
{
QQuickItem *item = indicatorForPos(pt);
const DropIndicatorOverlayInterface::DropLocation loc = item ? locationForIndicator(item)
: DropIndicatorOverlayInterface::DropLocation_None;
const DropLocation loc = item ? locationForIndicator(item)
: DropLocation_None;
classicIndicators()->setDropLocation(loc);
return loc;
}
@@ -327,7 +328,7 @@ void IndicatorWindow::updatePositions()
// Not needed to implement, the Indicators use QML anchors
}
QPoint IndicatorWindow::posForIndicator(KDDockWidgets::DropIndicatorOverlayInterface::DropLocation loc) const
QPoint IndicatorWindow::posForIndicator(KDDockWidgets::DropLocation loc) const
{
QQuickItem *indicator = IndicatorWindow::indicatorForLocation(loc);
return indicator->mapToGlobal(indicator->boundingRect().center()).toPoint();
@@ -335,7 +336,7 @@ QPoint IndicatorWindow::posForIndicator(KDDockWidgets::DropIndicatorOverlayInter
QString IndicatorWindow::iconName(int loc, bool active) const
{
return KDDockWidgets::iconName(DropIndicatorOverlayInterface::DropLocation(loc), active);
return KDDockWidgets::iconName(DropLocation(loc), active);
}
ClassicIndicators *IndicatorWindow::classicIndicators() const
@@ -343,7 +344,7 @@ ClassicIndicators *IndicatorWindow::classicIndicators() const
return m_classicIndicators;
}
QQuickItem *IndicatorWindow::indicatorForLocation(DropIndicatorOverlayInterface::DropLocation loc) const
QQuickItem *IndicatorWindow::indicatorForLocation(DropLocation loc) const
{
const QVector<QQuickItem *> indicators = indicatorItems();
Q_ASSERT(indicators.size() == 9);
@@ -357,9 +358,9 @@ QQuickItem *IndicatorWindow::indicatorForLocation(DropIndicatorOverlayInterface:
return nullptr;
}
DropIndicatorOverlayInterface::DropLocation IndicatorWindow::locationForIndicator(const QQuickItem *item) const
DropLocation IndicatorWindow::locationForIndicator(const QQuickItem *item) const
{
return DropIndicatorOverlayInterface::DropLocation(item->property("indicatorType").toInt());
return DropLocation(item->property("indicatorType").toInt());
}
QVector<QQuickItem *> IndicatorWindow::indicatorItems() const

View File

@@ -31,9 +31,9 @@ class IndicatorWindow : public QWidget
Q_OBJECT
public:
explicit IndicatorWindow(ClassicIndicators *classicIndicators);
DropIndicatorOverlayInterface::DropLocation hover(QPoint globalPos);
DropLocation hover(QPoint globalPos);
void updatePositions();
QPoint posForIndicator(DropIndicatorOverlayInterface::DropLocation) const;
QPoint posForIndicator(DropLocation) const;
private:
void updateIndicatorVisibility();
@@ -43,7 +43,7 @@ private:
// Only happens on Linux
void updateMask();
Indicator *indicatorForLocation(DropIndicatorOverlayInterface::DropLocation loc) const;
Indicator *indicatorForLocation(DropLocation loc) const;
ClassicIndicators *const classicIndicators;
Indicator *const m_center;
@@ -64,7 +64,7 @@ class Indicator : public QWidget
public:
typedef QList<Indicator *> List;
explicit Indicator(ClassicIndicators *classicIndicators, IndicatorWindow *parent,
DropIndicatorOverlayInterface::DropLocation location);
DropLocation location);
void paintEvent(QPaintEvent *) override;
void setHovered(bool hovered);
@@ -75,7 +75,7 @@ public:
QImage m_imageActive;
ClassicIndicators *const q;
bool m_hovered = false;
const DropIndicatorOverlayInterface::DropLocation m_dropLocation;
const DropLocation m_dropLocation;
};
}
@@ -92,15 +92,15 @@ class IndicatorWindow : public QQuickView
Q_PROPERTY(KDDockWidgets::ClassicIndicators *classicIndicators READ classicIndicators CONSTANT)
public:
explicit IndicatorWindow(ClassicIndicators *);
DropIndicatorOverlayInterface::DropLocation hover(QPoint);
DropLocation hover(QPoint);
void updatePositions();
QPoint posForIndicator(DropIndicatorOverlayInterface::DropLocation) const;
QPoint posForIndicator(DropLocation) const;
Q_INVOKABLE QString iconName(int loc, bool active) const;
KDDockWidgets::ClassicIndicators *classicIndicators() const;
QQuickItem *indicatorForLocation(DropIndicatorOverlayInterface::DropLocation loc) const;
QQuickItem *indicatorForLocation(DropLocation loc) const;
private:
DropIndicatorOverlayInterface::DropLocation locationForIndicator(const QQuickItem *) const;
DropLocation locationForIndicator(const QQuickItem *) const;
QQuickItem *indicatorForPos(QPoint) const;
QVector<QQuickItem *> indicatorItems() const;
ClassicIndicators *const m_classicIndicators;

View File

@@ -23,9 +23,18 @@ class DOCKS_EXPORT ClassicIndicators : public DropIndicatorOverlayInterface
{
Q_OBJECT
Q_PROPERTY(bool innerIndicatorsVisible READ innerIndicatorsVisible NOTIFY innerIndicatorsVisibleChanged)
Q_PROPERTY(bool outterIndicatorsVisible READ outterIndicatorsVisible NOTIFY outterIndicatorsVisibleChanged)
Q_PROPERTY(bool tabIndicatorVisible READ tabIndicatorVisible NOTIFY tabIndicatorVisibleChanged)
// Properties for QML
Q_PROPERTY(bool innerLeftIndicatorVisible READ innerLeftIndicatorVisible NOTIFY indicatorsVisibleChanged)
Q_PROPERTY(bool innerRightIndicatorVisible READ innerRightIndicatorVisible NOTIFY indicatorsVisibleChanged)
Q_PROPERTY(bool innerTopIndicatorVisible READ innerTopIndicatorVisible NOTIFY indicatorsVisibleChanged)
Q_PROPERTY(bool innerBottomIndicatorVisible READ innerBottomIndicatorVisible NOTIFY indicatorsVisibleChanged)
Q_PROPERTY(bool outterLeftIndicatorVisible READ outterLeftIndicatorVisible NOTIFY indicatorsVisibleChanged)
Q_PROPERTY(bool outterRightIndicatorVisible READ outterRightIndicatorVisible NOTIFY indicatorsVisibleChanged)
Q_PROPERTY(bool outterTopIndicatorVisible READ outterTopIndicatorVisible NOTIFY indicatorsVisibleChanged)
Q_PROPERTY(bool outterBottomIndicatorVisible READ outterBottomIndicatorVisible NOTIFY indicatorsVisibleChanged)
Q_PROPERTY(bool tabIndicatorVisible READ tabIndicatorVisible NOTIFY indicatorsVisibleChanged)
public:
explicit ClassicIndicators(DropArea *dropArea);
@@ -33,33 +42,32 @@ public:
DropLocation hover_impl(QPoint globalPos) override;
QPoint posForIndicator(DropLocation) const override;
bool innerIndicatorsVisible() const;
bool outterIndicatorsVisible() const;
// The tab/center indicator
// Lots of getters needed because of QML:
bool innerLeftIndicatorVisible() const;
bool innerRightIndicatorVisible() const;
bool innerTopIndicatorVisible() const;
bool innerBottomIndicatorVisible() const;
bool outterLeftIndicatorVisible() const;
bool outterRightIndicatorVisible() const;
bool outterTopIndicatorVisible() const;
bool outterBottomIndicatorVisible() const;
bool tabIndicatorVisible() const;
protected:
bool onResize(QSize newSize) override;
void updateVisibility() override;
Q_SIGNALS:
void innerIndicatorsVisibleChanged();
void outterIndicatorsVisibleChanged();
void tabIndicatorVisibleChanged();
void indicatorsVisibleChanged();
private:
friend class KDDockWidgets::Indicator;
friend class KDDockWidgets::IndicatorWindow;
bool rubberBandIsTopLevel() const;
void updateIndicatorsVisibility(bool visible);
void raiseIndicators();
QRect geometryForRubberband(QRect localRect) const;
void setDropLocation(DropLocation);
void updateWindowPosition();
bool m_innerIndicatorsVisible = false;
bool m_outterIndicatorsVisible = false;
bool m_tabIndicatorVisible = false;
QWidgetOrQuick *const m_rubberBand;
IndicatorWindow *const m_indicatorWindow;
};

View File

@@ -27,7 +27,7 @@ class DOCKS_EXPORT NullIndicators : public DropIndicatorOverlayInterface
public:
explicit NullIndicators(DropArea *);
~NullIndicators() override;
DropIndicatorOverlayInterface::DropLocation hover_impl(QPoint) override
DropLocation hover_impl(QPoint) override
{
return {};
};

View File

@@ -42,7 +42,7 @@ SegmentedIndicators::~SegmentedIndicators()
{
}
DropIndicatorOverlayInterface::DropLocation SegmentedIndicators::hover_impl(QPoint pt)
DropLocation SegmentedIndicators::hover_impl(QPoint pt)
{
m_hoveredPt = mapFromGlobal(pt);
updateSegments();
@@ -51,7 +51,7 @@ DropIndicatorOverlayInterface::DropLocation SegmentedIndicators::hover_impl(QPoi
return currentDropLocation();
}
DropIndicatorOverlayInterface::DropLocation SegmentedIndicators::dropLocationForPos(QPoint pos) const
DropLocation SegmentedIndicators::dropLocationForPos(QPoint pos) const
{
for (auto it = m_segments.cbegin(), end = m_segments.cend(); it != end; ++it) {
if (it.value().containsPoint(pos, Qt::OddEvenFill)) {
@@ -69,7 +69,7 @@ void SegmentedIndicators::paintEvent(QPaintEvent *)
drawSegments(&p);
}
QVector<QPolygon> SegmentedIndicators::segmentsForRect(QRect r, QPolygon &center, bool useOffset) const
QHash<DropLocation, QPolygon> SegmentedIndicators::segmentsForRect(QRect r, bool inner, bool useOffset) const
{
const int halfPenWidth = s_segmentPenWidth / 2;
@@ -99,8 +99,7 @@ QVector<QPolygon> SegmentedIndicators::segmentsForRect(QRect r, QPolygon &center
bottomRight + QPoint(-l, -l),
bottomLeft + QPoint(l, -l) };
{
if (inner) {
QPolygon bounds = QVector<QPoint> { topLeft + QPoint(l, l),
topRight + QPoint(-l, l),
bottomRight + QPoint(-l, -l),
@@ -119,7 +118,7 @@ QVector<QPolygon> SegmentedIndicators::segmentsForRect(QRect r, QPolygon &center
const int centerRectTop = centerPos.y() - indicatorHeight / 2;
center = QVector<QPoint> {
const auto center = QVector<QPoint> {
{ centerRectLeft, centerRectTop },
{ centerRectLeft + tabWidth, centerRectTop },
{ centerRectLeft + tabWidth, centerRectTop + tabHeight },
@@ -127,34 +126,39 @@ QVector<QPolygon> SegmentedIndicators::segmentsForRect(QRect r, QPolygon &center
{ centerRectRight, centerRectBottom },
{ centerRectLeft, centerRectBottom },
};
}
return { leftPoints, topPoints, rightPoints, bottomPoints };
return {
{ DropLocation_Left, leftPoints },
{ DropLocation_Top, topPoints },
{ DropLocation_Right, rightPoints },
{ DropLocation_Bottom, bottomPoints },
{ DropLocation_Center, center }
};
} else {
return {
{ DropLocation_OutterLeft, leftPoints },
{ DropLocation_OutterTop, topPoints },
{ DropLocation_OutterRight, rightPoints },
{ DropLocation_OutterBottom, bottomPoints }
};
}
}
void SegmentedIndicators::updateSegments()
{
m_segments.clear();
const bool hasMultipleFrames = m_dropArea->visibleCount() > 1;
const bool needsOutterIndicators = true; // Can't think of a reason not to show them
const bool needsInnerIndicators = needsOutterIndicators && hasMultipleFrames && hoveredFrameRect().isValid();
QPolygon center;
const bool needsOutterIndicators = dropIndicatorVisible(DropLocation_Outter);
const bool needsInnerIndicators = dropIndicatorVisible(DropLocation_Inner);
if (needsInnerIndicators) {
const bool useOffset = needsOutterIndicators;
auto segments = segmentsForRect(hoveredFrameRect(), /*by-ref*/ center, useOffset);
for (int i = 0; i < 4; ++i)
m_segments.insert(DropLocation(DropLocation_Left + i), segments[i]);
m_segments.insert(DropLocation_Center, center);
m_segments = segmentsForRect(hoveredFrameRect(), /*inner=*/true, useOffset);
}
if (needsOutterIndicators) {
auto segments = segmentsForRect(rect(), /*unused*/ center);
for (int i = 0; i < 4; ++i)
m_segments.insert(DropLocation(DropLocation_OutterLeft + i), segments[i]);
auto segments = segmentsForRect(rect(), /*inner=*/false);
m_segments.insert(segments);
}
update();
@@ -162,8 +166,16 @@ void SegmentedIndicators::updateSegments()
void SegmentedIndicators::drawSegments(QPainter *p)
{
for (int i = DropLocation_First; i <= DropLocation_Last; ++i)
drawSegment(p, m_segments.value(DropLocation(i)));
for (DropLocation loc : { DropLocation_Left,
DropLocation_Top,
DropLocation_Right,
DropLocation_Bottom,
DropLocation_Center,
DropLocation_OutterLeft,
DropLocation_OutterTop,
DropLocation_OutterRight,
DropLocation_OutterBottom })
drawSegment(p, m_segments.value(loc));
}
void SegmentedIndicators::drawSegment(QPainter *p, const QPolygon &segment)
@@ -183,7 +195,7 @@ void SegmentedIndicators::drawSegment(QPainter *p, const QPolygon &segment)
p->drawPolygon(segment);
}
QPoint KDDockWidgets::SegmentedIndicators::posForIndicator(DropIndicatorOverlayInterface::DropLocation) const
QPoint KDDockWidgets::SegmentedIndicators::posForIndicator(DropLocation) const
{
/// Doesn't apply to segmented indicators, completely different concept
return {};

View File

@@ -25,7 +25,7 @@ class DOCKS_EXPORT SegmentedIndicators : public DropIndicatorOverlayInterface
public:
explicit SegmentedIndicators(DropArea *dropArea);
~SegmentedIndicators() override;
DropIndicatorOverlayInterface::DropLocation hover_impl(QPoint globalPos) override;
DropLocation hover_impl(QPoint globalPos) override;
DropLocation dropLocationForPos(QPoint pos) const;
@@ -42,7 +42,7 @@ protected:
QPoint posForIndicator(DropLocation) const override;
private:
QVector<QPolygon> segmentsForRect(QRect, QPolygon &center, bool useOffset = false) const;
QHash<DropLocation, QPolygon> segmentsForRect(QRect, bool inner, bool useOffset = false) const;
void updateSegments();
void drawSegments(QPainter *p);
void drawSegment(QPainter *p, const QPolygon &segment);

View File

@@ -21,6 +21,7 @@
#include <QTimer>
#include <QGuiApplication>
#include <QScreen>
#include <algorithm>
#ifdef Q_CC_MSVC
#pragma warning(push)
@@ -2896,7 +2897,10 @@ QVector<int> ItemBoxContainer::calculateSqueezes(SizingInfo::List::ConstIterator
const auto count = availabilities.count();
QVector<int> squeezes(count, 0);
QVector<int> squeezes;
squeezes.resize(count);
std::fill(squeezes.begin(), squeezes.end(), 0);
int missing = needed;
if (strategy == NeighbourSqueezeStrategy::AllNeighbours) {

View File

@@ -14,9 +14,14 @@
#include "Logging_p.h"
#include "Item_p.h"
#include "MultiSplitterConfig.h"
#include "Config.h"
#include <QGuiApplication>
#ifdef KDDOCKWIDGETS_QTWIDGETS
# include <QWidget>
#endif
#ifdef Q_OS_WIN
#include <windows.h>
#endif
@@ -25,6 +30,15 @@ using namespace Layouting;
Separator *Separator::s_separatorBeingDragged = nullptr;
namespace {
bool rubberBandIsTopLevel()
{
return KDDockWidgets::Config::self().internalFlags() & KDDockWidgets::Config::InternalFlag_TopLevelIndicatorRubberBand;
}
}
/// @brief internal counter just for unit-tests
static int s_numSeparators = 0;
@@ -94,6 +108,10 @@ void Separator::onMousePress()
if (d->lazyResizeRubberBand) {
setLazyPosition(position());
d->lazyResizeRubberBand->show();
#ifdef KDDOCKWIDGETS_QTWIDGETS
if (rubberBandIsTopLevel())
d->lazyResizeRubberBand->asQWidget()->raise();
#endif
}
}
@@ -191,8 +209,8 @@ void Separator::init(ItemBoxContainer *parentContainer, Qt::Orientation orientat
d->parentContainer = parentContainer;
d->orientation = orientation;
d->lazyResizeRubberBand = d->usesLazyResize ? createRubberBand(d->m_hostWidget)
: nullptr;
d->lazyResizeRubberBand = d->usesLazyResize ? createRubberBand(rubberBandIsTopLevel() ? nullptr : d->m_hostWidget)
: nullptr;
asWidget()->setVisible(true);
}
@@ -238,7 +256,10 @@ void Separator::setLazyPosition(int pos)
} else {
geo.moveLeft(pos);
}
#ifdef KDDOCKWIDGETS_QTWIDGETS
if (rubberBandIsTopLevel())
geo.translate(d->m_hostWidget->asQWidget()->mapToGlobal(QPoint(0, 0)));
#endif
d->lazyResizeRubberBand->setGeometry(geo);
}
}

View File

@@ -87,11 +87,6 @@ void SeparatorWidget::mouseDoubleClickEvent(QMouseEvent *)
Layouting::Widget *SeparatorWidget::createRubberBand(Layouting::Widget *parent)
{
if (!parent) {
qWarning() << Q_FUNC_INFO << "Parent is required";
return nullptr;
}
return new Layouting::Widget_qwidget(new RubberBand(parent));
}

View File

@@ -46,3 +46,9 @@ QSize Widget::boundedMaxSize(QSize min, QSize max)
return max;
}
/** static */
QSize Widget::hardcodedMinimumSize()
{
return Item::hardcodedMinimumSize;
}

View File

@@ -15,7 +15,6 @@
#pragma once
#include "kddockwidgets/docks_export.h"
#include "Item_p.h"
#include <QRect>
#include <QSize>
@@ -148,6 +147,8 @@ public:
///@brief returns an id for corelation purposes for saving layouts
QString id() const;
static QSize hardcodedMinimumSize();
template<typename T>
static QSize widgetMinSize(const T *w)
{
@@ -157,7 +158,7 @@ public:
const int minH = w->minimumHeight() > 0 ? w->minimumHeight()
: w->minimumSizeHint().height();
return QSize(minW, minH).expandedTo(Item::hardcodedMinimumSize);
return QSize(minW, minH).expandedTo(hardcodedMinimumSize());
}
template<typename T>

View File

@@ -10,6 +10,7 @@
*/
#include "Widget_quick.h"
#include "Item_p.h"
#include <QDebug>
#include <QQmlEngine>

View File

@@ -24,6 +24,7 @@
#include "../DockRegistry_p.h"
#include "../Utils_p.h"
#include "../FloatingWindow_p.h"
#include "../multisplitter/Item_p.h"
#include <QResizeEvent>
#include <QMouseEvent>
@@ -33,6 +34,8 @@
#include <QQuickView>
#include <QScopedValueRollback>
#include <qpa/qplatformwindow.h>
using namespace KDDockWidgets;
namespace KDDockWidgets {
@@ -138,6 +141,8 @@ QWidgetAdapter::QWidgetAdapter(QQuickItem *parent, Qt::WindowFlags flags)
}
});
qApp->installEventFilter(this);
setSize(QSize(800, 800));
}
@@ -178,6 +183,14 @@ void QWidgetAdapter::onMouseRelease()
void QWidgetAdapter::onCloseEvent(QCloseEvent *)
{
}
void QWidgetAdapter::onResizeEvent(QResizeEvent *)
{
updateNormalGeometry();
}
void QWidgetAdapter::onMoveEvent(QMoveEvent *)
{
updateNormalGeometry();
}
void QWidgetAdapter::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data)
{
@@ -207,6 +220,27 @@ void QWidgetAdapter::itemChange(QQuickItem::ItemChange change, const QQuickItem:
}
}
void QWidgetAdapter::updateNormalGeometry()
{
QWindow *window = windowHandle();
if (!window) {
return;
}
QRect normalGeometry;
if (const QPlatformWindow *pw = window->handle()) {
normalGeometry = pw->normalGeometry();
}
if (!normalGeometry.isValid() && isNormalWindowState(window->windowState())) {
normalGeometry = window->geometry();
}
if (normalGeometry.isValid()) {
setNormalGeometry(normalGeometry);
}
}
void QWidgetAdapter::QQUICKITEMgeometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
{
// Send a few events manually, since QQuickItem doesn't do it for us.
@@ -300,23 +334,12 @@ QRect QWidgetAdapter::geometry() const
QRect QWidgetAdapter::normalGeometry() const
{
// TODO: There's no such concept in QWindow, do we need to workaround for QtQuick ?
return QWidgetAdapter::geometry();
return m_normalGeometry;
}
void QWidgetAdapter::setNormalGeometry(QRect geo)
{
if (!isTopLevel())
return;
if (QWindow *w = windowHandle()) {
if (isNormalWindowState(w->windowStates())) {
w->setGeometry(geo);
} else {
// Nothing better at this point, as QWindow doesn't have this concept
qDebug() << Q_FUNC_INFO << "TODO";
}
}
m_normalGeometry = geo;
}
QRect QWidgetAdapter::rect() const
@@ -749,6 +772,12 @@ bool QWidgetAdapter::eventFilter(QObject *watched, QEvent *ev)
break;
}
}
if (ev->type() == QEvent::Resize) {
onResizeEvent(static_cast<QResizeEvent *>(ev));
} else if (ev->type() == QEvent::Move) {
onMoveEvent(static_cast<QMoveEvent *>(ev));
}
}
return QQuickItem::eventFilter(watched, ev);

View File

@@ -251,9 +251,13 @@ protected:
virtual void onMouseMove(QPoint globalPos);
virtual void onMouseRelease();
virtual void onCloseEvent(QCloseEvent *);
virtual void onResizeEvent(QResizeEvent *);
virtual void onMoveEvent(QMoveEvent *);
void itemChange(QQuickItem::ItemChange, const QQuickItem::ItemChangeData &) override;
private:
void updateNormalGeometry();
QSize m_sizeHint;
QSizePolicy m_sizePolicy = QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
;
@@ -265,6 +269,7 @@ private:
bool m_isWrapper = false;
bool m_inSetParent = false;
MouseEventRedirector *m_mouseEventRedirector = nullptr;
QRect m_normalGeometry;
};
inline qreal logicalDpiFactor(const QQuickItem *item)

View File

@@ -15,7 +15,7 @@ import com.kdab.dockwidgets 1.0
Image {
id: root
property int indicatorType: DropIndicatorOverlayInterface.DropLocation_None
property int indicatorType: KDDockWidgets.DropLocation_None
readonly property bool isHovered: _window.classicIndicators.currentDropLocation === indicatorType
source: "qrc:/img/classic_indicators/" + _window.iconName(indicatorType, isHovered) + ".png";

View File

@@ -21,8 +21,8 @@ Item {
visible: width > 50 && height > 50 // don't show if window is too small'
ClassicIndicator {
visible: _window.classicIndicators.outterIndicatorsVisible
indicatorType: DropIndicatorOverlayInterface.DropLocation_OutterLeft
visible: _window.classicIndicators.outterLeftIndicatorVisible
indicatorType: KDDockWidgets.DropLocation_OutterLeft
anchors {
left: parent.left
leftMargin: outterMargin
@@ -31,8 +31,8 @@ Item {
}
ClassicIndicator {
visible: _window.classicIndicators.outterIndicatorsVisible
indicatorType: DropIndicatorOverlayInterface.DropLocation_OutterRight
visible: _window.classicIndicators.outterRightIndicatorVisible
indicatorType: KDDockWidgets.DropLocation_OutterRight
anchors {
right: parent.right
rightMargin: outterMargin
@@ -41,8 +41,8 @@ Item {
}
ClassicIndicator {
visible: _window.classicIndicators.outterIndicatorsVisible
indicatorType: DropIndicatorOverlayInterface.DropLocation_OutterTop
visible: _window.classicIndicators.outterTopIndicatorVisible
indicatorType: KDDockWidgets.DropLocation_OutterTop
anchors {
top: parent.top
topMargin: outterMargin
@@ -51,8 +51,8 @@ Item {
}
ClassicIndicator {
visible: _window.classicIndicators.outterIndicatorsVisible
indicatorType: DropIndicatorOverlayInterface.DropLocation_OutterBottom
visible: _window.classicIndicators.outterBottomIndicatorVisible
indicatorType: KDDockWidgets.DropLocation_OutterBottom
anchors {
bottom: parent.bottom
bottomMargin: outterMargin
@@ -69,10 +69,13 @@ Item {
width: (centerIndicator * 3) + (2 * innerMargin)
height: width
visible: _window.classicIndicators.innerIndicatorsVisible
visible: _window.classicIndicators.innerLeftIndicatorVisible || _window.classicIndicators.innerRightIndicatorVisible ||
_window.classicIndicators.innerTopIndicatorVisible || _window.classicIndicators.innerBottomIndicatorVisible || _window.classicIndicators.tabIndicatorVisible
ClassicIndicator {
indicatorType: DropIndicatorOverlayInterface.DropLocation_Left
id: innerLeft
visible: _window.classicIndicators.innerLeftIndicatorVisible
indicatorType: KDDockWidgets.DropLocation_Left
anchors {
right: centerIndicator.left
rightMargin: innerMargin
@@ -83,12 +86,14 @@ Item {
ClassicIndicator {
id: centerIndicator
visible: _window.classicIndicators.tabIndicatorVisible
indicatorType: DropIndicatorOverlayInterface.DropLocation_Center
indicatorType: KDDockWidgets.DropLocation_Center
anchors.centerIn: parent
}
ClassicIndicator {
indicatorType: DropIndicatorOverlayInterface.DropLocation_Right
id: innerRight
visible: _window.classicIndicators.innerRightIndicatorVisible
indicatorType: KDDockWidgets.DropLocation_Right
anchors {
left: centerIndicator.right
leftMargin: innerMargin
@@ -97,7 +102,9 @@ Item {
}
ClassicIndicator {
indicatorType: DropIndicatorOverlayInterface.DropLocation_Top
id: innerTop
visible: _window.classicIndicators.innerTopIndicatorVisible
indicatorType: KDDockWidgets.DropLocation_Top
anchors {
bottom: centerIndicator.top
bottomMargin: innerMargin
@@ -106,7 +113,9 @@ Item {
}
ClassicIndicator {
indicatorType: DropIndicatorOverlayInterface.DropLocation_Bottom
id: innerBottom
visible: _window.classicIndicators.innerBottomIndicatorVisible
indicatorType: KDDockWidgets.DropLocation_Bottom
anchors {
top: centerIndicator.bottom
topMargin: innerMargin

View File

@@ -133,15 +133,11 @@ QWidget *KDDockWidgets::Private::widgetForWindow(QWindow *window)
void QWidgetAdapter::setNormalGeometry(QRect geo)
{
if (isNormalWindowState(windowState())) {
setGeometry(geo);
QWidgetPrivate *priv = QWidgetPrivate::get(this);
if (priv->extra && priv->extra->topextra) {
priv->topData()->normalGeometry = geo;
} else {
QWidgetPrivate *priv = QWidgetPrivate::get(this);
if (priv->extra && priv->extra->topextra) {
priv->topData()->normalGeometry = geo;
} else {
qWarning() << Q_FUNC_INFO << "Failing to set normal geometry";
}
qWarning() << Q_FUNC_INFO << "Failing to set normal geometry";
}
}

View File

@@ -169,3 +169,15 @@ void TabBarWidget::moveTabTo(int from, int to)
{
moveTab(from, to);
}
void TabBarWidget::tabInserted(int index)
{
QTabBar::tabInserted(index);
Q_EMIT dockWidgetInserted(index);
}
void TabBarWidget::tabRemoved(int index)
{
QTabBar::tabRemoved(index);
Q_EMIT dockWidgetRemoved(index);
}

View File

@@ -50,12 +50,18 @@ public:
QRect rectForTab(int index) const override;
void moveTabTo(int from, int to) override;
Q_SIGNALS:
void dockWidgetInserted(int index);
void dockWidgetRemoved(int index);
protected:
bool dragCanStart(QPoint pressPos, QPoint pos) const override;
void mousePressEvent(QMouseEvent *) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mouseDoubleClickEvent(QMouseEvent *e) override;
bool event(QEvent *) override;
void tabInserted(int index) override;
void tabRemoved(int index) override;
private:
TabWidget *const m_tabWidget;

View File

@@ -31,7 +31,7 @@
using namespace KDDockWidgets;
TabWidgetWidget::TabWidgetWidget(Frame *parent)
TabWidgetWidget::TabWidgetWidget(Frame *parent, TabWidgetOptions options)
: QTabWidget(parent)
, TabWidget(this, parent)
, m_tabBar(Config::self().frameworkWidgetFactory()->createTabBar(this))
@@ -65,6 +65,8 @@ TabWidgetWidget::TabWidgetWidget(Frame *parent)
setFocusProxy(nullptr);
setupTabBarButtons();
setDocumentMode(options & TabWidgetOption_DocumentMode);
}
TabBar *TabWidgetWidget::tabBar() const

View File

@@ -39,7 +39,7 @@ class DOCKS_EXPORT TabWidgetWidget
{
Q_OBJECT
public:
explicit TabWidgetWidget(Frame *parent);
explicit TabWidgetWidget(Frame *parent, TabWidgetOptions = TabWidgetOption_None);
TabBar *tabBar() const override;

View File

@@ -59,7 +59,7 @@ protected:
bool isFloatButtonEnabled() const override;
#endif
private:
protected:
void init();
int buttonAreaWidth() const;
void updateMargins();

View File

@@ -25,6 +25,7 @@ set(TESTING_SRCS utils.cpp Testing.cpp)
include_directories(..)
include_directories(../src)
include_directories(../src/private)
add_definitions(-DQT_NO_KEYWORDS)
set(TESTING_SRCS utils.cpp Testing.cpp)

View File

@@ -25,7 +25,9 @@
#include "TabWidget_p.h"
#include "TitleBar_p.h"
#include "WindowBeingDragged_p.h"
#include "MDIArea.h"
#include "multisplitter/Separator_p.h"
#include "multisplitter/Item_p.h"
#include "private/MultiSplitter_p.h"
#include <QAction>
@@ -323,8 +325,8 @@ void TestDocks::tst_detachPos()
m->addDockWidget(dock1, Location_OnLeft);
m->addDockWidget(dock2, Location_OnRight);
QVERIFY(!dock1->dptr()->lastPositions().lastFloatingGeometry().isValid());
QVERIFY(!dock2->dptr()->lastPositions().lastFloatingGeometry().isValid());
QVERIFY(!dock1->dptr()->lastPosition()->lastFloatingGeometry().isValid());
QVERIFY(!dock2->dptr()->lastPosition()->lastFloatingGeometry().isValid());
const int previousWidth = dock1->width();
dock1->setFloating(true);
@@ -382,7 +384,7 @@ void TestDocks::tst_tabbingWithAffinities()
auto dropArea = m1->dropArea();
WindowBeingDragged wbd(fw2, fw2);
QVERIFY(!dropArea->drop(&wbd, dw1->dptr()->frame(),
DropIndicatorOverlayInterface::DropLocation_Center));
DropLocation_Center));
QVERIFY(dw1->window() != dw2->window());
}
@@ -790,11 +792,11 @@ void TestDocks::tst_doubleClose()
auto dock1 = createDockWidget("1", new QPushButton("1"));
m->addDockWidget(dock1, Location_OnBottom);
QVERIFY(!dock1->dptr()->lastPositions().wasFloating());
QVERIFY(!dock1->dptr()->lastPosition()->wasFloating());
dock1->close();
QVERIFY(!dock1->dptr()->lastPositions().wasFloating());
QVERIFY(!dock1->dptr()->lastPosition()->wasFloating());
dock1->close();
QVERIFY(!dock1->dptr()->lastPositions().wasFloating());
QVERIFY(!dock1->dptr()->lastPosition()->wasFloating());
}
}
@@ -1678,7 +1680,7 @@ void TestDocks::tst_removeItem()
m->addDockWidget(dock1, Location_OnBottom);
m->addDockWidget(dock2, Location_OnTop, nullptr, InitialVisibilityOption::StartHidden);
Item *item2 = dock2->dptr()->lastPositions().lastItem();
Item *item2 = dock2->dptr()->lastPosition()->lastItem();
auto dropArea = m->dropArea();
MultiSplitter *layout = dropArea;
@@ -1694,7 +1696,7 @@ void TestDocks::tst_removeItem()
QCOMPARE(layout->placeholderCount(), 0);
// 2. Remove an item that has an actual widget
Item *item1 = dock1->dptr()->lastPositions().lastItem();
Item *item1 = dock1->dptr()->lastPosition()->lastItem();
layout->removeItem(item1);
QCOMPARE(layout->count(), 0);
QCOMPARE(layout->placeholderCount(), 0);
@@ -1716,11 +1718,11 @@ void TestDocks::tst_removeItem()
QCOMPARE(layout->placeholderCount(), 2);
// Now remove the items
layout->removeItem(dock2->dptr()->lastPositions().lastItem());
layout->removeItem(dock2->dptr()->lastPosition()->lastItem());
QCOMPARE(layout->count(), 2);
QCOMPARE(layout->placeholderCount(), 1);
layout->checkSanity();
layout->removeItem(dock1->dptr()->lastPositions().lastItem());
layout->removeItem(dock1->dptr()->lastPosition()->lastItem());
QCOMPARE(layout->count(), 1);
QCOMPARE(layout->placeholderCount(), 0);
@@ -1733,11 +1735,11 @@ void TestDocks::tst_removeItem()
QVERIFY(Testing::waitForDeleted(frame1));
// Now remove the items, but first dock1
layout->removeItem(dock1->dptr()->lastPositions().lastItem());
layout->removeItem(dock1->dptr()->lastPosition()->lastItem());
QCOMPARE(layout->count(), 2);
QCOMPARE(layout->placeholderCount(), 1);
layout->checkSanity();
layout->removeItem(dock2->dptr()->lastPositions().lastItem());
layout->removeItem(dock2->dptr()->lastPosition()->lastItem());
QCOMPARE(layout->count(), 1);
QCOMPARE(layout->placeholderCount(), 0);
layout->checkSanity();
@@ -1755,7 +1757,7 @@ void TestDocks::tst_removeItem()
Testing::waitForDeleted(frame3);
// The second anchor is now following the 3rd, while the 3rd is following 'bottom'
layout->removeItem(dock3->dptr()->lastPositions().lastItem()); // will trigger the 3rd anchor to
layout->removeItem(dock3->dptr()->lastPosition()->lastItem()); // will trigger the 3rd anchor to
// be removed
QCOMPARE(layout->count(), 2);
QCOMPARE(layout->placeholderCount(), 1);
@@ -1883,7 +1885,7 @@ void TestDocks::tst_crash()
QVERIFY(dock1->isFloating());
QVERIFY(!dock1->isInMainWindow());
Item *layoutItem = dock1->dptr()->lastPositions().lastItem();
Item *layoutItem = dock1->dptr()->lastPosition()->lastItem();
QVERIFY(layoutItem && DockRegistry::self()->itemIsInMainWindow(layoutItem));
QCOMPARE(layoutItem, item1);
@@ -1940,7 +1942,7 @@ void TestDocks::tst_refUnrefItem()
QVERIFY(dock2);
QVERIFY(item2.data());
QCOMPARE(item2->refCount(), 1);
QCOMPARE(dock2->dptr()->lastPositions().lastItem(), item2.data());
QCOMPARE(dock2->dptr()->lastPosition()->lastItem(), item2.data());
delete dock2;
QVERIFY(!item2.data());
@@ -2534,8 +2536,8 @@ void TestDocks::tst_setFloatingWhenWasTabbed()
QVERIFY(!dock1->isFloating());
QVERIFY(dock2->isFloating());
QCOMPARE(dock2->dptr()->lastPositions().lastTabIndex(), 1);
QVERIFY(dock2->dptr()->lastPositions().isValid());
QCOMPARE(dock2->dptr()->lastPosition()->lastTabIndex(), 1);
QVERIFY(dock2->dptr()->lastPosition()->isValid());
dock2->setFloating(false);
QVERIFY(dock1->isTabbed());
@@ -2710,7 +2712,7 @@ void TestDocks::tst_dockWidgetGetsFocusWhenDocked()
QVERIFY(dw1->isFocused());
QVERIFY(fw1->isActiveWindow());
dragFloatingWindowTo(fw2, fw1->dropArea(), DropIndicatorOverlayInterface::DropLocation_Left);
dragFloatingWindowTo(fw2, fw1->dropArea(), DropLocation_Left);
Testing::waitForEvent(fw1, QEvent::WindowActivate);
/// We dropped into floating window 1, it should still be active
@@ -2806,10 +2808,10 @@ void TestDocks::tst_floatingLastPosAfterDoubleClose()
{
EnsureTopLevelsDeleted e;
auto d1 = new DockWidgetType(QStringLiteral("a"));
QVERIFY(d1->dptr()->lastPositions().lastFloatingGeometry().isNull());
QVERIFY(d1->dptr()->lastPosition()->lastFloatingGeometry().isNull());
QVERIFY(!d1->isVisible());
d1->close();
QVERIFY(d1->dptr()->lastPositions().lastFloatingGeometry().isNull());
QVERIFY(d1->dptr()->lastPosition()->lastFloatingGeometry().isNull());
delete d1;
}
@@ -2886,7 +2888,7 @@ void TestDocks::tst_dockWindowWithTwoSideBySideFramesIntoRight()
auto fw2 = createFloatingWindow();
fw2->move(fw->x() + fw->width() + 100, fw->y());
dragFloatingWindowTo(fw, fw2->dropArea(), DropIndicatorOverlayInterface::DropLocation_Right); // Outer right instead of Left
dragFloatingWindowTo(fw, fw2->dropArea(), DropLocation_Right); // Outer right instead of Left
QCOMPARE(fw2->frames().size(), 3);
QVERIFY(fw2->dropArea()->checkSanity());
@@ -2910,7 +2912,7 @@ void TestDocks::tst_dockWindowWithTwoSideBySideFramesIntoLeft()
fw2->move(fw->x() + fw->width() + 100, fw->y());
QVERIFY(fw2->dropArea()->checkSanity());
dragFloatingWindowTo(fw, fw2->dropArea(), DropIndicatorOverlayInterface::DropLocation_Left);
dragFloatingWindowTo(fw, fw2->dropArea(), DropLocation_Left);
QCOMPARE(fw2->frames().size(), 3);
QVERIFY(fw2->dropArea()->checkSanity());
@@ -2987,8 +2989,11 @@ void TestDocks::tst_preventClose()
fw->close();
QVERIFY(dock1->isVisible());
dock1->deleteLater();
QVERIFY(Testing::waitForDeleted(dock1));
// Put into a main window
auto m = createMainWindow();
m->addDockWidget(dock1, KDDockWidgets::Location_OnRight);
m->close();
QVERIFY(dock1->isVisible());
}
void TestDocks::tst_propagateMinSize()
@@ -3051,7 +3056,7 @@ void TestDocks::tst_addAndReadd()
auto fw = dock1->floatingWindow();
QVERIFY(fw);
auto dropArea = m->dropArea();
dragFloatingWindowTo(fw, dropArea, DropIndicatorOverlayInterface::DropLocation_Right);
dragFloatingWindowTo(fw, dropArea, DropLocation_Right);
QVERIFY(dock1->dptr()->frame()->titleBar()->isVisible());
fw->titleBar()->makeWindow();
m->layoutWidget()->checkSanity();
@@ -3147,7 +3152,7 @@ void TestDocks::tst_addToSmallMainWindow3()
auto fw = dock2->dptr()->morphIntoFloatingWindow();
QVERIFY(fw->isVisible());
QVERIFY(dropArea->checkSanity());
dragFloatingWindowTo(fw, dropArea, DropIndicatorOverlayInterface::DropLocation_Right);
dragFloatingWindowTo(fw, dropArea, DropLocation_Right);
QVERIFY(m->dropArea()->checkSanity());
delete fw;
}
@@ -4141,7 +4146,7 @@ void TestDocks::tst_dragOverTitleBar()
const QPoint titleBarPoint = fw1->titleBar()->mapToGlobal(QPoint(5, 5));
auto loc = da->hover(&wbd, titleBarPoint);
QCOMPARE(loc, DropIndicatorOverlayInterface::DropLocation_None);
QCOMPARE(loc, DropLocation_None);
}
delete fw1;
@@ -4205,7 +4210,7 @@ void TestDocks::tst_setFloatingAfterDraggedFromTabToSideBySide()
m->addDockWidget(dock1, KDDockWidgets::Location_OnLeft);
dock1->addDockWidgetAsTab(dock2);
Item *oldItem2 = dock2->dptr()->lastPositions().lastItem();
Item *oldItem2 = dock2->dptr()->lastPosition()->lastItem();
QCOMPARE(oldItem2, layout->itemForFrame(dock2->dptr()->frame()));
@@ -4214,7 +4219,7 @@ void TestDocks::tst_setFloatingAfterDraggedFromTabToSideBySide()
QVERIFY(layout->checkSanity());
auto fw2 = dock2->floatingWindow();
QVERIFY(fw2);
QCOMPARE(dock2->dptr()->lastPositions().lastItem(), oldItem2);
QCOMPARE(dock2->dptr()->lastPosition()->lastItem(), oldItem2);
Item *item2 = fw2->dropArea()->itemForFrame(dock2->dptr()->frame());
QVERIFY(item2);
QCOMPARE(item2->hostWidget()->asQObject(), fw2->dropArea());
@@ -4223,7 +4228,7 @@ void TestDocks::tst_setFloatingAfterDraggedFromTabToSideBySide()
// Move from tab to bottom
layout->addWidget(fw2->dropArea(), KDDockWidgets::Location_OnRight, nullptr);
QVERIFY(layout->checkSanity());
QVERIFY(dock2->dptr()->lastPositions().lastItem());
QVERIFY(dock2->dptr()->lastPosition()->lastItem());
QCOMPARE(layout->count(), 2);
QCOMPARE(layout->placeholderCount(), 0);
@@ -4843,7 +4848,7 @@ void TestDocks::tst_lastFloatingPositionIsRestored()
// Now dock it:
m1->addDockWidget(dock1, Location_OnTop);
QCOMPARE(dock1->dptr()->lastPositions().lastFloatingGeometry().topLeft(), targetPos);
QCOMPARE(dock1->dptr()->lastPosition()->lastFloatingGeometry().topLeft(), targetPos);
dock1->setFloating(true);
QCOMPARE(dock1->window()->geometry().topLeft(), targetPos);
@@ -5058,6 +5063,303 @@ void TestDocks::tst_dockableMainWindows()
fw->dropArea()->addDockWidget(dock1, Location::Location_OnLeft, nullptr);
}
void TestDocks::tst_mdi_mixed_with_docking()
{
EnsureTopLevelsDeleted e;
auto m = createMainWindow(QSize(500, 500), MainWindowOption_HasCentralWidget);
auto dock1 = createDockWidget("1", new QPushButton("1"));
m->addDockWidget(dock1, Location_OnBottom);
auto mdiArea = new MDIArea();
m->setPersistentCentralWidget(mdiArea);
auto mdiWidget1 = createDockWidget("mdi1", new QPushButton("mdi1"));
auto mdiWidget2 = createDockWidget("mdi2", new QPushButton("mdi12"));
mdiArea->addDockWidget(mdiWidget1, QPoint(10, 10));
mdiArea->addDockWidget(mdiWidget2, QPoint(50, 50));
Frame *frameMDI1 = mdiWidget1->d->frame();
Frame *frame1 = dock1->d->frame();
QVERIFY(!frame1->isMDI());
QVERIFY(frameMDI1->isMDI());
QVERIFY(!frame1->mdiLayoutWidget());
QVERIFY(frameMDI1->mdiLayoutWidget());
QVERIFY(!dock1->titleBar()->isMDI());
auto tb1 = mdiWidget1->titleBar();
QVERIFY(tb1->isMDI());
QVERIFY(Testing::waitForEvent(tb1, QEvent::Show));
QVERIFY(tb1->isVisible());
// Press the float button
tb1->onFloatClicked();
QVERIFY(mdiWidget1->d->lastPosition()->isValid());
QVERIFY(mdiWidget1->titleBar()->isVisible());
QVERIFY(mdiWidget1->isFloating());
// Dock again, and check it went back
mdiWidget1->titleBar()->onFloatClicked();
QVERIFY(!mdiWidget1->isFloating());
}
void TestDocks::tst_mdi_mixed_with_docking2()
{
// Here, the MDI dock widgets are themselves main windows which will show drop-indicators.
// It will be super nested: MainWindow -> MDI -> MainWindow
EnsureTopLevelsDeleted e;
auto m = createMainWindow(QSize(1000, 500), MainWindowOption_HasCentralWidget);
auto dock1 = createDockWidget("1", new QPushButton("1"));
m->addDockWidget(dock1, Location_OnBottom);
auto mdiArea = new MDIArea();
m->setPersistentCentralWidget(mdiArea);
auto createSheet = [](int id) -> DockWidgetBase* {
auto dock = new DockWidget(QStringLiteral("dw-sheet-%1").arg(id), DockWidgetBase::Option_MDINestable);
dock->setWidget(new QPushButton(QStringLiteral("Sheet %1").arg(id)));
dock->setTitle(QStringLiteral("Sheet %1").arg(id));
return dock;
};
auto mdiWidget1 = createSheet(1);
auto mdiWidget2 = createSheet(2);
auto mdiWidget3 = createSheet(3);
/*auto mdiWidget4 = createSheet(4);
auto mdiWidget5 = createSheet(5);
auto mdiWidget6 = createSheet(6);*/
mdiArea->addDockWidget(mdiWidget1, QPoint(10, 10));
mdiArea->addDockWidget(mdiWidget2, QPoint(50, 50));
Frame *frame1 = mdiWidget1->d->frame();
Frame *mdiFrame1 = frame1->mdiFrame();
QPointer<Frame> frame2 = mdiWidget2->d->frame();
QPointer<Frame> mdiFrame2 = frame2->mdiFrame();
QPointer<DropArea> dropArea2 = frame2->mdiDropAreaWrapper();
QPointer<DropArea> dropArea1 = frame1->mdiDropAreaWrapper();
dropArea1->addDockWidget(mdiWidget3, Location_OnLeft, nullptr);
QVERIFY(!frame1->isMDI());
QVERIFY(frame1->isMDIWrapper());
QVERIFY(frame1->mdiDockWidgetWrapper());
QVERIFY(dropArea1->isMDIWrapper());
QVERIFY(dropArea2->isMDIWrapper());
QVERIFY(!mdiFrame1->isMDIWrapper());
QVERIFY(mdiFrame1->isMDI());
QVERIFY(!mdiWidget1->d->isMDIWrapper());
// Test title bars:
auto tb1 = mdiWidget1->titleBar();
auto mdiTb1 = mdiFrame1->titleBar();
auto mdiTb2 = mdiFrame2->titleBar();
Testing::waitForEvent(mdiTb1, QEvent::Show);
QVERIFY(mdiTb1->isVisible());
QVERIFY(mdiTb2->isVisible());
QVERIFY(tb1->isVisible());
QCOMPARE(tb1->title(), QString("Sheet 1"));
QCOMPARE(mdiTb1->title(), QString("dockwidgets-unit-tests"));
QCOMPARE(mdiTb2->title(), QString("Sheet 2"));
QVERIFY(tb1 != mdiTb1);
QCOMPARE(mdiWidget2->titleBar(), mdiTb2);
// Test that closing will delete the wrappers:
mdiWidget2->close();
QVERIFY(!mdiWidget2->isOpen());
Testing::waitForDeleted(dropArea2);
QVERIFY(dropArea2.isNull());
QVERIFY(!mdiFrame2);
mdiWidget1->close();
QVERIFY(!mdiWidget1->isOpen());
QTest::qWait(500); // wait some event loops to make sure there's no delete later. (There isn't, but a bug could introduce them)
QVERIFY(!dropArea1.isNull()); // Not deleted, as sheet3 is still there
QCOMPARE(dropArea1->visibleCount(), 1);
QVERIFY(mdiTb1->isVisible());
QCOMPARE(mdiWidget3->titleBar(), mdiTb1);
Frame *frame3 = mdiWidget3->d->frame();
QVERIFY(!frame3->titleBar()->isVisible());
mdiWidget3->close();
QVERIFY(Testing::waitForDeleted(dropArea1));
QVERIFY(!mdiWidget3->isOpen());
QVERIFY(dropArea1.isNull());
// Reopen everything again:
mdiArea->addDockWidget(mdiWidget1, QPoint(10, 10));
mdiArea->addDockWidget(mdiWidget2, QPoint(50, 50));
frame1 = mdiWidget1->d->frame();
mdiFrame1 = frame1->mdiFrame();
dropArea1 = frame1->mdiDropAreaWrapper();
dropArea1->addDockWidget(mdiWidget3, Location_OnLeft, nullptr);
// Test floating:
frame2 = mdiWidget2->d->frame();
QPointer<DockWidgetBase> dwWrapper2 = frame2->mdiDockWidgetWrapper();
dropArea2 = frame2->mdiDropAreaWrapper();
QVERIFY(mdiWidget2->isVisible());
QVERIFY(frame2->isMDIWrapper());
QVERIFY(dwWrapper2->d->isMDIWrapper());
mdiFrame2 = frame2->mdiFrame();
mdiWidget2->setFloating(true);
QVERIFY(mdiWidget2->isFloating());
QVERIFY(!mdiWidget2->d->frame()->isMDI());
QVERIFY(!mdiWidget2->d->frame()->isMDIWrapper());
QVERIFY(Testing::waitForDeleted(mdiFrame2));
QVERIFY(dropArea2.isNull());
QVERIFY(dwWrapper2.isNull());
auto mdiFrames = mdiArea->frames();
QCOMPARE(mdiFrames.count(), 1);
mdiFrame1 = mdiFrames.first();
QVERIFY(mdiFrame1->isMDI());
QVERIFY(mdiFrame1->hasNestedMDIDockWidgets());
auto mdiTitleBar1 = mdiFrame1->titleBar();
QVERIFY(mdiFrame1->titleBar()->isVisible());
mdiTitleBar1->makeWindow();
QVERIFY(Testing::waitForDeleted(mdiFrame1));
QCOMPARE(mdiArea->frames().size(), 0);
// Dock again:
mdiArea->addDockWidget(mdiWidget1, QPoint(10, 10));
mdiArea->addDockWidget(mdiWidget2, QPoint(50, 50));
frame1 = mdiWidget1->d->frame();
dropArea1 = frame1->mdiDropAreaWrapper();
dropArea1->addDockWidget(mdiWidget3, Location_OnLeft, nullptr);
// Detach an internal dock widget by dragging
const QPoint globalSrc = mdiWidget1->mapToGlobal(QPoint(5, 5));
const QPoint globalDest = globalSrc + QPoint(100, 100);
drag(mdiWidget1, globalDest);
QCOMPARE(mdiArea->frames().count(), 2);
auto mdiTitleBar = mdiArea->frames().first()->titleBar();
QVERIFY(mdiTitleBar->isVisible());
QVERIFY(!mdiWidget3->isFloating());
QVERIFY(mdiWidget3->d->lastPosition()->isValid());
mdiTitleBar->onFloatClicked();
QVERIFY(mdiWidget3->isFloating());
QVERIFY(Testing::waitForDeleted(mdiArea->frames().constFirst()));
QCOMPARE(mdiArea->frames().size(), 1);
QVERIFY(!mdiWidget2->isFloating());
Frame *lastMdiFrame = mdiArea->frames().constFirst();
QVERIFY(lastMdiFrame->titleBar()->isVisible());
QVERIFY(!lastMdiFrame->titleBar()->isFloating());
lastMdiFrame->titleBar()->onFloatClicked();
QVERIFY(mdiWidget2->isFloating());
// put it in the MDI area again
mdiWidget2->titleBar()->onFloatClicked();
QVERIFY(!mdiWidget2->isFloating());
}
void TestDocks::tstCloseNestedMdi()
{
// Tests a bug where closing a mdi dock widget would close its main window too
EnsureTopLevelsDeleted e;
auto m = createMainWindow(QSize(1000, 500), MainWindowOption_HasCentralWidget);
QPointer<MainWindowBase> p = m.get();
auto mdi = new KDDockWidgets::MDIArea();
m->setPersistentCentralWidget(mdi);
auto dock1 = new KDDockWidgets::DockWidget(QStringLiteral("MyDock1"));
dock1->setWidget( new QPushButton("1"));
mdi->addDockWidget(dock1, {});
dock1->titleBar()->onCloseClicked();
QVERIFY(p);
QVERIFY(m->isVisible());
}
void TestDocks::tstCloseNestedMDIPropagates()
{
auto m = createMainWindow(QSize(1000, 500), MainWindowOption_HasCentralWidget);
QPointer<MainWindowBase> p = m.get();
auto mdi = new KDDockWidgets::MDIArea();
m->setPersistentCentralWidget(mdi);
auto dock1 = new KDDockWidgets::DockWidget(QStringLiteral("MyDock1"));
dock1->setWidget(new NonClosableWidget());
mdi->addDockWidget(dock1, {});
auto dock2 = new KDDockWidgets::DockWidget(QStringLiteral("MyDock2"));
dock2->setWidget(new NonClosableWidget());
dock2->show();
QVERIFY(Testing::waitForEvent(dock1, QEvent::Show));
QVERIFY(dock1->isVisible());
QVERIFY(dock2->isVisible());
m->close();
QVERIFY(dock2->isVisible());
QVERIFY(dock1->isVisible());
}
void TestDocks::tst_mdi_mixed_with_docking_setMDISize()
{
EnsureTopLevelsDeleted e;
auto m = createMainWindow(QSize(1000, 500), MainWindowOption_HasCentralWidget);
auto dock1 = createDockWidget("1", new QPushButton("1"));
m->addDockWidget(dock1, Location_OnBottom);
auto mdiArea = new MDIArea();
m->setPersistentCentralWidget(mdiArea);
auto createSheet = [](int id) -> DockWidgetBase* {
auto dock = new DockWidget(QStringLiteral("dw-sheet-%1").arg(id), DockWidgetBase::Option_MDINestable);
dock->setWidget(new QPushButton(QStringLiteral("Sheet %1").arg(id)));
dock->setTitle(QStringLiteral("Sheet %1").arg(id));
return dock;
};
auto mdiWidget1 = createSheet(1);
auto mdiWidget2 = createSheet(2);
mdiArea->addDockWidget(mdiWidget1, QPoint(10, 10));
mdiArea->addDockWidget(mdiWidget2, QPoint(50, 50));
Frame *frame1 = mdiArea->frames().at(0);
const QSize sz1 = frame1->QWidgetAdapter::size();
const QSize increment(200, 200);
QVERIFY(mdiWidget1->d->mdiLayout());
mdiWidget1->setMDISize(sz1 + increment);
QCOMPARE(frame1->QWidgetAdapter::size(), sz1 + increment);
}
// No need to port to QtQuick
void TestDocks::tst_floatingWindowDeleted()
{
@@ -5432,7 +5734,7 @@ void TestDocks::tst_embeddedMainWindow()
auto dropArea = window->mainWindow->dropArea();
auto fw = dock1->floatingWindow();
dragFloatingWindowTo(fw, dropArea, DropIndicatorOverlayInterface::DropLocation_Left);
dragFloatingWindowTo(fw, dropArea, DropLocation_Left);
auto layout = dropArea;
QVERIFY(Testing::waitForDeleted(fw));
@@ -7064,7 +7366,7 @@ void TestDocks::tst_dragByTabBar()
QVERIFY(fw->isVisible());
QVERIFY(!fw->titleBar()->isVisible());
dragFloatingWindowTo(fw, dropArea, DropIndicatorOverlayInterface::DropLocation_Right);
dragFloatingWindowTo(fw, dropArea, DropLocation_Right);
}
void TestDocks::tst_dock2FloatingWidgetsTabbed()
@@ -7382,6 +7684,58 @@ void TestDocks::tst_resizePropagatesEvenly()
QVERIFY(qAbs(dock2->height() - dock1->height()) < 3);
}
void TestDocks::tst_unfloatTabbedFloatingWidgets()
{
/// A main window with 2 tabbed dock widgets.
/// Tests that we're able to float the entire tab group and
/// put it back into the main window when float button is pressed again.
auto m = createMainWindow(QSize(1000, 1000), MainWindowOption_None);
auto dock0 = createDockWidget("dock0", new MyWidget2());
auto dock1 = createDockWidget("dock1", new MyWidget2());
m->addDockWidget(dock0, Location_OnLeft);
dock0->addDockWidgetAsTab(dock1);
dock0->titleBar()->onFloatClicked();
QVERIFY(dock0->titleBar()->isFloating());
QVERIFY(!dock0->mainWindow());
dock0->titleBar()->onFloatClicked();
QVERIFY(!dock0->titleBar()->isFloating());
QVERIFY(dock0->mainWindow());
}
void TestDocks::tst_unfloatTabbedFloatingWidgets2()
{
EnsureTopLevelsDeleted e;
// Like tst_unfloatTabbedFloatingWidgets, but now there's no main window, just a FloatingWindow
// with nesting.
auto dock0 = createDockWidget("dock0", new MyWidget2());
auto dock1 = createDockWidget("dock1", new MyWidget2());
auto dock2 = createDockWidget("dock2", new MyWidget2());
dock0->show();
dock1->show();
dock2->show();
dock0->addDockWidgetAsTab(dock1);
dock0->addDockWidgetToContainingWindow(dock2, KDDockWidgets::Location_OnBottom);
// Float:
dock0->titleBar()->onFloatClicked();
QVERIFY(dock0->titleBar()->isFloating());
QCOMPARE(dock0->floatingWindow(), dock1->floatingWindow());
QVERIFY(dock0->floatingWindow() != dock2->floatingWindow());
// redock, nothing happens as we don't have a previous docked position in main window
dock0->titleBar()->onFloatClicked();
QVERIFY(dock0->titleBar()->isFloating());
QCOMPARE(dock0->floatingWindow(), dock1->floatingWindow());
QVERIFY(dock0->floatingWindow() != dock2->floatingWindow());
}
void TestDocks::tst_addMDIDockWidget()
{
EnsureTopLevelsDeleted e;

View File

@@ -240,6 +240,8 @@ private Q_SLOTS:
void tst_toggleTabbed();
void tst_toggleTabbed2();
void tst_resizePropagatesEvenly();
void tst_unfloatTabbedFloatingWidgets();
void tst_unfloatTabbedFloatingWidgets2();
void tst_addMDIDockWidget();
void tst_redockToMDIRestoresPosition();
@@ -250,6 +252,11 @@ private Q_SLOTS:
void tst_mainWindowAlwaysHasCentralWidget();
void tst_dockableMainWindows();
void tst_mdi_mixed_with_docking();
void tst_mdi_mixed_with_docking2();
void tst_mdi_mixed_with_docking_setMDISize();
void tstCloseNestedMdi();
void tstCloseNestedMDIPropagates();
// But these are fine to be widget only:
void tst_tabsNotClickable();

View File

@@ -393,9 +393,13 @@ inline void drag(WidgetType *sourceWidget, QPoint pressGlobalPos, QPoint globalD
if (s_pauseBeforeMove)
QTest::qWait(DEBUGGING_PAUSE_DURATION);
qDebug() << "Moving sourceWidget to" << globalDest
qDebug() << "Moving sourceWidget=" << sourceWidget << "to" << globalDest
<< "; sourceWidget->size=" << sourceWidget->size()
<< "; from=" << QCursor::pos();
<< "; pressPosGlobal=" << pressGlobalPos
<< "; pressPosLocal=" << sourceWidget->mapFromGlobal(pressGlobalPos)
<< "; from=" << QCursor::pos()
<< "; actions=" << buttonActions
<< "; visible=" << sourceWidget->isVisible();
moveMouseTo(globalDest, sourceWidget);
qDebug() << "Arrived at" << QCursor::pos();
pressGlobalPos = KDDockWidgets::mapToGlobal(sourceWidget, QPoint(10, 10));
@@ -411,7 +415,7 @@ inline void drag(WidgetType *sourceWidget, QPoint globalDest,
WidgetType *draggable = draggableFor(sourceWidget);
Q_ASSERT(draggable && draggable->isVisible());
const QPoint pressGlobalPos = KDDockWidgets::mapToGlobal(draggable, QPoint(6, 6));
const QPoint pressGlobalPos = KDDockWidgets::mapToGlobal(draggable, QPoint(15, 15));
drag(draggable, pressGlobalPos, globalDest, buttonActions);
}
@@ -425,7 +429,7 @@ inline void dragFloatingWindowTo(FloatingWindow *fw, QPoint globalDest,
drag(draggable, KDDockWidgets::mapToGlobal(draggable, QPoint(10, 10)), globalDest, buttonActions);
}
inline void dragFloatingWindowTo(FloatingWindow *fw, DropArea *target, DropIndicatorOverlayInterface::DropLocation dropLocation)
inline void dragFloatingWindowTo(FloatingWindow *fw, DropArea *target, DropLocation dropLocation)
{
auto draggable = draggableFor(fw);
Q_ASSERT(draggable);