Compare commits

...

30 Commits

Author SHA1 Message Date
Renato Araujo Oliveira Filho
c0d8f22049 Use PySide from the system when possible 2020-06-16 17:57:56 -03:00
Renato Araujo Oliveira Filho
c2d8991283 Allow to install python bindings in a custom dir
Added cmake var PYTHON_BINDINGS_INSTALL_PREFIX
2020-06-16 17:57:46 -03:00
Renato Araujo Oliveira Filho
a599736c1b Create Python bidings
Added Cmake files
Ported examples
2020-06-16 15:12:41 -03:00
Renato Araujo Oliveira Filho
e6a335a3f7 Use enum explicitly constructor as default values
This will avoid problems generating python bindings, shiboken does not
handle well '{}' initialization.
2020-06-10 12:37:46 -03:00
Sergio Martins
a7878122ac Only use QT_DISABLE_DEPRECATED_BEFORE in developer-mode
Many projects just add kddw as a sub-folder and might have
QT_DISABLE_DEPRECATED_BEFORE with a different value, then you get
lots of warnings
2020-06-09 12:40:37 +01:00
Sergio Martins
fa32054085 Merge pull request #48 from KDAB/fix-warning
Fix -Wextra-semi
2020-06-08 21:20:26 +01:00
Colin Ogilvie
f3de1ad63d Fix -Wextra-semi 2020-06-08 19:36:18 +01:00
Sergio Martins
8baac15d24 Fix floating window position when dragging with constraints
When the detach starts we might make the window smaller so it
respects the max-size constraint. The quirk was that in that
case the window was no longer under the mouse cursor, so looked
weird while dragging, although it worked
2020-06-08 18:19:46 +01:00
Sergio Martins
4cf1159019 Two more places that should use setSuggestedGeometry()
Now dragging by the tab bar also respects the max-size
2020-06-08 17:40:17 +01:00
Sergio Martins
ee065795b9 Fix Frame max size accounting for the waste twice
Frame::biggestDockWidgetMaxSize() should return only the max size
of the dock widget, and not mix with sizes of the container frame
2020-06-07 23:12:47 +01:00
Sergio Martins
7f9d160658 Also honour QSizePolicy::Maximum
It's yet another way to say you want a max size
2020-06-07 20:14:50 +01:00
Sergio Martins
daa220d513 tests: let the widget receive a size hint 2020-06-07 20:01:37 +01:00
Sergio Martins
d5797a3aea Honour QSizePolicy::Fixed too
Some widgets, for example QPushButton, instead of having an explicit max size,
they communicate the need for a max size through their size policy
2020-06-07 19:53:06 +01:00
Sergio Martins
ab8545e2c2 Bound the item max size just in case 2020-06-06 20:27:36 +01:00
Sergio Martins
5fc16954f5 Fix max-size only working in one orientation
ItemContainer::maxSizeHint() algo was wrong.
Add unit-test too.
2020-06-06 20:11:09 +01:00
Sergio Martins
6610e48cb3 Fix qDebug for child items going out of scope before the parents
Made the child debug lines appear before
2020-06-06 18:12:44 +01:00
Sergio Martins
dabfeeaf3b Honour max-size when floating a window 2020-06-05 15:35:55 +01:00
Sergio Martins
9601f57050 example: Don't show the dock widgets immediately
It's not needed, as they are going to be added to the layout.
Saves us from flicker and also the temporary floating position
being saved
2020-06-05 13:43:35 +01:00
Sergio Martins
7c442dce85 Fix another place that should use setSuggestedGeometry 2020-06-04 21:47:47 +01:00
Sergio Martins
07ea3ff1a6 cleanup: Replace some qobject_casts with a function 2020-06-04 21:37:01 +01:00
Sergio Martins
874fd7d69f Don't set the FloatingWindow geometry directly
Let's have an indirection, so FloatingWindow can do some adjustments
and use a smaller rect incase of max-size.

Since we don't want to enforce max-size, just when showing the window.
2020-06-04 21:21:11 +01:00
Sergio Martins
69a737e286 FloatingWindow: Remove cruft 2020-06-04 21:16:22 +01:00
Sergio Martins
ddc49c9358 Don't warn when restoring and there's no last pos info
We now restore floating windows to their previous position too
when toggling float. If there's no last info it's fine
2020-06-04 18:21:52 +01:00
Sergio Martins
2b3c3b75bf Fix build with older cmake
Error was:
"install TARGETS given target "kddockwidgets_multisplitter" which does not
exist in this directory."

Fixes: #47
2020-06-04 15:29:34 +01:00
Sergio Martins
296889cace Move TabWidget usage to FrameWidget
Frame is abstract and doesn't care about such implementation details.
QQuick will role out their own QTabWidget equivalent. No need to
abstract tab widget too, it's overkill.
2020-06-04 09:57:59 +01:00
Sergio Martins
4ebe8ed631 harden Frame's pure virtuals
Too many paths to control, so protect against calling the pure
virtuals in ctor/dtor
2020-06-03 22:31:03 +01:00
Sergio Martins
ecfa43f801 Decouple TabWidget from Frame
TabWidget is now an implementation detail of FrameWidget.
QQuick will roll their own stuff with similar api, but no need to
abstract QTabWidget and QTabBar
2020-06-03 22:18:44 +01:00
Sergio Martins
f5f39a37a1 Decouple DockWidget from TabWidget
TabWidget will be an implementation detail of FrameWidget, and not
shared by QtQuick
2020-06-03 21:38:47 +01:00
Sergio Martins
0a75d89848 Add Frame::detachTab()
The tab widget will be an implementation detail of FrameWidget and
not accessed by anyone
2020-06-03 21:18:38 +01:00
Sergio Martins
e418725c13 Don't create Frame directly, but FrameWidget, depending on the factory 2020-06-03 21:18:13 +01:00
44 changed files with 1563 additions and 309 deletions

View File

@@ -15,6 +15,7 @@ cmake_policy(SET CMP0020 NEW)
cmake_policy(SET CMP0042 NEW)
option(OPTION_DEVELOPER_MODE "Developer Mode" OFF)
option(OPTION_BUILD_PYTHON_BINDINGS "Build python bindings" OFF)
# option(OPTION_QTQUICK "Build for QtQuick instead of QtWidgets" OFF)
find_package(Qt5Widgets)
@@ -22,7 +23,8 @@ find_package(Qt5Widgets)
set(CMAKE_AUTOMOC ON)
set(ECM_MODULE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/cmake/ECM/modules/")
set(CMAKE_MODULE_PATH ${ECM_MODULE_DIR})
set(PYTHON_MODULE_DIR "${CMAKE_CURRENT_LIST_DIR}/cmake/Python")
set(CMAKE_MODULE_PATH ${ECM_MODULE_DIR} ${PYTHON_MODULE_DIR})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
if (OPTION_DEVELOPER_MODE)
@@ -42,6 +44,9 @@ endif()
add_subdirectory(src)
add_subdirectory(examples/dockwidgets)
if (OPTION_BUILD_PYTHON_BINDINGS)
add_subdirectory(python)
endif()
if (OPTION_DEVELOPER_MODE)
if (NOT OPTION_QTQUICK)

View File

@@ -0,0 +1,168 @@
###
# This file is part of KDDockWidgets.
#
# Copyright (C) 2018-2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
# Author: Renato Araujo Oliveira Filho <renato.araujo@kdab.com>
#
# This program is free software you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
##
# PYSIDE_BASEDIR - Top of the PySide2 installation
# PYSIDE_INCLUDE_DIR - Directories to include to use PySide2
# PYSIDE_LIBRARY - Files to link against to use PySide2
# PYSIDE_TYPESYSTEMS - Type system files that should be used by other bindings extending PySide2
#
# You can install PySide2 from Qt repository with
# pip3 install --index-url=http://download.qt.io/snapshots/ci/pyside/<Qt-Version>/latest/ pyside2 --trusted-host download.qt.io
find_package(PkgConfig REQUIRED)
pkg_check_modules(PYSIDE2_PRIV pyside2 QUIET)
set(PYSIDE2_FOUND FALSE)
if(PYSIDE2_PRIV_FOUND)
set(PYSIDE2_FOUND TRUE)
message(STATUS "Using PySide2 found in the system!")
pkg_get_variable(SHIBOKEN_BINARY
pyside2
generator_location
)
pkg_get_variable(PYSIDE2_BASEDIR
pyside2
typesystemdir
)
pkg_get_variable(PYSIDE_INCLUDE_DIR
pyside2
includedir
)
set(PYSIDE_TYPESYSTEMS ${PYSIDE2_BASEDIR})
set(PYSIDE2_SO_VERSION ${PYSIDE2_PRIV_VERSION})
set(PYSIDE_LIBRARY ${PYSIDE2_PRIV_LINK_LIBRARIES})
list(GET PYSIDE_LIBRARY 0 PYSIDE_LIBRARY)
else()
# extract python library basename
list(GET Python3_LIBRARIES 0 PYTHON_LIBRARY_FILENAME)
get_filename_component(PYTHON_LIBRARY_FILENAME ${PYTHON_LIBRARY_FILENAME} NAME)
execute_process(
COMMAND ${Python3_EXECUTABLE} -c "if True:
import os, sys
try:
import PySide2.QtCore as QtCore
print(os.path.dirname(QtCore.__file__))
except Exception as error:
print(error, file=sys.stderr)
exit()
"
OUTPUT_VARIABLE PYSIDE2_BASEDIR
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(PYSIDE2_BASEDIR)
set(PYSIDE_BASEDIR ${PYSIDE2_BASEDIR} CACHE PATH "Top level install of PySide2" FORCE)
execute_process(
COMMAND ${Python3_EXECUTABLE} -c "if True:
import os
import PySide2.QtCore as QtCore
print(os.path.basename(QtCore.__file__).split('.', 1)[1])
"
OUTPUT_VARIABLE PYSIDE2_SUFFIX
OUTPUT_STRIP_TRAILING_WHITESPACE
)
execute_process(
COMMAND ${Python3_EXECUTABLE} -c "if True:
import os
import PySide2.QtCore as QtCore
print(';'.join(map(str, QtCore.__version_info__)))
"
OUTPUT_VARIABLE PYSIDE2_SO_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE
)
list(GET PYSIDE2_SO_VERSION 0 PYSIDE2_SO_MACRO_VERSION)
list(GET PYSIDE2_SO_VERSION 1 PYSIDE2_SO_MICRO_VERSION)
list(GET PYSIDE2_SO_VERSION 2 PYSIDE2_SO_MINOR_VERSION)
string(REPLACE ";" "." PYSIDE2_SO_VERSION "${PYSIDE2_SO_VERSION}")
if(NOT APPLE)
set(PYSIDE2_SUFFIX "${PYSIDE2_SUFFIX}.${PYSIDE2_SO_MACRO_VERSION}.${PYSIDE2_SO_MICRO_VERSION}")
else()
string(REPLACE ".so" "" PYSIDE2_SUFFIX ${PYSIDE2_SUFFIX})
set(PYSIDE2_SUFFIX "${PYSIDE2_SUFFIX}.${PYSIDE2_SO_MACRO_VERSION}.${PYSIDE2_SO_MICRO_VERSION}.dylib")
endif()
set(PYSIDE2_FOUND TRUE)
message(STATUS "PySide2 base dir: ${PYSIDE2_BASEDIR}" )
message(STATUS "PySide2 suffix: ${PYSIDE2_SUFFIX}")
endif()
if (PYSIDE2_FOUND)
#PySide
#===============================================================================
find_path(PYSIDE_INCLUDE_DIR
pyside.h
PATHS ${PYSIDE2_BASEDIR}/include ${PYSIDE2_CUSTOM_PREFIX}/include/PySide2
NO_DEFAULT_PATH)
# Platform specific library names
if(MSVC)
SET(PYSIDE_LIBRARY_BASENAMES "pyside2.abi3.lib")
elseif(CYGWIN)
SET(PYSIDE_LIBRARY_BASENAMES "")
elseif(WIN32)
SET(PYSIDE_LIBRARY_BASENAMES "libpyside2.${PYSIDE2_SUFFIX}")
else()
SET(PYSIDE_LIBRARY_BASENAMES "libpyside2.${PYSIDE2_SUFFIX}")
endif()
find_file(PYSIDE_LIBRARY
${PYSIDE_LIBRARY_BASENAMES}
PATHS ${PYSIDE2_BASEDIR} ${PYSIDE2_CUSTOM_PREFIX}/lib
NO_DEFAULT_PATH)
find_path(PYSIDE_TYPESYSTEMS
typesystem_core.xml
PATHS ${PYSIDE2_BASEDIR}/typesystems ${PYSIDE2_CUSTOM_PREFIX}/share/PySide2/typesystems
NO_DEFAULT_PATH)
endif()
endif()
if(PYSIDE2_FOUND)
message(STATUS "PySide include dir: ${PYSIDE_INCLUDE_DIR}")
message(STATUS "PySide library: ${PYSIDE_LIBRARY}")
message(STATUS "PySide typesystems: ${PYSIDE_TYPESYSTEMS}")
message(STATUS "PySide2 version: ${PYSIDE2_SO_VERSION}")
# Create PySide2 target
add_library(PySide2::pyside2 SHARED IMPORTED GLOBAL)
if(MSVC)
set_property(TARGET PySide2::pyside2 PROPERTY
IMPORTED_IMPLIB ${PYSIDE_LIBRARY})
endif()
set_property(TARGET PySide2::pyside2 PROPERTY
IMPORTED_LOCATION ${PYSIDE_LIBRARY})
set_property(TARGET PySide2::pyside2 APPEND PROPERTY
INTERFACE_INCLUDE_DIRECTORIES
${PYSIDE_INCLUDE_DIR}
${PYSIDE_INCLUDE_DIR}/QtCore/
${PYSIDE_INCLUDE_DIR}/QtGui/
${PYSIDE_INCLUDE_DIR}/QtWidgets/
${Python3_INCLUDE_DIRS}
)
endif()
find_package_handle_standard_args(PySide2
REQUIRED_VARS PYSIDE2_BASEDIR PYSIDE_INCLUDE_DIR PYSIDE_LIBRARY PYSIDE_TYPESYSTEMS
VERSION_VAR PYSIDE2_SO_VERSION
)

View File

@@ -0,0 +1,190 @@
###
# This file is part of KDDockWidgets.
#
# Copyright (C) 2018-2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
# Author: Renato Araujo Oliveira Filho <renato.araujo@kdab.com>
#
# This program is free software you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
##
# SHIBOKEN_INCLUDE_DIR - Directories to include to use SHIBOKEN
# SHIBOKEN_LIBRARY - Files to link against to use SHIBOKEN
# SHIBOKEN_BINARY - Executable name
# SHIBOKEN_BUILD_TYPE - Tells if Shiboken was compiled in Release or Debug mode.
# You can install Shiboken from Qt repository with
# pip3 install --index-url=http://download.qt.io/snapshots/ci/pyside/<Qt-Version>/latest/ shiboken2-generator --trusted-host download.qt.io
find_package(PkgConfig REQUIRED)
pkg_check_modules(SHIBOKEN2_PRIV shiboken2 QUIET)
set(SHIBOKEN_FOUND FALSE)
if(SHIBOKEN2_PRIV_FOUND)
set(SHIBOKEN_FOUND TRUE)
message(STATUS "Using shiboken found in the system!")
pkg_get_variable(SHIBOKEN_BINARY
shiboken2
generator_location
)
pkg_get_variable(SHIBOKEN_BASEDIR
shiboken2
libdir
)
pkg_get_variable(SHIBOKEN_INCLUDE_DIR
shiboken2
includedir
)
set(SHIBOKEN_VERSION ${SHIBOKEN2_PRIV_VERSION})
set(SHIBOKEN_LIBRARY ${SHIBOKEN2_PRIV_LINK_LIBRARIES})
else()
execute_process(
COMMAND ${Python3_EXECUTABLE} -c "if True:
import os
try:
import shiboken2_generator
print(shiboken2_generator.__path__[0])
except:
exit()
"
OUTPUT_VARIABLE SHIBOKEN_GENERATOR_BASEDIR
OUTPUT_STRIP_TRAILING_WHITESPACE
)
execute_process(
COMMAND ${Python3_EXECUTABLE} -c "if True:
import os
try:
import shiboken2
print(shiboken2.__path__[0])
except:
exit()
"
OUTPUT_VARIABLE SHIBOKEN_BASEDIR
OUTPUT_STRIP_TRAILING_WHITESPACE
)
execute_process(
COMMAND ${Python3_EXECUTABLE} -c "if True:
import os
import shiboken2
print(';'.join(filter(None, map(str, shiboken2.__version_info__))))
"
OUTPUT_VARIABLE SHIBOKEN_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE
)
list(GET SHIBOKEN_VERSION 0 SHIBOKEN_MACRO_VERSION)
list(GET SHIBOKEN_VERSION 1 SHIBOKEN_MICRO_VERSION)
list(GET SHIBOKEN_VERSION 2 SHIBOKEN_MINOR_VERSION)
string(REPLACE ";" "." SHIBOKEN_VERSION "${SHIBOKEN_VERSION}")
message(STATUS "ShibokenGenerator base dir: ${SHIBOKEN_GENERATOR_BASEDIR}")
message(STATUS "Shiboken base dir: ${SHIBOKEN_BASEDIR}")
message(STATUS "Shiboken custom path: ${SHIBOKEN_CUSTOM_PATH}")
if(SHIBOKEN_BASEDIR)
find_path(SHIBOKEN_INCLUDE_DIR
shiboken.h
PATHS ${SHIBOKEN_CUSTOM_PATH} ${SHIBOKEN_GENERATOR_BASEDIR}/include
NO_DEFAULT_PATH)
if(MSVC)
SET(SHIBOKEN_LIBRARY_BASENAMES "shiboken2.abi3.lib")
elseif(CYGWIN)
SET(SHIBOKEN_LIBRARY_BASENAMES "")
elseif(WIN32)
SET(SHIBOKEN_LIBRARY_BASENAMES "libshiboken2.${PYSIDE2_SUFFIX}")
else()
SET(SHIBOKEN_LIBRARY_BASENAMES
libshiboken2.abi3.so
libshiboken2.abi3.so.${SHIBOKEN_MACRO_VERSION}
libshiboken2.abi3.so.${SHIBOKEN_MACRO_VERSION}.${SHIBOKEN_MICRO_VERSION}
libshiboken2.abi3.so.${SHIBOKEN_VERSION}
libshiboken2.abi3.so
)
endif()
if (NOT SHIBOKEN_INCLUDE_DIR)
return()
endif()
set(SHIBOKEN_SEARCH_PATHS ${SHIBOKEN_CUSTOM_PATH})
list(APPEND SHIBOKEN_SEARCH_PATHS ${SHIBOKEN_BASEDIR})
list(APPEND SHIBOKEN_SEARCH_PATHS ${SHIBOKEN_GENERATOR_BASEDIR})
find_file(SHIBOKEN_LIBRARY
${SHIBOKEN_LIBRARY_BASENAMES}
PATHS ${SHIBOKEN_SEARCH_PATHS}
NO_DEFAULT_PATH)
find_program(SHIBOKEN_BINARY
shiboken2
PATHS ${SHIBOKEN_SEARCH_PATHS}
NO_DEFAULT_PATH
)
endif()
if (SHIBOKEN_INCLUDE_DIR AND SHIBOKEN_LIBRARY AND SHIBOKEN_BINARY)
set(SHIBOKEN_FOUND TRUE)
endif()
if(SHIBOKEN_FOUND)
endif()
if(MSVC)
# On Windows we must link to python3.dll that is a small library that links against python3x.dll
# that allow us to choose any python3x.dll at runtime
execute_process(
COMMAND ${Python3_EXECUTABLE} -c "if True:
for lib in '${Python3_LIBRARIES}'.split(';'):
if '/' in lib:
prefix, py = lib.rsplit('/', 1)
if py.startswith('python3'):
print(prefix + '/python3.lib')
break
"
OUTPUT_VARIABLE PYTHON_LIMITED_LIBRARIES
OUTPUT_STRIP_TRAILING_WHITESPACE
)
else()
# On Linux and MacOs our modules should not link with any python library
# that must be handled by the main process
set(PYTHON_LIMITED_LIBRARIES "")
endif()
endif()
if (SHIBOKEN_FOUND)
message(STATUS "Shiboken include dir: ${SHIBOKEN_INCLUDE_DIR}")
message(STATUS "Shiboken library: ${SHIBOKEN_LIBRARY}")
message(STATUS "Shiboken binary: ${SHIBOKEN_BINARY}")
message(STATUS "Shiboken version: ${SHIBOKEN_VERSION}")
# Create shiboke2 target
add_library(Shiboken2::libshiboken SHARED IMPORTED GLOBAL)
if(MSVC)
set_property(TARGET Shiboken2::libshiboken PROPERTY
IMPORTED_IMPLIB ${SHIBOKEN_LIBRARY})
endif()
set_property(TARGET Shiboken2::libshiboken PROPERTY
IMPORTED_LOCATION ${SHIBOKEN_LIBRARY})
set_property(TARGET Shiboken2::libshiboken APPEND PROPERTY
INTERFACE_INCLUDE_DIRECTORIES ${SHIBOKEN_INCLUDE_DIR} ${Python3_INCLUDE_DIRS})
set_property(TARGET Shiboken2::libshiboken APPEND PROPERTY
INTERFACE_LINK_LIBRARIES ${PYTHON_LIMITED_LIBRARIES})
# Generator target
add_executable(Shiboken2::shiboken IMPORTED GLOBAL)
set_property(TARGET Shiboken2::shiboken PROPERTY
IMPORTED_LOCATION ${SHIBOKEN_BINARY})
endif()
find_package_handle_standard_args(Shiboken2
REQUIRED_VARS SHIBOKEN_BASEDIR SHIBOKEN_INCLUDE_DIR SHIBOKEN_LIBRARY SHIBOKEN_BINARY
VERSION_VAR SHIBOKEN_VERSION
)

View File

@@ -0,0 +1,142 @@
###
# This file is part of KDDockWidgets.
#
# Copyright (C) 2018-2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
# Author: Renato Araujo Oliveira Filho <renato.araujo@kdab.com>
#
# This program is free software you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
##
set(PYTHON_BINDINGS_INSTALL_PREFIX ${Python3_SITELIB} CACHE FILEPATH "Custom path to install python bindings." )
message(STATUS "PYTHON INSTALL PREFIX ${PYTHON_BINDINGS_INSTALL_PREFIX}")
if (WIN32)
set(PATH_SEP "\;")
else()
set(PATH_SEP ":")
endif()
if (NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 17)
endif()
# Flags that we will pass to shiboken-generator
# --generator-set=shiboken: tells the generator that we want to use shiboken to generate code,
# a doc generator is also available
# --enable-parent-ctor-heuristic: Enable heuristics to detect parent relationship on constructors,
# this try to guess parent ownership based on the arguments of the constructors
# --enable-pyside-extensionsL: This will generate code for Qt based classes, adding extra attributes,
# like signal, slot;
# --enable-return-value-heuristic: Similar as --enable-parent-ctor-heuristic this use some logic to guess
# parent child relationship based on the returned argument
# --use-isnull-as-nb_nonzero: If a class have an isNull() const method, it will be used to compute
# the value of boolean casts.
# Example, QImage::isNull() will be used when on python side you do `if (myQImage)`
set(GENERATOR_EXTRA_FLAGS --generator-set=shiboken
--enable-parent-ctor-heuristic
--enable-pyside-extensions
--enable-return-value-heuristic
--use-isnull-as-nb_nonzero
-std=c++${CMAKE_CXX_STANDARD})
macro(make_path varname)
# accepts any number of path variables
string(REPLACE ";" "${PATH_SEP}" ${varname} "${ARGN}")
endmacro()
# Creates a PySide module target based on the arguments
# This will:
# 1 - Create a Cmake custom-target that call shiboken-generator passign the correct arguments
# 2 - Create a Cmake library target called "Py${LIBRARY_NAME}" the output name of this target
# will be changed to match PySide template
# Args:
# LIBRARY_NAME - The name of the output module
# TYPESYSTEM_PATHS - A list of paths where shiboken should look for typesystem files
# INCLUDE_PATHS - Include pahts necessary to parse your class. *This is not the same as build*
# OUTPUT_SOURCES - The files that will be generated by shiboken
# TARGET_INCLUDE_DIRS - This will be passed to target_include_directories
# TARGET_LINK_LIBRARIES - This will be passed to target_link_libraries
# GLOBAL_INCLUDE - A header-file that contains alls classes that will be generated
# TYPESYSTEM_XML - The target binding typesystem (that should be the full path)
# DEPENDS - This var will be passed to add_custom_command(DEPENDS) so a new generation will be
# trigger if one of these files changes
# MODULE_OUTPUT_DIR - Where the library file should be stored
macro(CREATE_PYTHON_BINDINGS
LIBRARY_NAME
TYPESYSTEM_PATHS
INCLUDE_PATHS
OUTPUT_SOURCES
TARGET_INCLUDE_DIRS
TARGET_LINK_LIBRARIES
GLOBAL_INCLUDE
TYPESYSTEM_XML
DEPENDS
MODULE_OUTPUT_DIR)
# Transform the path separators into something shiboken understands.
make_path(shiboken_include_dirs ${INCLUDE_PATHS})
make_path(shiboken_typesystem_dirs ${TYPESYSTEM_PATHS})
get_property(raw_python_dir_include_dirs DIRECTORY PROPERTY INCLUDE_DIRECTORIES)
make_path(python_dir_include_dirs ${raw_python_dir_include_dirs})
set(shiboken_include_dirs "${shiboken_include_dirs}${PATH_SEP}${python_dir_include_dirs}")
set(shiboken_framework_include_dirs_option "")
if(CMAKE_HOST_APPLE)
set(shiboken_framework_include_dirs "${QT_FRAMEWORK_INCLUDE_DIR}")
make_path(shiboken_framework_include_dirs ${shiboken_framework_include_dirs})
set(shiboken_framework_include_dirs_option "--framework-include-paths=${shiboken_framework_include_dirs}")
endif()
set_property(SOURCE ${OUTPUT_SOURCES} PROPERTY SKIP_AUTOGEN ON)
add_custom_command(OUTPUT ${OUTPUT_SOURCES}
COMMAND $<TARGET_PROPERTY:Shiboken2::shiboken,LOCATION> ${GENERATOR_EXTRA_FLAGS}
${GLOBAL_INCLUDE}
--include-paths=${shiboken_include_dirs}
--typesystem-paths=${shiboken_typesystem_dirs}
${shiboken_framework_include_dirs_option}
--output-directory=${CMAKE_CURRENT_BINARY_DIR}
${TYPESYSTEM_XML}
DEPENDS ${TYPESYSTEM_XML} ${DEPENDS}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Running generator for ${LIBRARY_NAME} binding...")
set(TARGET_NAME "Py${LIBRARY_NAME}")
set(MODULE_NAME "${LIBRARY_NAME}")
add_library(${TARGET_NAME} MODULE ${OUTPUT_SOURCES})
set_target_properties(${TARGET_NAME} PROPERTIES
PREFIX ""
OUTPUT_NAME ${MODULE_NAME}
LIBRARY_OUTPUT_DIRECTORY ${MODULE_OUTPUT_DIR}
)
if(WIN32)
set_target_properties(${TARGET_NAME} PROPERTIES SUFFIX ".pyd")
endif()
target_include_directories(${TARGET_NAME} PUBLIC
${TARGET_INCLUDE_DIRS}
${PYSIDE_EXTRA_INCLUDES}
)
target_link_libraries(${TARGET_NAME}
${TARGET_LINK_LIBRARIES}
PySide2::pyside2
Shiboken2::libshiboken
)
target_compile_definitions(${TARGET_NAME}
PRIVATE Py_LIMITED_API=0x03050000
)
if(APPLE)
set_property(TARGET ${TARGET_NAME} APPEND PROPERTY
LINK_FLAGS "-undefined dynamic_lookup")
endif()
install(TARGETS ${TARGET_NAME}
LIBRARY DESTINATION ${PYTHON_BINDINGS_INSTALL_PREFIX}/${TARGET_NAME})
endmacro()

View File

@@ -196,7 +196,6 @@ KDDockWidgets::DockWidgetBase *MyMainWindow::newDockWidget()
}
dock->resize(600, 600);
dock->show();
m_toggleMenu->addAction(dock->toggleAction());
count++;

8
python/CMakeLists.txt Normal file
View File

@@ -0,0 +1,8 @@
set(PYTHON_VERSION "3.7" CACHE STRING "Use specific python version to build the project.")
find_package(Python3 ${PYTHON_VERSION} REQUIRED COMPONENTS Interpreter Development)
find_package(Shiboken2 REQUIRED)
find_package(PySide2 ${Qt5Widgets_VERSION} EXACT REQUIRED)
include(PySide2ModuleBuild)
add_subdirectory(PyKDDockWidgets)

View File

@@ -0,0 +1,71 @@
# Auto-Genereate files every class will have his cpp/h files
set(PyKDDockWidgets_SRC
# individual classes
${CMAKE_CURRENT_BINARY_DIR}/KDDockWidgets/kddockwidgets_dockwidgetbase_wrapper.cpp
${CMAKE_CURRENT_BINARY_DIR}/KDDockWidgets/kddockwidgets_dockwidgetbase_wrapper.h
${CMAKE_CURRENT_BINARY_DIR}/KDDockWidgets/kddockwidgets_dockwidget_wrapper.cpp
${CMAKE_CURRENT_BINARY_DIR}/KDDockWidgets/kddockwidgets_dockwidget_wrapper.h
${CMAKE_CURRENT_BINARY_DIR}/KDDockWidgets/kddockwidgets_mainwindowbase_wrapper.cpp
${CMAKE_CURRENT_BINARY_DIR}/KDDockWidgets/kddockwidgets_mainwindowbase_wrapper.h
${CMAKE_CURRENT_BINARY_DIR}/KDDockWidgets/kddockwidgets_mainwindow_wrapper.cpp
${CMAKE_CURRENT_BINARY_DIR}/KDDockWidgets/kddockwidgets_mainwindow_wrapper.h
# namespace wrapper
${CMAKE_CURRENT_BINARY_DIR}/KDDockWidgets/kddockwidgets_wrapper.cpp
${CMAKE_CURRENT_BINARY_DIR}/KDDockWidgets/kddockwidgets_wrapper.h
# global module wrapper
${CMAKE_CURRENT_BINARY_DIR}/KDDockWidgets/kddockwidgets_module_wrapper.cpp
${CMAKE_CURRENT_BINARY_DIR}/KDDockWidgets/kddockwidgets_python.h
)
# includes necessary to parse and build the classes specified on typesystem
set(PyKDDockWidgets_include_paths
$<JOIN:$<TARGET_PROPERTY:KDAB::kddockwidgets,INTERFACE_INCLUDE_DIRECTORIES>,${PATH_SEP}>
)
# A list of paths where shiboken should look for typesystem
set(PyKDDockWidgets_typesystem_paths
# PySide path, this variable was exposed by FindPySide2.cmake
${PYSIDE_TYPESYSTEMS}
)
# Include flags/path that will be set in 'target_include_directories'
set(PyKDDockWidgets_target_include_directories
${CMAKE_SOURCE_DIR}
)
# Libraries that will be necessary to link the target, this will used in the command 'target_link_libraries'
set(PyKDDockWidgets_target_link_libraries
KDAB::kddockwidgets
Qt5::Core
Qt5::Gui
Qt5::Widgets
)
# changes on these files should trigger a new generation
set(PyKDDockWidgets_DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/kddockwidgets_global.h
${CMAKE_SOURCE_DIR}/src/DockWidgetBase.h
${CMAKE_SOURCE_DIR}/src/DockWidget.h
${CMAKE_SOURCE_DIR}/src/MainWindowBase.h
${CMAKE_SOURCE_DIR}/src/MainWindow.h
)
CREATE_PYTHON_BINDINGS(
"KDDockWidgets"
"${PyKDDockWidgets_typesystem_paths}"
"${PyKDDockWidgets_include_paths}"
"${PyKDDockWidgets_SRC}"
"${PyKDDockWidgets_target_include_directories}"
"${PyKDDockWidgets_target_link_libraries}"
${CMAKE_CURRENT_SOURCE_DIR}/kddockwidgets_global.h
${CMAKE_CURRENT_SOURCE_DIR}/typesystem_kddockwidgets.xml
"${PyKDDockWidgets_DEPENDS}"
${CMAKE_CURRENT_BINARY_DIR}
)
# Make moduled import from build dir works
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/__init__.py ${CMAKE_CURRENT_BINARY_DIR}/__init__.py)
# install
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/__init__.py DESTINATION ${PYTHON_BINDINGS_INSTALL_PREFIX}/PyKDDockWidgets)

View File

@@ -0,0 +1,14 @@
__all__ = ['KDDockWidgets']
# Preload PySide2 libraries to avoid missing libraries while loading KDDockWidgets
try:
from PySide2 import QtCore
except Exception:
print("Failed to lod PySide")
raise
# avoid duplicate namespace, due the PYSIDE-1325 bug I will have my package like this
# PyKDDockWidgets.KDDockWidgets.KDDockWidgets.MainWindow
# To avoid this I add a WORKAROUND to reduce it
from .KDDockWidgets import KDDockWidgets as _priv
KDDockWidgets = _priv

View File

@@ -0,0 +1,13 @@
#pragma once
// Make "signals:", "slots:" visible as access specifiers
#define QT_ANNOTATE_ACCESS_SPECIFIER(a) __attribute__((annotate(#a)))
// Define PYTHON_BINDINGS this will be used in some part of c++ to skip problematic parts
#define PYTHON_BINDINGS
#include <MainWindowBase.h>
#include <MainWindow.h>
#include <DockWidgetBase.h>
#include <DockWidget.h>

View File

@@ -0,0 +1,39 @@
<?xml version="1.0"?>
<!-- The package name -->
<typesystem package="KDDockWidgets">
<!-- Pre-defined typesystem that contains types used by our class
PySide has one typesystem for each module, here we use only the widgets
typesystem because it already include gui and core typesystem -->
<load-typesystem name="typesystem_widgets.xml" generate="no"/>
<!-- Our classes are declared in a namespace, so we should define this -->
<namespace-type name="KDDockWidgets">
<!-- this is used in a public virtual pure function we need to declare it
otherwise shiboken will ignore the function and will fail to create a wrapper -->
<primitive-type name="DropAreaWithCentralFrame"/>
<!-- Some plublic enum and flags -->
<enum-type name="Location"/>
<enum-type name="MainWindowOption" flags="MainWindowOptions"/>
<enum-type name="AddingOption"/>
<enum-type name="RestoreOption" flags="RestoreOptions"/>
<enum-type name="DefaultSizeMode"/>
<enum-type name="FrameOption" flags="FrameOptions"/>
<!-- our classes
For class we can use two types:
object-type: class that does not have a copy-contructor and can not be passed as value to functions;
value-type: class that can be passed as value for functions
Here we only use 'object-type' since all our classes are derived from QWidget
-->
<object-type name="MainWindowBase" />
<object-type name="MainWindow" />
<object-type name="DockWidgetBase" >
<!-- this class contains a internal enum, so it should be declared
inside of the object-type -->
<enum-type name="Option" flags="Options" />
</object-type>
<object-type name="DockWidget" />
</namespace-type>
</typesystem>

View File

@@ -0,0 +1,170 @@
################################################################################
## This file is part of KDDockWidgets.
##
## Copyright (C) 2018-2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
## Author: Renato Araujo Oliveira Filho <renato.araujo@kdab.com>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program. If not, see <http://www.gnu.org/licenses/>.
################################################################################
from PySide2 import QtCore, QtWidgets, QtGui
from PyKDDockWidgets import KDDockWidgets
from MyWidget1 import MyWidget1
from MyWidget2 import MyWidget2
from MyWidget3 import MyWidget3
def newMyWidget(parent = None):
randomNumber = QtCore.QRandomGenerator.global_().bounded(0, 100) + 1
if (randomNumber < 50):
if (randomNumber < 33):
return MyWidget1(parent)
else:
return MyWidget3(parent)
else:
return MyWidget2(parent)
class MyMainWindow(KDDockWidgets.MainWindow):
s_count = 0
s_menuCount = 0
def __init__(self, uniqueName, options = KDDockWidgets.MainWindowOption_None, dockWidget0IsNonClosable = False, nonDockableDockWidget9 = False, restoreIsRelative = False, maxSizeForDockWidget8 = False, affinityName = "", parent = None):
super().__init__(uniqueName, options, parent)
self.m_dockWidget0IsNonClosable = dockWidget0IsNonClosable
self.m_dockWidget9IsNonDockable = nonDockableDockWidget9
self.m_restoreIsRelative = restoreIsRelative
self.m_maxSizeForDockWidget8 = maxSizeForDockWidget8
self.m_dockwidgets = []
menubar = self.menuBar()
fileMenu = QtWidgets.QMenu("File")
self.m_toggleMenu = QtWidgets.QMenu("Toggle")
menubar.addMenu(fileMenu)
menubar.addMenu(self.m_toggleMenu)
newAction = fileMenu.addAction("New DockWidget")
newAction.triggered.connect(self._newDockWidget)
saveLayoutAction = fileMenu.addAction("Save Layout")
saveLayoutAction.triggered.connect(self._saveLayout)
restoreLayoutAction = fileMenu.addAction("Restore Layout")
restoreLayoutAction.triggered.connect(self._restoreLayout)
closeAllAction = fileMenu.addAction("Close All")
closeAllAction.triggered.connect(self._closeAll)
layoutEqually = fileMenu.addAction("Layout Equally")
layoutEqually.triggered.connect(self.layoutEqually)
quitAction = fileMenu.addAction("Quit")
quitAction.triggered.connect(QtWidgets.QApplication.instance().quit)
self.setAffinities([ affinityName ])
self.createDockWidgets()
def _newDockWidget(self):
MyMainWindow.s_menuCount += 1
w = newMyWidget(self)
w.setGeometry(100, 100, 400, 400)
dock = KDDockWidgets.DockWidget("new dock %d"%(MyMainWindow.s_menuCount))
dock.setWidget(w)
dock.resize(600, 600)
dock.show()
self.m_dockwidgets.append(dock)
def _saveLayout(self):
#saver = KDDockWidgets.LayoutSaver()
#result = saver.saveToFile("mylayout.json")
#print("Saving layout to disk. Result=", result)
print("Not available")
def _restoreLayout(self):
#options = KDDockWidgets.RestoreOption_None
#if self.m_restoreIsRelative:
# options |= KDDockWidgets.RestoreOption_RelativeToMainWindow
#saver = KDDockWidgets.LayoutSaver(options)
#saver.restoreFromFile("mylayout.json")
print("Not available")
def _closeAll(self):
for dw in self.m_dockwidgets:
dw.close()
def createDockWidgets(self):
if self.m_dockWidget9IsNonDockable:
numDockWidgets = 10
else:
numDockWidgets = 9
# numDockWidgets = 2
# Create 9 KDDockWidget::DockWidget and the respective widgets they're hosting (MyWidget instances)
for i in range(numDockWidgets):
self.m_dockwidgets.append(self.newDockWidget())
# MainWindow::addDockWidget() attaches a dock widget to the main window:
self.addDockWidget(self.m_dockwidgets[0], KDDockWidgets.Location_OnTop)
# Here, for finer granularity we specify right of dockwidgets[0]:
self.addDockWidget(self.m_dockwidgets[1], KDDockWidgets.Location_OnRight, self.m_dockwidgets[0])
self.addDockWidget(self.m_dockwidgets[2], KDDockWidgets.Location_OnLeft)
self.addDockWidget(self.m_dockwidgets[3], KDDockWidgets.Location_OnBottom)
self.addDockWidget(self.m_dockwidgets[4], KDDockWidgets.Location_OnBottom)
# Tab two dock widgets together
self.m_dockwidgets[3].addDockWidgetAsTab(self.m_dockwidgets[5])
# 6 is floating, as it wasn't added to the main window via MainWindow::addDockWidget().
# and we tab 7 with it.
self.m_dockwidgets[6].addDockWidgetAsTab(self.m_dockwidgets[7])
# Floating windows also support nesting, here we add 8 to the bottom of the group
self.m_dockwidgets[6].addDockWidgetToContainingWindow(self.m_dockwidgets[8], KDDockWidgets.Location_OnBottom)
floatingWindow = self.m_dockwidgets[6].window()
floatingWindow.move(100, 100)
def newDockWidget(self):
# Passing options is optional, we just want to illustrate Option_NotClosable here
options = KDDockWidgets.DockWidget.Option_None
if (MyMainWindow.s_count == 0) and self.m_dockWidget0IsNonClosable:
options |= KDDockWidgets.DockWidget.Option_NotClosable
if (MyMainWindow.s_count == 9) and self.m_dockWidget9IsNonDockable:
options |= KDDockWidgets.DockWidget.Option_NotDockable
dock = KDDockWidgets.DockWidget("DockWidget #%d"%(MyMainWindow.s_count), options)
dock.setAffinities(self.affinities()); # optional, just to show the feature. Pass -mi to the example to see incompatible dock widgets
if MyMainWindow.s_count == 1:
dock.setIcon(QtGui.QIcon.fromTheme("mail-message"))
myWidget = newMyWidget(self)
if (MyMainWindow.s_count == 8) and self.m_maxSizeForDockWidget8:
# Set a maximum size on dock #8
myWidget.setMaximumSize(200, 200)
dock.setWidget(myWidget)
if dock.options() & KDDockWidgets.DockWidget.Option_NotDockable:
dock.setTitle("DockWidget #%d (%s)" %(MyMainWindow.s_count, "non dockable"))
else:
dock.setTitle("DockWidget #%d"%(MyMainWindow.s_count))
dock.resize(600, 600)
self.m_toggleMenu.addAction(dock.toggleAction())
MyMainWindow.s_count += 1
return dock

View File

@@ -0,0 +1,58 @@
################################################################################
## This file is part of KDDockWidgets.
##
## Copyright (C) 2018-2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
## Author: Renato Araujo Oliveira Filho <renato.araujo@kdab.com>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program. If not, see <http://www.gnu.org/licenses/>.
################################################################################
from PySide2 import QtWidgets, QtGui, QtCore
class MyWidget(QtWidgets.QWidget):
s_images = {}
def __init__(self, backgroundFile, logoFile, parent = None):
super().__init__(parent)
self.m_background = self._lookupImage(backgroundFile)
self.m_logo = self._lookupImage(logoFile)
def _lookupImage(self, imageName):
if imageName == "":
return None
if imageName not in MyWidget.s_images:
MyWidget.s_images[imageName] = QtGui.QImage(imageName)
return MyWidget.s_images[imageName]
def drawLogo(self, p):
if not self.m_logo:
return
ratio = self.m_logo.height() / (self.m_logo.width() * 1.0)
maxWidth = int(0.80 * self.size().width())
maxHeight = int(0.80 * self.size().height())
proposedHeight = int(maxWidth * ratio)
if (proposedHeight <= maxHeight):
width = maxWidth
else:
width = int(maxHeight / ratio)
height = int(width * ratio)
targetLogoRect = QtCore.QRect(0,0, width, height)
targetLogoRect.moveCenter(self.rect().center() + QtCore.QPoint(0, -int(self.size().height() * 0.00)))
p.drawImage(targetLogoRect, self.m_logo, self.m_logo.rect());

View File

@@ -0,0 +1,36 @@
################################################################################
## This file is part of KDDockWidgets.
##
## Copyright (C) 2018-2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
## Author: Renato Araujo Oliveira Filho <renato.araujo@kdab.com>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program. If not, see <http://www.gnu.org/licenses/>.
################################################################################
from PySide2 import QtWidgets, QtGui
from MyWidget import MyWidget
class MyWidget1(MyWidget):
def __init__(self, parent = None):
super().__init__(":/assets/triangles.png", ":/assets/KDAB_bubble_white.png", parent)
def paintEvent(self, ev):
p = QtGui.QPainter(self)
p.fillRect(self.rect(), QtGui.QColor(0xCC, 0xCC, 0xCC))
p.drawImage(self.m_background.rect(), self.m_background, self.m_background.rect())
self.drawLogo(p)

View File

@@ -0,0 +1,34 @@
################################################################################
## This file is part of KDDockWidgets.
##
## Copyright (C) 2018-2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
## Author: Renato Araujo Oliveira Filho <renato.araujo@kdab.com>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program. If not, see <http://www.gnu.org/licenses/>.
################################################################################
from PySide2 import QtWidgets, QtGui, QtCore
from MyWidget import MyWidget
class MyWidget2(MyWidget):
def __init__(self, parent = None):
super().__init__("", ":/assets/KDAB_bubble_blue.png", parent)
def paintEvent(self, ev):
p = QtGui.QPainter(self)
p.fillRect(self.rect(), QtCore.Qt.white);
self.drawLogo(p)

View File

@@ -0,0 +1,39 @@
################################################################################
## This file is part of KDDockWidgets.
##
## Copyright (C) 2018-2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
## Author: Renato Araujo Oliveira Filho <renato.araujo@kdab.com>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program. If not, see <http://www.gnu.org/licenses/>.
################################################################################
from PySide2 import QtWidgets, QtGui, QtCore
from MyWidget import MyWidget
class MyWidget3(MyWidget):
def __init__(self, parent = None):
super().__init__(":/assets/base.png", ":/assets/KDAB_bubble_fulcolor.png", parent)
self.m_triangle = QtGui.QImage(":/assets/tri.png")
def paintEvent(self, ev):
p = QtGui.QPainter(self)
p.fillRect(self.rect(), QtGui.QColor(0xD5, 0xD5, 0xD5))
p.drawImage(self.m_background.rect(), self.m_background, self.m_background.rect())
targetRect = QtCore.QRect(QtCore.QPoint(self.width() - self.m_triangle.width(), self.height() - self.m_triangle.height()), self.m_triangle.size())
self.drawLogo(p)

View File

@@ -0,0 +1,8 @@
Running python example
======================
Generate resource file with:
~# rcc -g python -o rc_assets.py ../../examples/dockwidgets/resources_example.qrc
Run the app:
~# python3 main.py

44
python/examples/main.py Normal file
View File

@@ -0,0 +1,44 @@
################################################################################
## This file is part of KDDockWidgets.
##
## Copyright (C) 2018-2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
## Author: Renato Araujo Oliveira Filho <renato.araujo@kdab.com>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program. If not, see <http://www.gnu.org/licenses/>.
################################################################################
from PySide2 import QtWidgets, QtCore
from PyKDDockWidgets import KDDockWidgets
from MyMainWindow import MyMainWindow
import sys
import rc_assets
if __name__ == "__main__":
QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)
QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps)
app = QtWidgets.QApplication(sys.argv)
app.setOrganizationName("KDAB")
app.setApplicationName("Test app")
app.setStyle(QtWidgets.QStyleFactory.create("Fusion"))
mainWindow = MyMainWindow("MyMainWindow", )
mainWindow.setWindowTitle("Main Window 1")
mainWindow.resize(1200, 1200)
mainWindow.show()
app.exec_()

View File

@@ -6,7 +6,6 @@ add_definitions(-DQT_NO_SIGNALS_SLOTS_KEYWORDS
-DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT
-DQT_STRICT_ITERATORS
-DQT_NO_KEYWORDS
-DQT_DISABLE_DEPRECATED_BEFORE=0x060000
-DQT_NO_FOREACH
)
@@ -154,7 +153,7 @@ if (NOT WIN32 AND NOT APPLE)
target_link_libraries(kddockwidgets PUBLIC Qt5::X11Extras)
endif()
install (TARGETS kddockwidgets kddockwidgets_multisplitter
install (TARGETS kddockwidgets
EXPORT kddockwidgetsTargets
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
@@ -188,6 +187,9 @@ install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KDDockWidgetsConfig.cmake"
)
if (OPTION_DEVELOPER_MODE)
# Under developer mode since kddw might be a sub-folder of a project setting a different value for QT_DISABLE_DEPRECATED_BEFORE
target_compile_definitions(kddockwidgets PRIVATE QT_DISABLE_DEPRECATED_BEFORE=0x060000)
add_executable(kddockwidgets_linter layoutlinter_main.cpp)
target_link_libraries(kddockwidgets_linter kddockwidgets kddockwidgets_multisplitter Qt5::Widgets)
endif()

View File

@@ -56,7 +56,7 @@ public:
* when visible, or stays without a parent when hidden. This allows to support docking
* to different main windows.
*/
explicit DockWidget(const QString &uniqueName, Options options = {});
explicit DockWidget(const QString &uniqueName, Options options = DockWidgetBase::Options());
///@brief destructor
~DockWidget() override;

View File

@@ -23,7 +23,6 @@
#include "Frame_p.h"
#include "FloatingWindow_p.h"
#include "Logging_p.h"
#include "TabWidget_p.h"
#include "Utils_p.h"
#include "DockRegistry_p.h"
#include "WidgetResizeHandler_p.h"
@@ -85,6 +84,11 @@ public:
updateTitle();
}
FloatingWindow *floatingWindow() const
{
return qobject_cast<FloatingWindow*>(q->window());
}
QPoint defaultCenterPosForFloating();
void updateTitle();
@@ -94,7 +98,6 @@ public:
void updateFloatAction();
void onDockWidgetShown();
void onDockWidgetHidden();
TabWidget *parentTabWidget() const;
void show();
void close();
void restoreToPreviousPosition();
@@ -210,7 +213,7 @@ void DockWidgetBase::addDockWidgetToContainingWindow(DockWidgetBase *other, Loca
if (isWindow())
morphIntoFloatingWindow();
if (auto fw = qobject_cast<FloatingWindow *>(window())) {
if (auto fw = floatingWindow()) {
fw->dropArea()->addDockWidget(other, location, relativeTo);
} else {
qWarning() << Q_FUNC_INFO << "Couldn't find floating nested window";
@@ -223,6 +226,7 @@ void DockWidgetBase::setWidget(QWidget *w)
qCDebug(addwidget) << Q_FUNC_INFO << w;
d->widget = w;
setSizePolicy(w->sizePolicy());
Q_EMIT widgetChanged(w);
setWindowTitle(uniqueName());
}
@@ -237,7 +241,7 @@ bool DockWidgetBase::isFloating() const
if (isWindow())
return true;
auto fw = qobject_cast<FloatingWindow *>(window());
auto fw = floatingWindow();
return fw && fw->hasSingleDockWidget();
}
@@ -254,22 +258,23 @@ void DockWidgetBase::setFloating(bool floats)
if (floats) {
d->saveTabIndex();
if (isTabbed()) {
TabWidget *tabWidget= d->parentTabWidget();
if (!tabWidget) {
qWarning() << "DockWidget::setFloating: Tabbed but no tabbar exists"
auto frame = this->frame();
if (!frame) {
qWarning() << "DockWidget::setFloating: Tabbed but no frame exists"
<< this;
Q_ASSERT(false);
}
tabWidget->detachTab(this);
frame->detachTab(this);
} else {
frame()->titleBar()->makeWindow();
}
auto lastGeo = lastPositions().lastFloatingGeometry();
if (lastGeo.isValid())
window()->setGeometry(lastGeo);
if (lastGeo.isValid()) {
if (auto fw = floatingWindow())
fw->setSuggestedGeometry(lastGeo, /*preserveCenter=*/true);
}
} else {
saveLastFloatingGeometry();
d->restoreToPreviousPosition();
@@ -327,8 +332,8 @@ void DockWidgetBase::setOptions(Options options)
bool DockWidgetBase::isTabbed() const
{
if (TabWidget* tabWidget = d->parentTabWidget()) {
return frame()->alwaysShowsTabs() || tabWidget->numDockWidgets() > 1;
if (Frame *frame = this->frame()) {
return frame->alwaysShowsTabs() || frame->dockWidgetCount() > 1;
} else {
if (!isFloating())
qWarning() << "DockWidget::isTabbed() Couldn't find any tab widget.";
@@ -338,8 +343,8 @@ bool DockWidgetBase::isTabbed() const
bool DockWidgetBase::isCurrentTab() const
{
if (TabWidget* tabWidget = d->parentTabWidget()) {
return tabWidget->currentIndex() == tabWidget->indexOfDockWidget(const_cast<DockWidgetBase*>(this));
if (Frame *frame = this->frame()) {
return frame->currentIndex() == frame->indexOfDockWidget(const_cast<DockWidgetBase*>(this));
} else {
return true;
}
@@ -347,8 +352,8 @@ bool DockWidgetBase::isCurrentTab() const
void DockWidgetBase::setAsCurrentTab()
{
if (TabWidget* tabWidget = d->parentTabWidget())
tabWidget->setCurrentDockWidget(this);
if (Frame *frame = this->frame())
frame->setCurrentDockWidget(this);
}
void DockWidgetBase::setIcon(const QIcon &icon)
@@ -405,7 +410,7 @@ void DockWidgetBase::raise()
setAsCurrentTab();
if (auto fw = qobject_cast<FloatingWindow*>(window())) {
if (auto fw = floatingWindow()) {
fw->raise();
fw->activateWindow();
}
@@ -444,7 +449,7 @@ FloatingWindow *DockWidgetBase::morphIntoFloatingWindow()
qCDebug(creation) << "DockWidget::morphIntoFloatingWindow() this=" << this
<< "; visible=" << isVisible();
if (auto fw = qobject_cast<FloatingWindow*>(window()))
if (auto fw = floatingWindow())
return fw; // Nothing to do
if (isWindow()) {
@@ -459,7 +464,7 @@ FloatingWindow *DockWidgetBase::morphIntoFloatingWindow()
auto frame = Config::self().frameworkWidgetFactory()->createFrame();
frame->addWidget(this);
auto floatingWindow = Config::self().frameworkWidgetFactory()->createFloatingWindow(frame);
floatingWindow->setGeometry(geo);
floatingWindow->setSuggestedGeometry(geo);
floatingWindow->show();
return floatingWindow;
@@ -487,7 +492,7 @@ Frame *DockWidgetBase::frame() const
FloatingWindow *DockWidgetBase::floatingWindow() const
{
return qobject_cast<FloatingWindow*>(window());
return d->floatingWindow();
}
void DockWidgetBase::addPlaceholderItem(Layouting::Item *item)
@@ -547,9 +552,9 @@ void DockWidgetBase::Private::updateToggleAction()
{
QScopedValueRollback<bool> recursionGuard(m_updatingToggleAction, true); // Guard against recursiveness
m_updatingToggleAction = true;
if ((q->isVisible() || parentTabWidget()) && !toggleAction->isChecked()) {
if ((q->isVisible() || q->frame()) && !toggleAction->isChecked()) {
toggleAction->setChecked(true);
} else if ((!q->isVisible() && !parentTabWidget()) && toggleAction->isChecked()) {
} else if ((!q->isVisible() && !q->frame()) && toggleAction->isChecked()) {
toggleAction->setChecked(false);
}
}
@@ -583,14 +588,6 @@ void DockWidgetBase::Private::onDockWidgetHidden()
qCDebug(hiding) << Q_FUNC_INFO << "parent=" << q->parentWidget();
}
TabWidget *DockWidgetBase::Private::parentTabWidget() const
{
if (auto f = q->frame())
return f->tabWidget();
return nullptr;
}
void DockWidgetBase::Private::close()
{
if (!m_isForceClosing && q->isFloating() && q->isVisible()) { // only user-closing is interesting to save the geometry
@@ -602,18 +599,16 @@ void DockWidgetBase::Private::close()
saveTabIndex();
// Do some cleaning. Widget is hidden, but we must hide the tab containing it.
if (auto tabWidget = parentTabWidget()) {
tabWidget->removeDockWidget(q);
if (Frame *frame = q->frame()) {
frame->removeWidget(q);
q->setParent(nullptr);
}
}
void DockWidgetBase::Private::restoreToPreviousPosition()
{
if (!m_lastPositions.isValid()) {
qWarning() << Q_FUNC_INFO << "Only restoring to MainWindow supported for now";
if (!m_lastPositions.isValid())
return;
}
Layouting::Item *item = m_lastPositions.lastItem();
@@ -660,8 +655,8 @@ void DockWidgetBase::Private::maybeRestoreToPreviousPosition()
int DockWidgetBase::Private::currentTabIndex() const
{
TabWidget *tabWidget = parentTabWidget();
return tabWidget ? tabWidget->indexOfDockWidget(q) : 0;
Frame *frame = q->frame();
return frame ? frame->indexOfDockWidget(q) : 0;
}
void DockWidgetBase::Private::saveTabIndex()

View File

@@ -64,7 +64,11 @@ class StateDragging;
*
* Do not use instantiate directly in user code. Use DockWidget instead.
*/
#ifndef PYTHON_BINDINGS //Pyside bug: https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-1327
class DOCKS_EXPORT DockWidgetBase : public QWidgetOrQuick
#else
class DOCKS_EXPORT DockWidgetBase : public QWidget
#endif
{
Q_OBJECT
public:
@@ -86,7 +90,7 @@ public:
* There's no parent argument. The DockWidget is either parented to FloatingWindow or MainWindow
* when visible, or stays without a parent when hidden.
*/
explicit DockWidgetBase(const QString &uniqueName, Options options = {});
explicit DockWidgetBase(const QString &uniqueName, Options options = DockWidgetBase::Options());
///@brief destructor
~DockWidgetBase() override;

View File

@@ -49,7 +49,7 @@ public:
///@param parent QObject *parent to pass to QMainWindow constructor.
///@param flags Window flags to pass to QMainWindow constructor.
explicit MainWindow(const QString &uniqueName, MainWindowOptions options = MainWindowOption_None,
QWidget *parent = nullptr, Qt::WindowFlags flags = {});
QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags());
///@brief Destructor
~MainWindow() override;

View File

@@ -51,13 +51,17 @@ class DropAreaWithCentralFrame;
*
* Do not use instantiate directly in user code. Use MainWindow instead.
*/
#ifndef PYTHON_BINDINGS //Pyside bug: https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-1327
class DOCKS_EXPORT MainWindowBase : public QMainWindowOrQuick
#else
class DOCKS_EXPORT MainWindowBase : public QMainWindow
#endif
{
Q_OBJECT
public:
typedef QVector<MainWindowBase*> List;
explicit MainWindowBase(const QString &uniqueName, MainWindowOptions options = MainWindowOption_HasCentralFrame,
QWidgetOrQuick *parent = nullptr, Qt::WindowFlags flags = {});
QWidgetOrQuick *parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags());
~MainWindowBase() override;
@@ -79,7 +83,7 @@ public:
*/
void addDockWidget(DockWidgetBase *dockWidget,
KDDockWidgets::Location location,
DockWidgetBase *relativeTo = nullptr, AddingOption option = {});
DockWidgetBase *relativeTo = nullptr, AddingOption option = KDDockWidgets::AddingOption());
/**
* @brief Returns the unique name that was passed via constructor.

View File

@@ -184,6 +184,16 @@ void StateDragging::onEntry(QEvent *)
q->m_windowBeingDragged = q->m_draggable->makeWindow();
if (q->m_windowBeingDragged) {
qCDebug(state) << "StateDragging entered. m_draggable=" << q->m_draggable << "; m_windowBeingDragged=" << q->m_windowBeingDragged->floatingWindow();
auto fw = q->m_windowBeingDragged->floatingWindow();
if (!fw->geometry().contains(q->m_pressPos)) {
// The window shrunk when the drag started, this can happen if it has max-size constraints
// we make the floating window smaller. Has the downside that it might not be under the mouse
// cursor anymore, so make the change
if (fw->width() < q->m_offset.x()) { // make sure it shrunk
q->m_offset.setX(fw->width() / 2);
}
}
} else {
// Shouldn't happen
qWarning() << Q_FUNC_INFO << "No window being dragged for " << q->m_draggable->asWidget();

View File

@@ -42,8 +42,6 @@
# include <Windows.h>
#endif
static int s_dbg_numFloatingWindows = 0;
using namespace KDDockWidgets;
#ifdef Q_OS_WIN
@@ -223,6 +221,28 @@ const Frame::List FloatingWindow::frames() const
return m_dropArea->findChildren<Frame *>(QString(), Qt::FindDirectChildrenOnly);
}
void FloatingWindow::setSuggestedGeometry(QRect suggestedRect, bool preserveCenter)
{
const Frame::List frames = this->frames();
if (frames.size() == 1) {
// Let's honour max-size when we have a single-frame.
// multi-frame cases are more complicated and we're not sure if we want the window to bounce around.
// single-frame is the most common case, like floating a dock widget, so let's do that first, it's also
// easy.
Frame *frame = frames[0];
const QSize waste = (minimumSize() - frame->minSize()).expandedTo(QSize(0, 0));
const QSize size = (frame->maxSizeHint() + waste).boundedTo(suggestedRect.size());
// Resize to new size but preserve center
const QPoint originalCenter = suggestedRect.center();
suggestedRect.setSize(size);
if (preserveCenter)
suggestedRect.moveCenter(originalCenter);
}
setGeometry(suggestedRect);
}
void FloatingWindow::scheduleDeleteLater()
{
m_beingDeleted = true;
@@ -299,11 +319,6 @@ bool FloatingWindow::beingDeleted() const
return true;
}
int FloatingWindow::dbg_numFrames()
{
return s_dbg_numFloatingWindows;
}
void FloatingWindow::onFrameCountChanged(int count)
{
qCDebug(docking) << "FloatingWindow::onFrameCountChanged" << count;

View File

@@ -65,6 +65,16 @@ public:
*/
TitleBar *titleBar() const { return m_titleBar; }
/**
* @brief Equivalent to setGeometry(), but the value might be adjusted.
*
* For example, if the suggestedRect is bigger than max size, we'll make it smaller.
*
* @param preserveCenter, if true, then the center is preserved
*
*/
void setSuggestedGeometry(QRect suggestedRect, bool preserveCenter = false);
bool anyNonClosable() const;
bool anyNonDockable() const;
@@ -105,10 +115,6 @@ public:
*/
bool isInTitleBar(QPoint globalPoint) const;
///@brief For tests-only. Returns the number of Frame instances in the whole application.
static int dbg_numFrames();
///@brief updates the title and the icon
void updateTitleAndIcon();
void updateTitleBarVisibility();

View File

@@ -26,7 +26,6 @@
*/
#include "Frame_p.h"
#include "TabWidget_p.h"
#include "DropArea_p.h"
#include "Logging_p.h"
#include "DragController_p.h"
@@ -37,7 +36,6 @@
#include "Config.h"
#include "FrameworkWidgetFactory.h"
#include <QTabBar>
#include <QCloseEvent>
#include <QTimer>
@@ -60,7 +58,6 @@ static FrameOptions actualOptions(FrameOptions options)
Frame::Frame(QWidgetOrQuick *parent, FrameOptions options)
: QWidgetAdapter(parent)
, Layouting::Widget_qwidget(this)
, m_tabWidget(Config::self().frameworkWidgetFactory()->createTabWidget(this))
, m_titleBar(Config::self().frameworkWidgetFactory()->createTitleBar(this))
, m_options(actualOptions(options))
{
@@ -69,12 +66,12 @@ Frame::Frame(QWidgetOrQuick *parent, FrameOptions options)
qCDebug(creation) << "Frame" << ((void*)this) << s_dbg_numFrames;
connect(this, &Frame::currentDockWidgetChanged, this, &Frame::updateTitleAndIcon);
m_tabWidget->setTabBarAutoHide(!alwaysShowsTabs());
m_inCtor = false;
}
Frame::~Frame()
{
m_inDtor = true;
s_dbg_numFrames--;
if (m_layoutItem)
m_layoutItem->unref();
@@ -107,7 +104,7 @@ void Frame::updateTitleAndIcon()
void Frame::addWidget(DockWidgetBase *dockWidget, AddingOption addingOption)
{
insertWidget(dockWidget, m_tabWidget->numDockWidgets(), addingOption); // append
insertWidget(dockWidget, dockWidgetCount(), addingOption); // append
}
void Frame::addWidget(Frame *frame, AddingOption addingOption)
@@ -143,7 +140,7 @@ void Frame::insertWidget(DockWidgetBase *dockWidget, int index, AddingOption add
if (m_layoutItem)
dockWidget->addPlaceholderItem(m_layoutItem);
m_tabWidget->insertDockWidget(dockWidget, index);
insertDockWidget(dockWidget, index);
if (addingOption == AddingOption_StartHidden) {
dockWidget->close(); // Ensure closed
@@ -169,7 +166,70 @@ void Frame::removeWidget(DockWidgetBase *dw)
{
disconnect(dw, &DockWidgetBase::titleChanged, this, &Frame::updateTitleAndIcon);
disconnect(dw, &DockWidgetBase::iconChanged, this, &Frame::updateTitleAndIcon);
m_tabWidget->removeDockWidget(dw);
removeWidget_impl(dw);
}
void Frame::detachTab(DockWidgetBase *dw)
{
if (m_inCtor || m_inDtor) return;
detachTab_impl(dw);
}
int Frame::indexOfDockWidget(DockWidgetBase *dw)
{
if (m_inCtor || m_inDtor) return -1;
return indexOfDockWidget_impl(dw);
}
int Frame::currentIndex() const
{
if (m_inCtor || m_inDtor) return -1;
return currentIndex_impl();
}
void Frame::setCurrentTabIndex(int index)
{
if (m_inCtor || m_inDtor) return;
setCurrentTabIndex_impl(index);
}
void Frame::setCurrentDockWidget(DockWidgetBase *dw)
{
if (m_inCtor || m_inDtor) return;
setCurrentDockWidget_impl(dw);
}
void Frame::insertDockWidget(DockWidgetBase *dw, int index)
{
if (m_inCtor || m_inDtor) return;
insertDockWidget_impl(dw, index);
}
DockWidgetBase *Frame::dockWidgetAt(int index) const
{
if (m_inCtor || m_inDtor) return nullptr;
return dockWidgetAt_impl(index);
}
DockWidgetBase *Frame::currentDockWidget() const
{
if (m_inCtor || m_inDtor) return nullptr;
return currentDockWidget_impl();
}
int Frame::dockWidgetCount() const
{
if (m_inCtor || m_inDtor) return 0;
return dockWidgetCount_impl();
}
void Frame::onDockWidgetCountChanged()
@@ -201,7 +261,7 @@ void Frame::onCurrentTabChanged(int index)
void Frame::updateTitleBarVisibility()
{
if (m_updatingTitleBar) {
if (m_updatingTitleBar || m_beingDeleted) {
// To break a cyclic dependency
return;
}
@@ -261,6 +321,9 @@ QIcon Frame::icon() const
const DockWidgetBase::List Frame::dockWidgets() const
{
if (m_inCtor || m_inDtor)
return {};
DockWidgetBase::List dockWidgets;
const int count = dockWidgetCount();
dockWidgets.reserve(count);
@@ -329,7 +392,7 @@ void Frame::restoreToPreviousPosition()
int Frame::currentTabIndex() const
{
return m_tabWidget->currentIndex();
return currentIndex();
}
void Frame::onCloseEvent(QCloseEvent *e)
@@ -344,16 +407,6 @@ void Frame::onCloseEvent(QCloseEvent *e)
}
}
void Frame::setCurrentTabIndex(int index)
{
m_tabWidget->setCurrentDockWidget(index);
}
DockWidgetBase *Frame::currentDockWidget() const
{
return m_tabWidget->dockwidgetAt(m_tabWidget->currentIndex());
}
bool Frame::anyNonClosable() const
{
for (auto dw : dockWidgets()) {
@@ -433,13 +486,11 @@ bool Frame::beingDeletedLater() const
return m_beingDeleted;
}
TabWidget *Frame::tabWidget() const
{
return m_tabWidget;
}
bool Frame::hasTabsVisible() const
{
if (m_beingDeleted)
return false;
return alwaysShowsTabs() || dockWidgetCount() > 1;
}
@@ -452,11 +503,6 @@ QStringList Frame::affinities() const
}
}
DockWidgetBase *Frame::dockWidgetAt(int index) const
{
return qobject_cast<DockWidgetBase *>(m_tabWidget->dockwidgetAt(index));
}
void Frame::setDropArea(DropArea *dt)
{
if (dt != m_dropArea) {
@@ -503,11 +549,6 @@ bool Frame::isInMainWindow() const
return m_dropArea && m_dropArea->isInMainWindow();
}
int Frame::dockWidgetCount() const
{
return m_tabWidget->numDockWidgets();
}
bool Frame::event(QEvent *e)
{
if (e->type() == QEvent::ParentChange) {
@@ -585,16 +626,22 @@ QSize Frame::biggestDockWidgetMaxSize() const
{
QSize size = Layouting::Item::hardcodedMaximumSize;
for (DockWidgetBase *dw : dockWidgets()) {
const QSize dwMax = widgetMaxSize(dw);
if (size == Layouting::Item::hardcodedMaximumSize) {
size = dw->maximumSize();
size = dwMax;
continue;
}
const bool hasMaxSize = dw->maximumSize() != Layouting::Item::hardcodedMaximumSize;
const bool hasMaxSize = dwMax != Layouting::Item::hardcodedMaximumSize;
if (hasMaxSize)
size = dw->maximumSize().expandedTo(size);
}
// Interpret 0 max-size as not having one too.
if (size.width() == 0)
size.setWidth(Layouting::Item::hardcodedMaximumSize.width());
if (size.height() == 0)
size.setHeight(Layouting::Item::hardcodedMaximumSize.height());
return size;
}

View File

@@ -42,7 +42,6 @@
namespace KDDockWidgets {
class TitleBar;
class TabWidget;
class DropArea;
class DockWidgetBase;
class FloatingWindow;
@@ -85,16 +84,41 @@ public:
///@brief removes a dockwidget from the frame
void removeWidget(DockWidgetBase *);
///@brief detaches this dock widget
void detachTab(DockWidgetBase *);
///@brief returns the index of the specified dock widget
int indexOfDockWidget(DockWidgetBase *);
///@brief returns the index of the current tab
int currentIndex() const;
///@brief sets the current tab index
void setCurrentTabIndex(int index);
///@brief Sets the specified dock widget to be the current tab
void setCurrentDockWidget(DockWidgetBase *);
///@brief Inserts a dock widget into the specified index
void insertDockWidget(DockWidgetBase *, int index);
/// @brief Returns the dock widget at @p index
DockWidgetBase *dockWidgetAt(int index) const;
///@brief Returns the current dock widget
DockWidgetBase *currentDockWidget() const;
/// @brief returns the number of dock widgets inside the frame
int dockWidgetCount() const;
void updateTitleAndIcon();
void updateTitleBarVisibility();
bool containsMouse(QPoint globalPos) const;
TitleBar *titleBar() const;
TitleBar *actualTitleBar() const;
TabWidget *tabWidget() const;
QString title() const;
QIcon icon() const;
const QVector<DockWidgetBase *> dockWidgets() const;
DockWidgetBase *dockWidgetAt(int index) const;
void setDropArea(DropArea *);
bool isTheOnlyFrame() const;
@@ -139,9 +163,6 @@ public:
bool alwaysShowsTabs() const { return m_options & FrameOption_AlwaysShowsTabs; }
/// @brief returns the number of dock widgets inside the frame
int dockWidgetCount() const;
/// @brief returns whether the dockwidget @p w is inside this frame
bool contains(DockWidgetBase *w) const;
@@ -160,9 +181,6 @@ public:
void onCloseEvent(QCloseEvent *e) override;
int currentTabIndex() const;
void setCurrentTabIndex(int);
DockWidgetBase *currentDockWidget() const;
FrameOptions options() const { return m_options; }
bool anyNonClosable() const;
@@ -231,6 +249,17 @@ protected:
*/
QSize biggestDockWidgetMaxSize() const;
virtual void removeWidget_impl(DockWidgetBase *) = 0;
virtual void detachTab_impl(DockWidgetBase *) = 0;
virtual int indexOfDockWidget_impl(DockWidgetBase *) = 0;
virtual int currentIndex_impl() const = 0;
virtual void setCurrentTabIndex_impl(int index) = 0;
virtual void setCurrentDockWidget_impl(DockWidgetBase *) = 0;
virtual void insertDockWidget_impl(DockWidgetBase *, int index) = 0;
virtual DockWidgetBase *dockWidgetAt_impl(int index) const = 0;
virtual DockWidgetBase *currentDockWidget_impl() const = 0;
virtual int dockWidgetCount_impl() const = 0;
bool m_inDtor = false;
private:
Q_DISABLE_COPY(Frame)
friend class TestDocks;
@@ -239,7 +268,7 @@ private:
void onCurrentTabChanged(int index);
void scheduleDeleteLater();
bool event(QEvent *) override;
TabWidget *const m_tabWidget;
bool m_inCtor = true;
TitleBar *const m_titleBar;
DropArea *m_dropArea = nullptr;
const FrameOptions m_options;

View File

@@ -113,7 +113,7 @@ FloatingWindow * TabBar::detachTab(DockWidgetBase *dockWidget)
auto floatingWindow = Config::self().frameworkWidgetFactory()->createFloatingWindow(newFrame);
r.moveTopLeft(globalPoint);
floatingWindow->setGeometry(r);
floatingWindow->setSuggestedGeometry(r);
floatingWindow->show();
return floatingWindow;
@@ -231,7 +231,7 @@ std::unique_ptr<WindowBeingDragged> TabWidget::makeWindow()
auto floatingWindow = Config::self().frameworkWidgetFactory()->createFloatingWindow(m_frame);
r.moveTopLeft(globalPoint);
floatingWindow->setGeometry(r);
floatingWindow->setSuggestedGeometry(r);
floatingWindow->show();
return std::unique_ptr<WindowBeingDragged>(new WindowBeingDragged(floatingWindow, this));

View File

@@ -144,7 +144,7 @@ std::unique_ptr<WindowBeingDragged> TitleBar::makeWindow()
r.moveTopLeft(m_frame->mapToGlobal(QPoint(0, 0)));
auto floatingWindow = Config::self().frameworkWidgetFactory()->createFloatingWindow(m_frame);
floatingWindow->setGeometry(r);
floatingWindow->setSuggestedGeometry(r);
floatingWindow->show();
qCDebug(hovering) << "TitleBar::makeWindow setting geometry" << r << "actual=" << floatingWindow->geometry();

View File

@@ -7,6 +7,7 @@ set(MULTISPLITTER_SRCS
MultiSplitterConfig.h
Separator.cpp
Separator_p.h
Widget.cpp
Widget.h
multisplitter_export.h
)
@@ -58,6 +59,9 @@ endif()
target_compile_definitions(kddockwidgets_multisplitter PRIVATE BUILDING_MULTISPLITTER_LIBRARY)
if (OPTION_DEVELOPER_MODE)
# Under developer mode since kddw might be a sub-folder of a project setting a different value for QT_DISABLE_DEPRECATED_BEFORE
target_compile_definitions(kddockwidgets_multisplitter PRIVATE QT_DISABLE_DEPRECATED_BEFORE=0x060000)
add_subdirectory(tests)
add_subdirectory(examples)
endif()
@@ -68,3 +72,10 @@ target_include_directories(kddockwidgets_multisplitter
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
)
install (TARGETS kddockwidgets_multisplitter
EXPORT kddockwidgetsTargets
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)

View File

@@ -446,7 +446,7 @@ QSize Item::minSize() const
QSize Item::maxSizeHint() const
{
return m_sizingInfo.maxSizeHint;
return m_sizingInfo.maxSizeHint.boundedTo(QSize(KDDOCKWIDGETS_MAX_WIDTH, KDDOCKWIDGETS_MAX_HEIGHT));
}
void Item::setPos(QPoint pos)
@@ -1820,27 +1820,37 @@ QSize ItemContainer::minSize() const
QSize ItemContainer::maxSizeHint() const
{
int maxW = KDDOCKWIDGETS_MAX_WIDTH;
int maxH = KDDOCKWIDGETS_MAX_HEIGHT;
int maxW = isVertical() ? KDDOCKWIDGETS_MAX_WIDTH : 0;
int maxH = isVertical() ? 0 : KDDOCKWIDGETS_MAX_HEIGHT;
if (!isEmpty()) {
const Item::List visibleChildren = this->visibleChildren();
if (!visibleChildren.isEmpty()) {
for (Item *item : visibleChildren) {
const QSize itemMaxSz = item->maxSizeHint();
const int itemMaxWidth = itemMaxSz.width();
const int itemMaxHeight = itemMaxSz.height();
if (isVertical()) {
maxW = qMin(maxW, item->maxSizeHint().width());
maxH += item->maxSizeHint().height();
maxW = qMin(maxW, itemMaxWidth);
maxH = qMin(maxH + itemMaxHeight, KDDOCKWIDGETS_MAX_HEIGHT);
} else {
maxH = qMin(maxH, item->maxSizeHint().height());
maxW += item->maxSizeHint().width();
maxH = qMin(maxH, itemMaxHeight);
maxW = qMin(maxW + itemMaxWidth, KDDOCKWIDGETS_MAX_WIDTH);
}
}
const int separatorWaste = (visibleChildren.size() - 1) * separatorThickness;
if (isVertical())
maxH += separatorWaste;
else
maxW += separatorWaste;
if (isVertical()) {
maxH = qMin(maxH + separatorWaste, KDDOCKWIDGETS_MAX_HEIGHT);
} else {
maxW = qMin(maxW + separatorWaste, KDDOCKWIDGETS_MAX_WIDTH);
}
}
if (maxW == 0)
maxW = KDDOCKWIDGETS_MAX_WIDTH;
if (maxH == 0)
maxH = KDDOCKWIDGETS_MAX_HEIGHT;
return QSize(maxW, maxH).expandedTo(minSize());
}
@@ -2016,11 +2026,18 @@ void ItemContainer::dumpLayout(int level)
const QString typeStr = isRoot() ? QStringLiteral("* Root: ")
: QStringLiteral("* Layout: ");
qDebug().noquote() << indent << typeStr << d->m_orientation
{
auto dbg = qDebug().noquote();
dbg << indent << typeStr << d->m_orientation
<< m_sizingInfo.geometry /*<< "r=" << m_geometry.right() << "b=" << m_geometry.bottom()*/
<< "; min=" << minSize()
<< "; this=" << this << beingInserted << visible
<< "; %=" << d->childPercentages();
if (maxSizeHint() != Item::hardcodedMaximumSize)
dbg << "; max=" << maxSizeHint();
}
int i = 0;
for (Item *item : qAsConst(d->m_children)) {
item->dumpLayout(level + 1);

View File

@@ -0,0 +1,44 @@
/*
This file is part of KDDockWidgets.
Copyright (C) 2018-2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Author: Sérgio Martins <sergio.martins@kdab.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Widget.h"
#include "Item_p.h"
using namespace Layouting;
Widget::~Widget()
{
}
QSize Widget::boundedMaxSize(QSize min, QSize max)
{
// Max should be bigger than min, but not bigger than the hardcoded max
max = max.boundedTo(QSize(KDDOCKWIDGETS_MAX_WIDTH, KDDOCKWIDGETS_MAX_HEIGHT));
// 0 interpreted as not having max
if (max.width() <= 0)
max.setWidth(KDDOCKWIDGETS_MAX_WIDTH);
if (max.height() <= 0)
max.setHeight(KDDOCKWIDGETS_MAX_HEIGHT);
max = max.expandedTo(min);
return max;
}

View File

@@ -23,6 +23,8 @@
#pragma once
#include "multisplitter_export.h"
#include <QRect>
#include <QSize>
#include <QDebug>
@@ -47,19 +49,20 @@ class Item;
* Inherit from it via multi-inheritance so this wrapper is deleted when the actual QWidget/QQuickItem
* is deleted.
*/
class Widget
class MULTISPLITTER_EXPORT Widget
{
public:
explicit Widget(QObject *thisObj)
: m_thisObj(thisObj) {}
virtual ~Widget() {}
virtual ~Widget();
virtual void setLayoutItem(Item *) = 0;
// Not strickly necessary, but it's nice conveniance for kddw which is widget based.
virtual QWidget *asQWidget() const { return nullptr; };
virtual QWidget *asQWidget() const { return nullptr; }
virtual QSize sizeHint() const { return {}; }
virtual QSize minSize() const = 0;
virtual QSize maxSizeHint() const = 0;
virtual QRect geometry() const = 0;
@@ -100,6 +103,9 @@ public:
return obj == m_thisObj;
}
protected:
static QSize boundedMaxSize(QSize min, QSize max);
private:
QObject *const m_thisObj;
Q_DISABLE_COPY(Widget)

View File

@@ -29,6 +29,11 @@ Widget_qwidget::~Widget_qwidget()
{
}
QSize Widget_qwidget::sizeHint() const
{
return m_thisWidget->sizeHint();
}
QSize Widget_qwidget::minSize() const
{
return widgetMinSize(m_thisWidget);
@@ -36,7 +41,7 @@ QSize Widget_qwidget::minSize() const
QSize Widget_qwidget::maxSizeHint() const
{
return m_thisWidget->maximumSize();
return widgetMaxSize(m_thisWidget);
}
QRect Widget_qwidget::geometry() const
@@ -128,6 +133,26 @@ QSize Widget_qwidget::widgetMinSize(const QWidget *w)
return QSize(minW, minH).expandedTo(Item::hardcodedMinimumSize);
}
QSize Widget_qwidget::widgetMaxSize(const QWidget *w)
{
// The max size is usually QWidget::maximumSize(), but we also honour the QSizePolicy::Fixed+sizeHint() case
// as widgets don't need to have QWidget::maximumSize() to have a max size honoured
const QSize min = widgetMinSize(w);
QSize max = w->maximumSize();
max = boundedMaxSize(min, max); // for safety against weird values
const QSizePolicy policy = w->sizePolicy();
if (policy.verticalPolicy() == QSizePolicy::Fixed || policy.verticalPolicy() == QSizePolicy::Maximum)
max.setHeight(qMin(max.height(), w->sizeHint().height()));
if (policy.horizontalPolicy() == QSizePolicy::Fixed || policy.horizontalPolicy() == QSizePolicy::Maximum)
max.setWidth(qMin(max.width(), w->sizeHint().width()));
max = boundedMaxSize(min, max); // for safety against weird values
return max;
}
void Widget_qwidget::setSize(int width, int height)
{
m_thisWidget->resize(QSize(width, height));

View File

@@ -21,7 +21,6 @@
#pragma once
#include "Widget.h"
#include "multisplitter_export.h"
#include <QWidget>
@@ -47,6 +46,7 @@ public:
return m_thisWidget;
}
QSize sizeHint() const override;
QSize minSize() const override;
QSize maxSizeHint() const override;
QRect geometry() const override;
@@ -65,7 +65,7 @@ public:
void setHeight(int height) override;
static QSize widgetMinSize(const QWidget *w);
static QSize widgetMaxSize(const QWidget *w);
private:
QWidget *const m_thisWidget;
Q_DISABLE_COPY(Widget_qwidget)

View File

@@ -196,6 +196,7 @@ private Q_SLOTS:
void tst_minSizeChangedBeforeRestore();
void tst_separatorMoveCrash();
void tst_maxSizeHonoured1();
void tst_maxSizeHonoured2();
};
class MyHostWidget : public QWidget
@@ -1561,6 +1562,24 @@ void TestMultiSplitter::tst_maxSizeHonoured1()
QCOMPARE(item2->height(), maxHeight);
}
void TestMultiSplitter::tst_maxSizeHonoured2()
{
// Tests that a container gets the max size of its children
auto root1 = createRoot();
auto root2 = createRoot();
auto item1 = createItem();
auto item2 = createItem();
root1->insertItem(item1, Item::Location_OnTop);
root2->insertItem(item2, Item::Location_OnTop);
item2->setMaxSizeHint(QSize(200, 200));
root1->insertItem(root2.release(), Item::Location_OnBottom);
QCOMPARE(item2->parentContainer()->maxSizeHint(), item2->maxSizeHint());
}
int main(int argc, char *argv[])
{
bool qpaPassed = false;

View File

@@ -1,98 +0,0 @@
/*
This file is part of KDDockWidgets.
Copyright (C) 2018-2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Author: Sérgio Martins <sergio.martins@kdab.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @file
* @brief The QWidget counter part of TabWidget. Handles GUI while TabWidget handles state.
*
* @author Sérgio Martins \<sergio.martins@kdab.com\>
*/
#include "TabWidgetQuick_p.h"
#include "Frame_p.h"
#include "Config.h"
#include "FrameworkWidgetFactory.h"
using namespace KDDockWidgets;
TabWidgetQuick::TabWidgetQuick(Frame *parent)
: TabWidget(this, parent)
, m_tabBar(Config::self().frameworkWidgetFactory()->createTabBar(this))
{
}
TabBar *TabWidgetQuick::tabBar() const
{
return m_tabBar;
}
int TabWidgetQuick::numDockWidgets() const
{
return 0;
}
void TabWidgetQuick::removeDockWidget(DockWidgetBase *)
{
}
int TabWidgetQuick::indexOfDockWidget(DockWidgetBase *) const
{
return 0;
}
bool TabWidgetQuick::isPositionDraggable(QPoint) const
{
/* if (tabPosition() != QTabWidget::North) {
qWarning() << Q_FUNC_INFO << "Not implemented yet. Only North is supported";
return false;
}*/
return -1;
}
void TabWidgetQuick::setCurrentDockWidget(int)
{
}
void TabWidgetQuick::insertDockWidget(int , DockWidgetBase *,
const QIcon &, const QString &)
{
}
void TabWidgetQuick::setTabBarAutoHide(bool)
{
}
void TabWidgetQuick::detachTab(DockWidgetBase *dockWidget)
{
tabBar()->detachTab(dockWidget);
}
DockWidgetBase *TabWidgetQuick::dockwidgetAt(int) const
{
return 0;
}
int TabWidgetQuick::currentIndex() const
{
return -1;
}

View File

@@ -1,65 +0,0 @@
/*
This file is part of KDDockWidgets.
Copyright (C) 2018-2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Author: Sérgio Martins <sergio.martins@kdab.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @file
* @brief The QWidget counter part of TabWidget. Handles GUI while TabWidget handles state.
*
* @author Sérgio Martins \<sergio.martins@kdab.com\>
*/
#ifndef KDTABWIDGETQUICK_P_H
#define KDTABWIDGETQUICK_P_H
#include "TabWidget_p.h"
namespace KDDockWidgets {
class Frame;
class TabBar;
class DOCKS_EXPORT TabWidgetQuick : public TabWidget, public QWidgetAdapter
{
public:
explicit TabWidgetQuick(Frame *parent);
TabBar *tabBar() const override;
int numDockWidgets() const override;
void removeDockWidget(DockWidgetBase *) override;
int indexOfDockWidget(DockWidgetBase *) const override;
protected:
bool isPositionDraggable(QPoint p) const override;
void setCurrentDockWidget(int index) override;
void insertDockWidget(int index, DockWidgetBase *, const QIcon&, const QString &title) override;
void setTabBarAutoHide(bool) override;
void detachTab(DockWidgetBase *dockWidget) override;
DockWidgetBase *dockwidgetAt(int index) const override;
int currentIndex() const override;
private:
Q_DISABLE_COPY(TabWidgetQuick)
TabBar *const m_tabBar;
};
}
#endif

View File

@@ -28,6 +28,8 @@
#include "FrameWidget_p.h"
#include "TitleBar_p.h"
#include "TabWidget_p.h"
#include "Config.h"
#include "FrameworkWidgetFactory.h"
#include <QVBoxLayout>
#include <QPainter>
@@ -58,12 +60,20 @@ VBoxLayout::~VBoxLayout() = default;
FrameWidget::FrameWidget(QWidget *parent, FrameOptions options)
: Frame(parent, options)
, m_tabWidget(Config::self().frameworkWidgetFactory()->createTabWidget(this))
{
auto vlayout = new VBoxLayout(this);
vlayout->setContentsMargins(0, 0, 0, 0);
vlayout->setSpacing(0);
vlayout->addWidget(titleBar());
vlayout->addWidget(tabWidget()->asWidget());
vlayout->addWidget(m_tabWidget->asWidget());
m_tabWidget->setTabBarAutoHide(!alwaysShowsTabs());
}
FrameWidget::~FrameWidget()
{
m_inDtor = true;
}
void FrameWidget::paintEvent(QPaintEvent *)
@@ -83,8 +93,64 @@ QSize FrameWidget::maxSizeHint() const
return waste + biggestDockWidgetMaxSize();
}
void FrameWidget::detachTab_impl(DockWidgetBase *dw)
{
m_tabWidget->detachTab(dw);
}
int FrameWidget::indexOfDockWidget_impl(DockWidgetBase *dw)
{
return m_tabWidget->indexOfDockWidget(dw);
}
void FrameWidget::setCurrentDockWidget_impl(DockWidgetBase *dw)
{
m_tabWidget->setCurrentDockWidget(dw);
}
int FrameWidget::currentIndex_impl() const
{
return m_tabWidget->currentIndex();
}
void FrameWidget::insertDockWidget_impl(DockWidgetBase *dw, int index)
{
m_tabWidget->insertDockWidget(dw, index);
}
void FrameWidget::removeWidget_impl(DockWidgetBase *dw)
{
m_tabWidget->removeDockWidget(dw);
}
void FrameWidget::setCurrentTabIndex_impl(int index)
{
m_tabWidget->setCurrentDockWidget(index);
}
DockWidgetBase *FrameWidget::currentDockWidget_impl() const
{
return m_tabWidget->dockwidgetAt(m_tabWidget->currentIndex());
}
DockWidgetBase *FrameWidget::dockWidgetAt_impl(int index) const
{
return qobject_cast<DockWidgetBase *>(m_tabWidget->dockwidgetAt(index));
}
QTabBar *FrameWidget::tabBar() const
{
auto tw = static_cast<QTabWidget*>(tabWidget()->asWidget());
auto tw = static_cast<QTabWidget*>(m_tabWidget->asWidget());
return tw->tabBar();
}
TabWidget *FrameWidget::tabWidget() const
{
return m_tabWidget;
}
int FrameWidget::dockWidgetCount_impl() const
{
return m_tabWidget->numDockWidgets();
}

View File

@@ -30,6 +30,9 @@ QT_END_NAMESPACE
namespace KDDockWidgets {
class TestDocks;
class TabWidget;
/**
* @brief The GUI counterpart of Frame. Inherits Frame and implements paintEvent().
*/
@@ -38,10 +41,26 @@ class DOCKS_EXPORT FrameWidget : public Frame
Q_OBJECT
public:
explicit FrameWidget(QWidget *parent = nullptr, FrameOptions = FrameOption_None);
~FrameWidget();
QTabBar *tabBar() const;
TabWidget *tabWidget() const;
protected:
void paintEvent(QPaintEvent *) override;
QSize maxSizeHint() const override;
void detachTab_impl(DockWidgetBase *) override;
int indexOfDockWidget_impl(DockWidgetBase *) override;
void setCurrentDockWidget_impl(DockWidgetBase *) override;
int currentIndex_impl() const override;
void insertDockWidget_impl(DockWidgetBase *, int index) override;
void removeWidget_impl(DockWidgetBase *) override;
void setCurrentTabIndex_impl(int) override;
DockWidgetBase *currentDockWidget_impl() const override;
DockWidgetBase *dockWidgetAt_impl(int index) const override;
int dockWidgetCount_impl() const override;
private:
friend class TestDocks;
TabWidget *const m_tabWidget;
};

View File

@@ -153,7 +153,7 @@ void MultiSplitterLayout::addWidget(QWidgetOrQuick *w, Location location,
newItem->setGuestWidget(frame);
} else if (dw) {
newItem = new Layouting::Item(m_multiSplitter);
frame = new Frame();
frame = Config::self().frameworkWidgetFactory()->createFrame();
newItem->setGuestWidget(frame);
frame->addWidget(dw, option);
} else if (auto ms = qobject_cast<MultiSplitter*>(w)) {

View File

@@ -356,6 +356,8 @@ private Q_SLOTS:
void tst_moreTitleBarCornerCases();
void tst_maxSizePropagates();
void tst_maxSizeHonouredWhenDropped();
void tst_fixedSizePolicy();
void tst_maximumSizePolicy();
private:
std::unique_ptr<MultiSplitter> createMultiSplitterFromSetup(MultiSplitterSetup setup, QHash<QWidget *, Frame *> &frameMap) const;
@@ -386,13 +388,14 @@ public:
explicit MyWidget2(QSize minSz = QSize(1,1))
: m_minSz(minSz)
, m_sizeHint(minSz)
{
}
QSize sizeHint() const override
{
return m_minSz;
return m_sizeHint;
}
QSize minimumSizeHint() const override
@@ -406,7 +409,13 @@ public:
updateGeometry();
}
void setSizeHint(QSize s)
{
m_sizeHint = s;
}
QSize m_minSz;
QSize m_sizeHint;
};
}
@@ -2190,7 +2199,7 @@ void TestDocks::tst_setFloatingWhenWasTabbed()
dock2->setVisible(false);
QVERIFY(dock2->isTabbed());
QVERIFY(!dock1->isFloating());
QCOMPARE(dock2->frame()->m_tabWidget->numDockWidgets(), 2);
QCOMPARE(static_cast<FrameWidget*>(dock2->frame())->m_tabWidget->numDockWidgets(), 2);
// 3. Set one floating. Now both cease to be tabbed, and both are floating.
dock1->setFloating(true);
@@ -2245,7 +2254,7 @@ void TestDocks::tst_setFloatingWhenWasTabbed()
dock2->setFloating(false);
QVERIFY(!dock2->isFloating());
QVERIFY(dock2->isTabbed());
QCOMPARE(dock2->frame()->m_tabWidget->indexOfDockWidget(dock2), 1);
QCOMPARE(static_cast<FrameWidget*>(dock2->frame())->m_tabWidget->indexOfDockWidget(dock2), 1);
// 10. Float dock1, and dock it to main window as tab. This tests Option_AlwaysShowsTabs.
@@ -2258,7 +2267,7 @@ void TestDocks::tst_setFloatingWhenWasTabbed()
QVERIFY(dock1->isTabbed());
dock1->setFloating(true);
dock1->setFloating(false);
QCOMPARE(dock1->frame()->m_tabWidget->numDockWidgets(), 1);
QCOMPARE(static_cast<FrameWidget*>(dock1->frame())->m_tabWidget->numDockWidgets(), 1);
// Cleanup
m->addDockWidgetAsTab(dock2);
@@ -2367,7 +2376,7 @@ void TestDocks::tst_setFloatingAfterDraggedFromTabToSideBySide()
// Detach tab
dock1->frame()->m_tabWidget->detachTab(dock2);
dock1->frame()->detachTab(dock2);
QVERIFY(layout->checkSanity());
auto fw2 = dock2->floatingWindow();
QVERIFY(fw2);
@@ -3655,6 +3664,7 @@ void TestDocks::tst_toggleDockWidgetWithHiddenTitleBar()
auto f1 = d1->frame();
Testing::waitForDeleted(f1);
d1->toggleAction()->setChecked(true);
QVERIFY(d1->frame());
QVERIFY(!d1->frame()->titleBar()->isVisible());
}
@@ -3694,7 +3704,7 @@ void TestDocks::tst_dragByTabBar()
dock2->addDockWidgetAsTab(dock3);
if (documentMode)
static_cast<QTabWidget*>(dock2->frame()->tabWidget()->asWidget())->setDocumentMode(true);
static_cast<QTabWidget*>(static_cast<FrameWidget*>(dock2->frame())->tabWidget()->asWidget())->setDocumentMode(true);
auto fw = dock2->floatingWindow();
fw->move(m->pos() + QPoint(500, 500));
@@ -4345,7 +4355,7 @@ void TestDocks::tst_invalidLayoutAfterRestore()
// Detach dock2
QPointer<Frame> f2 = dock2->frame();
f2->m_tabWidget->detachTab(dock2);
f2->detachTab(dock2);
QVERIFY(!f2.data());
QTest::qWait(200); // Not sure why. Some event we're waiting for. TODO: Investigate
auto fw2 = dock2->floatingWindow();
@@ -5222,7 +5232,7 @@ void TestDocks::tst_lastFloatingPositionIsRestored()
EnsureTopLevelsDeleted e;
auto m1 = createMainWindow();
auto dock1 = createDockWidget("dock1", new QPushButton("foo"));
auto dock1 = createDockWidget("dock1", new QWidget());
dock1->show();
QPoint targetPos = QPoint(340, 340);
dock1->window()->move(targetPos);
@@ -5384,6 +5394,54 @@ void TestDocks::tst_maxSizeHonouredWhenDropped()
QCOMPARE(dock2->frame()->width(), droppedWidth);
}
void TestDocks::tst_fixedSizePolicy()
{
// tests that KDDW also takes into account QSizePolicy::Fixed for calculating the max size hint.
// Since QPushButton for example doesn't set QWidget::maximumSize(), but instead uses sizeHint()
// + QSizePolicy::Fixed.
EnsureTopLevelsDeleted e;
auto button = new QPushButton("one");
auto dock1 = createDockWidget("dock1", button);
Frame *frame = dock1->frame();
// Just a precondition from the test. If QPushButton ever changes, replace with a QWidget and set fixed size policy
QCOMPARE(button->sizePolicy().verticalPolicy(), QSizePolicy::Fixed);
const int buttonMaxHeight = button->sizeHint().height();
QCOMPARE(dock1->sizeHint(), button->sizeHint());
QCOMPARE(dock1->sizePolicy().verticalPolicy(), button->sizePolicy().verticalPolicy());
QCOMPARE(dock1->sizePolicy().horizontalPolicy(), button->sizePolicy().horizontalPolicy());
QCOMPARE(frame->maxSizeHint().height(), qMax(buttonMaxHeight, KDDOCKWIDGETS_MIN_HEIGHT));
delete dock1->window();
}
void TestDocks::tst_maximumSizePolicy()
{
EnsureTopLevelsDeleted e;
auto widget = new MyWidget2();
const int maxHeight = 250;
widget->setMinSize(QSize(200, 200));
widget->setSizeHint(QSize(250, maxHeight));
widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
auto dock1 = createDockWidget("dock1", widget);
dock1->show();
dock1->window()->resize(QSize(500, 500));
auto oldFw = dock1->window();
dock1->close();
dock1->show();
QVERIFY(dock1->window()->height() <= maxHeight + 20); // + 20 as the floating window is a bit bigger, due to margins etc.
QVERIFY(dock1->height() <= maxHeight);
delete oldFw;
delete dock1->window();
}
int main(int argc, char *argv[])
{
if (!qpaPassedAsArgument(argc, argv)) {

View File

@@ -173,7 +173,9 @@ QWidget *KDDockWidgets::Tests::draggableFor(QWidget *w)
if (auto frame = dock->frame())
draggable = frame->titleBar();
} else if (auto fw = qobject_cast<FloatingWindow *>(w)) {
draggable = ((Config::self().flags() & Config::Flag_HideTitleBarWhenTabsVisible) && fw->hasSingleFrame() && fw->frames().first()->hasTabsVisible()) ? static_cast<QWidget*>(fw->frames().first()->tabWidget()->asWidget())
auto frame = fw->hasSingleFrame() ? static_cast<FrameWidget*>(fw->frames().first())
: nullptr;
draggable = ((Config::self().flags() & Config::Flag_HideTitleBarWhenTabsVisible) && frame && frame->hasTabsVisible()) ? static_cast<QWidget*>(frame->tabWidget()->asWidget())
: static_cast<QWidget*>(fw->titleBar());
} else if (qobject_cast<TabWidgetWidget *>(w) || qobject_cast<TitleBar *>(w)) {