Compare commits

...

75 Commits
3.7.2 ... 3.8.3

Author SHA1 Message Date
Uwe Kindler
89ff0ad311 Added PRE Workbench to Showcase section 2022-07-29 13:57:08 +02:00
Uwe Kindler
6caeac2cb4 Merge branch 'master' of https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System 2022-07-29 13:26:23 +02:00
Uwe Kindler
a0f0640c9f Fixed CustomCloseHandling for floating widgets 2022-07-29 13:25:14 +02:00
Mira Weller
d3f5879119 upgrade PyQt build (#404)
* Add new flags from 3.7 and 3.8

* remove unnecessarily deep namespace for Python module

* Update PyQt5 bindings build to new build system

* fix opening tabbed dockWidgets

* fix RESOURCES configuration for sip build

* increment build version

* remove leftovers

* add file names explicitly

* Update pyproject.toml
2022-07-19 13:17:45 +02:00
bjb-work
df74686287 call CDockWidget::closeDockWidgetInternal when close attempt and CustomCloseHandling (#438)
Co-authored-by: bjb-work <N/A>
2022-07-15 14:47:20 +02:00
Uwe Kindler
75d58b3ea3 Fixed #433 - Link issue of debug library with MSVC19 2022-07-04 07:41:26 +02:00
Uwe Kindler
bedd25021c Fixed issue #429 - Raise floating widget on toggleView() request 2022-06-17 08:31:24 +02:00
Uwe Kindler
0e566cb8a3 Fixed #420 - Floating docks title doesn't update
added tests for DockWidget->setWindowTitle() in demo application
2022-05-10 13:41:19 +02:00
Mira Weller
23f80334f6 Fix issue #415 (#416) 2022-05-03 08:47:31 +02:00
Uwe Kindler
7063909cee Added MetGem to showcase section 2022-04-21 16:29:55 +02:00
Igor Sirotin
a284e3bd65 CMake bugfixes [target alias, license install] (#412)
* Added alias to cmake library target

* Fixed CMake license install

Co-authored-by: Igor Sirotin <i.sirotin@1440.space>
2022-04-18 23:07:48 +02:00
ARSanderson
823887daf8 Tidy/doc updates (#410)
* minor updates to clarify when the dock manager should be created

* fixed typos
2022-04-12 07:58:18 +02:00
Uwe Kindler
78a4166e42 Updated CFloatingDockContainer::closeEvent() function to delete all dock widgets with DockWidgetDeleteOnClose flag set 2022-03-25 13:31:40 +01:00
tytan652
3d3b694040 Avoid calling XCB functions under non-XCB Linux platform (#405) 2022-03-24 19:43:39 +01:00
ruffianhy
ad06241c9d fix(issue-350): on RedHat/CentOS, sometimes there will be one more move event after user release their mouse which will cause the floating overlay can not be hide. Here we use the WindowActivate and WindowDeactivate event to check whether user release their mouse. (#401) 2022-03-13 21:22:07 +01:00
Uwe Kindler
6a1ea6c7de Fixed examples/sidebar bug 2022-03-04 13:43:21 +01:00
Uwe Kindler
931719c474 Updated README.md 2022-03-04 08:04:21 +01:00
Uwe Kindler
70ef00932a Updated marketplace description 2022-03-02 11:52:38 +01:00
Uwe Kindler
a1bc4a063b Updated qt_markectplace files 2022-03-02 11:48:51 +01:00
Uwe Kindler
abf7224582 Updated ads logo and icon 2022-03-02 11:31:25 +01:00
Uwe Kindler
7ab4c9af2e Handle StyleChange event in DockWidgetTab to update icon 2022-02-25 21:41:46 +01:00
kzl
d5fefaa35f while dockManager.removeDockWidget , the dockManager() returns null (#399) 2022-02-18 19:33:55 +01:00
Ben Hetherington
3c4af9c6af Add method to add dock widget to dock container (#398)
This adds a `addDockWidgetToContainer()` method to `CDockManager`, which allows a dock widget to be registered with the dock manager, and added to a specific container, even if that container doesn't currently have any `DockAreaWidget`s.

This is pretty much just a clone of `addDockWidget()`, but with the ability to specify a container widget instead of a dock area.
2022-02-06 08:21:43 +01:00
githubuser0xFFFF
12ec819aeb Update README.md
Updated CETONI Elements showcase
2022-01-02 21:55:00 +01:00
Uwe Kindler
e6fc1c14bd Added NotepadNext to the showcase section 2021-12-22 07:47:24 +01:00
SleepProgger
6fb45c055c Removed ContentMargin in DockContainer (#387) 2021-12-20 07:46:26 +01:00
Ralph Minderhoud
535b926095 CMake: Allow version to be explicitly set without requiring git (#384)
The main CMakeLists.txt was updated to allow explicitly setting the
version for the project by setting the `ADS_VERSION` variable (e.g.
cmake .. -DADS_VERSION=1.0.0).

The default behavior is to determine the version by reading the
information from git. Adding the option to override this variable at
configure time allows the library to be built outside of its git
repository, such as when the code is vendored directly into another
project and added using `add_subdirectory`.
2021-12-13 08:36:53 +01:00
Uwe Kindler
b15bc26a63 Some small coding style fixes 2021-12-12 17:28:11 +01:00
Uwe Kindler
1d90e8e823 Merge branch 'fix_issue_380' of https://github.com/jporcher/Qt-Advanced-Docking-System into jporcher-fix_issue_380 2021-12-12 17:19:27 +01:00
Jean Porcherot
6b3027401d Fix issue #380 and add example 2021-12-09 10:11:05 +01:00
githubuser0xFFFF
e35bd65a91 Update README.md 2021-12-07 11:01:26 +01:00
githubuser0xFFFF
8888f6800b Update README.md 2021-12-07 11:00:52 +01:00
Uwe Kindler
2afe62ec77 Fixed issue #378 - Don't show empty floating containers on startup 2021-12-06 09:42:12 +01:00
jporcher
4b27af959b Merge branch 'githubuser0xFFFF:master' into master 2021-12-06 07:57:29 +01:00
Ben Hetherington
0df1a41a1d Fix potential crash when restoring container state (#381)
This fixes a crash which could occur when restoring the state of a floating container to a non-floating container.

This was because `ads::CDockContainerWidget::RestoreState()` will unconditionally assume this is floating if the XML has the `Floating` boolean set, and will dereference `floatingWidget()`. If this isn't floating, `floatingWidget()` will return `nullptr`, leading to a crash when it's subsequently used.

Fixes #379.
2021-12-03 17:01:16 +01:00
anoy
21badd592e add QFlag operators (#376) 2021-11-26 19:09:09 +01:00
Uwe Kindler
a110d53a53 Fixed crash in DockFocusController
DockWidget pointers are now wrapped into QPointer to detect deleted
DockWidgets
2021-11-18 22:10:11 +01:00
Uwe Kindler
0270993782 Fixed error caused by broken merge request 2021-11-17 07:51:14 +01:00
Uwe Kindler
2c0d899b2a Fixed pixmap warning in ElidingLabel 2021-11-17 07:50:46 +01:00
Ben Hetherington
1abe101ef7 Prevent middle-click from closing unclosable tabs (#372)
This fixes a bug whereby you could close a dock widget by middle-clicking its tab, even if its `CDockWidget::DockWidgetClosable` flag is set to `false`.
2021-11-16 06:24:39 +01:00
Uwe Kindler
0ebc170cfa Fixed doc link to issue 365 2021-10-29 10:36:09 +02:00
Uwe Kindler
ee6ddfadc3 Documented DockWidget feature flags 2021-10-29 10:32:58 +02:00
Uwe Kindler
720b5f0c72 Added Plot Juggler to the showcase section 2021-10-29 10:02:09 +02:00
Uwe Kindler
aceabd8455 Added new and noteworthy for 3.8 release 2021-10-28 10:30:49 +02:00
Uwe Kindler
ab4869a0e1 Merge branch 'master' of https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System 2021-10-22 11:47:44 +02:00
Uwe Kindler
adf5793ccd Fixed Windows Qt 5.15 build 2021-10-22 11:47:27 +02:00
Rodrigo Oliva
31e26c2c1c Fix crash when adding closed dock widgets to the manager. (#366)
Co-authored-by: Rodrigo Oliva <Rodrigo.Oliva@king.com>
2021-10-20 16:54:47 +02:00
Uwe Kindler
de05ddd203 Renamed RecreateContentsWidgetOnCloseAndOpen into DeleteContentOnClose 2021-10-14 13:57:06 +02:00
Uwe Kindler
03a8eaa44f Merge branch 'ff/editor/dev/widget_factory' of https://github.com/rolivav/Qt-Advanced-Docking-System into rolivav-ff/editor/dev/widget_factory 2021-10-14 13:18:54 +02:00
Uwe Kindler
48e79f12a7 Fixed Linux bug in DockFocusController.cpp 2021-10-11 22:05:37 +02:00
Uwe Kindler
89aa3d5251 Fixed null pointer bug in DockFocusController.cpp 2021-10-11 21:20:21 +02:00
Rodrigo Oliva
87b0596ebc Add feature to close tabs with the middle mouse button. (#360)
Co-authored-by: Rodrigo Oliva <Rodrigo.Oliva@king.com>
2021-10-11 20:33:56 +02:00
Rodrigo Oliva
a08857804a Add new flag that will delete the contained widget and recreate it from a factory when closing and opening the dock widget. 2021-10-10 21:37:16 +02:00
nmiglio
dbca6d79cf Update DockWidget.h (#364) 2021-10-08 19:40:57 +02:00
Francis Hart
a24221c002 Fix MSVC compiler warning C4275 (#361)
The CFloatingDockContainer is exported, but inherits from a base
class that is not exported. This triggers a compiler warning under
MSVC toolchain. This patch fixes the issue by exporting the
IFloatingWidget class.
2021-10-08 19:40:30 +02:00
githubuser0xFFFF
5f89dd0465 Update linux-builds.yml
Removed ubuntu 16.04 from GitHub workflow because it is not supported by GitHUb any longer
2021-10-05 06:56:51 +02:00
Lucas
0b82ff30fe Fix issue #351: https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/351 (#357) 2021-10-01 15:30:33 +02:00
Nicolas Elie
130b0de646 Python Bindings: Fix findParent that was not working as expected (#349) 2021-09-06 20:48:17 +02:00
Nicolas Elie
b5b251dffb Update Python Bindings (#348)
* Update Python bindings

* Add X11Extras to setup.py for Linux builds

* Update Python Bindings

* Update Python bindings

* Update Python Bindings
2021-08-26 13:51:09 +02:00
Rodrigo Oliva
0c44accb44 Remove deprecation warning while building with Qt 5.15. (#344)
Co-authored-by: Rodrigo Oliva <Rodrigo.Oliva@king.com>
2021-08-23 19:47:15 +02:00
Uwe Kindler
bbdf0ef29d Added test for resizing of DockAera 2021-08-03 14:59:28 +02:00
Uwe Kindler
401e8cf492 Improved initial size of floating dock widget 2021-07-30 13:50:17 +02:00
Uwe Kindler
83cdc69fc8 Merge branch 'improve_focus_highlighting' 2021-07-28 14:14:25 +02:00
Uwe Kindler
1e3af06bb0 Fixed notifyFloatingWidgetDrop() function 2021-07-28 13:44:34 +02:00
Uwe Kindler
2c8abee668 Added some build instructions for Linux about required private header files 2021-07-28 12:14:37 +02:00
Uwe Kindler
5af7492b67 Changed DockFocusController to properly handle window activation 2021-07-27 12:22:31 +02:00
Uwe Kindler
8d1465a81f Added initial support for setting focus highlighting without uisng setFocus 2021-07-25 18:12:27 +02:00
githubuser0xFFFF
5ead4684f5 Update linux-builds.yml
Added qtbase-private-dev
2021-07-17 14:52:09 +02:00
Charles Cross
fc91502162 Removes dependency on QtX11Extras for Qt6.2-beta (#331)
* Removes dependency on QtX11Extras and implements placeholder solution with QtGUI private API

* Fixes CMakeLists.txt to work with Qt5 and Qt6

* Removes all other uses of X11extras

* Renames and moves x11 methods into ads::internal

* Fixes typo

* Adds gui-private to src.pro
2021-07-17 14:49:20 +02:00
Uwe Kindler
3a99bdcfb4 Added showcase for Ramses Composer 2021-07-14 11:05:04 +02:00
Jean Porcherot
d0c100995e Fix crash when dockindock example is closed. Due to code in dockindock supposed to fix memory leaks: now leaks were fixed in CDockManager (https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/307), the code from dockindock ends up deleting objects already deleted by parent. 2021-04-27 10:05:07 +02:00
Jean Porcherot
f54e4c8ac2 Merge remote-tracking branch 'upstream/master' 2021-04-27 09:12:56 +02:00
Jean Porcherot
2c7b5982b5 Replace dockdepth1 by dockindock 2021-04-14 15:36:00 +02:00
Jean Porcherot
bac0698581 Fix compilation (include assert.h) 2021-04-09 13:36:51 +02:00
Jean Porcherot
be3180df6f Add dockdepth1 example 2021-04-07 16:51:46 +02:00
75 changed files with 2388 additions and 998 deletions

View File

@@ -6,7 +6,7 @@ jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, ubuntu-18.04, ubuntu-16.04]
os: [ubuntu-latest, ubuntu-20.04, ubuntu-18.04]
runs-on: ${{ matrix.os }}
@@ -16,7 +16,7 @@ jobs:
run: |
sudo apt-get update --fix-missing
sudo apt-get install qt5-default
sudo apt-get install libqt5x11extras5-dev
sudo apt-get install qtbase5-private-dev
- name: qmake
run: qmake
- name: make

View File

@@ -9,8 +9,6 @@ matrix:
os: linux
dist: trusty
group: stable
before_install:
- sudo apt-get -y install libqt5x11extras5-dev
addons:
apt:
sources:
@@ -20,8 +18,6 @@ matrix:
packages:
- qt55base
- qt55tools
- qt55x11extras
- libqt5x11extras5-dev
- gcc-9
- g++-9
script:
@@ -39,8 +35,6 @@ matrix:
services:
- xvfb
compiler: gcc
before_install:
- sudo apt-get -y install libqt5x11extras5-dev
addons:
apt:
sources:
@@ -50,8 +44,6 @@ matrix:
packages:
- qt514base
- qt514tools
- qt514x11extras
- libqt5x11extras5-dev
- gcc-9
- g++-9
- libc6-i386
@@ -73,8 +65,6 @@ matrix:
services:
- xvfb
compiler: gcc
before_install:
- sudo apt-get -y install libqt5x11extras5-dev
addons:
apt:
sources:
@@ -84,8 +74,6 @@ matrix:
packages:
- qt514base
- qt514tools
- qt514x11extras
- libqt5x11extras5-dev
- gcc-9
- g++-9
- libc6-i386
@@ -107,8 +95,6 @@ matrix:
services:
- xvfb
compiler: gcc
before_install:
- sudo apt-get -y install libqt5x11extras5-dev
addons:
apt:
sources:
@@ -118,8 +104,6 @@ matrix:
packages:
- qt514base
- qt514tools
- qt514x11extras
- libqt5x11extras5-dev
- gcc-9
- g++-9
- libc6-i386
@@ -147,8 +131,6 @@ matrix:
services:
- xvfb
compiler: gcc
before_install:
- sudo apt-get -y install libqt5x11extras5-dev
addons:
apt:
sources:
@@ -158,8 +140,6 @@ matrix:
packages:
- qt514base
- qt514tools
- qt514x11extras
- libqt5x11extras5-dev
- gcc-9
- g++-9
- libc6-i386

View File

@@ -1,20 +1,41 @@
cmake_minimum_required(VERSION 3.5)
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH})
include(GetGitRevisionDescription)
git_describe(GitTagVersion --tags)
string(REGEX REPLACE "^([0-9]+)\\..*" "\\1" VERSION_MAJOR "${GitTagVersion}")
string(REGEX REPLACE "^[0-9]+\\.([0-9]+).*" "\\1" VERSION_MINOR "${GitTagVersion}")
string(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" VERSION_PATCH "${GitTagVersion}")
set(VERSION_SHORT "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}")
# By default, the version information is extracted from the git index. However,
# we can override this behavior by explicitly setting ADS_VERSION and
# skipping the git checks. This is useful for cases where this project is being
# used independently of its original git repo (e.g. vendored in another project)
if(NOT ADS_VERSION)
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH})
include(GetGitRevisionDescription)
git_describe(GitTagVersion --tags)
string(REGEX REPLACE "^([0-9]+)\\..*" "\\1" VERSION_MAJOR "${GitTagVersion}")
string(REGEX REPLACE "^[0-9]+\\.([0-9]+).*" "\\1" VERSION_MINOR "${GitTagVersion}")
string(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" VERSION_PATCH "${GitTagVersion}")
set(VERSION_SHORT "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}")
else()
string(REGEX MATCHALL "[\.]" VERSION_DOT_MATCHES ${ADS_VERSION})
list(LENGTH VERSION_DOT_MATCHES VERSION_DOT_COUNT)
if(VERSION_DOT_COUNT EQUAL 2)
set(VERSION_SHORT ${ADS_VERSION})
else()
message(FATAL_ERROR "ADS_VERSION must be in major.minor.patch format, e.g. 3.8.1. Got ${ADS_VERSION}")
endif()
endif()
project(QtADS LANGUAGES CXX VERSION ${VERSION_SHORT})
option(BUILD_STATIC "Build the static library" OFF)
option(BUILD_EXAMPLES "Build the examples" ON)
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4")
set(ads_PlatformDir "x86")
else()
set(ads_PlatformDir "x64")
endif()
add_subdirectory(src)
if(BUILD_EXAMPLES)
add_subdirectory(examples)
add_subdirectory(demo)

View File

@@ -1,10 +0,0 @@
from .ads import ads
from .._version import *
import inspect
for name, member in inspect.getmembers(ads):
if not name.startswith('_'):
globals()[name] = member
del ads

View File

@@ -1,9 +0,0 @@
from .rc import *
from ._version import get_versions
__version__ = get_versions()['version']
short_version = __version__
version = __version__
full_version = __version__
git_revision = get_versions()['full-revisionid']
release = not get_versions()['dirty']
del get_versions

View File

@@ -1,520 +0,0 @@
# This file helps to compute a version number in source trees obtained from
# git-archive tarball (such as those provided by githubs download-from-tag
# feature). Distribution tarballs (built by setup.py sdist) and build
# directories (produced by setup.py build) will contain a much shorter file
# that just contains the computed version number.
# This file is released into the public domain. Generated by
# versioneer-0.18 (https://github.com/warner/python-versioneer)
"""Git implementation of _version.py."""
import errno
import os
import re
import subprocess
import sys
def get_keywords():
"""Get the keywords needed to look up the version information."""
# these strings will be replaced by git during git-archive.
# setup.py/versioneer.py will grep for the variable names, so they must
# each be defined on a line of their own. _version.py will just call
# get_keywords().
git_refnames = "$Format:%d$"
git_full = "$Format:%H$"
git_date = "$Format:%ci$"
keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
return keywords
class VersioneerConfig:
"""Container for Versioneer configuration parameters."""
def get_config():
"""Create, populate and return the VersioneerConfig() object."""
# these strings are filled in when 'setup.py versioneer' creates
# _version.py
cfg = VersioneerConfig()
cfg.VCS = "git"
cfg.style = "pep440"
cfg.tag_prefix = ""
cfg.parentdir_prefix = "None"
cfg.versionfile_source = "PyQtAds/_version.py"
cfg.verbose = False
return cfg
class NotThisMethod(Exception):
"""Exception raised if a method is not valid for the current scenario."""
LONG_VERSION_PY = {}
HANDLERS = {}
def register_vcs_handler(vcs, method): # decorator
"""Create decorator to mark a method as the handler of a VCS."""
def decorate(f):
"""Store f in HANDLERS[vcs][method]."""
if vcs not in HANDLERS:
HANDLERS[vcs] = {}
HANDLERS[vcs][method] = f
return f
return decorate
def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
env=None):
"""Call the given command(s)."""
assert isinstance(commands, list)
p = None
for c in commands:
try:
dispcmd = str([c] + args)
# remember shell=False, so use git.cmd on windows, not just git
p = subprocess.Popen([c] + args, cwd=cwd, env=env,
stdout=subprocess.PIPE,
stderr=(subprocess.PIPE if hide_stderr
else None))
break
except EnvironmentError:
e = sys.exc_info()[1]
if e.errno == errno.ENOENT:
continue
if verbose:
print("unable to run %s" % dispcmd)
print(e)
return None, None
else:
if verbose:
print("unable to find command, tried %s" % (commands,))
return None, None
stdout = p.communicate()[0].strip()
if sys.version_info[0] >= 3:
stdout = stdout.decode()
if p.returncode != 0:
if verbose:
print("unable to run %s (error)" % dispcmd)
print("stdout was %s" % stdout)
return None, p.returncode
return stdout, p.returncode
def versions_from_parentdir(parentdir_prefix, root, verbose):
"""Try to determine the version from the parent directory name.
Source tarballs conventionally unpack into a directory that includes both
the project name and a version string. We will also support searching up
two directory levels for an appropriately named parent directory
"""
rootdirs = []
for i in range(3):
dirname = os.path.basename(root)
if dirname.startswith(parentdir_prefix):
return {"version": dirname[len(parentdir_prefix):],
"full-revisionid": None,
"dirty": False, "error": None, "date": None}
else:
rootdirs.append(root)
root = os.path.dirname(root) # up a level
if verbose:
print("Tried directories %s but none started with prefix %s" %
(str(rootdirs), parentdir_prefix))
raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
@register_vcs_handler("git", "get_keywords")
def git_get_keywords(versionfile_abs):
"""Extract version information from the given file."""
# the code embedded in _version.py can just fetch the value of these
# keywords. When used from setup.py, we don't want to import _version.py,
# so we do it with a regexp instead. This function is not used from
# _version.py.
keywords = {}
try:
f = open(versionfile_abs, "r")
for line in f.readlines():
if line.strip().startswith("git_refnames ="):
mo = re.search(r'=\s*"(.*)"', line)
if mo:
keywords["refnames"] = mo.group(1)
if line.strip().startswith("git_full ="):
mo = re.search(r'=\s*"(.*)"', line)
if mo:
keywords["full"] = mo.group(1)
if line.strip().startswith("git_date ="):
mo = re.search(r'=\s*"(.*)"', line)
if mo:
keywords["date"] = mo.group(1)
f.close()
except EnvironmentError:
pass
return keywords
@register_vcs_handler("git", "keywords")
def git_versions_from_keywords(keywords, tag_prefix, verbose):
"""Get version information from git keywords."""
if not keywords:
raise NotThisMethod("no keywords at all, weird")
date = keywords.get("date")
if date is not None:
# git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant
# datestamp. However we prefer "%ci" (which expands to an "ISO-8601
# -like" string, which we must then edit to make compliant), because
# it's been around since git-1.5.3, and it's too difficult to
# discover which version we're using, or to work around using an
# older one.
date = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
refnames = keywords["refnames"].strip()
if refnames.startswith("$Format"):
if verbose:
print("keywords are unexpanded, not using")
raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
refs = set([r.strip() for r in refnames.strip("()").split(",")])
# starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
# just "foo-1.0". If we see a "tag: " prefix, prefer those.
TAG = "tag: "
tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
if not tags:
# Either we're using git < 1.8.3, or there really are no tags. We use
# a heuristic: assume all version tags have a digit. The old git %d
# expansion behaves like git log --decorate=short and strips out the
# refs/heads/ and refs/tags/ prefixes that would let us distinguish
# between branches and tags. By ignoring refnames without digits, we
# filter out many common branch names like "release" and
# "stabilization", as well as "HEAD" and "master".
tags = set([r for r in refs if re.search(r'\d', r)])
if verbose:
print("discarding '%s', no digits" % ",".join(refs - tags))
if verbose:
print("likely tags: %s" % ",".join(sorted(tags)))
for ref in sorted(tags):
# sorting will prefer e.g. "2.0" over "2.0rc1"
if ref.startswith(tag_prefix):
r = ref[len(tag_prefix):]
if verbose:
print("picking %s" % r)
return {"version": r,
"full-revisionid": keywords["full"].strip(),
"dirty": False, "error": None,
"date": date}
# no suitable tags, so version is "0+unknown", but full hex is still there
if verbose:
print("no suitable tags, using unknown + full revision id")
return {"version": "0+unknown",
"full-revisionid": keywords["full"].strip(),
"dirty": False, "error": "no suitable tags", "date": None}
@register_vcs_handler("git", "pieces_from_vcs")
def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
"""Get version from 'git describe' in the root of the source tree.
This only gets called if the git-archive 'subst' keywords were *not*
expanded, and _version.py hasn't already been rewritten with a short
version string, meaning we're inside a checked out source tree.
"""
GITS = ["git"]
if sys.platform == "win32":
GITS = ["git.cmd", "git.exe"]
out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root,
hide_stderr=True)
if rc != 0:
if verbose:
print("Directory %s not under git control" % root)
raise NotThisMethod("'git rev-parse --git-dir' returned error")
# if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
# if there isn't one, this yields HEX[-dirty] (no NUM)
describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty",
"--always", "--long",
"--match", "%s*" % tag_prefix],
cwd=root)
# --long was added in git-1.5.5
if describe_out is None:
raise NotThisMethod("'git describe' failed")
describe_out = describe_out.strip()
full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
if full_out is None:
raise NotThisMethod("'git rev-parse' failed")
full_out = full_out.strip()
pieces = {}
pieces["long"] = full_out
pieces["short"] = full_out[:7] # maybe improved later
pieces["error"] = None
# parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
# TAG might have hyphens.
git_describe = describe_out
# look for -dirty suffix
dirty = git_describe.endswith("-dirty")
pieces["dirty"] = dirty
if dirty:
git_describe = git_describe[:git_describe.rindex("-dirty")]
# now we have TAG-NUM-gHEX or HEX
if "-" in git_describe:
# TAG-NUM-gHEX
mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
if not mo:
# unparseable. Maybe git-describe is misbehaving?
pieces["error"] = ("unable to parse git-describe output: '%s'"
% describe_out)
return pieces
# tag
full_tag = mo.group(1)
if not full_tag.startswith(tag_prefix):
if verbose:
fmt = "tag '%s' doesn't start with prefix '%s'"
print(fmt % (full_tag, tag_prefix))
pieces["error"] = ("tag '%s' doesn't start with prefix '%s'"
% (full_tag, tag_prefix))
return pieces
pieces["closest-tag"] = full_tag[len(tag_prefix):]
# distance: number of commits since tag
pieces["distance"] = int(mo.group(2))
# commit: short hex revision ID
pieces["short"] = mo.group(3)
else:
# HEX: no tags
pieces["closest-tag"] = None
count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"],
cwd=root)
pieces["distance"] = int(count_out) # total number of commits
# commit date: see ISO-8601 comment in git_versions_from_keywords()
date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"],
cwd=root)[0].strip()
pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
return pieces
def plus_or_dot(pieces):
"""Return a + if we don't already have one, else return a ."""
if "+" in pieces.get("closest-tag", ""):
return "."
return "+"
def render_pep440(pieces):
"""Build up version string, with post-release "local version identifier".
Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty
Exceptions:
1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
"""
if pieces["closest-tag"]:
rendered = pieces["closest-tag"]
if pieces["distance"] or pieces["dirty"]:
rendered += plus_or_dot(pieces)
rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
if pieces["dirty"]:
rendered += ".dirty"
else:
# exception #1
rendered = "0+untagged.%d.g%s" % (pieces["distance"],
pieces["short"])
if pieces["dirty"]:
rendered += ".dirty"
return rendered
def render_pep440_pre(pieces):
"""TAG[.post.devDISTANCE] -- No -dirty.
Exceptions:
1: no tags. 0.post.devDISTANCE
"""
if pieces["closest-tag"]:
rendered = pieces["closest-tag"]
if pieces["distance"]:
rendered += ".post.dev%d" % pieces["distance"]
else:
# exception #1
rendered = "0.post.dev%d" % pieces["distance"]
return rendered
def render_pep440_post(pieces):
"""TAG[.postDISTANCE[.dev0]+gHEX] .
The ".dev0" means dirty. Note that .dev0 sorts backwards
(a dirty tree will appear "older" than the corresponding clean one),
but you shouldn't be releasing software with -dirty anyways.
Exceptions:
1: no tags. 0.postDISTANCE[.dev0]
"""
if pieces["closest-tag"]:
rendered = pieces["closest-tag"]
if pieces["distance"] or pieces["dirty"]:
rendered += ".post%d" % pieces["distance"]
if pieces["dirty"]:
rendered += ".dev0"
rendered += plus_or_dot(pieces)
rendered += "g%s" % pieces["short"]
else:
# exception #1
rendered = "0.post%d" % pieces["distance"]
if pieces["dirty"]:
rendered += ".dev0"
rendered += "+g%s" % pieces["short"]
return rendered
def render_pep440_old(pieces):
"""TAG[.postDISTANCE[.dev0]] .
The ".dev0" means dirty.
Exceptions:
1: no tags. 0.postDISTANCE[.dev0]
"""
if pieces["closest-tag"]:
rendered = pieces["closest-tag"]
if pieces["distance"] or pieces["dirty"]:
rendered += ".post%d" % pieces["distance"]
if pieces["dirty"]:
rendered += ".dev0"
else:
# exception #1
rendered = "0.post%d" % pieces["distance"]
if pieces["dirty"]:
rendered += ".dev0"
return rendered
def render_git_describe(pieces):
"""TAG[-DISTANCE-gHEX][-dirty].
Like 'git describe --tags --dirty --always'.
Exceptions:
1: no tags. HEX[-dirty] (note: no 'g' prefix)
"""
if pieces["closest-tag"]:
rendered = pieces["closest-tag"]
if pieces["distance"]:
rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
else:
# exception #1
rendered = pieces["short"]
if pieces["dirty"]:
rendered += "-dirty"
return rendered
def render_git_describe_long(pieces):
"""TAG-DISTANCE-gHEX[-dirty].
Like 'git describe --tags --dirty --always -long'.
The distance/hash is unconditional.
Exceptions:
1: no tags. HEX[-dirty] (note: no 'g' prefix)
"""
if pieces["closest-tag"]:
rendered = pieces["closest-tag"]
rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
else:
# exception #1
rendered = pieces["short"]
if pieces["dirty"]:
rendered += "-dirty"
return rendered
def render(pieces, style):
"""Render the given version pieces into the requested style."""
if pieces["error"]:
return {"version": "unknown",
"full-revisionid": pieces.get("long"),
"dirty": None,
"error": pieces["error"],
"date": None}
if not style or style == "default":
style = "pep440" # the default
if style == "pep440":
rendered = render_pep440(pieces)
elif style == "pep440-pre":
rendered = render_pep440_pre(pieces)
elif style == "pep440-post":
rendered = render_pep440_post(pieces)
elif style == "pep440-old":
rendered = render_pep440_old(pieces)
elif style == "git-describe":
rendered = render_git_describe(pieces)
elif style == "git-describe-long":
rendered = render_git_describe_long(pieces)
else:
raise ValueError("unknown style '%s'" % style)
return {"version": rendered, "full-revisionid": pieces["long"],
"dirty": pieces["dirty"], "error": None,
"date": pieces.get("date")}
def get_versions():
"""Get version information or return default if unable to do so."""
# I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
# __file__, we can work backwards from there to the root. Some
# py2exe/bbfreeze/non-CPython implementations don't do __file__, in which
# case we can only use expanded keywords.
cfg = get_config()
verbose = cfg.verbose
try:
return git_versions_from_keywords(get_keywords(), cfg.tag_prefix,
verbose)
except NotThisMethod:
pass
try:
root = os.path.realpath(__file__)
# versionfile_source is the relative path from the top of the source
# tree (where the .git directory might live) to this file. Invert
# this to find the root from __file__.
for i in cfg.versionfile_source.split('/'):
root = os.path.dirname(root)
except NameError:
return {"version": "0+unknown", "full-revisionid": None,
"dirty": None,
"error": "unable to find root of source tree",
"date": None}
try:
pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose)
return render(pieces, cfg.style)
except NotThisMethod:
pass
try:
if cfg.parentdir_prefix:
return versions_from_parentdir(cfg.parentdir_prefix, root, verbose)
except NotThisMethod:
pass
return {"version": "0+unknown", "full-revisionid": None,
"dirty": None,
"error": "unable to compute version", "date": None}

113
README.md
View File

@@ -1,10 +1,14 @@
# Advanced Docking System for Qt
![ukraine](doc/ukraine.jpg)
![logo](doc/ads_logo.svg)
------------------
[![Build status](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/workflows/linux-builds/badge.svg)](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/actions?query=workflow%3Alinux-builds)
[![Build status](https://ci.appveyor.com/api/projects/status/qcfb3cy932jw9mpy/branch/master?svg=true)](https://ci.appveyor.com/project/githubuser0xFFFF/qt-advanced-docking-system/branch/master)
[![License: LGPL v2.1](https://img.shields.io/badge/License-LGPL%20v2.1-blue.svg)](gnu-lgpl-v2.1.md)
[What's new](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/releases/latest)
[What's new](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/releases/latest)
[Documentation](doc/user-guide.md)
Qt Advanced Docking System lets you create customizable layouts using a full
@@ -15,13 +19,20 @@ integrated development environments (IDEs) such as Visual Studio.
## New and Noteworthy
The [release 3.7](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/releases/tag/3.7.0)
The [release 3.8](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/releases/latest)
adds the following features:
- option to close tabs with the middle mouse button
- `DeleteContentOnClose` flag for dynamic deletion and creation of dock widget
content
The [release 3.7](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/releases/tag/3.7.2)
adds the following features:
- support for **Qt6.**
- support for [empty dock area](doc/user-guide.md#empty-dock-area)
The [release 3.6](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/releases/tag/3.6.0)
The [release 3.6](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/releases/tag/3.6.3)
adds some nice new features:
- support for [central widget](doc/user-guide.md#central-widget) concept
@@ -77,10 +88,15 @@ know it from Visual Studio.
- [Showcase](#showcase)
- [Qt Creator IDE](#qt-creator-ide)
- [Qt Design Studio](#qt-design-studio)
- [QmixElements](#qmixelements)
- [CETONI Elements](#cetoni-elements)
- [ezEditor](#ezeditor)
- [D-Tect X](#d-tect-x)
- [HiveWE](#hivewe)
- [Ramses Composer](#ramses-composer)
- [Plot Juggler](#plot-juggler)
- [Notepad Next](#notepad-next)
- [MetGem](#metgem)
- [PRE Workbench](#pre-workbench)
### Docking everywhere - no central widget
@@ -219,7 +235,13 @@ Screenshot Ubuntu:
## Build
Open the `ads.pro` with QtCreator and start the build, that's it.
The Linux build requires private header files. Make sure that they are installed:
```bash
sudo apt install qtbase5-private-dev
```
Open the `ads.pro` file with QtCreator and start the build, that's it.
You can run the demo project and test it yourself.
## Getting started / Example
@@ -266,8 +288,9 @@ MainWindow::MainWindow(QWidget *parent) :
{
ui->setupUi(this);
// Create the dock manager. Because the parent parameter is a QMainWindow
// the dock manager registers itself as the central widget.
// Create the dock manager after the ui is setup. Because the
// parent parameter is a QMainWindow the dock manager registers
// itself as the central widget as such the ui must be set up first.
m_DockManager = new ads::CDockManager(this);
// Create example content label - this can be any application specific
@@ -356,13 +379,12 @@ Taken from the [Qt Blog](https://www.qt.io/blog/qt-design-studio-1.5-beta-releas
[![Qt Design Studio](doc/showcase_qt_design_studio_video.png)](https://youtu.be/za9KBWcFXEw?t=84)
### [QmixElements](https://www.cetoni.com/products/qmixelements/)
### [CETONI Elements](https://www.cetoni.com/products/qmixelements/)
The QmixElements software from [CETONI](https://www.cetoni.com) is a comprehensive,
plugin-based and modular laboratory automation software for controlling CETONI devices using a joint graphical user interface. The software features a powerful script system to automate processes. This [blog post](https://www.cetoni.com/blog/qmixelements-advanced-docking-system/) gives a nice overview about the use of the Qt
Advanced Docking System in the QmixElements sofware.
The CETONI Elements software from [CETONI](https://www.cetoni.com) is a comprehensive,
plugin-based and modular laboratory automation software for controlling CETONI devices using a joint graphical user interface. The software features a powerful script system to automate processes. The software uses the advanced docking system to give the user the freedom to arrange all the views and windows that are provided by the various plugins.
![QmixElements](doc/showcase_qmix_elements.png)
![CETONI_Elements](doc/showcase_qmix_elements.png)
### [ezEditor](https://github.com/ezEngine/ezEngine)
@@ -391,3 +413,68 @@ of the open editor windows.
[learn more...](https://github.com/stijnherfst/HiveWE)
![HiveWE](doc/showcase_hivewe.png)
### [Ramses Composer](https://github.com/GENIVI/ramses-composer)
Ramses Composer is the authoring tool for the open source [RAMSES](https://github.com/GENIVI/ramses)
rendering ecosystem.
Ramses is a low-level rendering engine which is optimized for embedded hardware
mobile devices, automotive ECUs, IoT electronics. Ramses was initially developed
at the BMW Group and open-sourced in 2018 as part of a collaboration initiative
with the Genivi Alliance. It is an important part of the BMW infotainment cluster
and digital portfolio.
[learn more...](https://github.com/GENIVI/ramses-composer)
![RamsesComposer](doc/showcase_ramses_composer.png)
### [Plot Juggler](https://github.com/facontidavide/PlotJuggler)
PlotJuggler is a fast, powerful and intuitive tool to visualize time series.
It makes it easy to visualize data but also to analyze it. You can manipulate
your time series using a simple and extendable Transform Editor. Some of the
highlights are:
- Simple Drag & Drop user interface.
- Load data from file.
- Connect to live streaming of data.
- Save the visualization layout and configurations to re-use them later.
- Fast OpenGL visualization.
- Can handle thousands of timeseries and millions of data points.
- Transform your data using a simple editor: derivative, moving average, integral, etc…
- PlotJuggler can be easily extended using plugins.
[read more...](https://github.com/facontidavide/PlotJuggler)
[![Plot Juggler](doc/showcase_plot_juggler.png)](https://vimeo.com/480588113#t=46s)
### [Notepad Next](https://github.com/dail8859/NotepadNext)
Notepad Next is a cross-platform reimplementation of Notepad++ that uses the
Advanced Docking System to arrange the open source files on the screen.
[read more...](https://github.com/dail8859/NotepadNext)
![NotepadNext](doc/showcase_notepad_next.png)
### [MetGem](https://metgem.github.io/)
MetGem is an open-source software for tandem mass-spectrometry data visualization.
It's key features are standalone molecular networking and t-SNE based projections.
MetGem uses the Qt-Advanced-Docking-System to manage docks and to create independent
molecular network views.
[read more...](https://metgem.github.io/)
![MetGem](doc/showcase_metgem.png)
### [PRE Workbench](https://luelista.github.io/pre_workbench/)
Protocol Reverse Engineering Workbench is a software to support researchers in reverse engineering protocols and documenting the results. It supports various sources to import protocol traffic from, helps the discovery process by displaying different views and heuristic-based highlighting on data, and aids in documenting and sharing findings.
PRE Workbench is a Python software and uses the ADS PyQt integration.
[read more...](https://luelista.github.io/pre_workbench/)
[![PRE Workbench](doc/showcase_pre_workbench.png)](https://youtu.be/U3op5UreV1Q)

11
ads.pri
View File

@@ -1,6 +1,14 @@
CONFIG(debug, debug|release){
win32 {
win32-g++ {
versionAtLeast(QT_VERSION, 5.15.0) {
LIBS += -lqtadvanceddocking
}
else {
LIBS += -lqtadvanceddockingd
}
}
else:msvc {
LIBS += -lqtadvanceddockingd
}
else:mac {
@@ -17,5 +25,4 @@ else{
unix:!macx {
LIBS += -lxcb
QT += x11extras
}

View File

@@ -2,7 +2,4 @@ include(CMakeFindDependencyMacro)
find_dependency(Qt5Core ${REQUIRED_QT_VERSION} REQUIRED)
find_dependency(Qt5Gui ${REQUIRED_QT_VERSION} REQUIRED)
find_dependency(Qt5Widgets ${REQUIRED_QT_VERSION} REQUIRED)
if(UNIX AND NOT APPLE)
find_dependency(Qt5X11Extras ${REQUIRED_QT_VERSION} REQUIRED)
endif()
include("${CMAKE_CURRENT_LIST_DIR}/adsTargets.cmake")

View File

@@ -79,7 +79,7 @@
#include "FloatingDockContainer.h"
#include "DockComponentsFactory.h"
#include "StatusDialog.h"
#include "DockSplitter.h"
/**
@@ -417,6 +417,23 @@ void MainWindowPrivate::createContent()
DockWidget = createCalendarDockWidget();
DockWidget->setTabToolTip(QString("Tab ToolTip\nHodie est dies magna"));
auto DockArea = DockManager->addDockWidget(ads::CenterDockWidgetArea, DockWidget, TopDockArea);
// Now we create a action to test resizing of DockArea widget
auto Action = ui.menuTests->addAction(QString("Resize %1").arg(DockWidget->windowTitle()));
QObject::connect(Action, &QAction::triggered, [DockArea]()
{
// Resizing only works, if the Splitter is visible and has a valid
// sizes
auto Splitter = ads::internal::findParent<ads::CDockSplitter*>(DockArea);
if (!Splitter)
{
return;
}
// We change the sizes of the splitter that contains the Calendar 1 widget
// to resize the dock widget
int Width = Splitter->width();
Splitter->setSizes({Width * 2/3, Width * 1/3});
});
DockWidget->setWindowTitle(QString("My " + DockWidget->windowTitle()));
// Now we add a custom button to the dock area title bar that will create
// new editor widgets when clicked
@@ -441,15 +458,40 @@ void MainWindowPrivate::createContent()
DockManager->addDockWidget(ads::TopDockWidgetArea, createLongTextLabelDockWidget(), RighDockArea);
auto BottomDockArea = DockManager->addDockWidget(ads::BottomDockWidgetArea, createLongTextLabelDockWidget(), RighDockArea);
DockManager->addDockWidget(ads::CenterDockWidgetArea, createLongTextLabelDockWidget(), RighDockArea);
DockManager->addDockWidget(ads::CenterDockWidgetArea, createLongTextLabelDockWidget(), BottomDockArea);
auto LabelDockWidget = createLongTextLabelDockWidget();
std::cout << "DockWidget " << LabelDockWidget->objectName().toStdString() << std::endl;
DockManager->addDockWidget(ads::CenterDockWidgetArea, LabelDockWidget, BottomDockArea);
auto Action = ui.menuTests->addAction(QString("Set %1 Floating").arg(DockWidget->windowTitle()));
// Tests CustomCloseHandling without DeleteOnClose
LabelDockWidget->setFeature(ads::CDockWidget::CustomCloseHandling, true);
QObject::connect(LabelDockWidget, &ads::CDockWidget::closeRequested, [LabelDockWidget, this]()
{
int Result = QMessageBox::question(_this, "Custom Close Request",
"Do you really want to close this dock widget?");
if (QMessageBox::Yes == Result)
{
LabelDockWidget->closeDockWidget();
}
});
Action = ui.menuTests->addAction(QString("Set %1 Floating").arg(DockWidget->windowTitle()));
DockWidget->connect(Action, SIGNAL(triggered()), SLOT(setFloating()));
Action = ui.menuTests->addAction(QString("Set %1 As Current Tab").arg(DockWidget->windowTitle()));
DockWidget->connect(Action, SIGNAL(triggered()), SLOT(setAsCurrentTab()));
Action = ui.menuTests->addAction(QString("Raise %1").arg(DockWidget->windowTitle()));
DockWidget->connect(Action, SIGNAL(triggered()), SLOT(raise()));
// Test hidden floating dock widget
DockWidget = createLongTextLabelDockWidget();
DockManager->addDockWidgetFloating(DockWidget);
DockWidget->toggleView(false);
// Test visible floating dock widget
DockWidget = createCalendarDockWidget();
DockManager->addDockWidgetFloating(DockWidget);
DockWidget->setWindowTitle(QString("My " + DockWidget->windowTitle()));
#ifdef Q_OS_WIN
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
if (!ads::CDockManager::testConfigFlag(ads::CDockManager::OpaqueUndocking))
@@ -613,6 +655,9 @@ CMainWindow::CMainWindow(QWidget *parent) :
// uncomment if you would like to enable an equal distribution of the
// available size of a splitter to all contained dock widgets
// CDockManager::setConfigFlag(CDockManager::EqualSplitOnInsertion, true);
// uncomment if you would like to close tabs with the middle mouse button, web browser style
// CDockManager::setConfigFlag(CDockManager::MiddleMouseButtonClosesTab, true);
// Now create the dock manager and its content
d->DockManager = new CDockManager(this);

View File

@@ -215,6 +215,17 @@ class MainWindow(MainWindowUI, MainWindowBase):
dock_widget = self.create_calendar_dock_widget()
dock_widget.setTabToolTip("Tab ToolTip\nHodie est dies magna")
dock_area = self.dock_manager.addDockWidget(QtAds.CenterDockWidgetArea, dock_widget, top_dock_area)
# Now we create a action to test resizing of DockArea widget
action = self.menuTests.addAction("Resize {}".format(dock_widget.windowTitle()))
def action_triggered():
splitter = QtAds.internal.findParent(QtAds.CDockSplitter, dock_area)
if not splitter:
return
# We change the sizes of the splitter that contains the Calendar 1 widget
# to resize the dock widget
width = splitter.width()
splitter.setSizes([width * 2/3, width * 1/3])
action.triggered.connect(action_triggered)
# Now we add a custom button to the dock area title bar that will create
# new editor widgets when clicked

View File

@@ -1,123 +1,77 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
viewBox="0,0,1024,1024"
id="svg1145"
viewBox="0 0 1023.99 1023.99"
id="svg26"
sodipodi:docname="ads_icon.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
inkscape:export-filename="C:\CodingXP\cetoni_projects\QtAdvancedDockingSystem\doc\ads_icon_256.png"
inkscape:export-xdpi="24"
inkscape:export-ydpi="24">
<metadata
id="metadata1151">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
width="1023.99"
height="1023.99"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1149" />
id="defs30" />
<sodipodi:namedview
id="namedview28"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:zoom="0.12999302"
inkscape:cx="3277.099"
inkscape:cy="-257.70614"
inkscape:window-width="1920"
inkscape:window-height="1017"
id="namedview1147"
showgrid="false"
inkscape:zoom="0.32593202"
inkscape:cx="256.13402"
inkscape:cy="235.82602"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg1145" />
inkscape:current-layer="svg26"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<desc
id="desc1129">window_size icon - Licensed under Iconfu Standard License v1.0 (https://www.iconfu.com/iconfu_standard_license) - Incors GmbH</desc>
<path
id="path1797-1"
visibility="hidden"
d="M 71.53142,291.89723 H 950.1828 l 0.6743,0.6742 v 586.08 l -0.6743,0.6743 H 71.53142 l -0.6743,-0.6743 v -586.08 z"
inkscape:connector-curvature="0"
style="visibility:hidden;mix-blend-mode:normal;fill:#ffffff;fill-rule:nonzero;stroke-width:0.99999988;fill-opacity:1" />
<rect
transform="rotate(-90)"
y="412.13489"
x="-911.34436"
height="560.90375"
width="327.70862"
id="rect1739-8"
style="opacity:1;fill:#ffd292;fill-opacity:1;stroke:none;stroke-width:37.79526901;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke" />
<path
id="path1799"
d="M 988.40121,578.11175 V 651.2546 H 409.96541 V 578.11175 Z"
inkscape:connector-curvature="0"
style="mix-blend-mode:normal;fill:#546e7a;fill-rule:nonzero;stroke-width:0.99999988" />
<rect
style="opacity:1;fill:#b3c3cb;fill-opacity:1;stroke:none;stroke-width:37.79526901;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
id="rect1739-6-1"
width="655.41724"
height="338.99207"
x="-914.85529"
y="38.243896"
transform="rotate(-90)" />
<path
id="path1801-7"
d="m 73.1429,73.14287 h 877.7142 c 40.2857,0 73.1429,32.86857 73.1429,73.14285 V 914.2857 c 0,20.12571 -16.4458,36.57142 -36.5715,36.57142 H 36.5714 C 16.4343,950.85712 0,934.42284 0,914.2857 V 146.28572 C 0,106.00001 32.8571,73.14287 73.1429,73.14287 Z m 0,219.42856 V 877.71427 H 950.8571 V 292.57143 Z"
inkscape:connector-curvature="0"
style="mix-blend-mode:normal;fill:#546e7a;fill-rule:nonzero;stroke-width:0.99999988" />
<path
id="path1799-6"
d="M 359.8801,276.79402 H 433.023 V 886.78767 H 359.8801 Z"
inkscape:connector-curvature="0"
style="mix-blend-mode:normal;fill:#546e7a;fill-rule:nonzero;stroke-width:0.99999988" />
<circle
r="36.81749"
cy="169.78555"
cx="920.66248"
id="path1917-42"
style="opacity:1;fill:#ffa726;fill-opacity:1;stroke:none;stroke-width:63.79999924;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" />
<circle
r="36.81749"
cy="169.78555"
cx="817.22302"
id="path1917-4-6"
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:63.79999924;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" />
<path
id="path1799-8"
d="m 615.67076,292.13464 v 73.14285 H 433.023 v -73.14285 z"
inkscape:connector-curvature="0"
style="mix-blend-mode:normal;fill:#95abb6;fill-rule:nonzero;stroke-width:0.99999994;fill-opacity:1" />
<path
id="path1799-8-1"
d="m 798.31852,292.13464 v 73.14285 H 615.67076 v -73.14285 z"
inkscape:connector-curvature="0"
style="mix-blend-mode:normal;fill:#c7d4d9;fill-rule:nonzero;stroke-width:0.99999994;fill-opacity:1" />
<path
id="path1799-8-7"
d="m 255.79066,292.57143 v 73.14285 H 73.1429 v -73.14285 z"
inkscape:connector-curvature="0"
style="mix-blend-mode:normal;fill:#dfe5e9;fill-opacity:1;fill-rule:nonzero;stroke-width:0.99999994" />
<path
id="path1799-8-7-6"
d="m 616.34083,651.15739 v 73.14285 H 433.69307 v -73.14285 z"
inkscape:connector-curvature="0"
style="mix-blend-mode:normal;fill:#ffba56;fill-opacity:1;fill-rule:nonzero;stroke-width:0.99999994" />
id="desc2">electric_iron icon - Licensed under Iconfu Standard License v1.0 (https://www.iconfu.com/iconfu_standard_license) - Incors GmbH</desc>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none"
x="1251.1022"
y="1305.4956"
id="text9788"><tspan
sodipodi:role="line"
id="tspan9786"
x="1251.1022"
y="1305.4956" /></text>
<g
id="g94691"
transform="translate(581.23034,1750.5233)">
<path
d="m 191.63966,-726.53328 h -521.75 c -138.69,0 -251.12,-112.43001 -251.12,-251.12 v -521.75002 c 0,-138.69 112.43,-251.12 251.12,-251.12 h 521.75 c 138.69,0 251.12,112.43 251.12,251.12 v 521.75002 c 0,138.68999 -112.43,251.12 -251.12,251.12 z"
fill="#707070"
id="path4-5"
style="mix-blend-mode:normal;fill:#e0e0e0;fill-opacity:1;fill-rule:nonzero" />
<path
id="path927"
style="mix-blend-mode:normal;fill:#009ddd;fill-opacity:1;fill-rule:nonzero"
d="m -175.90039,-1515.8633 v 256 h 469.3301 v -256 z" />
<path
id="path16513"
style="mix-blend-mode:normal;fill:#ff9833;fill-opacity:1;fill-rule:nonzero"
d="m 80.099609,-1217.1934 v 256.00004 H 293.42969 v -256.00004 z" />
<path
id="path16513-5"
style="mix-blend-mode:normal;fill:#accb01;fill-opacity:1;fill-rule:nonzero"
d="m -175.90039,-1217.1933 v 256 H 37.42969 v -256 z" />
<path
id="path24788"
style="mix-blend-mode:normal;fill:#0083c3;fill-opacity:1;fill-rule:nonzero"
d="m -431.90039,-1515.8633 v 554.66994 h 213.33008 v -554.66994 z" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

88
doc/ads_logo.svg Normal file
View File

@@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
viewBox="0 0 6907.3028 1023.99"
id="svg26"
sodipodi:docname="ads_logo.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
width="6907.3027"
height="1023.99"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs30" />
<sodipodi:namedview
id="namedview28"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:zoom="0.12999302"
inkscape:cx="3277.099"
inkscape:cy="-257.70614"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg26"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<desc
id="desc2">electric_iron icon - Licensed under Iconfu Standard License v1.0 (https://www.iconfu.com/iconfu_standard_license) - Incors GmbH</desc>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none"
x="1251.1022"
y="1305.4956"
id="text9788"><tspan
sodipodi:role="line"
id="tspan9786"
x="1251.1022"
y="1305.4956" /></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:628.003px;line-height:1.25;font-family:sans-serif;fill:#8f918f;fill-opacity:1;stroke:none;stroke-width:1"
x="1178.9221"
y="718.37329"
id="text15608"><tspan
sodipodi:role="line"
id="tspan15606"
x="1178.9221"
y="718.37329"
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:628.003px;font-family:'Segoe UI';-inkscape-font-specification:'Segoe UI Light';fill:#8f918f;fill-opacity:1;stroke-width:1">Qt Advanced Docking</tspan></text>
<g
id="g94691"
transform="translate(581.23034,1750.5233)">
<path
d="m 191.63966,-726.53328 h -521.75 c -138.69,0 -251.12,-112.43001 -251.12,-251.12 v -521.75002 c 0,-138.69 112.43,-251.12 251.12,-251.12 h 521.75 c 138.69,0 251.12,112.43 251.12,251.12 v 521.75002 c 0,138.68999 -112.43,251.12 -251.12,251.12 z"
fill="#707070"
id="path4-5"
style="mix-blend-mode:normal;fill:#e0e0e0;fill-opacity:1;fill-rule:nonzero" />
<path
id="path927"
style="mix-blend-mode:normal;fill:#009ddd;fill-opacity:1;fill-rule:nonzero"
d="m -175.90039,-1515.8633 v 256 h 469.3301 v -256 z" />
<path
id="path16513"
style="mix-blend-mode:normal;fill:#ff9833;fill-opacity:1;fill-rule:nonzero"
d="m 80.099609,-1217.1934 v 256.00004 H 293.42969 v -256.00004 z" />
<path
id="path16513-5"
style="mix-blend-mode:normal;fill:#accb01;fill-opacity:1;fill-rule:nonzero"
d="m -175.90039,-1217.1933 v 256 H 37.42969 v -256 z" />
<path
id="path24788"
style="mix-blend-mode:normal;fill:#0083c3;fill-opacity:1;fill-rule:nonzero"
d="m -431.90039,-1515.8633 v 554.66994 h 213.33008 v -554.66994 z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -12,20 +12,6 @@ styles as much as possible.
## Features
### Overview
- [Features](#features)
- [Overview](#overview)
- [Docking everywhere - no central widget](#docking-everywhere---no-central-widget)
- [Docking inside floating windows](#docking-inside-floating-windows)
- [Grouped dragging](#grouped-dragging)
- [Perspectives for fast switching of the complete main window layout](#perspectives-for-fast-switching-of-the-complete-main-window-layout)
- [Opaque and non-opaque splitter resizing](#opaque-and-non-opaque-splitter-resizing)
- [Opaque and non-opaque undocking](#opaque-and-non-opaque-undocking)
- [Tab-menu for easy handling of many tabbed dock widgets](#tab-menu-for-easy-handling-of-many-tabbed-dock-widgets)
- [Many different ways to detach dock widgets](#many-different-ways-to-detach-dock-widgets)
- [Supports deletion of dynamically created dock widgets](#supports-deletion-of-dynamically-created-dock-widgets)
### Docking everywhere - no central widget
There is no central widget like in the Qt docking system. You can dock on every
@@ -106,3 +92,10 @@ You can detach dock widgets and also dock areas in the following ways:
### Supports deletion of dynamically created dock widgets
Normally clicking the close button of a dock widget will just hide the widget and the user can show it again using the toggleView() action of the dock widget. This is meant for user interfaces with a static amount of widgets. But the advanced docking system also supports dynamic dock widgets that will get deleted on close. If you set the dock widget flag `DockWidgetDeleteOnClose` for a certain dock widget, then it will be deleted as soon as you close this dock widget. This enables the implementation of user interfaces with dynamically created editors, like in word processing applications or source code development tools.
### Python PyQt5 Bindings
![Python Logo](https://raw.githubusercontent.com/githubuser0xFFFF/Qt-Advanced-Docking-System/master/doc/python_logo.png)
The Advanced Docking System comes with a complete Python integration based on
PyQt5 bindings. The package is available via [conda-forge](https://github.com/conda-forge/pyqtads-feedstock).

View File

@@ -4,21 +4,21 @@
"extensionType": [
"library"
],
"version": "3.0.0",
"version": "3.8.2",
"vendor": {
"name": "githubuser0xFFFF",
"url": "https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System"
},
"contact": "https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues",
"icon": "https://raw.githubusercontent.com/githubuser0xFFFF/Qt-Advanced-Docking-System/master/doc/ads_icon_512.png",
"icon": "https://raw.githubusercontent.com/githubuser0xFFFF/Qt-Advanced-Docking-System/master/doc/ads_icon.svg",
"licenses": [
{ "licenseType": "LGPLv2.1",
{ "licenseType": "LGPL-2.1-only",
"licenseUrl": "https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html" }
],
"created": "2017-03-30",
"lastUpdate": "2020-01-16",
"lastUpdate": "2022-03-02",
"platforms": [
"Windows 7-10", "Kubuntu 18.04", "Kubuntu 19.10", "Ubuntu 19.10"
"Windows 7-11", "Kubuntu 18.04", "Kubuntu 19.10", "Ubuntu 19.10", "Ubuntu 20.04"
],
"qtVersions": [
"5.5.1 or newer"

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

BIN
doc/showcase_metgem.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 KiB

BIN
doc/ukraine.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

View File

@@ -27,6 +27,17 @@
- [`EqualSplitOnInsertion`](#equalsplitoninsertion)
- [`FloatingContainerForceNativeTitleBar` (Linux only)](#floatingcontainerforcenativetitlebar-linux-only)
- [`FloatingContainerForceQWidgetTitleBar` (Linux only)](#floatingcontainerforceqwidgettitlebar-linux-only)
- [`MiddleMouseButtonClosesTab`](#middlemousebuttonclosestab)
- [DockWidget Feature Flags](#dockwidget-feature-flags)
- [`DockWidgetClosable`](#dockwidgetclosable)
- [`DockWidgetMovable`](#dockwidgetmovable)
- [`DockWidgetFloatable`](#dockwidgetfloatable)
- [`DockWidgetDeleteOnClose`](#dockwidgetdeleteonclose)
- [`CustomCloseHandling`](#customclosehandling)
- [`DockWidgetFocusable`](#dockwidgetfocusable)
- [`DockWidgetForceCloseWithArea`](#dockwidgetforceclosewitharea)
- [`NoTab`](#notab)
- [`DeleteContentOnClose`](#deletecontentonclose)
- [Central Widget](#central-widget)
- [Empty Dock Area](#empty-dock-area)
- [Custom Close Handling](#custom-close-handling)
@@ -41,9 +52,10 @@ configuration will be explained in detail in the following sections.
### Setting Configuration Flags
You should set the configuration flags before you create the dock manager
instance. That means, setting the configurations flags is the first thing
you do, if you use the library.
You must set the configuration flags before creating the dock manager
instance otherwise the manager will not be created correctly and will
crash upon being created. That means, setting the configurations flags
is the first thing you must do, if you use the library.
```c++
CDockManager::setConfigFlags(CDockManager::DefaultOpaqueConfig);
@@ -464,6 +476,69 @@ If you would like to overwrite autodetection, then you can activate this flag
to force QWidget based title bars. You can overwrite autodetection and this
flag, if you set the environment variable `ADS_UseNativeTitle` to 0 or 1.
### `MiddleMouseButtonClosesTab`
If the flag is set, the user can use the mouse middle button to close the tab
under the mouse. So you do not need to exactly hit the tab close button to
close tab. Just click with the middle mouse button on a tab like this is
possible in various web browsers.
![MiddleMouseButtonClosesTab true](cfg_flag_MiddleMouseButtonClosesTab.gif)
## DockWidget Feature Flags
### `DockWidgetClosable`
If set, the dock widget will have a close button.
### `DockWidgetMovable`
If a dock widget is movable, then it and can be moved to a new position in the
current dock container. Disable this flag to prevent moving of a dock widget
via mouse. If the `OpaqueUndocking` configuration flag is set, then dock widgets
are immediately undocked into floating widgets. That means, moving is only
possible in this case, if the dock widget is also floatable (feature flag
`DockWidgetFloatable` is set).
### `DockWidgetFloatable`
If set, a dock widget can be dragged into a floating window.
### `DockWidgetDeleteOnClose`
Deletes the dock widget and its content when it is closed.
### `CustomCloseHandling`
Clicking the close button will not close the dock widget but emits the
`closeRequested()` signal instead. This allows the application to implement
a custom close handling.
### `DockWidgetFocusable`
If this is enabled, a dock widget can get focus highlighting.
### `DockWidgetForceCloseWithArea`
A dock widget will be closed when the dock area hosting it is closed. If the
`DockWidgetDeleteOnClose` feature is enabled for a dock widget, then it will
be deleted, if the user clicks the close button of this dock widget. If the
user clicks the close button of the dock area that contains this widget,
then only the visibility of the dock widget is toggled. If this feature flag
is set, the closing the dock area also closes the dock widget. That means, if
the dock widget feature `DockWidgetDeleteOnClose` is set for the dock widgets
in a dock area, then all dock widgets will be deleted if the dock area is closed.
### `NoTab`
A dock widget tab will never be shown if this flag is set.
### `DeleteContentOnClose`
Deletes only the contained widget on close, keeping the dock widget intact and
in place. Attempts to rebuild the contents widget on show if there is a widget
factory set. See [issue #365](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/pull/365) for more details.
## Central Widget
The Advanced Docking System has been developed to overcome the limitations of

View File

@@ -1,6 +1,7 @@
cmake_minimum_required(VERSION 3.5)
project(QtADSExamples LANGUAGES CXX VERSION ${VERSION_SHORT})
add_subdirectory(simple)
add_subdirectory(hideshow)
add_subdirectory(sidebar)
add_subdirectory(deleteonclose)
add_subdirectory(centralwidget)

View File

@@ -12,19 +12,7 @@ from PyQtAds import QtAds
UI_FILE = os.path.join(os.path.dirname(__file__), 'mainwindow.ui')
MainWindowUI, MainWindowBase = uic.loadUiType(UI_FILE)
import demo_rc # pyrcc5 demo\demo.qrc -o examples\centralWidget\demo_rc.py
def svg_icon(filename: str):
'''Helper function to create an SVG icon'''
# This is a workaround, because because in item views SVG icons are not
# properly scaled and look blurry or pixelate
icon = QIcon(filename)
icon.addPixmap(icon.pixmap(92))
return icon
class MainWindow(MainWindowUI, MainWindowBase):
def __init__(self, parent=None):
@@ -46,27 +34,26 @@ class MainWindow(MainWindowUI, MainWindowBase):
central_dock_area.setAllowedAreas(QtAds.DockWidgetArea.OuterDockAreas)
# create other dock widgets
file_tree = QTreeView()
file_tree.setFrameShape(QFrame.NoFrame)
file_model = QFileSystemModel(file_tree)
file_model.setRootPath(QDir.currentPath())
file_tree.setModel(file_model)
data_dock_widget = QtAds.CDockWidget("File system")
data_dock_widget.setWidget(file_tree)
data_dock_widget.resize(150, 250)
data_dock_widget.setMinimumSize(100, 250)
file_area = self.dock_manager.addDockWidget(QtAds.DockWidgetArea.LeftDockWidgetArea, data_dock_widget, central_dock_area)
self.menuView.addAction(data_dock_widget.toggleViewAction())
table = QTableWidget()
table.setColumnCount(3)
table.setRowCount(10)
table_dock_widget = QtAds.CDockWidget("Table")
table_dock_widget = QtAds.CDockWidget("Table 1")
table_dock_widget.setWidget(table)
table_dock_widget.setMinimumSizeHintMode(QtAds.CDockWidget.MinimumSizeHintFromDockWidget)
table_dock_widget.resize(250, 150)
table_dock_widget.setMinimumSize(200, 150)
self.dock_manager.addDockWidget(QtAds.DockWidgetArea.BottomDockWidgetArea, table_dock_widget, file_area)
table_area = self.dock_manager.addDockWidget(QtAds.DockWidgetArea.LeftDockWidgetArea, table_dock_widget)
self.menuView.addAction(table_dock_widget.toggleViewAction())
table = QTableWidget()
table.setColumnCount(5)
table.setRowCount(1020)
table_dock_widget = QtAds.CDockWidget("Table 2")
table_dock_widget.setWidget(table)
table_dock_widget.setMinimumSizeHintMode(QtAds.CDockWidget.MinimumSizeHintFromDockWidget)
table_dock_widget.resize(250, 150)
table_dock_widget.setMinimumSize(200, 150)
table_area = self.dock_manager.addDockWidget(QtAds.DockWidgetArea.BottomDockWidgetArea, table_dock_widget, table_area)
self.menuView.addAction(table_dock_widget.toggleViewAction())
properties_table = QTableWidget()
@@ -76,7 +63,7 @@ class MainWindow(MainWindowUI, MainWindowBase):
properties_dock_widget.setWidget(properties_table)
properties_dock_widget.setMinimumSizeHintMode(QtAds.CDockWidget.MinimumSizeHintFromDockWidget)
properties_dock_widget.resize(250, 150)
properties_dock_widget.setMinimumSize(200,150)
properties_dock_widget.setMinimumSize(200, 150)
self.dock_manager.addDockWidget(QtAds.DockWidgetArea.RightDockWidgetArea, properties_dock_widget, central_dock_area)
self.menuView.addAction(properties_dock_widget.toggleViewAction())
@@ -84,7 +71,6 @@ class MainWindow(MainWindowUI, MainWindowBase):
def create_perspective_ui(self):
save_perspective_action = QAction("Create Perspective", self)
save_perspective_action.setIcon(svg_icon(":/adsdemo/images/picture_in_picture.svg"))
save_perspective_action.triggered.connect(self.save_perspective)
perspective_list_action = QWidgetAction(self)
self.perspective_combobox = QComboBox(self)

View File

@@ -41,19 +41,39 @@ int main(int argc, char *argv[])
now->widget()->setFocus();
});
QAction *action = new QAction("New Delete On Close", &w);
QAction *action = new QAction("New [DockWidgetDeleteOnClose]", &w);
w.menuBar()->addAction(action);
int i = 0;
QObject::connect(action, &QAction::triggered, [&]() {
auto dw = new ads::CDockWidget(QStringLiteral("test doc %1").arg(i++), &w);
auto dw = new ads::CDockWidget(QStringLiteral("test %1 [DockWidgetDeleteOnClose]").arg(i++), &w);
auto editor = new QTextEdit(QStringLiteral("lorem ipsum..."), dw);
dw->setWidget(editor);
dw->setFeature(ads::CDockWidget::DockWidgetDeleteOnClose, true);
auto area = dockManager->addDockWidgetTab(ads::CenterDockWidgetArea, dw);
qDebug() << "doc dock widget created!" << dw << area;
});
auto dw = new ads::CDockWidget(QStringLiteral("test %1 [DeleteContentOnClose]").arg(i++), &w);
auto editor = new QTextEdit(QStringLiteral("recreated lorem ipsum......"), dw);
dw->setWidget(editor);
dw->setFeature(ads::CDockWidget::DeleteContentOnClose, true);
dw->setWidgetFactory([](QWidget* dw)
{
static int timesRecreated = 0;
return new QTextEdit(QStringLiteral("recreated lorem ipsum... times %1").arg(++timesRecreated), dw);
});
auto area = dockManager->addDockWidgetTab(ads::CenterDockWidgetArea, dw);
qDebug() << "DeleteContentOnClose dock widget created!" << dw << area;
action = new QAction("Toggle [DeleteContentOnClose]", &w);
w.menuBar()->addAction(action);
QObject::connect(action, &QAction::triggered, [dw]() {
dw->toggleView(dw->isClosed());
qDebug() << QString("dock widget %1! contents widget %2!").arg(dw->isClosed() ? "closed" : "open", dw->widget() ? "created" : "deleted");
});
action = new QAction("New", &w);
w.menuBar()->addAction(action);
QObject::connect(action, &QAction::triggered, [&]() {

View File

@@ -0,0 +1,203 @@
import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QMessageBox,
QInputDialog, QMenu, QLineEdit)
from PyQt5.QtGui import QIcon
from PyQtAds import QtAds
from dockindockmanager import DockInDockManager
from perspectiveactions import LoadPerspectiveAction, RemovePerspectiveAction
class DockInDockWidget(QWidget):
def __init__(self, parent, perspectives_manager: 'PerspectivesManager', can_create_new_groups: bool = False, top_level_widget = None):
super().__init__(parent)
if top_level_widget is not None:
self.__can_create_new_groups = top_level_widget.can_create_new_groups
else:
self.__can_create_new_groups = can_create_new_groups
self.__top_level_dock_widget = top_level_widget if top_level_widget else self
self.__perspectives_manager = perspectives_manager
self.__new_perspective_default_name: str = ''
layout = QVBoxLayout(self)
layout.setContentsMargins(0,0,0,0)
self.__mgr = DockInDockManager(self)
layout.addWidget(self.__mgr)
def getManager(self) -> 'DockInDockManager':
return self.__mgr
def getTopLevelDockWidget(self) -> 'DockInDockWidget':
return self.__top_level_dock_widget
def canCreateNewGroups(self) -> bool:
return self.__can_create_new_groups
def getPerspectivesManager(self) -> 'PerspectivesManager':
return self.__perspectives_manager
def addTabWidget(self, widget: QWidget, name: str, after: QtAds.CDockAreaWidget, icon = QIcon()) -> QtAds.CDockAreaWidget:
for existing in self.getTopLevelDockWidget().getManager().allDockWidgets(True, True):
if existing[1].objectName() == name:
QMessageBox.critical(self, "Error", "Name '" + name + "' already in use")
return
dock_widget = QtAds.CDockWidget(name)
dock_widget.setWidget(widget)
dock_widget.setIcon(icon)
# Add the dock widget to the top dock widget area
return self.__mgr.addDockWidget(QtAds.CenterDockWidgetArea, dock_widget, after)
def isTopLevel(self) -> bool:
return not self.objectName()
def getGroupNameError(self, group_name: str) -> str:
if not group_name:
return "Group must have a non-empty name"
dock_managers = self.__mgr.allManagers(True, True)
for mgr in dock_managers:
if mgr.getGroupName() == group_name:
return "Group name '" + group_name + "' already used"
return ""
def createGroup(self, group_name: str, insert_pos: QtAds.CDockAreaWidget, icon = QIcon()) -> 'DockInDockWidget':
error = self.getGroupNameError(group_name)
if error:
QMessageBox.critical(None, "Error", error)
return
child = DockInDockWidget(self, self.__top_level_dock_widget, self.__perspectives_manager)
child.setObjectName(group_name)
dock_widget = QtAds.CDockWidget(group_name)
dock_widget.setWidget(child)
dock_widget.setIcon(icon)
insert_pos = self.__mgr.addDockWidget(QtAds.CenterDockWidgetArea, dock_widget, insert_pos)
return child, insert_pos
def destroyGroup(self, widget_to_remove: 'DockInDockWidget') -> None:
top_level_widget = widget_to_remove.getTopLevelDockWidget()
if top_level_widget and top_level_widget != widget_to_remove:
for dock_widget in widget_to_remove.getManager().getWidgetsInGUIOrder(): #don't use allDockWidgets to preserve sub-groups
MoveDockWidgetAction.move(dock_widget, top_level_widget.getManager())
assert not widget_to_remove.getManager().allDockWidgets(True, True)
# find widget's parent:
for dock_widget in top_level_widget.getManager().allDockWidgets(True, True):
if dockwidget[1].widget() == widget_to_remove:
dockwidget[0].removeDockWidget(dockwidget[1])
del dockwidget[1]
# delete widgetToRemove; automatically deleted when dockWidget is deleted
widget_to_remove = None
break
assert widget_to_remove == None
else:
assert False
def attachViewMenu(self, menu: QMenu) -> None:
menu.aboutToShow.connect(self.autoFillAttachedViewMenu)
def autoFillAttachedViewMenu(self) -> None:
menu = self.sender()
if menu:
menu.clear()
self.setupViewMenu(menu)
else:
assert False
def setupViewMenu(self, menu):
dock_managers = self.__mgr.allManagers(True, True)
has_perspectives_menu = False
if self.getTopLevelDockWidget() == self:
has_perspectives_menu = (self.__perspectives_manager != None)
else:
assert False
organize = menu
if has_perspectives_menu:
organize = menu.addMenu("Organize")
self.setupMenu(organize, dock_managers)
if has_perspectives_menu:
perspectives = menu.addMenu("Perspectives")
self.fillPerspectivesMenu(perspectives)
def setupMenu(self, menu: QMenu, move_to: 'list[DockInDockManager]') -> None:
self.__mgr.fillViewMenu(menu, move_to)
menu.addSeparator()
move_menu = menu.addMenu("Move")
self.__mgr.fillMoveMenu(move_menu, move_to)
def fillPerspectivesMenu(self, menu: QMenu):
menu.addAction("Create perspective...", self.createPerspective)
perspectives_names = []
if self.__perspectives_manager:
perspectives_names = self.__perspectives_manager.perspectiveNames()
if perspectives_names:
load = menu.addMenu("Load perspective")
for name in perspectives_names:
load.addAction(LoadPerspectiveAction(load, name, self))
remove = menu.addMenu("Remove perspective")
for name in perspectives_names:
remove.addAction(RemovePerspectiveAction(remove, name, self))
def setNewPerspectiveDefaultName(default_name: str) -> None:
self.__new_perspective_default_name = default_name
def createPerspective(self) -> None:
if not self.__perspectives_manager:
return
name = self.__new_perspective_default_name
if self.__new_perspective_default_name:
index = 2
while name in self.__perspectives_manager.perspectiveNames():
name = f"{self.__new_perspective_default_name}({index})"
index += 1
while True:
name, ok = QInputDialog.getText(None, "Create perspective", "Enter perspective name", QLineEdit.Normal, name)
if ok:
if not name:
QMessageBox.critical(None, "Error", "Perspective name cannot be empty")
continue
elif name in self.__perspectives_manager.perspectiveNames():
if QMessageBox.critical(None, "Error", f"Perspective '{name}' already exists, overwrite it?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.No:
continue
self.__perspectives_manager.addPerspective(name, self)
break
else:
break
def dumpStatus(self, echo: callable = print, widget: QtAds.CDockWidget = None, tab: str = '', suffix: str = '') -> str:
if widget is not None:
as_mgr = DockInDockManager.dockInAManager(widget)
if as_mgr:
as_mgr.parent().dumpStatus(tab=tab)
else:
echo(tab + widget.objectName() + suffix)
else:
echo(tab + "Group: " + self.getManager().getGroupName())
tab += " "
visible_widgets = set()
for widget in self.getManager().getWidgetsInGUIOrder():
visible_widgets.add(widget)
self.dumpStatus(widget=widget, tab=tab)
for closed in self.getManager().dockWidgetsMap().values():
if not closed in visible_widgets:
self.dumpStatus(widget=closed, tab=tab, suffix=" (closed)")

View File

@@ -0,0 +1,214 @@
from PyQt5.QtWidgets import QAction, QMenu, QInputDialog, QLineEdit
from PyQt5.QtCore import QSettings
from PyQtAds import QtAds
CHILD_PREFIX = "Child-"
class DockInDockManager(QtAds.CDockManager):
def __init__(self, parent: 'DockInDockWidget'):
super().__init__()
self.__parent = parent
def parent(self) -> 'DockInDockWidget':
return self.__parent
def fillViewMenu(self, menu: QMenu, move_to: 'dict[DockInDockManager]') -> None:
from dockindock import DockInDockWidget # Prevent cyclic import
widgets_map = self.dockWidgetsMap()
for key, value in widgets_map.items():
widget = value.widget()
action = value.toggleViewAction()
if isinstance(widget, DockInDockWidget):
sub_menu = menu.addMenu(key)
sub_menu.addAction(action)
sub_menu.addSeparator()
widget.setupMenu(sub_menu, move_to)
else:
menu.addAction(action)
if self.parent().canCreateNewGroups():
# see how this works, to create it in the right place,
# and also to have load perspective work when some groups are missing
menu.addSeparator()
menu.addAction(CreateChildDockAction(self.__parent, menu))
if self.parent().getTopLevelDockWidget().getManager() != self:
menu.addAction(DestroyGroupAction( self.parent, menu))
def fillMoveMenu(self, menu: QMenu, move_to: 'list[DockInDockManager]') -> None:
widgets_map = self.dockWidgetsMap()
for key, value in widgets_map.items():
sub_menu = menu.addMenu(key)
for mgr in move_to:
# iterate over all possible target managers
if mgr == self:
pass # if dock is already in mgr, no reason to move it there
elif mgr == DockInDockManager.dockInAManager(value):
pass # if target is the group itself, can't move it there, would make no sense
else:
sub_menu.addAction(MoveDockWidgetAction(value, mgr, sub_menu))
def addPerspectiveRec(self, name: str) -> None:
managers = self.allManagers(True, True)
for child in managers:
child.addPerspective(name)
def openPerspectiveRec(self, name: str) -> None:
managers = self.allManagers(True, True)
for child in managers:
child.openPerspective(name)
def getGroupName(self) -> str:
return self.parent().objectName()
def getPersistGroupName(self) -> str:
group = "Top"
if self.getGroupName():
group = CHILD_PREFIX + self.getGroupName()
return group
def getGroupNameFromPersistGroupName(self, persist_group_name) -> str:
if persist_group_name.startswith(CHILD_PREFIX):
persist_group_name = persist_group_name[len(CHILD_PREFIX):]
else:
assert False
return persist_group_name
def loadPerspectivesRec(self, settings: QSettings) -> None:
children = self.allManagers(True, True)
for mgr in children:
settings.beginGroup(mgr.getPersistGroupName())
mgr.loadPerspectives(settings)
settings.endGroup()
def savePerspectivesRec(self, settings: QSettings) -> None:
children = self.allManagers(True, True)
for mgr in children:
settings.beginGroup(mgr.getPersistGroupName())
mgr.savePerspectives(settings)
settings.endGroup()
def removePerspectivesRec(self, settings: QSettings) -> None:
children = self.allManagers(True, True)
for mgr in children:
child.removePerspectives(child.perspectiveNames())
@staticmethod
def dockInAManager(widget) -> 'DockInDockManager':
from dockindock import DockInDockWidget # Prevent cyclic import
dock_widget = widget.widget() if widget else None
return dock_widget.getManager() if isinstance(dock_widget, DockInDockWidget) else None
def childManagers(self, managers: 'list[DockInDockManager]', rec: bool) -> None:
widgets = self.getWidgetsInGUIOrder()
for widget in widgets:
as_mgr = DockInDockManager.dockInAManager(widget)
if as_mgr:
managers.append(as_mgr)
if rec:
as_mgr.childManagers(managers, rec)
def allManagers(self, include_self: bool, rec: bool) -> 'list[DockInDockManager]':
managers = []
if include_self:
managers.append(self)
self.childManagers(managers, rec)
return managers
def allDockWidgets(self, include_self: bool, rec: bool) -> 'list[tuple[DockInDockManager, QtAds.CDockWidget]]':
widgets = []
for mgr in self.allManagers(include_self, rec):
for widget in mgr.getWidgetsInGUIOrder():
widgets.append((mgr, widget))
return widgets
def getGroupContents(self) -> 'dict[str, list[str]]':
result = {}
managers = self.allManagers(True, True)
for mgr in managers:
result[mgr.getPersistGroupName()] = mgr.dockWidgetsMap().keys()
return result
def getInsertDefaultPos(self) -> QtAds.CDockAreaWidget:
default_pos = None
if self.dockAreaCount() != 0:
default_pos = self.dockArea(self.dockAreaCount()-1)
return default_pos
def getWidgetsInGUIOrder(self) -> 'list[QtAds.CDockWidget]':
result = []
for i in range(self.dockAreaCount()):
for widget in self.dockArea(i).dockWidgets():
result.append(widget)
return result
class CreateChildDockAction(QAction):
def __init__(self, dock_in_dock: 'DockInDockWidget', menu: QMenu):
super().__init__("New group...", menu)
self.__dock_in_dock = dock_in_dock
self.triggered.connect(self.createGroup)
def createGroup(self) -> None:
name = ""
while True:
name, ok = QInputDialog.getText(None, self.text(), "Enter group name", QLineEdit.Normal, name)
if ok:
error = ""
if self.__dock_in_dock.getTopLevelDockWidget():
error = self.__dock_in_dock.getTopLevelDockWidget().getGroupNameError(name)
else:
assert False
if not error:
self.__dock_in_dock.createGroup(name, None)
break
else:
QMessageBox.critical(None, "Error", error)
continue
else:
break
class DestroyGroupAction(QAction):
def __init__(self, widget: 'DockInDockWidget', menu: QMenu):
super().__init__("Destroy" + widget.getManager().getGroupName(), menu)
self.__widget = widget
self.triggered.connect(self.destroyGroup)
def destroyGroup(self) -> None:
self.__widget.getTopLevelDockWidget().destroyGroup(self.__widget)
class MoveDockWidgetAction(QAction):
def __init__(self, widget: 'DockInDockWidget', move_to: DockInDockManager, menu: QMenu):
super().__init__(menu)
self.__widget = widget
self.__move_to = move_to
if move_to.parent().isTopLevel():
self.setText("To top")
else:
self.setText(f"To {move_to.parent().objectName()}")
self.triggered.connect(self._move)
def _move(self) -> None:
self.move(self.__widget, self.__move_to)
def move(self, widget: QtAds.CDockWidget, move_to: QtAds.CDockManager) -> None:
if widget and move_to:
widget.dockManager().removeDockWidget(widget)
move_to.addDockWidget(QtAds.CenterDockWidgetArea, widget, move_to.getInsertDefaultPos())
else:
assert False

View File

@@ -0,0 +1,72 @@
import sys
import os
import atexit
from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel
from PyQt5.QtCore import Qt
from PyQtAds import QtAds
from perspectives import PerspectivesManager
from dockindock import DockInDockWidget
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.perspectives_manager = PerspectivesManager("persist")
self.resize(400, 400)
self.dock_manager = DockInDockWidget(self, self.perspectives_manager, can_create_new_groups=True)
self.setCentralWidget(self.dock_manager)
self.dock_manager.attachViewMenu(self.menuBar().addMenu("View"))
previous_dock_widget = None
for i in range(3):
l = QLabel()
l.setWordWrap(True)
l.setAlignment(Qt.AlignTop | Qt.AlignLeft)
l.setText("Lorem ipsum dolor sit amet, consectetuer adipiscing elit. ")
previous_dock_widget = self.dock_manager.addTabWidget(l, f"Top label {i}", previous_dock_widget)
last_top_level_dock = previous_dock_widget
for j in range(2):
group_manager, _ = self.dock_manager.createGroup(f"Group {j}", last_top_level_dock)
previous_dock_widget = None
for i in range(3):
# Create example content label - this can be any application specific widget
l = QLabel()
l.setWordWrap(True)
l.setAlignment(Qt.AlignTop | Qt.AlignLeft)
l.setText("Lorem ipsum dolor sit amet, consectetuer adipiscing elit. ")
previous_dock_widget = group_manager.addTabWidget(l, f"ZInner {j}/{i}", previous_dock_widget)
# create sub-group
sub_group, _ = group_manager.createGroup(f"SubGroup {j}", previous_dock_widget)
previous_dock_widget = None
for i in range(3):
# Create example content label - this can be any application specific widget
l = QLabel()
l.setWordWrap(True)
l.setAlignment(Qt.AlignTop | Qt.AlignLeft)
l.setText("Lorem ipsum dolor sit amet, consectetuer adipiscing elit. ")
previous_dock_widget = sub_group.addTabWidget(l, f"SubInner {j}/{i}", previous_dock_widget)
self.perspectives_manager.loadPerspectives()
atexit.register(self.cleanup)
def cleanup(self):
self.perspectives_manager.savePerspectives()
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()

View File

@@ -0,0 +1,25 @@
from PyQt5.QtWidgets import QAction, QMenu
class LoadPerspectiveAction(QAction):
def __init__(self, parent: QMenu, name: str, dock_manager: 'DockInDockWidget'):
super().__init__(name, parent)
self.name = name
self.dock_manager = dock_manager
self.triggered.connect(self.load)
def load(self):
self.dock_manager.getPerspectivesManager().openPerspective(self.name, self.dock_manager)
class RemovePerspectiveAction(QAction):
def __init__(self, parent: QMenu, name: str, dock_manager: 'DockInDockWidget'):
super().__init__(name, parent)
self.name = name
self.dock_manager = dock_manager
self.triggered.connect(self.remove)
def remove(self):
self.dock_manager.getPerspectivesManager().removePerspective(self.name)

View File

@@ -0,0 +1,203 @@
import os
import tempfile
import shutil
import atexit
from PyQt5.QtCore import pyqtSignal, QSettings, QObject
from PyQtAds import QtAds
from dockindockmanager import DockInDockManager
from dockindock import DockInDockWidget
GROUP_PREFIX = "Group"
def findWidget(name, managers: 'list[DockInDockManager]') -> QtAds.CDockWidget:
for mgr in managers:
widget = mgr.findDockWidget(name)
if widget:
return widget
class PerspectiveInfo:
# Partially bypass ADS perspective management, store list here
# and then ADS will only have one perspective loaded
# this is because all docking widgets must exist when a perspective is loaded
# we will guarantee that!
settings = QSettings()
groups: 'dict[str, list[str]]' = {}
class PerspectivesManager(QObject):
perspectivesListChanged = pyqtSignal()
openingPerspective = pyqtSignal()
openedPerspective = pyqtSignal()
def __init__(self, perspectives_folder):
super().__init__()
self.__perspectives_folder = perspectives_folder
self.__perspectives = {}
atexit.register(self.cleanup)
def cleanup(self):
for perspective in self.__perspectives.values():
filename = perspective.settings.fileName()
try:
os.remove(filename)
except FileNotFoundError:
pass
def perspectiveNames(self) -> 'list[str]':
return self.__perspectives.keys()
def addPerspective(self, name: str, widget: DockInDockWidget) -> None:
if self.__perspectives_folder:
self.__perspectives[name] = perspective = PerspectiveInfo()
perspective.settings = self.getSettingsObject(self.getSettingsFileName(name, True))
perspective.groups = widget.getManager().getGroupContents()
# save perspective internally
widget.getManager().addPerspectiveRec(name)
# store it in QSettings object
widget.getManager().savePerspectivesRec(perspective.settings)
# remove internal perspectives
widget.getManager().removePerspectives(widget.getManager().perspectiveNames())
self.perspectivesListChanged.emit()
def openPerspective(name: str, widget: DockInDockWidget) -> None:
assert widget.getTopLevelDockWidget() == widget
if self.__perspectives_folder:
if name in self.__perspectives:
self.openingPerspective.emit()
if widget.canCreateNewGroups():
cur_groups = widget.getManager().allManagers(True, True)
for group in self.__perspectives[name].groups.keys():
found = False
for curgroup in cur_groups:
if curgroup.getPerspectiveGroupName() == group:
found = True
break
if not found:
group = DockInDockManager.getGroupNameFromPersistGroupName(group)
# restore group in file but not in GUI yet
widget.createGroup(group, None)
cur_groups = widget.getManager().allManagers(False, True)
for curgroup in cur_groups:
if curgroup.getPersistGroupName() not in self.__perspectives[name].groups.keys():
widget.destroyGroup(curgroup.parent())
managers = widget.getManager().allManagers(True, True)
for group in self.__perspectives[name].groups().keys():
for mgr in managers:
if mgr.getPersistGroupName() == group:
for widget_name in self.__perspectives[name].groups[group]:
widget = findWidget(widget_name, [mgr])
if widget:
pass # OK, widget is already in the good manager!
else:
widget = findWidget(widget_name, managers)
if widget:
# move dock widget in the same group as it used to be when perspective was saved
# this guarantee load/open perspectives will work smartly
MoveDockWidgetAction.move(widget, mgr)
# internally load perspectives from QSettings
widget.getManager().loadPerspectivesRec(self.__perspectives[name].settings)
# load perspective (update GUI)
widget.getManager().openPerspectiveRec(name)
# remove internal perspectives
widget.getManager().removePerspectives(widget.getManager().perspectiveNames())
self.openedPerspective().emit()
else:
assert False
def removePerspectives(self) -> None:
self.__perspectives.clear()
self.perspectivesListChanged.emit()
def removePerspective(self, name: str) -> None:
del self.__perspectives[name]
self.perspectivesListChanged.emit()
def getSettingsFileName(self, perspective: str, temp: bool) -> str:
name = "perspectives.ini" if not perspective else f"perspectives_{perspective + '.tmp' if temp else perspective + '.ini'}"
return os.path.join(self.__perspectives_folder, name)
def getSettingsObject(self, file_path: str) -> QSettings:
return QSettings(file_path, QSettings.IniFormat)
def loadPerspectives(self) -> None:
if self.__perspectives_folder:
tempfile.mktemp(dir=self.__perspectives_folder)
self.__perspectives.clear()
main_settings = self.getSettingsObject(self.getSettingsFileName("", False))
debug = main_settings.fileName()
size = main_settings.beginReadArray("Perspectives")
for i in range(0, size):
main_settings.setArrayIndex(i)
perspective = main_settings.value("Name")
if perspective:
to_load = self.getSettingsFileName(perspective, False)
loaded = self.getSettingsFileName(perspective, True)
try:
os.remove(loaded)
except FileNotFoundError:
pass
if not shutil.copy(to_load, loaded):
assert False
self.__perspectives[perspective] = PerspectiveInfo()
self.__perspectives[perspective].settings = self.getSettingsObject(loaded)
# load group info:
main_settings.beginGroup(GROUP_PREFIX)
for key in main_settings.allKeys():
self.__perspectives[perspective].groups[key] = main_settings.value(key)
main_settings.endGroup()
else:
assert False
main_settings.endArray()
self.perspectivesListChanged.emit()
def savePerspectives(self) -> None:
if self.__perspectives_folder:
main_settings = self.getSettingsObject(self.getSettingsFileName("", False))
# Save list of perspective and group organization
main_settings.beginWriteArray("Perspectives", len(self.__perspectives))
for i, perspective in enumerate(self.__perspectives.keys()):
main_settings.setArrayIndex(i)
main_settings.setValue("Name", perspective)
main_settings.beginGroup(GROUP_PREFIX)
for group in self.__perspectives[perspective].groups.keys():
main_settings.setValue(group, list(self.__perspectives[perspective].groups[group]))
main_settings.endGroup()
main_settings.endArray()
# Save perspectives themselves
for perspective_name in self.__perspectives.keys():
to_save = self.getSettingsFileName(perspective_name, False)
settings = self.__perspectives[perspective_name].settings
settings.sync()
try:
os.remove(to_save)
except FileNotFoundError:
pass
if not shutil.copy(settings.fileName(), to_save):
assert False

View File

@@ -0,0 +1,108 @@
import sys
import os
from PyQt5 import uic
from PyQt5.QtCore import Qt, QSignalBlocker
from PyQt5.QtWidgets import (QApplication, QMainWindow, QLabel, QComboBox, QTableWidget,
QAction, QWidgetAction, QSizePolicy, QInputDialog)
from PyQt5.QtGui import QCloseEvent
from PyQtAds import QtAds
UI_FILE = os.path.join(os.path.dirname(__file__), 'mainwindow.ui')
MainWindowUI, MainWindowBase = uic.loadUiType(UI_FILE)
class CMainWindow(MainWindowUI, MainWindowBase):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
QtAds.CDockManager.setConfigFlag(QtAds.CDockManager.OpaqueSplitterResize, True)
QtAds.CDockManager.setConfigFlag(QtAds.CDockManager.XmlCompressionEnabled, False)
QtAds.CDockManager.setConfigFlag(QtAds.CDockManager.FocusHighlighting, True)
self.dock_manager = QtAds.CDockManager(self)
# Set central widget
label = QLabel()
label.setText("This is a DockArea which is always visible, even if it does not contain any DockWidgets.")
label.setAlignment(Qt.AlignCenter)
central_dock_widget = QtAds.CDockWidget("CentralWidget")
central_dock_widget.setWidget(label)
central_dock_widget.setFeature(QtAds.CDockWidget.NoTab, True)
central_dock_area = self.dock_manager.setCentralWidget(central_dock_widget)
# create other dock widgets
table = QTableWidget()
table.setColumnCount(3)
table.setRowCount(10)
table_dock_widget = QtAds.CDockWidget("Table 1")
table_dock_widget.setWidget(table)
table_dock_widget.setMinimumSizeHintMode(QtAds.CDockWidget.MinimumSizeHintFromDockWidget)
table_dock_widget.resize(250, 150)
table_dock_widget.setMinimumSize(200,150)
self.dock_manager.addDockWidgetTabToArea(table_dock_widget, central_dock_area)
table_area = self.dock_manager.addDockWidget(QtAds.DockWidgetArea.LeftDockWidgetArea, table_dock_widget)
self.menuView.addAction(table_dock_widget.toggleViewAction())
table = QTableWidget()
table.setColumnCount(5)
table.setRowCount(1020)
table_dock_widget = QtAds.CDockWidget("Table 2")
table_dock_widget.setWidget(table)
table_dock_widget.setMinimumSizeHintMode(QtAds.CDockWidget.MinimumSizeHintFromDockWidget)
table_dock_widget.resize(250, 150)
table_dock_widget.setMinimumSize(200,150)
self.dock_manager.addDockWidget(QtAds.DockWidgetArea.BottomDockWidgetArea, table_dock_widget, table_area)
self.menuView.addAction(table_dock_widget.toggleViewAction())
properties_table = QTableWidget()
properties_table.setColumnCount(3)
properties_table.setRowCount(10)
properties_dock_widget = QtAds.CDockWidget("Properties")
properties_dock_widget.setWidget(properties_table)
properties_dock_widget.setMinimumSizeHintMode(QtAds.CDockWidget.MinimumSizeHintFromDockWidget)
properties_dock_widget.resize(250, 150)
properties_dock_widget.setMinimumSize(200,150)
self.dock_manager.addDockWidget(QtAds.DockWidgetArea.RightDockWidgetArea, properties_dock_widget, central_dock_area)
self.menuView.addAction(properties_dock_widget.toggleViewAction())
self.createPerspectiveUi()
def createPerspectiveUi(self):
save_perspective_action = QAction("Create Perspective", self)
save_perspective_action.triggered.connect(self.savePerspective)
perspective_list_action = QWidgetAction(self)
self.perspective_combo_box = QComboBox(self)
self.perspective_combo_box.setSizeAdjustPolicy(QComboBox.AdjustToContents)
self.perspective_combo_box.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
self.perspective_combo_box.activated[str].connect(self.dock_manager.openPerspective)
perspective_list_action.setDefaultWidget(self.perspective_combo_box)
self.toolBar.addSeparator()
self.toolBar.addAction(perspective_list_action)
self.toolBar.addAction(save_perspective_action)
def savePerspective(self):
perspective_name, ok = QInputDialog.getText(self, "Save Perspective", "Enter unique name:")
if not perspective_name or not ok:
return
self.dock_manager.addPerspective(perspective_name)
blocker = QSignalBlocker(self.perspective_combo_box)
self.perspective_combo_box.clear()
self.perspective_combo_box.addItems(self.dock_manager.perspectiveNames())
self.perspective_combo_box.setCurrentText(perspective_name)
def closeEvent(self, event: QCloseEvent):
# Delete dock manager here to delete all floating widgets. This ensures
# that all top level windows of the dock manager are properly closed
self.dock_manager.deleteLater()
super().closeEvent(event)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = CMainWindow()
w.show()
app.exec_()

View File

@@ -3,6 +3,7 @@ TEMPLATE = subdirs
SUBDIRS = \
centralwidget \
simple \
hideshow \
sidebar \
deleteonclose \
emptydockarea \

View File

@@ -0,0 +1,28 @@
cmake_minimum_required(VERSION 3.5)
project(ads_example_hideshow VERSION ${VERSION_SHORT})
find_package(QT NAMES Qt6 Qt5 COMPONENTS Core REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} 5.5 COMPONENTS Core Gui Widgets REQUIRED)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
add_executable(HideShowExample WIN32
main.cpp
MainWindow.cpp
MainWindow.ui
)
target_include_directories(HideShowExample PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../../src")
target_link_libraries(HideShowExample PRIVATE qtadvanceddocking)
target_link_libraries(HideShowExample PUBLIC Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::Gui
Qt${QT_VERSION_MAJOR}::Widgets)
set_target_properties(HideShowExample PROPERTIES
AUTOMOC ON
AUTORCC ON
AUTOUIC ON
CXX_STANDARD 14
CXX_STANDARD_REQUIRED ON
CXX_EXTENSIONS OFF
VERSION ${VERSION_SHORT}
EXPORT_NAME "Qt Advanced Docking System Hide,Show Example"
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${ads_PlatformDir}/lib"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${ads_PlatformDir}/lib"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${ads_PlatformDir}/bin"
)

View File

@@ -0,0 +1,78 @@
#include "../../examples/hideshow/MainWindow.h"
#include "ui_MainWindow.h"
#include <QLabel>
#include <QPushButton>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->centralWidget->setLayout( m_layout = new QStackedLayout() );
m_welcomeWidget = new QWidget(this);
auto welcomeLayout = new QVBoxLayout(m_welcomeWidget);
welcomeLayout->addStretch();
QPushButton* openButton = new QPushButton("Open project");
welcomeLayout->addWidget( openButton );
welcomeLayout->addStretch();
connect( openButton, SIGNAL(clicked()), this, SLOT(openProject()) );
m_DockManager = new ads::CDockManager(ui->centralWidget);
// Create example content label - this can be any application specific
// widget
QLabel* l = new QLabel();
l->setWordWrap(true);
l->setAlignment(Qt::AlignTop | Qt::AlignLeft);
l->setText("Lorem ipsum dolor sit amet, consectetuer adipiscing elit. ");
// Create a dock widget with the title Label 1 and set the created label
// as the dock widget content
ads::CDockWidget* DockWidget = new ads::CDockWidget("Label 1");
DockWidget->setWidget(l);
// Add the toggleViewAction of the dock widget to the menu to give
// the user the possibility to show the dock widget if it has been closed
ui->menuView->addAction(DockWidget->toggleViewAction());
connect( ui->actionOpen, SIGNAL(triggered()), this, SLOT(openProject()) );
connect( ui->actionClose, SIGNAL(triggered()), this, SLOT(closeProject()) );
// Add the dock widget to the top dock widget area
m_DockManager->addDockWidget(ads::TopDockWidgetArea, DockWidget);
ui->centralWidget->layout()->addWidget( m_welcomeWidget );
ui->centralWidget->layout()->addWidget( m_DockManager );
closeProject();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::openProject()
{
ui->actionOpen->setEnabled(false);
ui->actionClose->setEnabled(true);
ui->menuView->setEnabled(true);
m_layout->setCurrentWidget( m_DockManager );
}
void MainWindow::closeProject()
{
ui->actionOpen->setEnabled(true);
ui->actionClose->setEnabled(false);
ui->menuView->setEnabled(false);
m_DockManager->hideManagerAndFloatingWidgets();
m_layout->setCurrentWidget( m_welcomeWidget );
}

View File

@@ -0,0 +1,33 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QStackedLayout>
#include "DockManager.h"
QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
void openProject();
void closeProject();
private:
Ui::MainWindow *ui;
QWidget* m_welcomeWidget;
ads::CDockManager* m_DockManager;
QStackedLayout* m_layout;
};
#endif // MAINWINDOW_H

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralWidget"/>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>26</height>
</rect>
</property>
<widget class="QMenu" name="menuView">
<property name="title">
<string>View</string>
</property>
</widget>
<widget class="QMenu" name="menuFile">
<property name="title">
<string>File</string>
</property>
<addaction name="actionOpen"/>
<addaction name="actionClose"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuView"/>
</widget>
<widget class="QStatusBar" name="statusBar"/>
<action name="actionOpen">
<property name="text">
<string>Open project</string>
</property>
</action>
<action name="actionClose">
<property name="text">
<string>Close project</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,31 @@
ADS_OUT_ROOT = $${OUT_PWD}/../..
QT += core gui widgets
TARGET = HideShowExample
DESTDIR = $${ADS_OUT_ROOT}/lib
TEMPLATE = app
CONFIG += c++14
CONFIG += debug_and_release
adsBuildStatic {
DEFINES += ADS_STATIC
}
DEFINES += QT_DEPRECATED_WARNINGS
SOURCES += \
main.cpp \
MainWindow.cpp
HEADERS += \
MainWindow.h
FORMS += \
MainWindow.ui
LIBS += -L$${ADS_OUT_ROOT}/lib
include(../../ads.pri)
INCLUDEPATH += ../../src
DEPENDPATH += ../../src

View File

@@ -0,0 +1,11 @@
#include <QApplication>
#include "../../examples/hideshow/MainWindow.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}

View File

@@ -43,6 +43,7 @@ MainWindow::MainWindow(QWidget *parent) :
QPlainTextEdit* te = new QPlainTextEdit();
te->setPlaceholderText("Please enter your text here into this QPlainTextEdit...");
DockWidget = new ads::CDockWidget("Editor 1");
DockWidget->setWidget(te);
ui->menuView->addAction(DockWidget->toggleViewAction());
m_DockManager->addDockWidget(ads::BottomDockWidgetArea, DockWidget);
}

27
project.py Normal file
View File

@@ -0,0 +1,27 @@
import os
from pyqtbuild import PyQtBindings, PyQtProject
from sipbuild import Option
class PyQtAds(PyQtProject):
def __init__(self):
""" Initialise the project. """
super().__init__()
self.bindings_factories = [ads]
class ads(PyQtBindings):
def __init__(self, project):
""" Initialise the bindings. """
super().__init__(project, 'ads')
def apply_user_defaults(self, tool):
""" Set default values for user options that haven't been set yet. """
resource_file = os.path.join(self.project.root_dir,'src','ads.qrc')
print("Adding resource file to qmake project: ", resource_file)
self.builder_settings.append('RESOURCES += '+resource_file)
super().apply_user_defaults(tool)

65
pyproject.toml Normal file
View File

@@ -0,0 +1,65 @@
# Specify the build system.
[build-system]
requires = ["sip >=6.0.2, <6.6", "PyQt-builder >=1.6, <2", "PyQt5>=5.15.4", "PyQt5-sip<13,>=12.8"]
build-backend = "sipbuild.api"
# Specify the PEP 566 metadata for the project.
[tool.sip.metadata]
name = "PyQtAds"
version = "3.8.2"
summary = "Python bindings for Qt Advanced Docking System"
home-page = "https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/"
license = "LGPL v2.1"
description-file = "README.md"
requires-dist = "PyQt5 (>=5.15.4)"
description-content-type = "text/markdown"
[tool.sip.project]
tag-prefix = "QtAds"
[tool.sip.bindings.ads]
define-macros = ["ADS_SHARED_EXPORT"]
sip-file = "ads.sip"
include-dirs = ["src"]
qmake-QT = ["widgets"]
headers = [
"src/DockAreaTabBar.h",
"src/DockAreaTitleBar.h",
"src/DockAreaTitleBar_p.h",
"src/DockAreaWidget.h",
"src/DockComponentsFactory.h",
"src/DockContainerWidget.h",
"src/DockFocusController.h",
"src/DockManager.h",
"src/DockOverlay.h",
"src/DockSplitter.h",
"src/DockWidget.h",
"src/DockWidgetTab.h",
"src/DockingStateReader.h",
"src/ElidingLabel.h",
"src/FloatingDockContainer.h",
"src/FloatingDragPreview.h",
"src/IconProvider.h",
"src/ads_globals.h",
# "src/linux/FloatingWidgetTitleBar.h",
]
sources = [
"src/DockAreaTabBar.cpp",
"src/DockAreaTitleBar.cpp",
"src/DockAreaWidget.cpp",
"src/DockComponentsFactory.cpp",
"src/DockContainerWidget.cpp",
"src/DockFocusController.cpp",
"src/DockManager.cpp",
"src/DockOverlay.cpp",
"src/DockSplitter.cpp",
"src/DockWidget.cpp",
"src/DockWidgetTab.cpp",
"src/DockingStateReader.cpp",
"src/ElidingLabel.cpp",
"src/FloatingDockContainer.cpp",
"src/FloatingDragPreview.cpp",
"src/IconProvider.cpp",
"src/ads_globals.cpp",
# "src/linux/FloatingWidgetTitleBar.cpp",
]

View File

@@ -227,9 +227,7 @@ class build_ext(sipdistutils.build_ext):
extension.extra_link_args += ['-F' + self.qtconfig.QT_INSTALL_LIBS,
'-mmacosx-version-min=10.9']
elif sys.platform == 'linux':
extension.extra_compile_args += ['-D', 'QT_X11EXTRAS_LIB', '-std=c++11']
extension.include_dirs += [os.path.join(self.qt_include_dir, 'QtX11Extras')]
extension.libraries += ['Qt5X11Extras' + self.qt_libinfix]
extension.extra_compile_args += ['-std=c++11']
return super().swig_sources(sources, extension)

View File

@@ -21,6 +21,7 @@ public:
void notifyWidgetOrAreaRelocation(QWidget* RelocatedWidget);
void notifyFloatingWidgetDrop(ads::CFloatingDockContainer* FloatingWidget);
ads::CDockWidget* focusedDockWidget() const;
void setDockWidgetTabFocused(ads::CDockWidgetTab* Tab);
public slots:
void setDockWidgetFocused(ads::CDockWidget* focusedNow);

View File

@@ -172,6 +172,7 @@ public:
EqualSplitOnInsertion,
FloatingContainerForceNativeTitleBar,
FloatingContainerForceQWidgetTitleBar,
MiddleMouseButtonClosesTab,
DefaultDockAreaButtons,
DefaultBaseConfig,
DefaultOpaqueConfig,

View File

@@ -33,8 +33,11 @@ public:
CustomCloseHandling,
DockWidgetFocusable,
DockWidgetForceCloseWithArea,
NoTab,
DeleteContentOnClose,
DefaultDockWidgetFeatures,
AllDockWidgetFeatures,
DockWidgetAlwaysCloseAndDelete,
NoDockWidgetFeatures
};
typedef QFlags<ads::CDockWidget::DockWidgetFeature> DockWidgetFeatures;

View File

@@ -1,4 +1,4 @@
%Module(name=PyQtAds.QtAds.ads, call_super_init=True, keyword_arguments="Optional", use_limited_api=True)
%Module(name=PyQtAds, call_super_init=True, keyword_arguments="Optional", use_limited_api=True)
%Import QtCore/QtCoremod.sip
%DefaultSupertype sip.simplewrapper

View File

@@ -1,60 +1,138 @@
%Import QtWidgets/QtWidgetsmod.sip
%If (Qt_5_0_0 -)
namespace ads
{
%TypeHeaderCode
#include <ads_globals.h>
%End
enum DockWidgetArea
{
NoDockWidgetArea,
LeftDockWidgetArea,
RightDockWidgetArea,
TopDockWidgetArea,
BottomDockWidgetArea,
CenterDockWidgetArea,
InvalidDockWidgetArea,
OuterDockAreas,
AllDockAreas
};
typedef QFlags<ads::DockWidgetArea> DockWidgetAreas;
enum TitleBarButton
{
TitleBarButtonTabsMenu,
TitleBarButtonUndock,
TitleBarButtonClose
};
enum eDragState
{
DraggingInactive,
DraggingMousePressed,
DraggingTab,
DraggingFloatingWidget
};
enum eIcon
{
TabCloseIcon,
DockAreaMenuIcon,
DockAreaUndockIcon,
DockAreaCloseIcon,
IconCount,
};
enum eBitwiseOperator
{
BitwiseAnd,
BitwiseOr
};
};
%End
%Import QtWidgets/QtWidgetsmod.sip
%If (Qt_5_0_0 -)
%ModuleHeaderCode
PyObject *qtads_FindParent(PyObject* type, const QWidget *child);
%End
%ModuleCode
PyObject *qtads_FindParent(PyObject* type, const QWidget *w)
{
// Check that the types checking was successful.
if (!type)
return 0;
QWidget* parentWidget = w->parentWidget();
while (parentWidget)
{
PyObject *ParentImpl = sipConvertFromType(parentWidget, sipType_QObject, 0);
if (!ParentImpl)
{
return 0;
}
if (PyObject_IsInstance(ParentImpl, type))
return ParentImpl;
Py_DECREF(ParentImpl);
parentWidget = parentWidget->parentWidget();
}
Py_INCREF(Py_None);
return Py_None;
}
%End
namespace ads
{
%TypeHeaderCode
#include <ads_globals.h>
%End
enum DockWidgetArea
{
NoDockWidgetArea,
LeftDockWidgetArea,
RightDockWidgetArea,
TopDockWidgetArea,
BottomDockWidgetArea,
CenterDockWidgetArea,
InvalidDockWidgetArea,
OuterDockAreas,
AllDockAreas
};
typedef QFlags<ads::DockWidgetArea> DockWidgetAreas;
enum TitleBarButton
{
TitleBarButtonTabsMenu,
TitleBarButtonUndock,
TitleBarButtonClose
};
enum eDragState
{
DraggingInactive,
DraggingMousePressed,
DraggingTab,
DraggingFloatingWidget
};
enum eIcon
{
TabCloseIcon,
DockAreaMenuIcon,
DockAreaUndockIcon,
DockAreaCloseIcon,
IconCount,
};
enum eBitwiseOperator
{
BitwiseAnd,
BitwiseOr
};
namespace internal
{
void replaceSplitterWidget(QSplitter* Splitter, QWidget* From, QWidget* To);
void hideEmptyParentSplitters(ads::CDockSplitter* FirstParentSplitter);
class CDockInsertParam
{
%TypeHeaderCode
#include <ads_globals.h>
%End
public:
Qt::Orientation orientation() const;
bool append() const;
int insertOffset() const;
};
ads::internal::CDockInsertParam dockAreaInsertParameters(ads::DockWidgetArea Area);
SIP_PYOBJECT findParent(SIP_PYTYPE type, const QWidget *w) const /TypeHint="QObject"/;
%MethodCode
sipRes = qtads_FindParent(a0, a1);
if (!sipRes)
{
sipIsErr = 1;
}
%End
QPixmap createTransparentPixmap(const QPixmap& Source, qreal Opacity);
QPoint globalPositionOf(QMouseEvent* ev);
void setButtonIcon(QAbstractButton* Button, QStyle::StandardPixmap StandarPixmap, ads::eIcon CustomIconId);
enum eRepolishChildOptions
{
RepolishIgnoreChildren,
RepolishDirectChildren,
RepolishChildrenRecursively
};
void repolishStyle(QWidget* w, ads::internal::eRepolishChildOptions Options = ads::internal::RepolishIgnoreChildren);
};
};
%End

View File

@@ -3,7 +3,7 @@ project(QtAdvancedDockingSystem LANGUAGES CXX VERSION ${VERSION_SHORT})
find_package(QT NAMES Qt6 Qt5 COMPONENTS Core REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} 5.5 COMPONENTS Core Gui Widgets REQUIRED)
if (UNIX AND NOT APPLE)
find_package(Qt${QT_VERSION_MAJOR} 5.5 COMPONENTS X11Extras REQUIRED)
include_directories(${Qt${QT_VERSION_MAJOR}Gui_PRIVATE_INCLUDE_DIRS})
endif()
set(CMAKE_INCLUDE_CURRENT_DIR ON)
if(BUILD_STATIC)
@@ -61,13 +61,12 @@ else()
add_library(qtadvanceddocking SHARED ${ads_SRCS} ${ads_HEADERS})
target_compile_definitions(qtadvanceddocking PRIVATE ADS_SHARED_EXPORT)
endif()
add_library(ads::qtadvanceddocking ALIAS qtadvanceddocking)
target_link_libraries(qtadvanceddocking PUBLIC Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::Gui
Qt${QT_VERSION_MAJOR}::Widgets)
if(UNIX AND NOT APPLE)
target_link_libraries(qtadvanceddocking PUBLIC Qt${QT_VERSION_MAJOR}::X11Extras)
target_link_libraries(qtadvanceddocking PRIVATE xcb)
endif()
set_target_properties(qtadvanceddocking PROPERTIES
AUTOMOC ON
AUTORCC ON
@@ -91,9 +90,9 @@ install(FILES ${ads_HEADERS}
COMPONENT headers
)
install(FILES
"${CMAKE_SOURCE_DIR}/LICENSE"
"${CMAKE_SOURCE_DIR}/gnu-lgpl-v2.1.md"
DESTINATION license
"${CMAKE_CURRENT_SOURCE_DIR}/../LICENSE"
"${CMAKE_CURRENT_SOURCE_DIR}/../gnu-lgpl-v2.1.md"
DESTINATION license/ads
COMPONENT license
)
install(TARGETS qtadvanceddocking

View File

@@ -51,6 +51,7 @@
#include "DockAreaTabBar.h"
#include "IconProvider.h"
#include "DockComponentsFactory.h"
#include "DockFocusController.h"
#include <iostream>
@@ -471,7 +472,8 @@ void CDockAreaTitleBar::mousePressEvent(QMouseEvent* ev)
if (CDockManager::testConfigFlag(CDockManager::FocusHighlighting))
{
d->TabBar->currentTab()->setFocus(Qt::OtherFocusReason);
//d->TabBar->currentTab()->setFocus(Qt::OtherFocusReason);
d->dockManager()->dockFocusController()->setDockWidgetTabFocused(d->TabBar->currentTab());
}
return;
}

View File

@@ -538,7 +538,7 @@ void CDockAreaWidget::onTabCloseRequested(int Index)
{
ADS_PRINT("CDockAreaWidget::onTabCloseRequested " << Index);
auto* DockWidget = dockWidget(Index);
if (DockWidget->features().testFlag(CDockWidget::DockWidgetDeleteOnClose))
if (DockWidget->features().testFlag(CDockWidget::DockWidgetDeleteOnClose) || DockWidget->features().testFlag(CDockWidget::CustomCloseHandling))
{
DockWidget->closeDockWidgetInternal();
}
@@ -956,9 +956,12 @@ QAbstractButton* CDockAreaWidget::titleBarButton(TitleBarButton which) const
void CDockAreaWidget::closeArea()
{
// If there is only one single dock widget and this widget has the
// DeleteOnClose feature, then we delete the dock widget now
// DeleteOnClose feature or CustomCloseHandling, then we delete the dock widget now;
// in the case of CustomCloseHandling, the CDockWidget class will emit its
// closeRequested signal and not actually delete unless the signal is handled in a way that deletes it
auto OpenDockWidgets = openedDockWidgets();
if (OpenDockWidgets.count() == 1 && OpenDockWidgets[0]->features().testFlag(CDockWidget::DockWidgetDeleteOnClose))
if (OpenDockWidgets.count() == 1 &&
(OpenDockWidgets[0]->features().testFlag(CDockWidget::DockWidgetDeleteOnClose) || OpenDockWidgets[0]->features().testFlag(CDockWidget::CustomCloseHandling)))
{
OpenDockWidgets[0]->closeDockWidgetInternal();
}
@@ -966,7 +969,8 @@ void CDockAreaWidget::closeArea()
{
for (auto DockWidget : openedDockWidgets())
{
if (DockWidget->features().testFlag(CDockWidget::DockWidgetDeleteOnClose) && DockWidget->features().testFlag(CDockWidget::DockWidgetForceCloseWithArea))
if ((DockWidget->features().testFlag(CDockWidget::DockWidgetDeleteOnClose) && DockWidget->features().testFlag(CDockWidget::DockWidgetForceCloseWithArea)) ||
DockWidget->features().testFlag(CDockWidget::CustomCloseHandling))
DockWidget->closeDockWidgetInternal();
else
DockWidget->toggleView(false);

View File

@@ -367,7 +367,8 @@ Q_SIGNALS:
*/
void viewToggled(bool Open);
}; // class DockAreaWidget
}
// namespace ads
} // namespace ads
Q_DECLARE_OPERATORS_FOR_FLAGS(ads::CDockAreaWidget::DockAreaFlags)
//-----------------------------------------------------------------------------
#endif // DockAreaWidgetH

View File

@@ -1286,7 +1286,7 @@ CDockContainerWidget::CDockContainerWidget(CDockManager* DockManager, QWidget *p
d->isFloating = floatingWidget() != nullptr;
d->Layout = new QGridLayout();
d->Layout->setContentsMargins(0, 1, 0, 1);
d->Layout->setContentsMargins(0, 0, 0, 0);
d->Layout->setSpacing(0);
setLayout(d->Layout);
@@ -1565,8 +1565,9 @@ void CDockContainerWidget::dropFloatingWidget(CFloatingDockContainer* FloatingWi
}
if (Dropped)
{
FloatingWidget->deleteLater();
{
// Fix https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/351
FloatingWidget->hideAndDeleteLater();
// If we dropped a floating widget with only one single dock widget, then we
// drop a top level widget that changes from floating to docked now
@@ -1624,6 +1625,37 @@ QList<CDockAreaWidget*> CDockContainerWidget::openedDockAreas() const
}
//============================================================================
QList<CDockWidget*> CDockContainerWidget::openedDockWidgets() const
{
QList<CDockWidget*> DockWidgetList;
for (auto DockArea : d->DockAreas)
{
if (!DockArea->isHidden())
{
DockWidgetList.append(DockArea->openedDockWidgets());
}
}
return DockWidgetList;
}
//============================================================================
bool CDockContainerWidget::hasOpenDockAreas() const
{
for (auto DockArea : d->DockAreas)
{
if (!DockArea->isHidden())
{
return true;
}
}
return false;
}
//============================================================================
void CDockContainerWidget::saveState(QXmlStreamWriter& s) const
{
@@ -1679,7 +1711,10 @@ bool CDockContainerWidget::restoreState(CDockingStateReader& s, bool Testing)
if (!Testing)
{
CFloatingDockContainer* FloatingWidget = floatingWidget();
FloatingWidget->restoreGeometry(Geometry);
if (FloatingWidget)
{
FloatingWidget->restoreGeometry(Geometry);
}
}
}

View File

@@ -51,6 +51,7 @@ class CFloatingDragPreview;
struct FloatingDragPreviewPrivate;
class CDockingStateReader;
/**
* Container that manages a number of dock areas with single dock widgets
* or tabyfied dock widgets in each area.
@@ -216,6 +217,18 @@ public:
*/
QList<CDockAreaWidget*> openedDockAreas() const;
/**
* Returns a list for all open dock widgets in all open dock areas
*/
QList<CDockWidget*> openedDockWidgets() const;
/**
* This function returns true, if the container has open dock areas.
* This functions is a little bit faster than calling openedDockAreas().isEmpty()
* because it returns as soon as it finds an open dock area
*/
bool hasOpenDockAreas() const;
/**
* This function returns true if this dock area has only one single
* visible dock widget.

View File

@@ -16,6 +16,7 @@
#include <QPointer>
#include <QApplication>
#include <QAbstractButton>
#include <QWindow>
#include "DockWidget.h"
#include "DockAreaWidget.h"
@@ -31,6 +32,8 @@
namespace ads
{
static const char* const FocusedDockWidgetProperty = "FocusedDockWidget";
/**
* Private data class of CDockFocusController class (pimpl)
*/
@@ -56,8 +59,8 @@ struct DockFocusControllerPrivate
* the dock area that it belongs to
*/
void updateDockWidgetFocus(CDockWidget* DockWidget);
};
// struct DockFocusControllerPrivate
}; // struct DockFocusControllerPrivate
//===========================================================================
@@ -115,6 +118,17 @@ void DockFocusControllerPrivate::updateDockWidgetFocus(CDockWidget* DockWidget)
return;
}
QWindow* Window = nullptr;
auto DockContainer = DockWidget->dockContainer();
if (DockContainer)
{
Window = DockContainer->window()->windowHandle();
}
if (Window)
{
Window->setProperty(FocusedDockWidgetProperty, QVariant::fromValue(QPointer<CDockWidget>(DockWidget)));
}
CDockAreaWidget* NewFocusedDockArea = nullptr;
if (FocusedDockWidget)
{
@@ -139,31 +153,36 @@ void DockFocusControllerPrivate::updateDockWidgetFocus(CDockWidget* DockWidget)
}
auto NewFloatingWidget = FocusedDockWidget->dockContainer()->floatingWidget();
CFloatingDockContainer* NewFloatingWidget = nullptr;
DockContainer = FocusedDockWidget->dockContainer();
if (DockContainer)
{
NewFloatingWidget = DockContainer->floatingWidget();
}
if (NewFloatingWidget)
{
NewFloatingWidget->setProperty("FocusedDockWidget", QVariant::fromValue(DockWidget));
NewFloatingWidget->setProperty(FocusedDockWidgetProperty, QVariant::fromValue(QPointer<CDockWidget>(DockWidget)));
}
#ifdef Q_OS_LINUX
// This code is required for styling the floating widget titlebar for linux
// depending on the current focus state
if (FloatingWidget == NewFloatingWidget)
{
return;
}
if (FloatingWidget != NewFloatingWidget)
{
if (FloatingWidget)
{
updateFloatingWidgetFocusStyle(FloatingWidget, false);
}
FloatingWidget = NewFloatingWidget;
if (FloatingWidget)
{
updateFloatingWidgetFocusStyle(FloatingWidget, false);
}
FloatingWidget = NewFloatingWidget;
if (FloatingWidget)
{
updateFloatingWidgetFocusStyle(FloatingWidget, true);
}
if (FloatingWidget)
{
updateFloatingWidgetFocusStyle(FloatingWidget, true);
}
}
#endif
if (old == DockWidget && !ForceFocusChangedSignal)
@@ -206,6 +225,8 @@ CDockFocusController::CDockFocusController(CDockManager* DockManager) :
d->DockManager = DockManager;
connect(QApplication::instance(), SIGNAL(focusChanged(QWidget*, QWidget*)),
this, SLOT(onApplicationFocusChanged(QWidget*, QWidget*)));
connect(QApplication::instance(), SIGNAL(focusWindowChanged(QWindow*)),
this, SLOT(onFocusWindowChanged(QWindow*)));
connect(d->DockManager, SIGNAL(stateRestored()), SLOT(onStateRestored()));
}
@@ -216,9 +237,35 @@ CDockFocusController::~CDockFocusController()
}
//============================================================================
void CDockFocusController::onFocusWindowChanged(QWindow *focusWindow)
{
if (!focusWindow)
{
return;
}
auto vDockWidget = focusWindow->property(FocusedDockWidgetProperty);
if (!vDockWidget.isValid())
{
return;
}
auto DockWidget = vDockWidget.value<QPointer<CDockWidget>>();
if (!DockWidget)
{
return;
}
d->updateDockWidgetFocus(DockWidget);
}
//===========================================================================
void CDockFocusController::onApplicationFocusChanged(QWidget* focusedOld, QWidget* focusedNow)
{
Q_UNUSED(focusedOld);
if (d->DockManager->isRestoringState())
{
return;
@@ -231,47 +278,7 @@ void CDockFocusController::onApplicationFocusChanged(QWidget* focusedOld, QWidge
return;
}
// If the close button in another tab steals the focus from the current
// active dock widget content, i.e. if the user clicks its close button,
// then we immediately give the focus back to the previous focused widget
// focusedOld
if (CDockManager::testConfigFlag(CDockManager::AllTabsHaveCloseButton))
{
auto OtherDockWidgetTab = internal::findParent<CDockWidgetTab*>(focusedNow);
if (OtherDockWidgetTab && focusedOld)
{
auto OldFocusedDockWidget = internal::findParent<CDockWidget*>(focusedOld);
if (OldFocusedDockWidget)
{
focusedOld->setFocus();
}
return;
}
}
CDockWidget* DockWidget = nullptr;
auto DockWidgetTab = qobject_cast<CDockWidgetTab*>(focusedNow);
if (DockWidgetTab)
{
DockWidget = DockWidgetTab->dockWidget();
// If the DockWidgetTab "steals" the focus from a widget in the same
// DockWidget, then we immediately give the focus back to the previous
// focused widget focusedOld
if (focusedOld)
{
auto OldFocusedDockWidget = internal::findParent<CDockWidget*>(focusedOld);
if (OldFocusedDockWidget && OldFocusedDockWidget == DockWidget)
{
focusedOld->setFocus();
}
}
}
if (!DockWidget)
{
DockWidget = qobject_cast<CDockWidget*>(focusedNow);
}
CDockWidget* DockWidget = qobject_cast<CDockWidget*>(focusedNow);
if (!DockWidget)
{
DockWidget = internal::findParent<CDockWidget*>(focusedNow);
@@ -293,6 +300,17 @@ void CDockFocusController::onApplicationFocusChanged(QWidget* focusedOld, QWidge
}
//===========================================================================
void CDockFocusController::setDockWidgetTabFocused(CDockWidgetTab* Tab)
{
auto DockWidget = Tab->dockWidget();
if (DockWidget)
{
d->updateDockWidgetFocus(DockWidget);
}
}
//===========================================================================
void CDockFocusController::setDockWidgetFocused(CDockWidget* focusedNow)
{
@@ -320,7 +338,7 @@ void CDockFocusController::onFocusedDockAreaViewToggled(bool Open)
return;
}
CDockManager::setWidgetFocus(OpenedDockAreas[0]->currentDockWidget()->tabWidget());
d->updateDockWidgetFocus(OpenedDockAreas[0]->currentDockWidget());
}
@@ -348,7 +366,7 @@ void CDockFocusController::notifyWidgetOrAreaRelocation(QWidget* DroppedWidget)
}
d->ForceFocusChangedSignal = true;
CDockManager::setWidgetFocus(DockWidget->tabWidget());
CDockManager::setWidgetFocus(DockWidget);
}
@@ -360,18 +378,17 @@ void CDockFocusController::notifyFloatingWidgetDrop(CFloatingDockContainer* Floa
return;
}
auto vDockWidget = FloatingWidget->property("FocusedDockWidget");
auto vDockWidget = FloatingWidget->property(FocusedDockWidgetProperty);
if (!vDockWidget.isValid())
{
return;
}
auto DockWidget = vDockWidget.value<CDockWidget*>();
auto DockWidget = vDockWidget.value<QPointer<CDockWidget>>();
if (DockWidget)
{
d->FocusedDockWidget = nullptr;
DockWidget->dockAreaWidget()->setCurrentDockWidget(DockWidget);
CDockManager::setWidgetFocus(DockWidget->tabWidget());
CDockManager::setWidgetFocus(DockWidget);
}
}

View File

@@ -32,6 +32,7 @@ private:
private Q_SLOTS:
void onApplicationFocusChanged(QWidget *old, QWidget *now);
void onFocusWindowChanged(QWindow *focusWindow);
void onFocusedDockAreaViewToggled(bool Open);
void onStateRestored();
void onDockWidgetVisibilityChanged(bool Visible);
@@ -48,21 +49,6 @@ public:
*/
virtual ~CDockFocusController();
/**
* Helper function to set focus depending on the configuration of the
* FocusStyling flag
*/
template <class QWidgetPtr>
static void setWidgetFocus(QWidgetPtr widget)
{
if (!CDockManager::testConfigFlag(CDockManager::FocusHighlighting))
{
return;
}
widget->setFocus(Qt::OtherFocusReason);
}
/**
* A container needs to call this function if a widget has been dropped
* into it
@@ -83,6 +69,12 @@ public:
*/
CDockWidget* focusedDockWidget() const;
/**
* Request focus highlighting for the given dock widget assigned to the tab
* given in Tab parameter
*/
void setDockWidgetTabFocused(CDockWidgetTab* Tab);
public Q_SLOTS:
/**
* Request a focus change to the given dock widget

View File

@@ -98,6 +98,7 @@ struct DockManagerPrivate
{
CDockManager* _this;
QList<CFloatingDockContainer*> FloatingWidgets;
QList<CFloatingDockContainer*> HiddenFloatingWidgets;
QList<CDockContainerWidget*> Containers;
CDockOverlay* ContainerOverlay;
CDockOverlay* DockAreaOverlay;
@@ -546,9 +547,16 @@ bool CDockManager::eventFilter(QObject *obj, QEvent *e)
// setWindowFlags(Qt::WindowStaysOnTopHint) will hide the window and thus requires a show call.
// This then leads to flickering and a nasty endless loop (also buggy behaviour on Ubuntu).
// So we just do it ourself.
internal::xcb_update_prop(true, _window->window()->winId(),
"_NET_WM_STATE", "_NET_WM_STATE_ABOVE", "_NET_WM_STATE_STAYS_ON_TOP");
}
if(QGuiApplication::platformName() == QLatin1String("xcb"))
{
internal::xcb_update_prop(true, _window->window()->winId(),
"_NET_WM_STATE", "_NET_WM_STATE_ABOVE", "_NET_WM_STATE_STAYS_ON_TOP");
}
else
{
_window->setWindowFlag(Qt::WindowStaysOnTopHint, true);
}
}
}
else if (e->type() == QEvent::WindowDeactivate)
{
@@ -558,8 +566,16 @@ bool CDockManager::eventFilter(QObject *obj, QEvent *e)
{
continue;
}
internal::xcb_update_prop(false, _window->window()->winId(),
"_NET_WM_STATE", "_NET_WM_STATE_ABOVE", "_NET_WM_STATE_STAYS_ON_TOP");
if(QGuiApplication::platformName() == QLatin1String("xcb"))
{
internal::xcb_update_prop(false, _window->window()->winId(),
"_NET_WM_STATE", "_NET_WM_STATE_ABOVE", "_NET_WM_STATE_STAYS_ON_TOP");
}
else
{
_window->setWindowFlag(Qt::WindowStaysOnTopHint, false);
}
_window->raise();
}
}
@@ -755,6 +771,9 @@ CFloatingDockContainer* CDockManager::addDockWidgetFloating(CDockWidget* Dockwid
void CDockManager::showEvent(QShowEvent *event)
{
Super::showEvent(event);
// Fix Issue #380
restoreHiddenFloatingWidgets();
if (d->UninitializedFloatingWidgets.empty())
{
return;
@@ -762,12 +781,52 @@ void CDockManager::showEvent(QShowEvent *event)
for (auto FloatingWidget : d->UninitializedFloatingWidgets)
{
FloatingWidget->show();
// Check, if someone closed a floating dock widget before the dock
// manager is shown
if (FloatingWidget->dockContainer()->hasOpenDockAreas())
{
FloatingWidget->show();
}
}
d->UninitializedFloatingWidgets.clear();
}
//============================================================================
void CDockManager::restoreHiddenFloatingWidgets()
{
if (d->HiddenFloatingWidgets.isEmpty())
{
return;
}
// Restore floating widgets that were hidden upon hideManagerAndFloatingWidgets
for (auto FloatingWidget : d->HiddenFloatingWidgets)
{
bool hasDockWidgetVisible = false;
// Needed to prevent CFloatingDockContainer being shown empty
// Could make sense to move this to CFloatingDockContainer::showEvent(QShowEvent *event)
// if experiencing CFloatingDockContainer being shown empty in other situations, but let's keep
// it here for now to make sure changes to fix Issue #380 does not impact existing behaviours
for (auto dockWidget : FloatingWidget->dockWidgets())
{
if (dockWidget->toggleViewAction()->isChecked())
{
dockWidget->toggleView(true);
hasDockWidgetVisible = true;
}
}
if (hasDockWidgetVisible)
{
FloatingWidget->show();
}
}
d->HiddenFloatingWidgets.clear();
}
//============================================================================
CDockAreaWidget* CDockManager::addDockWidget(DockWidgetArea area,
CDockWidget* Dockwidget, CDockAreaWidget* DockAreaWidget)
@@ -779,6 +838,16 @@ CDockAreaWidget* CDockManager::addDockWidget(DockWidgetArea area,
return AreaOfAddedDockWidget;
}
//============================================================================
CDockAreaWidget* CDockManager::addDockWidgetToContainer(DockWidgetArea area,
CDockWidget* Dockwidget, CDockContainerWidget* DockContainerWidget)
{
d->DockWidgetsMap.insert(Dockwidget->objectName(), Dockwidget);
auto AreaOfAddedDockWidget = DockContainerWidget->addDockWidget(area, Dockwidget);
Q_EMIT dockWidgetAdded(Dockwidget);
return AreaOfAddedDockWidget;
}
//============================================================================
CDockAreaWidget* CDockManager::addDockWidgetTab(DockWidgetArea area,
@@ -789,10 +858,6 @@ CDockAreaWidget* CDockManager::addDockWidgetTab(DockWidgetArea area,
{
return addDockWidget(ads::CenterDockWidgetArea, Dockwidget, AreaWidget);
}
else if (!openedDockAreas().isEmpty())
{
return addDockWidget(area, Dockwidget, openedDockAreas().last());
}
else
{
return addDockWidget(area, Dockwidget, nullptr);
@@ -1097,6 +1162,38 @@ void CDockManager::setDockWidgetFocused(CDockWidget* DockWidget)
}
}
//===========================================================================
void CDockManager::hideManagerAndFloatingWidgets()
{
hide();
d->HiddenFloatingWidgets.clear();
// Hide updates of floating widgets from user
for (auto FloatingWidget : d->FloatingWidgets)
{
if ( FloatingWidget->isVisible() )
{
QList<CDockWidget*> VisibleWidgets;
for ( auto dockWidget : FloatingWidget->dockWidgets() )
{
if ( dockWidget->toggleViewAction()->isChecked() )
VisibleWidgets.push_back( dockWidget );
}
// save as floating widget to be shown when CDockManager will be shown back
d->HiddenFloatingWidgets.push_back( FloatingWidget );
FloatingWidget->hide();
// hidding floating widget automatically marked contained CDockWidgets as hidden
// but they must remain marked as visible as we want them to be restored visible
// when CDockManager will be shown back
for ( auto dockWidget : VisibleWidgets )
{
dockWidget->toggleViewAction()->setChecked(true);
}
}
}
}
//===========================================================================
CDockWidget* CDockManager::focusedDockWidget() const
@@ -1140,6 +1237,13 @@ void CDockManager::setSplitterSizes(CDockAreaWidget *ContainedArea, const QList<
}
}
//===========================================================================
CDockFocusController* CDockManager::dockFocusController() const
{
return d->FocusController;
}
} // namespace ads
//---------------------------------------------------------------------------

View File

@@ -53,6 +53,7 @@ struct DockWidgetTabPrivate;
struct DockAreaWidgetPrivate;
class CIconProvider;
class CDockComponentsFactory;
class CDockFocusController;
/**
* The central dock manager that maintains the complete docking system.
@@ -134,12 +135,23 @@ protected:
*/
void notifyFloatingWidgetDrop(CFloatingDockContainer* FloatingWidget);
/**
* Show the floating widgets that has been created floating
*/
virtual void showEvent(QShowEvent *event) override;
/**
* Acces for the internal dock focus controller.
* This function only returns a valid object, if the FocusHighlighting
* flag is set.
*/
CDockFocusController* dockFocusController() const;
/**
* Restore floating widgets hidden by an earlier call to hideManagerAndFloatingWidgets.
*/
void restoreHiddenFloatingWidgets();
public:
using Super = CDockContainerWidget;
@@ -188,6 +200,7 @@ public:
FloatingContainerForceQWidgetTitleBar = 0x1000000,//!< Linux only ! Forces all FloatingContainer to use a QWidget based title bar.
//!< If neither this nor FloatingContainerForceNativeTitleBar is set (the default) native titlebars are used except on known bad systems.
//! Users can overwrite this by setting the environment variable ADS_UseNativeTitle to "1" or "0".
MiddleMouseButtonClosesTab = 0x2000000, //! If the flag is set, the user can use the mouse middle button to close the tab under the mouse
DefaultDockAreaButtons = DockAreaHasCloseButton
| DockAreaHasUndockButton
@@ -270,6 +283,15 @@ public:
CDockAreaWidget* addDockWidget(DockWidgetArea area, CDockWidget* Dockwidget,
CDockAreaWidget* DockAreaWidget = nullptr);
/**
* Adds dockwidget into the given container.
* This allows you to place the dock widget into a container, even if that
* container does not yet contain a DockAreaWidget.
* \return Returns the dock area widget that contains the new DockWidget
*/
CDockAreaWidget* addDockWidgetToContainer(DockWidgetArea area, CDockWidget* Dockwidget,
CDockContainerWidget* DockContainerWidget);
/**
* This function will add the given Dockwidget to the given dock area as
* a new tab.
@@ -519,6 +541,12 @@ public Q_SLOTS:
*/
void setDockWidgetFocused(CDockWidget* DockWidget);
/**
* hide CDockManager and all floating widgets (See Issue #380). Calling regular QWidget::hide()
* hides the CDockManager but not the floating widgets;
*/
void hideManagerAndFloatingWidgets();
Q_SIGNALS:
/**
* This signal is emitted if the list of perspectives changed.
@@ -609,5 +637,7 @@ Q_SIGNALS:
void focusedDockWidgetChanged(ads::CDockWidget* old, ads::CDockWidget* now);
}; // class DockManager
} // namespace ads
Q_DECLARE_OPERATORS_FOR_FLAGS(ads::CDockManager::ConfigFlags)
//-----------------------------------------------------------------------------
#endif // DockManagerH

View File

@@ -806,7 +806,7 @@ void CDockOverlayCross::setIconColors(const QString& Colors)
{"Arrow", CDockOverlayCross::ArrowColor},
{"Shadow", CDockOverlayCross::ShadowColor}};
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0))
auto SkipEmptyParts = QString::SkipEmptyParts;
#else
auto SkipEmptyParts = Qt::SkipEmptyParts;

View File

@@ -66,6 +66,12 @@ namespace ads
*/
struct DockWidgetPrivate
{
struct WidgetFactory
{
CDockWidget::FactoryFunc createWidget;
CDockWidget::eInsertMode insertMode;
};
CDockWidget* _this = nullptr;
QBoxLayout* Layout = nullptr;
QWidget* Widget = nullptr;
@@ -84,7 +90,8 @@ struct DockWidgetPrivate
bool IsFloatingTopLevel = false;
QList<QAction*> TitleBarActions;
CDockWidget::eMinimumSizeHintMode MinimumSizeHintMode = CDockWidget::MinimumSizeHintFromDockWidget;
WidgetFactory* Factory = nullptr;
/**
* Private data constructor
*/
@@ -116,6 +123,12 @@ struct DockWidgetPrivate
* Setup the main scroll area
*/
void setupScrollArea();
/**
* Creates the content widget with the registered widget factory and
* returns true on success.
*/
bool createWidgetFromFactory();
};
// struct DockWidgetPrivate
@@ -130,10 +143,23 @@ DockWidgetPrivate::DockWidgetPrivate(CDockWidget* _public) :
//============================================================================
void DockWidgetPrivate::showDockWidget()
{
if (!Widget)
{
if (!createWidgetFromFactory())
{
Q_ASSERT(!Features.testFlag(CDockWidget::DeleteContentOnClose)
&& "DeleteContentOnClose flag was set, but the widget "
"factory is missing or it doesn't return a valid QWidget.");
return;
}
}
if (!DockArea)
{
CFloatingDockContainer* FloatingWidget = new CFloatingDockContainer(_this);
FloatingWidget->resize(_this->size());
// We use the size hint of the content widget to provide a good
// initial size
FloatingWidget->resize(Widget ? Widget->sizeHint() : _this->sizeHint());
TabWidget->show();
FloatingWidget->show();
}
@@ -165,6 +191,12 @@ void DockWidgetPrivate::hideDockWidget()
{
TabWidget->hide();
updateParentDockArea();
if (Features.testFlag(CDockWidget::DeleteContentOnClose))
{
Widget->deleteLater();
Widget = nullptr;
}
}
@@ -218,6 +250,30 @@ void DockWidgetPrivate::setupScrollArea()
}
//============================================================================
bool DockWidgetPrivate::createWidgetFromFactory()
{
if (!Features.testFlag(CDockWidget::DeleteContentOnClose))
{
return false;
}
if (!Factory)
{
return false;
}
QWidget* w = Factory->createWidget(_this);
if (!w)
{
return false;
}
_this->setWidget(w, Factory->insertMode);
return true;
}
//============================================================================
CDockWidget::CDockWidget(const QString &title, QWidget *parent) :
QFrame(parent),
@@ -288,6 +344,17 @@ void CDockWidget::setWidget(QWidget* widget, eInsertMode InsertMode)
d->Widget->setProperty("dockWidgetContent", true);
}
//============================================================================
void CDockWidget::setWidgetFactory(FactoryFunc createWidget, eInsertMode insertMode)
{
if (d->Factory)
{
delete d->Factory;
}
d->Factory = new DockWidgetPrivate::WidgetFactory { createWidget, insertMode };
}
//============================================================================
QWidget* CDockWidget::takeWidget()
@@ -389,6 +456,14 @@ CDockContainerWidget* CDockWidget::dockContainer() const
}
//============================================================================
CFloatingDockContainer* CDockWidget::floatingDockContainer() const
{
auto DockContainer = dockContainer();
return DockContainer ? DockContainer->floatingWidget() : nullptr;
}
//============================================================================
CDockAreaWidget* CDockWidget::dockAreaWidget() const
{
@@ -480,6 +555,7 @@ void CDockWidget::toggleView(bool Open)
{
Open = true;
}
// If the dock widget state is different, then we really need to toggle
// the state. If we are in the right state, then we simply make this
// dock widget the current dock widget
@@ -489,7 +565,7 @@ void CDockWidget::toggleView(bool Open)
}
else if (Open && d->DockArea)
{
d->DockArea->setCurrentDockWidget(this);
raise();
}
}
@@ -530,7 +606,8 @@ void CDockWidget::toggleViewInternal(bool Open)
CDockWidget* TopLevelDockWidgetAfter = DockContainer
? DockContainer->topLevelDockWidget() : nullptr;
CDockWidget::emitTopLevelEventForWidget(TopLevelDockWidgetAfter, true);
CFloatingDockContainer* FloatingContainer = DockContainer->floatingWidget();
CFloatingDockContainer* FloatingContainer = DockContainer
? DockContainer->floatingWidget() : nullptr;
if (FloatingContainer)
{
FloatingContainer->updateWindowTitle();
@@ -602,6 +679,12 @@ bool CDockWidget::event(QEvent *e)
{
d->DockArea->markTitleBarMenuOutdated();//update tabs menu
}
auto FloatingWidget = floatingDockContainer();
if (FloatingWidget)
{
FloatingWidget->updateWindowTitle();
}
Q_EMIT titleChanged(title);
}
break;
@@ -825,7 +908,10 @@ void CDockWidget::setFloating()
//============================================================================
void CDockWidget::deleteDockWidget()
{
dockManager()->removeDockWidget(this);
auto manager=dockManager();
if(manager){
manager->removeDockWidget(this);
}
deleteLater();
d->Closed = true;
}

View File

@@ -147,18 +147,19 @@ public:
enum DockWidgetFeature
{
DockWidgetClosable = 0x01,///< dock widget has a close button
DockWidgetMovable = 0x02,///< dock widget is movable and can be moved to a new position in the current dock container
DockWidgetFloatable = 0x04,///< dock widget can be dragged into a floating window
DockWidgetDeleteOnClose = 0x08, ///< deletes the dock widget when it is closed
CustomCloseHandling = 0x10, ///< clicking the close button will not close the dock widget but emits the closeRequested() signal instead
DockWidgetFocusable = 0x20, ///< if this is enabled, a dock widget can get focus highlighting
DockWidgetForceCloseWithArea = 0x40, ///< dock widget will be closed when the dock area hosting it is closed
NoTab = 0x80, ///< dock widget tab will never be shown if this flag is set
DockWidgetClosable = 0x001,///< dock widget has a close button
DockWidgetMovable = 0x002,///< dock widget is movable and can be moved to a new position in the current dock container
DockWidgetFloatable = 0x004,///< dock widget can be dragged into a floating window
DockWidgetDeleteOnClose = 0x008, ///< deletes the dock widget when it is closed
CustomCloseHandling = 0x010, ///< clicking the close button will not close the dock widget but emits the closeRequested() signal instead
DockWidgetFocusable = 0x020, ///< if this is enabled, a dock widget can get focus highlighting
DockWidgetForceCloseWithArea = 0x040, ///< dock widget will be closed when the dock area hosting it is closed
NoTab = 0x080, ///< dock widget tab will never be shown if this flag is set
DeleteContentOnClose = 0x100, ///< deletes only the contained widget on close, keeping the dock widget intact and in place. Attempts to rebuild the contents widget on show if there is a widget factory set.
DefaultDockWidgetFeatures = DockWidgetClosable | DockWidgetMovable | DockWidgetFloatable | DockWidgetFocusable,
AllDockWidgetFeatures = DefaultDockWidgetFeatures | DockWidgetDeleteOnClose | CustomCloseHandling,
DockWidgetAlwaysCloseAndDelete = DockWidgetForceCloseWithArea | DockWidgetDeleteOnClose,
NoDockWidgetFeatures = 0x00
NoDockWidgetFeatures = 0x000
};
Q_DECLARE_FLAGS(DockWidgetFeatures, DockWidgetFeature)
@@ -254,7 +255,7 @@ public:
/**
* Sets the widget for the dock widget to widget.
* The InsertMode defines how the widget is inserted into the dock widget.
* The content of a dock widget should be resizable do a very small size to
* The content of a dock widget should be resizable to a very small size to
* prevent the dock widget from blocking the resizing. To ensure, that a
* dock widget can be resized very well, it is better to insert the content+
* widget into a scroll area or to provide a widget that is already a scroll
@@ -269,7 +270,19 @@ public:
* provide the InsertMode ForceNoScrollArea
*/
void setWidget(QWidget* widget, eInsertMode InsertMode = AutoScrollArea);
/**
* Only used when the feature flag DeleteContentOnClose is set.
* Using the flag and setting a widget factory allows to free the resources
* of the widget of your application while retaining the position the next
* time you want to show your widget, unlike the flag DockWidgetDeleteOnClose
* which deletes the dock widget itself. Since we keep the dock widget, all
* regular features of ADS should work as normal, including saving and
* restoring the state of the docking system and using perspectives.
*/
using FactoryFunc = std::function<QWidget*(QWidget*)>;
void setWidgetFactory(FactoryFunc createWidget, eInsertMode InsertMode = AutoScrollArea);
/**
* Remove the widget from the dock and give ownership back to the caller
*/
@@ -318,6 +331,12 @@ public:
*/
CDockContainerWidget* dockContainer() const;
/**
* This function return the floating DockContainer if is isFloating() is true
* and a nullptr if this dock widget is not floating.
*/
CFloatingDockContainer* floatingDockContainer() const;
/**
* Returns the dock area widget this dock widget belongs to or 0
* if this dock widget has not been docked yet
@@ -587,7 +606,8 @@ Q_SIGNALS:
*/
void featuresChanged(ads::CDockWidget::DockWidgetFeatures features);
}; // class DockWidget
}
// namespace ads
} // namespace ads
Q_DECLARE_OPERATORS_FOR_FLAGS(ads::CDockWidget::DockWidgetFeatures)
//-----------------------------------------------------------------------------
#endif // DockWidgetH

View File

@@ -50,6 +50,7 @@
#include "DockOverlay.h"
#include "DockManager.h"
#include "IconProvider.h"
#include "DockFocusController.h"
namespace ads
@@ -207,6 +208,14 @@ struct DockWidgetTabPrivate
IconLabel->setVisible(true);
}
/**
* Convenience function for access to the dock manager dock focus controller
*/
CDockFocusController* focusController() const
{
return DockWidget->dockManager()->dockFocusController();
}
};
// struct DockWidgetTabPrivate
@@ -234,6 +243,7 @@ void DockWidgetTabPrivate::createLayout()
CloseButton->setObjectName("tabCloseButton");
internal::setButtonIcon(CloseButton, QStyle::SP_TitleBarCloseButton, TabCloseIcon);
CloseButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
CloseButton->setFocusPolicy(Qt::NoFocus);
updateCloseButtonSizePolicy();
internal::setToolTip(CloseButton, QObject::tr("Close Tab"));
_this->connect(CloseButton, SIGNAL(clicked()), SIGNAL(closeRequested()));
@@ -331,10 +341,11 @@ CDockWidgetTab::CDockWidgetTab(CDockWidget* DockWidget, QWidget *parent) :
setAttribute(Qt::WA_NoMousePropagation, true);
d->DockWidget = DockWidget;
d->createLayout();
if (CDockManager::testConfigFlag(CDockManager::FocusHighlighting))
setFocusPolicy(Qt::NoFocus);
/*if (CDockManager::testConfigFlag(CDockManager::FocusHighlighting))
{
setFocusPolicy(Qt::ClickFocus);
}
}*/
}
//============================================================================
@@ -353,6 +364,10 @@ void CDockWidgetTab::mousePressEvent(QMouseEvent* ev)
ev->accept();
d->saveDragStartMousePosition(internal::globalPositionOf(ev));
d->DragState = DraggingMousePressed;
if (CDockManager::testConfigFlag(CDockManager::FocusHighlighting))
{
d->focusController()->setDockWidgetTabFocused(this);
}
Q_EMIT clicked();
return;
}
@@ -377,16 +392,30 @@ void CDockWidgetTab::mouseReleaseEvent(QMouseEvent* ev)
// End of tab moving, emit signal
if (d->DockArea)
{
ev->accept();
Q_EMIT moved(internal::globalPositionOf(ev));
}
break;
case DraggingFloatingWidget:
ev->accept();
d->FloatingWidget->finishDragging();
break;
default:; // do nothing
}
}
else if (ev->button() == Qt::MiddleButton)
{
if (CDockManager::testConfigFlag(CDockManager::MiddleMouseButtonClosesTab) && d->DockWidget->features().testFlag(CDockWidget::DockWidgetClosable))
{
// Only attempt to close if the mouse is still
// on top of the widget, to allow the user to cancel.
if (rect().contains(mapFromGlobal(QCursor::pos()))) {
ev->accept();
Q_EMIT closeRequested();
}
}
}
Super::mouseReleaseEvent(ev);
@@ -515,7 +544,8 @@ void CDockWidgetTab::setActiveTab(bool active)
bool UpdateFocusStyle = false;
if (active && !hasFocus())
{
setFocus(Qt::OtherFocusReason);
//setFocus(Qt::OtherFocusReason);
d->focusController()->setDockWidgetTabFocused(this);
UpdateFocusStyle = true;
}
@@ -612,14 +642,18 @@ QString CDockWidgetTab::text() const
//============================================================================
void CDockWidgetTab::mouseDoubleClickEvent(QMouseEvent *event)
{
// If this is the last dock area in a dock container it does not make
// sense to move it to a new floating widget and leave this one
// empty
if ((!d->DockArea->dockContainer()->isFloating() || d->DockArea->dockWidgetsCount() > 1)
&& d->DockWidget->features().testFlag(CDockWidget::DockWidgetFloatable))
if (event->button() == Qt::LeftButton)
{
d->saveDragStartMousePosition(internal::globalPositionOf(event));
d->startFloating(DraggingInactive);
// If this is the last dock area in a dock container it does not make
// sense to move it to a new floating widget and leave this one
// empty
if ((!d->DockArea->dockContainer()->isFloating() || d->DockArea->dockWidgetsCount() > 1)
&& d->DockWidget->features().testFlag(CDockWidget::DockWidgetFloatable))
{
event->accept();
d->saveDragStartMousePosition(internal::globalPositionOf(event));
d->startFloating(DraggingInactive);
}
}
Super::mouseDoubleClickEvent(event);
@@ -682,6 +716,10 @@ bool CDockWidgetTab::event(QEvent *e)
}
}
#endif
if (e->type() == QEvent::StyleChange)
{
d->updateIcon();
}
return Super::event(e);
}

View File

@@ -163,8 +163,8 @@ void CElidingLabel::resizeEvent(QResizeEvent *event)
//============================================================================
QSize CElidingLabel::minimumSizeHint() const
{
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
bool HasPixmap = !pixmap().isNull();
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
bool HasPixmap = !pixmap(Qt::ReturnByValue).isNull();
#else
bool HasPixmap = (pixmap() != nullptr);
#endif
@@ -185,8 +185,8 @@ QSize CElidingLabel::minimumSizeHint() const
//============================================================================
QSize CElidingLabel::sizeHint() const
{
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
bool HasPixmap = !pixmap().isNull();
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
bool HasPixmap = !pixmap(Qt::ReturnByValue).isNull();
#else
bool HasPixmap = (pixmap() != nullptr);
#endif

View File

@@ -373,6 +373,7 @@ struct FloatingDockContainerPrivate
CDockAreaWidget *SingleDockArea = nullptr;
QPoint DragStartPos;
bool Hiding = false;
bool AutoHideChildren = true;
#ifdef Q_OS_LINUX
QWidget* MouseEventHandler = nullptr;
CFloatingWidgetTitleBar* TitleBar = nullptr;
@@ -801,29 +802,43 @@ void CFloatingDockContainer::closeEvent(QCloseEvent *event)
ADS_PRINT("CFloatingDockContainer closeEvent");
d->setState(DraggingInactive);
event->ignore();
if (isClosable())
if (!isClosable())
{
auto TopLevelDockWidget = topLevelDockWidget();
if (TopLevelDockWidget && TopLevelDockWidget->features().testFlag(CDockWidget::DockWidgetDeleteOnClose))
return;
}
bool HasOpenDockWidgets = false;
for (auto DockWidget : d->DockContainer->openedDockWidgets())
{
if (DockWidget->features().testFlag(CDockWidget::DockWidgetDeleteOnClose) || DockWidget->features().testFlag(CDockWidget::CustomCloseHandling))
{
if (!TopLevelDockWidget->closeDockWidgetInternal())
bool Closed = DockWidget->closeDockWidgetInternal();
if (!Closed)
{
return;
HasOpenDockWidgets = true;
}
}
// In Qt version after 5.9.2 there seems to be a bug that causes the
// QWidget::event() function to not receive any NonClientArea mouse
// events anymore after a close/show cycle. The bug is reported here:
// https://bugreports.qt.io/browse/QTBUG-73295
// The following code is a workaround for Qt versions > 5.9.2 that seems
// to work
// Starting from Qt version 5.12.2 this seems to work again. But
// now the QEvent::NonClientAreaMouseButtonPress function returns always
// Qt::RightButton even if the left button was pressed
this->hide();
else
{
DockWidget->toggleView(false);
}
}
if (HasOpenDockWidgets)
{
return;
}
// In Qt version after 5.9.2 there seems to be a bug that causes the
// QWidget::event() function to not receive any NonClientArea mouse
// events anymore after a close/show cycle. The bug is reported here:
// https://bugreports.qt.io/browse/QTBUG-73295
// The following code is a workaround for Qt versions > 5.9.2 that seems
// to work
// Starting from Qt version 5.12.2 this seems to work again. But
// now the QEvent::NonClientAreaMouseButtonPress function returns always
// Qt::RightButton even if the left button was pressed
this->hide();
}
//============================================================================
@@ -841,15 +856,18 @@ void CFloatingDockContainer::hideEvent(QHideEvent *event)
return;
}
d->Hiding = true;
for (auto DockArea : d->DockContainer->openedDockAreas())
if ( d->AutoHideChildren )
{
for (auto DockWidget : DockArea->openedDockWidgets())
d->Hiding = true;
for ( auto DockArea : d->DockContainer->openedDockAreas() )
{
DockWidget->toggleView(false);
for ( auto DockWidget : DockArea->openedDockWidgets() )
{
DockWidget->toggleView( false );
}
}
d->Hiding = false;
}
d->Hiding = false;
}
@@ -1035,6 +1053,18 @@ QList<CDockWidget*> CFloatingDockContainer::dockWidgets() const
return d->DockContainer->dockWidgets();
}
//============================================================================
void CFloatingDockContainer::hideAndDeleteLater()
{
// Widget has been redocked, so it must be hidden right way (see
// https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/351)
// but AutoHideChildren must be set to false because "this" still contains
// dock widgets that shall not be toggled hidden.
d->AutoHideChildren = false;
hide();
deleteLater();
}
//============================================================================
void CFloatingDockContainer::finishDragging()
{
@@ -1223,12 +1253,12 @@ void CFloatingDockContainer::resizeEvent(QResizeEvent *event)
Super::resizeEvent(event);
}
static bool s_mousePressed = false;
//============================================================================
void CFloatingDockContainer::moveEvent(QMoveEvent *event)
{
Super::moveEvent(event);
if (!d->IsResizing && event->spontaneous())
if (!d->IsResizing && event->spontaneous() && s_mousePressed)
{
d->DraggingState = DraggingFloatingWidget;
d->updateDropOverlays(QCursor::pos());
@@ -1236,6 +1266,23 @@ void CFloatingDockContainer::moveEvent(QMoveEvent *event)
d->IsResizing = false;
}
//============================================================================
bool CFloatingDockContainer::event(QEvent *e)
{
bool result = Super::event(e);
switch (e->type())
{
case QEvent::WindowActivate:
s_mousePressed = false;
break;
case QEvent::WindowDeactivate:
s_mousePressed = true;
break;
default:
break;
}
return result;
}
//============================================================================
bool CFloatingDockContainer::hasNativeTitleBar()

View File

@@ -65,7 +65,7 @@ class CDockingStateReader;
* This interface is used for opaque and non-opaque undocking. If opaque
* undocking is used, the a real CFloatingDockContainer widget will be created
*/
class IFloatingWidget
class ADS_EXPORT IFloatingWidget
{
public:
virtual ~IFloatingWidget() = default;
@@ -188,6 +188,7 @@ protected: // reimplements QWidget
#ifdef Q_OS_LINUX
virtual void moveEvent(QMoveEvent *event) override;
virtual void resizeEvent(QResizeEvent *event) override;
virtual bool event(QEvent *e) override;
#endif
#ifdef Q_OS_WIN
@@ -258,6 +259,11 @@ public:
*/
QList<CDockWidget*> dockWidgets() const;
/**
* This function hides the floating bar instantely and delete it later.
*/
void hideAndDeleteLater();
#ifdef Q_OS_LINUX
/**
* This is a function that responds to FloatingWidgetTitleBar::maximizeRequest()

View File

@@ -39,13 +39,11 @@
#include "ads_globals.h"
#ifdef Q_OS_LINUX
#include <QX11Info>
#include <QSettings>
#include <QFile>
#endif
#include <QApplication>
#include <qpa/qplatformnativeinterface.h>
#endif
namespace ads
{
@@ -57,10 +55,31 @@ static QString _window_manager;
static QHash<QString, xcb_atom_t> _xcb_atom_cache;
//============================================================================
bool is_platform_x11()
{
return QGuiApplication::platformName() == QLatin1String("xcb");
}
//============================================================================
xcb_connection_t* x11_connection()
{
if (!qApp)
return nullptr;
QPlatformNativeInterface *native = qApp->platformNativeInterface();
if (!native)
return nullptr;
void *connection = native->nativeResourceForIntegration(QByteArray("connection"));
return reinterpret_cast<xcb_connection_t *>(connection);
}
//============================================================================
xcb_atom_t xcb_get_atom(const char *name)
{
if (!QX11Info::isPlatformX11())
if (!is_platform_x11())
{
return XCB_ATOM_NONE;
}
@@ -69,7 +88,7 @@ xcb_atom_t xcb_get_atom(const char *name)
{
return _xcb_atom_cache[key];
}
xcb_connection_t *connection = QX11Info::connection();
xcb_connection_t *connection = x11_connection();
xcb_intern_atom_cookie_t request = xcb_intern_atom(connection, 1, strlen(name), name);
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(connection, request, NULL);
if (!reply)
@@ -93,7 +112,7 @@ xcb_atom_t xcb_get_atom(const char *name)
//============================================================================
void xcb_update_prop(bool set, WId window, const char *type, const char *prop, const char *prop2)
{
auto connection = QX11Info::connection();
auto connection = x11_connection();
xcb_atom_t type_atom = xcb_get_atom(type);
xcb_atom_t prop_atom = xcb_get_atom(prop);
xcb_client_message_event_t event;
@@ -118,11 +137,11 @@ void xcb_update_prop(bool set, WId window, const char *type, const char *prop, c
//============================================================================
xcb_get_property_reply_t* _xcb_get_props(WId window, const char *type, unsigned int atom_type)
{
if (!QX11Info::isPlatformX11())
if (!is_platform_x11())
{
return nullptr;
}
xcb_connection_t *connection = QX11Info::connection();
xcb_connection_t *connection = x11_connection();
xcb_atom_t type_atom = xcb_get_atom(type);
if (type_atom == XCB_ATOM_NONE)
{
@@ -191,7 +210,7 @@ bool xcb_dump_props(WId window, const char *type)
QVector<xcb_atom_t> atoms;
xcb_get_prop_list(window, type, atoms, XCB_ATOM_ATOM);
qDebug() << "\n\n!!!" << type << " - " << atoms.length();
xcb_connection_t *connection = QX11Info::connection();
xcb_connection_t *connection = x11_connection();
for (auto atom : atoms)
{
auto foo = xcb_get_atom_name(connection, atom);
@@ -206,7 +225,7 @@ bool xcb_dump_props(WId window, const char *type)
//============================================================================
void xcb_add_prop(bool state, WId window, const char *type, const char *prop)
{
if (!QX11Info::isPlatformX11())
if (!is_platform_x11())
{
return;
}
@@ -227,7 +246,7 @@ void xcb_add_prop(bool state, WId window, const char *type, const char *prop)
{
atoms.remove(index);
}
xcb_connection_t *connection = QX11Info::connection();
xcb_connection_t *connection = x11_connection();
xcb_change_property(connection, XCB_PROP_MODE_REPLACE, window, type_atom, XCB_ATOM_ATOM, 32, atoms.count(), atoms.constData());
xcb_flush(connection);
}
@@ -238,11 +257,11 @@ QString detectWindowManagerX11()
{
// Tries to detect the windowmanager via X11.
// See: https://specifications.freedesktop.org/wm-spec/1.3/ar01s03.html#idm46018259946000
if (!QX11Info::isPlatformX11())
if (!is_platform_x11())
{
return "UNKNOWN";
}
xcb_connection_t *connection = QX11Info::connection();
xcb_connection_t *connection = x11_connection();
xcb_screen_t *first_screen = xcb_setup_roots_iterator (xcb_get_setup (connection)).data;
if(!first_screen)
{

View File

@@ -308,5 +308,6 @@ void repolishStyle(QWidget* w, eRepolishChildOptions Options = RepolishIgnoreChi
} // namespace internal
} // namespace ads
Q_DECLARE_OPERATORS_FOR_FLAGS(ads::DockWidgetAreas)
//---------------------------------------------------------------------------
#endif // ads_globalsH

View File

@@ -73,8 +73,8 @@ SOURCES += \
unix:!macx {
HEADERS += linux/FloatingWidgetTitleBar.h
SOURCES += linux/FloatingWidgetTitleBar.cpp
QT += x11extras
LIBS += -lxcb
QT += gui-private
}
isEmpty(PREFIX){

View File

@@ -4,6 +4,9 @@
ads--CDockContainerWidget {
background: palette(dark);
}
ads--CDockContainerWidget > QSplitter{
padding: 1 0 1 0;
}
ads--CDockContainerWidget ads--CDockSplitter::handle {
background: palette(dark);
@@ -90,4 +93,4 @@ QScrollArea#dockWidgetScrollArea {
qproperty-icon: url(:/ads/images/detach-button.svg),
url(:/ads/images/detach-button-disabled.svg) disabled;
qproperty-iconSize: 16px;
}
}

View File

@@ -4,6 +4,9 @@
ads--CDockContainerWidget {
background: palette(dark);
}
ads--CDockContainerWidget > QSplitter{
padding: 1 0 1 0;
}
ads--CDockContainerWidget ads--CDockSplitter::handle {
background: palette(dark);

View File

@@ -4,6 +4,9 @@
ads--CDockContainerWidget {
background: palette(dark);
}
ads--CDockContainerWidget > QSplitter{
padding: 1 0 1 0;
}
ads--CDockAreaWidget {
background: palette(window);
@@ -123,4 +126,4 @@ ads--CDockAreaWidget[focused="true"] ads--CDockAreaTitleBar {
background: transparent;
border-bottom: 2px solid palette(highlight);
padding-bottom: 0px;
}
}

View File

@@ -4,6 +4,10 @@
ads--CDockContainerWidget {
background: palette(dark);
}
ads--CDockContainerWidget > QSplitter{
padding: 1 0 1 0;
}
ads--CDockContainerWidget ads--CDockSplitter::handle {
background: palette(dark);